Passed
Push — master ( df0bb9...726976 )
by Christoph
17:20 queued 12s
created
core/Controller/LoginController.php 1 patch
Indentation   +327 added lines, -327 removed lines patch added patch discarded remove patch
@@ -61,331 +61,331 @@
 block discarded – undo
61 61
 use OCP\Util;
62 62
 
63 63
 class LoginController extends Controller {
64
-	public const LOGIN_MSG_INVALIDPASSWORD = 'invalidpassword';
65
-	public const LOGIN_MSG_USERDISABLED = 'userdisabled';
66
-
67
-	private IUserManager $userManager;
68
-	private IConfig $config;
69
-	private ISession $session;
70
-	/** @var IUserSession|Session */
71
-	private $userSession;
72
-	private IURLGenerator $urlGenerator;
73
-	private Defaults $defaults;
74
-	private Throttler $throttler;
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
-								IInitialStateService $initialStateService,
90
-								WebAuthnManager $webAuthnManager,
91
-								IManager $manager,
92
-								IL10N $l10n) {
93
-		parent::__construct($appName, $request);
94
-		$this->userManager = $userManager;
95
-		$this->config = $config;
96
-		$this->session = $session;
97
-		$this->userSession = $userSession;
98
-		$this->urlGenerator = $urlGenerator;
99
-		$this->defaults = $defaults;
100
-		$this->throttler = $throttler;
101
-		$this->initialStateService = $initialStateService;
102
-		$this->webAuthnManager = $webAuthnManager;
103
-		$this->manager = $manager;
104
-		$this->l10n = $l10n;
105
-	}
106
-
107
-	/**
108
-	 * @NoAdminRequired
109
-	 *
110
-	 * @return RedirectResponse
111
-	 */
112
-	#[UseSession]
113
-	public function logout() {
114
-		$loginToken = $this->request->getCookie('nc_token');
115
-		if (!is_null($loginToken)) {
116
-			$this->config->deleteUserValue($this->userSession->getUser()->getUID(), 'login_token', $loginToken);
117
-		}
118
-		$this->userSession->logout();
119
-
120
-		$response = new RedirectResponse($this->urlGenerator->linkToRouteAbsolute(
121
-			'core.login.showLoginForm',
122
-			['clear' => true] // this param the code in login.js may be removed when the "Clear-Site-Data" is working in the browsers
123
-		));
124
-
125
-		$this->session->set('clearingExecutionContexts', '1');
126
-		$this->session->close();
127
-
128
-		if (!$this->request->isUserAgent([Request::USER_AGENT_CHROME, Request::USER_AGENT_ANDROID_MOBILE_CHROME])) {
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
-	}
64
+    public const LOGIN_MSG_INVALIDPASSWORD = 'invalidpassword';
65
+    public const LOGIN_MSG_USERDISABLED = 'userdisabled';
66
+
67
+    private IUserManager $userManager;
68
+    private IConfig $config;
69
+    private ISession $session;
70
+    /** @var IUserSession|Session */
71
+    private $userSession;
72
+    private IURLGenerator $urlGenerator;
73
+    private Defaults $defaults;
74
+    private Throttler $throttler;
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
+                                IInitialStateService $initialStateService,
90
+                                WebAuthnManager $webAuthnManager,
91
+                                IManager $manager,
92
+                                IL10N $l10n) {
93
+        parent::__construct($appName, $request);
94
+        $this->userManager = $userManager;
95
+        $this->config = $config;
96
+        $this->session = $session;
97
+        $this->userSession = $userSession;
98
+        $this->urlGenerator = $urlGenerator;
99
+        $this->defaults = $defaults;
100
+        $this->throttler = $throttler;
101
+        $this->initialStateService = $initialStateService;
102
+        $this->webAuthnManager = $webAuthnManager;
103
+        $this->manager = $manager;
104
+        $this->l10n = $l10n;
105
+    }
106
+
107
+    /**
108
+     * @NoAdminRequired
109
+     *
110
+     * @return RedirectResponse
111
+     */
112
+    #[UseSession]
113
+    public function logout() {
114
+        $loginToken = $this->request->getCookie('nc_token');
115
+        if (!is_null($loginToken)) {
116
+            $this->config->deleteUserValue($this->userSession->getUser()->getUID(), 'login_token', $loginToken);
117
+        }
118
+        $this->userSession->logout();
119
+
120
+        $response = new RedirectResponse($this->urlGenerator->linkToRouteAbsolute(
121
+            'core.login.showLoginForm',
122
+            ['clear' => true] // this param the code in login.js may be removed when the "Clear-Site-Data" is working in the browsers
123
+        ));
124
+
125
+        $this->session->set('clearingExecutionContexts', '1');
126
+        $this->session->close();
127
+
128
+        if (!$this->request->isUserAgent([Request::USER_AGENT_CHROME, Request::USER_AGENT_ANDROID_MOBILE_CHROME])) {
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.
core/Controller/WebAuthnController.php 1 patch
Indentation   +73 added lines, -73 removed lines patch added patch discarded remove patch
@@ -42,77 +42,77 @@
 block discarded – undo
42 42
 use Webauthn\PublicKeyCredentialRequestOptions;
43 43
 
44 44
 class WebAuthnController extends Controller {
45
-	private const WEBAUTHN_LOGIN = 'webauthn_login';
46
-	private const WEBAUTHN_LOGIN_UID = 'webauthn_login_uid';
47
-
48
-	private Manager $webAuthnManger;
49
-	private ISession $session;
50
-	private LoggerInterface $logger;
51
-	private WebAuthnChain $webAuthnChain;
52
-	private UrlGenerator $urlGenerator;
53
-
54
-	public function __construct($appName, IRequest $request, Manager $webAuthnManger, ISession $session, LoggerInterface $logger, WebAuthnChain $webAuthnChain, URLGenerator $urlGenerator) {
55
-		parent::__construct($appName, $request);
56
-
57
-		$this->webAuthnManger = $webAuthnManger;
58
-		$this->session = $session;
59
-		$this->logger = $logger;
60
-		$this->webAuthnChain = $webAuthnChain;
61
-		$this->urlGenerator = $urlGenerator;
62
-	}
63
-
64
-	/**
65
-	 * @NoAdminRequired
66
-	 * @PublicPage
67
-	 */
68
-	#[UseSession]
69
-	public function startAuthentication(string $loginName): JSONResponse {
70
-		$this->logger->debug('Starting WebAuthn login');
71
-
72
-		$this->logger->debug('Converting login name to UID');
73
-		$uid = $loginName;
74
-		Util::emitHook(
75
-			'\OCA\Files_Sharing\API\Server2Server',
76
-			'preLoginNameUsedAsUserName',
77
-			['uid' => &$uid]
78
-		);
79
-		$this->logger->debug('Got UID: ' . $uid);
80
-
81
-		$publicKeyCredentialRequestOptions = $this->webAuthnManger->startAuthentication($uid, $this->request->getServerHost());
82
-		$this->session->set(self::WEBAUTHN_LOGIN, json_encode($publicKeyCredentialRequestOptions));
83
-		$this->session->set(self::WEBAUTHN_LOGIN_UID, $uid);
84
-
85
-		return new JSONResponse($publicKeyCredentialRequestOptions);
86
-	}
87
-
88
-	/**
89
-	 * @NoAdminRequired
90
-	 * @PublicPage
91
-	 */
92
-	#[UseSession]
93
-	public function finishAuthentication(string $data): JSONResponse {
94
-		$this->logger->debug('Validating WebAuthn login');
95
-
96
-		if (!$this->session->exists(self::WEBAUTHN_LOGIN) || !$this->session->exists(self::WEBAUTHN_LOGIN_UID)) {
97
-			$this->logger->debug('Trying to finish WebAuthn login without session data');
98
-			return new JSONResponse([], Http::STATUS_BAD_REQUEST);
99
-		}
100
-
101
-		// Obtain the publicKeyCredentialOptions from when we started the registration
102
-		$publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions::createFromString($this->session->get(self::WEBAUTHN_LOGIN));
103
-		$uid = $this->session->get(self::WEBAUTHN_LOGIN_UID);
104
-		$this->webAuthnManger->finishAuthentication($publicKeyCredentialRequestOptions, $data, $uid);
105
-
106
-		//TODO: add other parameters
107
-		$loginData = new LoginData(
108
-			$this->request,
109
-			$uid,
110
-			''
111
-		);
112
-		$this->webAuthnChain->process($loginData);
113
-
114
-		return new JSONResponse([
115
-			'defaultRedirectUrl' => $this->urlGenerator->linkToDefaultPageUrl(),
116
-		]);
117
-	}
45
+    private const WEBAUTHN_LOGIN = 'webauthn_login';
46
+    private const WEBAUTHN_LOGIN_UID = 'webauthn_login_uid';
47
+
48
+    private Manager $webAuthnManger;
49
+    private ISession $session;
50
+    private LoggerInterface $logger;
51
+    private WebAuthnChain $webAuthnChain;
52
+    private UrlGenerator $urlGenerator;
53
+
54
+    public function __construct($appName, IRequest $request, Manager $webAuthnManger, ISession $session, LoggerInterface $logger, WebAuthnChain $webAuthnChain, URLGenerator $urlGenerator) {
55
+        parent::__construct($appName, $request);
56
+
57
+        $this->webAuthnManger = $webAuthnManger;
58
+        $this->session = $session;
59
+        $this->logger = $logger;
60
+        $this->webAuthnChain = $webAuthnChain;
61
+        $this->urlGenerator = $urlGenerator;
62
+    }
63
+
64
+    /**
65
+     * @NoAdminRequired
66
+     * @PublicPage
67
+     */
68
+    #[UseSession]
69
+    public function startAuthentication(string $loginName): JSONResponse {
70
+        $this->logger->debug('Starting WebAuthn login');
71
+
72
+        $this->logger->debug('Converting login name to UID');
73
+        $uid = $loginName;
74
+        Util::emitHook(
75
+            '\OCA\Files_Sharing\API\Server2Server',
76
+            'preLoginNameUsedAsUserName',
77
+            ['uid' => &$uid]
78
+        );
79
+        $this->logger->debug('Got UID: ' . $uid);
80
+
81
+        $publicKeyCredentialRequestOptions = $this->webAuthnManger->startAuthentication($uid, $this->request->getServerHost());
82
+        $this->session->set(self::WEBAUTHN_LOGIN, json_encode($publicKeyCredentialRequestOptions));
83
+        $this->session->set(self::WEBAUTHN_LOGIN_UID, $uid);
84
+
85
+        return new JSONResponse($publicKeyCredentialRequestOptions);
86
+    }
87
+
88
+    /**
89
+     * @NoAdminRequired
90
+     * @PublicPage
91
+     */
92
+    #[UseSession]
93
+    public function finishAuthentication(string $data): JSONResponse {
94
+        $this->logger->debug('Validating WebAuthn login');
95
+
96
+        if (!$this->session->exists(self::WEBAUTHN_LOGIN) || !$this->session->exists(self::WEBAUTHN_LOGIN_UID)) {
97
+            $this->logger->debug('Trying to finish WebAuthn login without session data');
98
+            return new JSONResponse([], Http::STATUS_BAD_REQUEST);
99
+        }
100
+
101
+        // Obtain the publicKeyCredentialOptions from when we started the registration
102
+        $publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions::createFromString($this->session->get(self::WEBAUTHN_LOGIN));
103
+        $uid = $this->session->get(self::WEBAUTHN_LOGIN_UID);
104
+        $this->webAuthnManger->finishAuthentication($publicKeyCredentialRequestOptions, $data, $uid);
105
+
106
+        //TODO: add other parameters
107
+        $loginData = new LoginData(
108
+            $this->request,
109
+            $uid,
110
+            ''
111
+        );
112
+        $this->webAuthnChain->process($loginData);
113
+
114
+        return new JSONResponse([
115
+            'defaultRedirectUrl' => $this->urlGenerator->linkToDefaultPageUrl(),
116
+        ]);
117
+    }
118 118
 }
Please login to merge, or discard this patch.
core/Controller/ClientFlowLoginController.php 1 patch
Indentation   +329 added lines, -329 removed lines patch added patch discarded remove patch
@@ -57,333 +57,333 @@
 block discarded – undo
57 57
 use OCP\Session\Exceptions\SessionNotAvailableException;
58 58
 
59 59
 class ClientFlowLoginController extends Controller {
60
-	private IUserSession $userSession;
61
-	private IL10N $l10n;
62
-	private Defaults $defaults;
63
-	private ISession $session;
64
-	private IProvider $tokenProvider;
65
-	private ISecureRandom $random;
66
-	private IURLGenerator $urlGenerator;
67
-	private ClientMapper $clientMapper;
68
-	private AccessTokenMapper $accessTokenMapper;
69
-	private ICrypto $crypto;
70
-	private IEventDispatcher $eventDispatcher;
71
-
72
-	public const STATE_NAME = 'client.flow.state.token';
73
-
74
-	public function __construct(string $appName,
75
-								IRequest $request,
76
-								IUserSession $userSession,
77
-								IL10N $l10n,
78
-								Defaults $defaults,
79
-								ISession $session,
80
-								IProvider $tokenProvider,
81
-								ISecureRandom $random,
82
-								IURLGenerator $urlGenerator,
83
-								ClientMapper $clientMapper,
84
-								AccessTokenMapper $accessTokenMapper,
85
-								ICrypto $crypto,
86
-								IEventDispatcher $eventDispatcher) {
87
-		parent::__construct($appName, $request);
88
-		$this->userSession = $userSession;
89
-		$this->l10n = $l10n;
90
-		$this->defaults = $defaults;
91
-		$this->session = $session;
92
-		$this->tokenProvider = $tokenProvider;
93
-		$this->random = $random;
94
-		$this->urlGenerator = $urlGenerator;
95
-		$this->clientMapper = $clientMapper;
96
-		$this->accessTokenMapper = $accessTokenMapper;
97
-		$this->crypto = $crypto;
98
-		$this->eventDispatcher = $eventDispatcher;
99
-	}
100
-
101
-	private function getClientName(): string {
102
-		$userAgent = $this->request->getHeader('USER_AGENT');
103
-		return $userAgent !== '' ? $userAgent : 'unknown';
104
-	}
105
-
106
-	private function isValidToken(string $stateToken): bool {
107
-		$currentToken = $this->session->get(self::STATE_NAME);
108
-		if (!is_string($currentToken)) {
109
-			return false;
110
-		}
111
-		return hash_equals($currentToken, $stateToken);
112
-	}
113
-
114
-	private function stateTokenForbiddenResponse(): StandaloneTemplateResponse {
115
-		$response = new StandaloneTemplateResponse(
116
-			$this->appName,
117
-			'403',
118
-			[
119
-				'message' => $this->l10n->t('State token does not match'),
120
-			],
121
-			'guest'
122
-		);
123
-		$response->setStatus(Http::STATUS_FORBIDDEN);
124
-		return $response;
125
-	}
126
-
127
-	/**
128
-	 * @PublicPage
129
-	 * @NoCSRFRequired
130
-	 */
131
-	#[UseSession]
132
-	public function showAuthPickerPage(string $clientIdentifier = '', string $user = '', int $direct = 0): StandaloneTemplateResponse {
133
-		$clientName = $this->getClientName();
134
-		$client = null;
135
-		if ($clientIdentifier !== '') {
136
-			$client = $this->clientMapper->getByIdentifier($clientIdentifier);
137
-			$clientName = $client->getName();
138
-		}
139
-
140
-		// No valid clientIdentifier given and no valid API Request (APIRequest header not set)
141
-		$clientRequest = $this->request->getHeader('OCS-APIREQUEST');
142
-		if ($clientRequest !== 'true' && $client === null) {
143
-			return new StandaloneTemplateResponse(
144
-				$this->appName,
145
-				'error',
146
-				[
147
-					'errors' =>
148
-					[
149
-						[
150
-							'error' => 'Access Forbidden',
151
-							'hint' => 'Invalid request',
152
-						],
153
-					],
154
-				],
155
-				'guest'
156
-			);
157
-		}
158
-
159
-		$stateToken = $this->random->generate(
160
-			64,
161
-			ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS
162
-		);
163
-		$this->session->set(self::STATE_NAME, $stateToken);
164
-
165
-		$csp = new Http\ContentSecurityPolicy();
166
-		if ($client) {
167
-			$csp->addAllowedFormActionDomain($client->getRedirectUri());
168
-		} else {
169
-			$csp->addAllowedFormActionDomain('nc://*');
170
-		}
171
-
172
-		$response = new StandaloneTemplateResponse(
173
-			$this->appName,
174
-			'loginflow/authpicker',
175
-			[
176
-				'client' => $clientName,
177
-				'clientIdentifier' => $clientIdentifier,
178
-				'instanceName' => $this->defaults->getName(),
179
-				'urlGenerator' => $this->urlGenerator,
180
-				'stateToken' => $stateToken,
181
-				'serverHost' => $this->getServerPath(),
182
-				'oauthState' => $this->session->get('oauth.state'),
183
-				'user' => $user,
184
-				'direct' => $direct,
185
-			],
186
-			'guest'
187
-		);
188
-
189
-		$response->setContentSecurityPolicy($csp);
190
-		return $response;
191
-	}
192
-
193
-	/**
194
-	 * @NoAdminRequired
195
-	 * @NoCSRFRequired
196
-	 * @NoSameSiteCookieRequired
197
-	 */
198
-	#[UseSession]
199
-	public function grantPage(string $stateToken = '',
200
-				  string $clientIdentifier = '',
201
-				  int $direct = 0): StandaloneTemplateResponse {
202
-		if (!$this->isValidToken($stateToken)) {
203
-			return $this->stateTokenForbiddenResponse();
204
-		}
205
-
206
-		$clientName = $this->getClientName();
207
-		$client = null;
208
-		if ($clientIdentifier !== '') {
209
-			$client = $this->clientMapper->getByIdentifier($clientIdentifier);
210
-			$clientName = $client->getName();
211
-		}
212
-
213
-		$csp = new Http\ContentSecurityPolicy();
214
-		if ($client) {
215
-			$csp->addAllowedFormActionDomain($client->getRedirectUri());
216
-		} else {
217
-			$csp->addAllowedFormActionDomain('nc://*');
218
-		}
219
-
220
-		/** @var IUser $user */
221
-		$user = $this->userSession->getUser();
222
-
223
-		$response = new StandaloneTemplateResponse(
224
-			$this->appName,
225
-			'loginflow/grant',
226
-			[
227
-				'userId' => $user->getUID(),
228
-				'userDisplayName' => $user->getDisplayName(),
229
-				'client' => $clientName,
230
-				'clientIdentifier' => $clientIdentifier,
231
-				'instanceName' => $this->defaults->getName(),
232
-				'urlGenerator' => $this->urlGenerator,
233
-				'stateToken' => $stateToken,
234
-				'serverHost' => $this->getServerPath(),
235
-				'oauthState' => $this->session->get('oauth.state'),
236
-				'direct' => $direct,
237
-			],
238
-			'guest'
239
-		);
240
-
241
-		$response->setContentSecurityPolicy($csp);
242
-		return $response;
243
-	}
244
-
245
-	/**
246
-	 * @NoAdminRequired
247
-	 *
248
-	 * @return Http\RedirectResponse|Response
249
-	 */
250
-	#[UseSession]
251
-	public function generateAppPassword(string $stateToken,
252
-										string $clientIdentifier = '') {
253
-		if (!$this->isValidToken($stateToken)) {
254
-			$this->session->remove(self::STATE_NAME);
255
-			return $this->stateTokenForbiddenResponse();
256
-		}
257
-
258
-		$this->session->remove(self::STATE_NAME);
259
-
260
-		try {
261
-			$sessionId = $this->session->getId();
262
-		} catch (SessionNotAvailableException $ex) {
263
-			$response = new Response();
264
-			$response->setStatus(Http::STATUS_FORBIDDEN);
265
-			return $response;
266
-		}
267
-
268
-		try {
269
-			$sessionToken = $this->tokenProvider->getToken($sessionId);
270
-			$loginName = $sessionToken->getLoginName();
271
-			try {
272
-				$password = $this->tokenProvider->getPassword($sessionToken, $sessionId);
273
-			} catch (PasswordlessTokenException $ex) {
274
-				$password = null;
275
-			}
276
-		} catch (InvalidTokenException $ex) {
277
-			$response = new Response();
278
-			$response->setStatus(Http::STATUS_FORBIDDEN);
279
-			return $response;
280
-		}
281
-
282
-		$clientName = $this->getClientName();
283
-		$client = false;
284
-		if ($clientIdentifier !== '') {
285
-			$client = $this->clientMapper->getByIdentifier($clientIdentifier);
286
-			$clientName = $client->getName();
287
-		}
288
-
289
-		$token = $this->random->generate(72, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
290
-		$uid = $this->userSession->getUser()->getUID();
291
-		$generatedToken = $this->tokenProvider->generateToken(
292
-			$token,
293
-			$uid,
294
-			$loginName,
295
-			$password,
296
-			$clientName,
297
-			IToken::PERMANENT_TOKEN,
298
-			IToken::DO_NOT_REMEMBER
299
-		);
300
-
301
-		if ($client) {
302
-			$code = $this->random->generate(128, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
303
-			$accessToken = new AccessToken();
304
-			$accessToken->setClientId($client->getId());
305
-			$accessToken->setEncryptedToken($this->crypto->encrypt($token, $code));
306
-			$accessToken->setHashedCode(hash('sha512', $code));
307
-			$accessToken->setTokenId($generatedToken->getId());
308
-			$this->accessTokenMapper->insert($accessToken);
309
-
310
-			$redirectUri = $client->getRedirectUri();
311
-
312
-			if (parse_url($redirectUri, PHP_URL_QUERY)) {
313
-				$redirectUri .= '&';
314
-			} else {
315
-				$redirectUri .= '?';
316
-			}
317
-
318
-			$redirectUri .= sprintf(
319
-				'state=%s&code=%s',
320
-				urlencode($this->session->get('oauth.state')),
321
-				urlencode($code)
322
-			);
323
-			$this->session->remove('oauth.state');
324
-		} else {
325
-			$redirectUri = 'nc://login/server:' . $this->getServerPath() . '&user:' . urlencode($loginName) . '&password:' . urlencode($token);
326
-
327
-			// Clear the token from the login here
328
-			$this->tokenProvider->invalidateToken($sessionId);
329
-		}
330
-
331
-		$this->eventDispatcher->dispatchTyped(
332
-			new AppPasswordCreatedEvent($generatedToken)
333
-		);
334
-
335
-		return new Http\RedirectResponse($redirectUri);
336
-	}
337
-
338
-	/**
339
-	 * @PublicPage
340
-	 */
341
-	public function apptokenRedirect(string $stateToken, string $user, string $password): Response {
342
-		if (!$this->isValidToken($stateToken)) {
343
-			return $this->stateTokenForbiddenResponse();
344
-		}
345
-
346
-		try {
347
-			$token = $this->tokenProvider->getToken($password);
348
-			if ($token->getLoginName() !== $user) {
349
-				throw new InvalidTokenException('login name does not match');
350
-			}
351
-		} catch (InvalidTokenException $e) {
352
-			$response = new StandaloneTemplateResponse(
353
-				$this->appName,
354
-				'403',
355
-				[
356
-					'message' => $this->l10n->t('Invalid app password'),
357
-				],
358
-				'guest'
359
-			);
360
-			$response->setStatus(Http::STATUS_FORBIDDEN);
361
-			return $response;
362
-		}
363
-
364
-		$redirectUri = 'nc://login/server:' . $this->getServerPath() . '&user:' . urlencode($user) . '&password:' . urlencode($password);
365
-		return new Http\RedirectResponse($redirectUri);
366
-	}
367
-
368
-	private function getServerPath(): string {
369
-		$serverPostfix = '';
370
-
371
-		if (strpos($this->request->getRequestUri(), '/index.php') !== false) {
372
-			$serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/index.php'));
373
-		} elseif (strpos($this->request->getRequestUri(), '/login/flow') !== false) {
374
-			$serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/login/flow'));
375
-		}
376
-
377
-		$protocol = $this->request->getServerProtocol();
378
-
379
-		if ($protocol !== "https") {
380
-			$xForwardedProto = $this->request->getHeader('X-Forwarded-Proto');
381
-			$xForwardedSSL = $this->request->getHeader('X-Forwarded-Ssl');
382
-			if ($xForwardedProto === 'https' || $xForwardedSSL === 'on') {
383
-				$protocol = 'https';
384
-			}
385
-		}
386
-
387
-		return $protocol . "://" . $this->request->getServerHost() . $serverPostfix;
388
-	}
60
+    private IUserSession $userSession;
61
+    private IL10N $l10n;
62
+    private Defaults $defaults;
63
+    private ISession $session;
64
+    private IProvider $tokenProvider;
65
+    private ISecureRandom $random;
66
+    private IURLGenerator $urlGenerator;
67
+    private ClientMapper $clientMapper;
68
+    private AccessTokenMapper $accessTokenMapper;
69
+    private ICrypto $crypto;
70
+    private IEventDispatcher $eventDispatcher;
71
+
72
+    public const STATE_NAME = 'client.flow.state.token';
73
+
74
+    public function __construct(string $appName,
75
+                                IRequest $request,
76
+                                IUserSession $userSession,
77
+                                IL10N $l10n,
78
+                                Defaults $defaults,
79
+                                ISession $session,
80
+                                IProvider $tokenProvider,
81
+                                ISecureRandom $random,
82
+                                IURLGenerator $urlGenerator,
83
+                                ClientMapper $clientMapper,
84
+                                AccessTokenMapper $accessTokenMapper,
85
+                                ICrypto $crypto,
86
+                                IEventDispatcher $eventDispatcher) {
87
+        parent::__construct($appName, $request);
88
+        $this->userSession = $userSession;
89
+        $this->l10n = $l10n;
90
+        $this->defaults = $defaults;
91
+        $this->session = $session;
92
+        $this->tokenProvider = $tokenProvider;
93
+        $this->random = $random;
94
+        $this->urlGenerator = $urlGenerator;
95
+        $this->clientMapper = $clientMapper;
96
+        $this->accessTokenMapper = $accessTokenMapper;
97
+        $this->crypto = $crypto;
98
+        $this->eventDispatcher = $eventDispatcher;
99
+    }
100
+
101
+    private function getClientName(): string {
102
+        $userAgent = $this->request->getHeader('USER_AGENT');
103
+        return $userAgent !== '' ? $userAgent : 'unknown';
104
+    }
105
+
106
+    private function isValidToken(string $stateToken): bool {
107
+        $currentToken = $this->session->get(self::STATE_NAME);
108
+        if (!is_string($currentToken)) {
109
+            return false;
110
+        }
111
+        return hash_equals($currentToken, $stateToken);
112
+    }
113
+
114
+    private function stateTokenForbiddenResponse(): StandaloneTemplateResponse {
115
+        $response = new StandaloneTemplateResponse(
116
+            $this->appName,
117
+            '403',
118
+            [
119
+                'message' => $this->l10n->t('State token does not match'),
120
+            ],
121
+            'guest'
122
+        );
123
+        $response->setStatus(Http::STATUS_FORBIDDEN);
124
+        return $response;
125
+    }
126
+
127
+    /**
128
+     * @PublicPage
129
+     * @NoCSRFRequired
130
+     */
131
+    #[UseSession]
132
+    public function showAuthPickerPage(string $clientIdentifier = '', string $user = '', int $direct = 0): StandaloneTemplateResponse {
133
+        $clientName = $this->getClientName();
134
+        $client = null;
135
+        if ($clientIdentifier !== '') {
136
+            $client = $this->clientMapper->getByIdentifier($clientIdentifier);
137
+            $clientName = $client->getName();
138
+        }
139
+
140
+        // No valid clientIdentifier given and no valid API Request (APIRequest header not set)
141
+        $clientRequest = $this->request->getHeader('OCS-APIREQUEST');
142
+        if ($clientRequest !== 'true' && $client === null) {
143
+            return new StandaloneTemplateResponse(
144
+                $this->appName,
145
+                'error',
146
+                [
147
+                    'errors' =>
148
+                    [
149
+                        [
150
+                            'error' => 'Access Forbidden',
151
+                            'hint' => 'Invalid request',
152
+                        ],
153
+                    ],
154
+                ],
155
+                'guest'
156
+            );
157
+        }
158
+
159
+        $stateToken = $this->random->generate(
160
+            64,
161
+            ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS
162
+        );
163
+        $this->session->set(self::STATE_NAME, $stateToken);
164
+
165
+        $csp = new Http\ContentSecurityPolicy();
166
+        if ($client) {
167
+            $csp->addAllowedFormActionDomain($client->getRedirectUri());
168
+        } else {
169
+            $csp->addAllowedFormActionDomain('nc://*');
170
+        }
171
+
172
+        $response = new StandaloneTemplateResponse(
173
+            $this->appName,
174
+            'loginflow/authpicker',
175
+            [
176
+                'client' => $clientName,
177
+                'clientIdentifier' => $clientIdentifier,
178
+                'instanceName' => $this->defaults->getName(),
179
+                'urlGenerator' => $this->urlGenerator,
180
+                'stateToken' => $stateToken,
181
+                'serverHost' => $this->getServerPath(),
182
+                'oauthState' => $this->session->get('oauth.state'),
183
+                'user' => $user,
184
+                'direct' => $direct,
185
+            ],
186
+            'guest'
187
+        );
188
+
189
+        $response->setContentSecurityPolicy($csp);
190
+        return $response;
191
+    }
192
+
193
+    /**
194
+     * @NoAdminRequired
195
+     * @NoCSRFRequired
196
+     * @NoSameSiteCookieRequired
197
+     */
198
+    #[UseSession]
199
+    public function grantPage(string $stateToken = '',
200
+                    string $clientIdentifier = '',
201
+                    int $direct = 0): StandaloneTemplateResponse {
202
+        if (!$this->isValidToken($stateToken)) {
203
+            return $this->stateTokenForbiddenResponse();
204
+        }
205
+
206
+        $clientName = $this->getClientName();
207
+        $client = null;
208
+        if ($clientIdentifier !== '') {
209
+            $client = $this->clientMapper->getByIdentifier($clientIdentifier);
210
+            $clientName = $client->getName();
211
+        }
212
+
213
+        $csp = new Http\ContentSecurityPolicy();
214
+        if ($client) {
215
+            $csp->addAllowedFormActionDomain($client->getRedirectUri());
216
+        } else {
217
+            $csp->addAllowedFormActionDomain('nc://*');
218
+        }
219
+
220
+        /** @var IUser $user */
221
+        $user = $this->userSession->getUser();
222
+
223
+        $response = new StandaloneTemplateResponse(
224
+            $this->appName,
225
+            'loginflow/grant',
226
+            [
227
+                'userId' => $user->getUID(),
228
+                'userDisplayName' => $user->getDisplayName(),
229
+                'client' => $clientName,
230
+                'clientIdentifier' => $clientIdentifier,
231
+                'instanceName' => $this->defaults->getName(),
232
+                'urlGenerator' => $this->urlGenerator,
233
+                'stateToken' => $stateToken,
234
+                'serverHost' => $this->getServerPath(),
235
+                'oauthState' => $this->session->get('oauth.state'),
236
+                'direct' => $direct,
237
+            ],
238
+            'guest'
239
+        );
240
+
241
+        $response->setContentSecurityPolicy($csp);
242
+        return $response;
243
+    }
244
+
245
+    /**
246
+     * @NoAdminRequired
247
+     *
248
+     * @return Http\RedirectResponse|Response
249
+     */
250
+    #[UseSession]
251
+    public function generateAppPassword(string $stateToken,
252
+                                        string $clientIdentifier = '') {
253
+        if (!$this->isValidToken($stateToken)) {
254
+            $this->session->remove(self::STATE_NAME);
255
+            return $this->stateTokenForbiddenResponse();
256
+        }
257
+
258
+        $this->session->remove(self::STATE_NAME);
259
+
260
+        try {
261
+            $sessionId = $this->session->getId();
262
+        } catch (SessionNotAvailableException $ex) {
263
+            $response = new Response();
264
+            $response->setStatus(Http::STATUS_FORBIDDEN);
265
+            return $response;
266
+        }
267
+
268
+        try {
269
+            $sessionToken = $this->tokenProvider->getToken($sessionId);
270
+            $loginName = $sessionToken->getLoginName();
271
+            try {
272
+                $password = $this->tokenProvider->getPassword($sessionToken, $sessionId);
273
+            } catch (PasswordlessTokenException $ex) {
274
+                $password = null;
275
+            }
276
+        } catch (InvalidTokenException $ex) {
277
+            $response = new Response();
278
+            $response->setStatus(Http::STATUS_FORBIDDEN);
279
+            return $response;
280
+        }
281
+
282
+        $clientName = $this->getClientName();
283
+        $client = false;
284
+        if ($clientIdentifier !== '') {
285
+            $client = $this->clientMapper->getByIdentifier($clientIdentifier);
286
+            $clientName = $client->getName();
287
+        }
288
+
289
+        $token = $this->random->generate(72, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
290
+        $uid = $this->userSession->getUser()->getUID();
291
+        $generatedToken = $this->tokenProvider->generateToken(
292
+            $token,
293
+            $uid,
294
+            $loginName,
295
+            $password,
296
+            $clientName,
297
+            IToken::PERMANENT_TOKEN,
298
+            IToken::DO_NOT_REMEMBER
299
+        );
300
+
301
+        if ($client) {
302
+            $code = $this->random->generate(128, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
303
+            $accessToken = new AccessToken();
304
+            $accessToken->setClientId($client->getId());
305
+            $accessToken->setEncryptedToken($this->crypto->encrypt($token, $code));
306
+            $accessToken->setHashedCode(hash('sha512', $code));
307
+            $accessToken->setTokenId($generatedToken->getId());
308
+            $this->accessTokenMapper->insert($accessToken);
309
+
310
+            $redirectUri = $client->getRedirectUri();
311
+
312
+            if (parse_url($redirectUri, PHP_URL_QUERY)) {
313
+                $redirectUri .= '&';
314
+            } else {
315
+                $redirectUri .= '?';
316
+            }
317
+
318
+            $redirectUri .= sprintf(
319
+                'state=%s&code=%s',
320
+                urlencode($this->session->get('oauth.state')),
321
+                urlencode($code)
322
+            );
323
+            $this->session->remove('oauth.state');
324
+        } else {
325
+            $redirectUri = 'nc://login/server:' . $this->getServerPath() . '&user:' . urlencode($loginName) . '&password:' . urlencode($token);
326
+
327
+            // Clear the token from the login here
328
+            $this->tokenProvider->invalidateToken($sessionId);
329
+        }
330
+
331
+        $this->eventDispatcher->dispatchTyped(
332
+            new AppPasswordCreatedEvent($generatedToken)
333
+        );
334
+
335
+        return new Http\RedirectResponse($redirectUri);
336
+    }
337
+
338
+    /**
339
+     * @PublicPage
340
+     */
341
+    public function apptokenRedirect(string $stateToken, string $user, string $password): Response {
342
+        if (!$this->isValidToken($stateToken)) {
343
+            return $this->stateTokenForbiddenResponse();
344
+        }
345
+
346
+        try {
347
+            $token = $this->tokenProvider->getToken($password);
348
+            if ($token->getLoginName() !== $user) {
349
+                throw new InvalidTokenException('login name does not match');
350
+            }
351
+        } catch (InvalidTokenException $e) {
352
+            $response = new StandaloneTemplateResponse(
353
+                $this->appName,
354
+                '403',
355
+                [
356
+                    'message' => $this->l10n->t('Invalid app password'),
357
+                ],
358
+                'guest'
359
+            );
360
+            $response->setStatus(Http::STATUS_FORBIDDEN);
361
+            return $response;
362
+        }
363
+
364
+        $redirectUri = 'nc://login/server:' . $this->getServerPath() . '&user:' . urlencode($user) . '&password:' . urlencode($password);
365
+        return new Http\RedirectResponse($redirectUri);
366
+    }
367
+
368
+    private function getServerPath(): string {
369
+        $serverPostfix = '';
370
+
371
+        if (strpos($this->request->getRequestUri(), '/index.php') !== false) {
372
+            $serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/index.php'));
373
+        } elseif (strpos($this->request->getRequestUri(), '/login/flow') !== false) {
374
+            $serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/login/flow'));
375
+        }
376
+
377
+        $protocol = $this->request->getServerProtocol();
378
+
379
+        if ($protocol !== "https") {
380
+            $xForwardedProto = $this->request->getHeader('X-Forwarded-Proto');
381
+            $xForwardedSSL = $this->request->getHeader('X-Forwarded-Ssl');
382
+            if ($xForwardedProto === 'https' || $xForwardedSSL === 'on') {
383
+                $protocol = 'https';
384
+            }
385
+        }
386
+
387
+        return $protocol . "://" . $this->request->getServerHost() . $serverPostfix;
388
+    }
389 389
 }
Please login to merge, or discard this patch.
core/Controller/ClientFlowLoginV2Controller.php 1 patch
Indentation   +301 added lines, -301 removed lines patch added patch discarded remove patch
@@ -48,305 +48,305 @@
 block discarded – undo
48 48
 use OCP\Security\ISecureRandom;
49 49
 
50 50
 class ClientFlowLoginV2Controller extends Controller {
51
-	public const TOKEN_NAME = 'client.flow.v2.login.token';
52
-	public const STATE_NAME = 'client.flow.v2.state.token';
53
-
54
-	private LoginFlowV2Service $loginFlowV2Service;
55
-	private IURLGenerator $urlGenerator;
56
-	private IUserSession $userSession;
57
-	private ISession $session;
58
-	private ISecureRandom $random;
59
-	private Defaults $defaults;
60
-	private ?string $userId;
61
-	private IL10N $l10n;
62
-
63
-	public function __construct(string $appName,
64
-								IRequest $request,
65
-								LoginFlowV2Service $loginFlowV2Service,
66
-								IURLGenerator $urlGenerator,
67
-								ISession $session,
68
-								IUserSession $userSession,
69
-								ISecureRandom $random,
70
-								Defaults $defaults,
71
-								?string $userId,
72
-								IL10N $l10n) {
73
-		parent::__construct($appName, $request);
74
-		$this->loginFlowV2Service = $loginFlowV2Service;
75
-		$this->urlGenerator = $urlGenerator;
76
-		$this->session = $session;
77
-		$this->userSession = $userSession;
78
-		$this->random = $random;
79
-		$this->defaults = $defaults;
80
-		$this->userId = $userId;
81
-		$this->l10n = $l10n;
82
-	}
83
-
84
-	/**
85
-	 * @NoCSRFRequired
86
-	 * @PublicPage
87
-	 */
88
-	public function poll(string $token): JSONResponse {
89
-		try {
90
-			$creds = $this->loginFlowV2Service->poll($token);
91
-		} catch (LoginFlowV2NotFoundException $e) {
92
-			return new JSONResponse([], Http::STATUS_NOT_FOUND);
93
-		}
94
-
95
-		return new JSONResponse($creds);
96
-	}
97
-
98
-	/**
99
-	 * @NoCSRFRequired
100
-	 * @PublicPage
101
-	 */
102
-	#[UseSession]
103
-	public function landing(string $token, $user = ''): Response {
104
-		if (!$this->loginFlowV2Service->startLoginFlow($token)) {
105
-			return $this->loginTokenForbiddenResponse();
106
-		}
107
-
108
-		$this->session->set(self::TOKEN_NAME, $token);
109
-
110
-		return new RedirectResponse(
111
-			$this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.showAuthPickerPage', ['user' => $user])
112
-		);
113
-	}
114
-
115
-	/**
116
-	 * @NoCSRFRequired
117
-	 * @PublicPage
118
-	 */
119
-	#[UseSession]
120
-	public function showAuthPickerPage($user = ''): StandaloneTemplateResponse {
121
-		try {
122
-			$flow = $this->getFlowByLoginToken();
123
-		} catch (LoginFlowV2NotFoundException $e) {
124
-			return $this->loginTokenForbiddenResponse();
125
-		}
126
-
127
-		$stateToken = $this->random->generate(
128
-			64,
129
-			ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS
130
-		);
131
-		$this->session->set(self::STATE_NAME, $stateToken);
132
-
133
-		return new StandaloneTemplateResponse(
134
-			$this->appName,
135
-			'loginflowv2/authpicker',
136
-			[
137
-				'client' => $flow->getClientName(),
138
-				'instanceName' => $this->defaults->getName(),
139
-				'urlGenerator' => $this->urlGenerator,
140
-				'stateToken' => $stateToken,
141
-				'user' => $user,
142
-			],
143
-			'guest'
144
-		);
145
-	}
146
-
147
-	/**
148
-	 * @NoAdminRequired
149
-	 * @NoCSRFRequired
150
-	 * @NoSameSiteCookieRequired
151
-	 */
152
-	#[UseSession]
153
-	public function grantPage(string $stateToken): StandaloneTemplateResponse {
154
-		if (!$this->isValidStateToken($stateToken)) {
155
-			return $this->stateTokenForbiddenResponse();
156
-		}
157
-
158
-		try {
159
-			$flow = $this->getFlowByLoginToken();
160
-		} catch (LoginFlowV2NotFoundException $e) {
161
-			return $this->loginTokenForbiddenResponse();
162
-		}
163
-
164
-		/** @var IUser $user */
165
-		$user = $this->userSession->getUser();
166
-
167
-		return new StandaloneTemplateResponse(
168
-			$this->appName,
169
-			'loginflowv2/grant',
170
-			[
171
-				'userId' => $user->getUID(),
172
-				'userDisplayName' => $user->getDisplayName(),
173
-				'client' => $flow->getClientName(),
174
-				'instanceName' => $this->defaults->getName(),
175
-				'urlGenerator' => $this->urlGenerator,
176
-				'stateToken' => $stateToken,
177
-			],
178
-			'guest'
179
-		);
180
-	}
181
-
182
-	/**
183
-	 * @PublicPage
184
-	 */
185
-	public function apptokenRedirect(string $stateToken, string $user, string $password) {
186
-		if (!$this->isValidStateToken($stateToken)) {
187
-			return $this->stateTokenForbiddenResponse();
188
-		}
189
-
190
-		try {
191
-			$this->getFlowByLoginToken();
192
-		} catch (LoginFlowV2NotFoundException $e) {
193
-			return $this->loginTokenForbiddenResponse();
194
-		}
195
-
196
-		$loginToken = $this->session->get(self::TOKEN_NAME);
197
-
198
-		// Clear session variables
199
-		$this->session->remove(self::TOKEN_NAME);
200
-		$this->session->remove(self::STATE_NAME);
201
-
202
-		try {
203
-			$token = \OC::$server->get(\OC\Authentication\Token\IProvider::class)->getToken($password);
204
-			if ($token->getLoginName() !== $user) {
205
-				throw new InvalidTokenException('login name does not match');
206
-			}
207
-		} catch (InvalidTokenException $e) {
208
-			$response = new StandaloneTemplateResponse(
209
-				$this->appName,
210
-				'403',
211
-				[
212
-					'message' => $this->l10n->t('Invalid app password'),
213
-				],
214
-				'guest'
215
-			);
216
-			$response->setStatus(Http::STATUS_FORBIDDEN);
217
-			return $response;
218
-		}
219
-
220
-		$result = $this->loginFlowV2Service->flowDoneWithAppPassword($loginToken, $this->getServerPath(), $token->getLoginName(), $password);
221
-		return $this->handleFlowDone($result);
222
-	}
223
-
224
-	/**
225
-	 * @NoAdminRequired
226
-	 */
227
-	#[UseSession]
228
-	public function generateAppPassword(string $stateToken): Response {
229
-		if (!$this->isValidStateToken($stateToken)) {
230
-			return $this->stateTokenForbiddenResponse();
231
-		}
232
-
233
-		try {
234
-			$this->getFlowByLoginToken();
235
-		} catch (LoginFlowV2NotFoundException $e) {
236
-			return $this->loginTokenForbiddenResponse();
237
-		}
238
-
239
-		$loginToken = $this->session->get(self::TOKEN_NAME);
240
-
241
-		// Clear session variables
242
-		$this->session->remove(self::TOKEN_NAME);
243
-		$this->session->remove(self::STATE_NAME);
244
-		$sessionId = $this->session->getId();
245
-
246
-		$result = $this->loginFlowV2Service->flowDone($loginToken, $sessionId, $this->getServerPath(), $this->userId);
247
-		return $this->handleFlowDone($result);
248
-	}
249
-
250
-	private function handleFlowDone(bool $result): StandaloneTemplateResponse {
251
-		if ($result) {
252
-			return new StandaloneTemplateResponse(
253
-				$this->appName,
254
-				'loginflowv2/done',
255
-				[],
256
-				'guest'
257
-			);
258
-		}
259
-
260
-		$response = new StandaloneTemplateResponse(
261
-			$this->appName,
262
-			'403',
263
-			[
264
-				'message' => $this->l10n->t('Could not complete login'),
265
-			],
266
-			'guest'
267
-		);
268
-		$response->setStatus(Http::STATUS_FORBIDDEN);
269
-		return $response;
270
-	}
271
-
272
-	/**
273
-	 * @NoCSRFRequired
274
-	 * @PublicPage
275
-	 */
276
-	public function init(): JSONResponse {
277
-		// Get client user agent
278
-		$userAgent = $this->request->getHeader('USER_AGENT');
279
-
280
-		$tokens = $this->loginFlowV2Service->createTokens($userAgent);
281
-
282
-		$data = [
283
-			'poll' => [
284
-				'token' => $tokens->getPollToken(),
285
-				'endpoint' => $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.poll')
286
-			],
287
-			'login' => $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.landing', ['token' => $tokens->getLoginToken()]),
288
-		];
289
-
290
-		return new JSONResponse($data);
291
-	}
292
-
293
-	private function isValidStateToken(string $stateToken): bool {
294
-		$currentToken = $this->session->get(self::STATE_NAME);
295
-		if (!is_string($stateToken) || !is_string($currentToken)) {
296
-			return false;
297
-		}
298
-		return hash_equals($currentToken, $stateToken);
299
-	}
300
-
301
-	private function stateTokenForbiddenResponse(): StandaloneTemplateResponse {
302
-		$response = new StandaloneTemplateResponse(
303
-			$this->appName,
304
-			'403',
305
-			[
306
-				'message' => $this->l10n->t('State token does not match'),
307
-			],
308
-			'guest'
309
-		);
310
-		$response->setStatus(Http::STATUS_FORBIDDEN);
311
-		return $response;
312
-	}
313
-
314
-	/**
315
-	 * @return LoginFlowV2
316
-	 * @throws LoginFlowV2NotFoundException
317
-	 */
318
-	private function getFlowByLoginToken(): LoginFlowV2 {
319
-		$currentToken = $this->session->get(self::TOKEN_NAME);
320
-		if (!is_string($currentToken)) {
321
-			throw new LoginFlowV2NotFoundException('Login token not set in session');
322
-		}
323
-
324
-		return $this->loginFlowV2Service->getByLoginToken($currentToken);
325
-	}
326
-
327
-	private function loginTokenForbiddenResponse(): StandaloneTemplateResponse {
328
-		$response = new StandaloneTemplateResponse(
329
-			$this->appName,
330
-			'403',
331
-			[
332
-				'message' => $this->l10n->t('Your login token is invalid or has expired'),
333
-			],
334
-			'guest'
335
-		);
336
-		$response->setStatus(Http::STATUS_FORBIDDEN);
337
-		return $response;
338
-	}
339
-
340
-	private function getServerPath(): string {
341
-		$serverPostfix = '';
342
-
343
-		if (strpos($this->request->getRequestUri(), '/index.php') !== false) {
344
-			$serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/index.php'));
345
-		} elseif (strpos($this->request->getRequestUri(), '/login/v2') !== false) {
346
-			$serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/login/v2'));
347
-		}
348
-
349
-		$protocol = $this->request->getServerProtocol();
350
-		return $protocol . '://' . $this->request->getServerHost() . $serverPostfix;
351
-	}
51
+    public const TOKEN_NAME = 'client.flow.v2.login.token';
52
+    public const STATE_NAME = 'client.flow.v2.state.token';
53
+
54
+    private LoginFlowV2Service $loginFlowV2Service;
55
+    private IURLGenerator $urlGenerator;
56
+    private IUserSession $userSession;
57
+    private ISession $session;
58
+    private ISecureRandom $random;
59
+    private Defaults $defaults;
60
+    private ?string $userId;
61
+    private IL10N $l10n;
62
+
63
+    public function __construct(string $appName,
64
+                                IRequest $request,
65
+                                LoginFlowV2Service $loginFlowV2Service,
66
+                                IURLGenerator $urlGenerator,
67
+                                ISession $session,
68
+                                IUserSession $userSession,
69
+                                ISecureRandom $random,
70
+                                Defaults $defaults,
71
+                                ?string $userId,
72
+                                IL10N $l10n) {
73
+        parent::__construct($appName, $request);
74
+        $this->loginFlowV2Service = $loginFlowV2Service;
75
+        $this->urlGenerator = $urlGenerator;
76
+        $this->session = $session;
77
+        $this->userSession = $userSession;
78
+        $this->random = $random;
79
+        $this->defaults = $defaults;
80
+        $this->userId = $userId;
81
+        $this->l10n = $l10n;
82
+    }
83
+
84
+    /**
85
+     * @NoCSRFRequired
86
+     * @PublicPage
87
+     */
88
+    public function poll(string $token): JSONResponse {
89
+        try {
90
+            $creds = $this->loginFlowV2Service->poll($token);
91
+        } catch (LoginFlowV2NotFoundException $e) {
92
+            return new JSONResponse([], Http::STATUS_NOT_FOUND);
93
+        }
94
+
95
+        return new JSONResponse($creds);
96
+    }
97
+
98
+    /**
99
+     * @NoCSRFRequired
100
+     * @PublicPage
101
+     */
102
+    #[UseSession]
103
+    public function landing(string $token, $user = ''): Response {
104
+        if (!$this->loginFlowV2Service->startLoginFlow($token)) {
105
+            return $this->loginTokenForbiddenResponse();
106
+        }
107
+
108
+        $this->session->set(self::TOKEN_NAME, $token);
109
+
110
+        return new RedirectResponse(
111
+            $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.showAuthPickerPage', ['user' => $user])
112
+        );
113
+    }
114
+
115
+    /**
116
+     * @NoCSRFRequired
117
+     * @PublicPage
118
+     */
119
+    #[UseSession]
120
+    public function showAuthPickerPage($user = ''): StandaloneTemplateResponse {
121
+        try {
122
+            $flow = $this->getFlowByLoginToken();
123
+        } catch (LoginFlowV2NotFoundException $e) {
124
+            return $this->loginTokenForbiddenResponse();
125
+        }
126
+
127
+        $stateToken = $this->random->generate(
128
+            64,
129
+            ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS
130
+        );
131
+        $this->session->set(self::STATE_NAME, $stateToken);
132
+
133
+        return new StandaloneTemplateResponse(
134
+            $this->appName,
135
+            'loginflowv2/authpicker',
136
+            [
137
+                'client' => $flow->getClientName(),
138
+                'instanceName' => $this->defaults->getName(),
139
+                'urlGenerator' => $this->urlGenerator,
140
+                'stateToken' => $stateToken,
141
+                'user' => $user,
142
+            ],
143
+            'guest'
144
+        );
145
+    }
146
+
147
+    /**
148
+     * @NoAdminRequired
149
+     * @NoCSRFRequired
150
+     * @NoSameSiteCookieRequired
151
+     */
152
+    #[UseSession]
153
+    public function grantPage(string $stateToken): StandaloneTemplateResponse {
154
+        if (!$this->isValidStateToken($stateToken)) {
155
+            return $this->stateTokenForbiddenResponse();
156
+        }
157
+
158
+        try {
159
+            $flow = $this->getFlowByLoginToken();
160
+        } catch (LoginFlowV2NotFoundException $e) {
161
+            return $this->loginTokenForbiddenResponse();
162
+        }
163
+
164
+        /** @var IUser $user */
165
+        $user = $this->userSession->getUser();
166
+
167
+        return new StandaloneTemplateResponse(
168
+            $this->appName,
169
+            'loginflowv2/grant',
170
+            [
171
+                'userId' => $user->getUID(),
172
+                'userDisplayName' => $user->getDisplayName(),
173
+                'client' => $flow->getClientName(),
174
+                'instanceName' => $this->defaults->getName(),
175
+                'urlGenerator' => $this->urlGenerator,
176
+                'stateToken' => $stateToken,
177
+            ],
178
+            'guest'
179
+        );
180
+    }
181
+
182
+    /**
183
+     * @PublicPage
184
+     */
185
+    public function apptokenRedirect(string $stateToken, string $user, string $password) {
186
+        if (!$this->isValidStateToken($stateToken)) {
187
+            return $this->stateTokenForbiddenResponse();
188
+        }
189
+
190
+        try {
191
+            $this->getFlowByLoginToken();
192
+        } catch (LoginFlowV2NotFoundException $e) {
193
+            return $this->loginTokenForbiddenResponse();
194
+        }
195
+
196
+        $loginToken = $this->session->get(self::TOKEN_NAME);
197
+
198
+        // Clear session variables
199
+        $this->session->remove(self::TOKEN_NAME);
200
+        $this->session->remove(self::STATE_NAME);
201
+
202
+        try {
203
+            $token = \OC::$server->get(\OC\Authentication\Token\IProvider::class)->getToken($password);
204
+            if ($token->getLoginName() !== $user) {
205
+                throw new InvalidTokenException('login name does not match');
206
+            }
207
+        } catch (InvalidTokenException $e) {
208
+            $response = new StandaloneTemplateResponse(
209
+                $this->appName,
210
+                '403',
211
+                [
212
+                    'message' => $this->l10n->t('Invalid app password'),
213
+                ],
214
+                'guest'
215
+            );
216
+            $response->setStatus(Http::STATUS_FORBIDDEN);
217
+            return $response;
218
+        }
219
+
220
+        $result = $this->loginFlowV2Service->flowDoneWithAppPassword($loginToken, $this->getServerPath(), $token->getLoginName(), $password);
221
+        return $this->handleFlowDone($result);
222
+    }
223
+
224
+    /**
225
+     * @NoAdminRequired
226
+     */
227
+    #[UseSession]
228
+    public function generateAppPassword(string $stateToken): Response {
229
+        if (!$this->isValidStateToken($stateToken)) {
230
+            return $this->stateTokenForbiddenResponse();
231
+        }
232
+
233
+        try {
234
+            $this->getFlowByLoginToken();
235
+        } catch (LoginFlowV2NotFoundException $e) {
236
+            return $this->loginTokenForbiddenResponse();
237
+        }
238
+
239
+        $loginToken = $this->session->get(self::TOKEN_NAME);
240
+
241
+        // Clear session variables
242
+        $this->session->remove(self::TOKEN_NAME);
243
+        $this->session->remove(self::STATE_NAME);
244
+        $sessionId = $this->session->getId();
245
+
246
+        $result = $this->loginFlowV2Service->flowDone($loginToken, $sessionId, $this->getServerPath(), $this->userId);
247
+        return $this->handleFlowDone($result);
248
+    }
249
+
250
+    private function handleFlowDone(bool $result): StandaloneTemplateResponse {
251
+        if ($result) {
252
+            return new StandaloneTemplateResponse(
253
+                $this->appName,
254
+                'loginflowv2/done',
255
+                [],
256
+                'guest'
257
+            );
258
+        }
259
+
260
+        $response = new StandaloneTemplateResponse(
261
+            $this->appName,
262
+            '403',
263
+            [
264
+                'message' => $this->l10n->t('Could not complete login'),
265
+            ],
266
+            'guest'
267
+        );
268
+        $response->setStatus(Http::STATUS_FORBIDDEN);
269
+        return $response;
270
+    }
271
+
272
+    /**
273
+     * @NoCSRFRequired
274
+     * @PublicPage
275
+     */
276
+    public function init(): JSONResponse {
277
+        // Get client user agent
278
+        $userAgent = $this->request->getHeader('USER_AGENT');
279
+
280
+        $tokens = $this->loginFlowV2Service->createTokens($userAgent);
281
+
282
+        $data = [
283
+            'poll' => [
284
+                'token' => $tokens->getPollToken(),
285
+                'endpoint' => $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.poll')
286
+            ],
287
+            'login' => $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.landing', ['token' => $tokens->getLoginToken()]),
288
+        ];
289
+
290
+        return new JSONResponse($data);
291
+    }
292
+
293
+    private function isValidStateToken(string $stateToken): bool {
294
+        $currentToken = $this->session->get(self::STATE_NAME);
295
+        if (!is_string($stateToken) || !is_string($currentToken)) {
296
+            return false;
297
+        }
298
+        return hash_equals($currentToken, $stateToken);
299
+    }
300
+
301
+    private function stateTokenForbiddenResponse(): StandaloneTemplateResponse {
302
+        $response = new StandaloneTemplateResponse(
303
+            $this->appName,
304
+            '403',
305
+            [
306
+                'message' => $this->l10n->t('State token does not match'),
307
+            ],
308
+            'guest'
309
+        );
310
+        $response->setStatus(Http::STATUS_FORBIDDEN);
311
+        return $response;
312
+    }
313
+
314
+    /**
315
+     * @return LoginFlowV2
316
+     * @throws LoginFlowV2NotFoundException
317
+     */
318
+    private function getFlowByLoginToken(): LoginFlowV2 {
319
+        $currentToken = $this->session->get(self::TOKEN_NAME);
320
+        if (!is_string($currentToken)) {
321
+            throw new LoginFlowV2NotFoundException('Login token not set in session');
322
+        }
323
+
324
+        return $this->loginFlowV2Service->getByLoginToken($currentToken);
325
+    }
326
+
327
+    private function loginTokenForbiddenResponse(): StandaloneTemplateResponse {
328
+        $response = new StandaloneTemplateResponse(
329
+            $this->appName,
330
+            '403',
331
+            [
332
+                'message' => $this->l10n->t('Your login token is invalid or has expired'),
333
+            ],
334
+            'guest'
335
+        );
336
+        $response->setStatus(Http::STATUS_FORBIDDEN);
337
+        return $response;
338
+    }
339
+
340
+    private function getServerPath(): string {
341
+        $serverPostfix = '';
342
+
343
+        if (strpos($this->request->getRequestUri(), '/index.php') !== false) {
344
+            $serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/index.php'));
345
+        } elseif (strpos($this->request->getRequestUri(), '/login/v2') !== false) {
346
+            $serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/login/v2'));
347
+        }
348
+
349
+        $protocol = $this->request->getServerProtocol();
350
+        return $protocol . '://' . $this->request->getServerHost() . $serverPostfix;
351
+    }
352 352
 }
Please login to merge, or discard this patch.
core/Controller/TwoFactorChallengeController.php 1 patch
Indentation   +199 added lines, -199 removed lines patch added patch discarded remove patch
@@ -42,228 +42,228 @@
 block discarded – undo
42 42
 use Psr\Log\LoggerInterface;
43 43
 
44 44
 class TwoFactorChallengeController extends Controller {
45
-	private Manager $twoFactorManager;
46
-	private IUserSession $userSession;
47
-	private ISession $session;
48
-	private LoggerInterface $logger;
49
-	private IURLGenerator $urlGenerator;
45
+    private Manager $twoFactorManager;
46
+    private IUserSession $userSession;
47
+    private ISession $session;
48
+    private LoggerInterface $logger;
49
+    private IURLGenerator $urlGenerator;
50 50
 
51
-	public function __construct($appName, IRequest $request, Manager $twoFactorManager, IUserSession $userSession,
52
-		ISession $session, IURLGenerator $urlGenerator, LoggerInterface $logger) {
53
-		parent::__construct($appName, $request);
54
-		$this->twoFactorManager = $twoFactorManager;
55
-		$this->userSession = $userSession;
56
-		$this->session = $session;
57
-		$this->urlGenerator = $urlGenerator;
58
-		$this->logger = $logger;
59
-	}
51
+    public function __construct($appName, IRequest $request, Manager $twoFactorManager, IUserSession $userSession,
52
+        ISession $session, IURLGenerator $urlGenerator, LoggerInterface $logger) {
53
+        parent::__construct($appName, $request);
54
+        $this->twoFactorManager = $twoFactorManager;
55
+        $this->userSession = $userSession;
56
+        $this->session = $session;
57
+        $this->urlGenerator = $urlGenerator;
58
+        $this->logger = $logger;
59
+    }
60 60
 
61
-	/**
62
-	 * @return string
63
-	 */
64
-	protected function getLogoutUrl() {
65
-		return OC_User::getLogoutUrl($this->urlGenerator);
66
-	}
61
+    /**
62
+     * @return string
63
+     */
64
+    protected function getLogoutUrl() {
65
+        return OC_User::getLogoutUrl($this->urlGenerator);
66
+    }
67 67
 
68
-	/**
69
-	 * @param IProvider[] $providers
70
-	 */
71
-	private function splitProvidersAndBackupCodes(array $providers): array {
72
-		$regular = [];
73
-		$backup = null;
74
-		foreach ($providers as $provider) {
75
-			if ($provider->getId() === 'backup_codes') {
76
-				$backup = $provider;
77
-			} else {
78
-				$regular[] = $provider;
79
-			}
80
-		}
68
+    /**
69
+     * @param IProvider[] $providers
70
+     */
71
+    private function splitProvidersAndBackupCodes(array $providers): array {
72
+        $regular = [];
73
+        $backup = null;
74
+        foreach ($providers as $provider) {
75
+            if ($provider->getId() === 'backup_codes') {
76
+                $backup = $provider;
77
+            } else {
78
+                $regular[] = $provider;
79
+            }
80
+        }
81 81
 
82
-		return [$regular, $backup];
83
-	}
82
+        return [$regular, $backup];
83
+    }
84 84
 
85
-	/**
86
-	 * @NoAdminRequired
87
-	 * @NoCSRFRequired
88
-	 * @TwoFactorSetUpDoneRequired
89
-	 *
90
-	 * @param string $redirect_url
91
-	 * @return StandaloneTemplateResponse
92
-	 */
93
-	public function selectChallenge($redirect_url) {
94
-		$user = $this->userSession->getUser();
95
-		$providerSet = $this->twoFactorManager->getProviderSet($user);
96
-		$allProviders = $providerSet->getProviders();
97
-		[$providers, $backupProvider] = $this->splitProvidersAndBackupCodes($allProviders);
98
-		$setupProviders = $this->twoFactorManager->getLoginSetupProviders($user);
85
+    /**
86
+     * @NoAdminRequired
87
+     * @NoCSRFRequired
88
+     * @TwoFactorSetUpDoneRequired
89
+     *
90
+     * @param string $redirect_url
91
+     * @return StandaloneTemplateResponse
92
+     */
93
+    public function selectChallenge($redirect_url) {
94
+        $user = $this->userSession->getUser();
95
+        $providerSet = $this->twoFactorManager->getProviderSet($user);
96
+        $allProviders = $providerSet->getProviders();
97
+        [$providers, $backupProvider] = $this->splitProvidersAndBackupCodes($allProviders);
98
+        $setupProviders = $this->twoFactorManager->getLoginSetupProviders($user);
99 99
 
100
-		$data = [
101
-			'providers' => $providers,
102
-			'backupProvider' => $backupProvider,
103
-			'providerMissing' => $providerSet->isProviderMissing(),
104
-			'redirect_url' => $redirect_url,
105
-			'logout_url' => $this->getLogoutUrl(),
106
-			'hasSetupProviders' => !empty($setupProviders),
107
-		];
108
-		return new StandaloneTemplateResponse($this->appName, 'twofactorselectchallenge', $data, 'guest');
109
-	}
100
+        $data = [
101
+            'providers' => $providers,
102
+            'backupProvider' => $backupProvider,
103
+            'providerMissing' => $providerSet->isProviderMissing(),
104
+            'redirect_url' => $redirect_url,
105
+            'logout_url' => $this->getLogoutUrl(),
106
+            'hasSetupProviders' => !empty($setupProviders),
107
+        ];
108
+        return new StandaloneTemplateResponse($this->appName, 'twofactorselectchallenge', $data, 'guest');
109
+    }
110 110
 
111
-	/**
112
-	 * @NoAdminRequired
113
-	 * @NoCSRFRequired
114
-	 * @TwoFactorSetUpDoneRequired
115
-	 *
116
-	 * @param string $challengeProviderId
117
-	 * @param string $redirect_url
118
-	 * @return StandaloneTemplateResponse|RedirectResponse
119
-	 */
120
-	#[UseSession]
121
-	public function showChallenge($challengeProviderId, $redirect_url) {
122
-		$user = $this->userSession->getUser();
123
-		$providerSet = $this->twoFactorManager->getProviderSet($user);
124
-		$provider = $providerSet->getProvider($challengeProviderId);
111
+    /**
112
+     * @NoAdminRequired
113
+     * @NoCSRFRequired
114
+     * @TwoFactorSetUpDoneRequired
115
+     *
116
+     * @param string $challengeProviderId
117
+     * @param string $redirect_url
118
+     * @return StandaloneTemplateResponse|RedirectResponse
119
+     */
120
+    #[UseSession]
121
+    public function showChallenge($challengeProviderId, $redirect_url) {
122
+        $user = $this->userSession->getUser();
123
+        $providerSet = $this->twoFactorManager->getProviderSet($user);
124
+        $provider = $providerSet->getProvider($challengeProviderId);
125 125
 
126
-		if (is_null($provider)) {
127
-			return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.selectChallenge'));
128
-		}
126
+        if (is_null($provider)) {
127
+            return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.selectChallenge'));
128
+        }
129 129
 
130
-		$backupProvider = $providerSet->getProvider('backup_codes');
131
-		if (!is_null($backupProvider) && $backupProvider->getId() === $provider->getId()) {
132
-			// Don't show the backup provider link if we're already showing that provider's challenge
133
-			$backupProvider = null;
134
-		}
130
+        $backupProvider = $providerSet->getProvider('backup_codes');
131
+        if (!is_null($backupProvider) && $backupProvider->getId() === $provider->getId()) {
132
+            // Don't show the backup provider link if we're already showing that provider's challenge
133
+            $backupProvider = null;
134
+        }
135 135
 
136
-		$errorMessage = '';
137
-		$error = false;
138
-		if ($this->session->exists('two_factor_auth_error')) {
139
-			$this->session->remove('two_factor_auth_error');
140
-			$error = true;
141
-			$errorMessage = $this->session->get("two_factor_auth_error_message");
142
-			$this->session->remove('two_factor_auth_error_message');
143
-		}
144
-		$tmpl = $provider->getTemplate($user);
145
-		$tmpl->assign('redirect_url', $redirect_url);
146
-		$data = [
147
-			'error' => $error,
148
-			'error_message' => $errorMessage,
149
-			'provider' => $provider,
150
-			'backupProvider' => $backupProvider,
151
-			'logout_url' => $this->getLogoutUrl(),
152
-			'redirect_url' => $redirect_url,
153
-			'template' => $tmpl->fetchPage(),
154
-		];
155
-		$response = new StandaloneTemplateResponse($this->appName, 'twofactorshowchallenge', $data, 'guest');
156
-		if ($provider instanceof IProvidesCustomCSP) {
157
-			$response->setContentSecurityPolicy($provider->getCSP());
158
-		}
159
-		return $response;
160
-	}
136
+        $errorMessage = '';
137
+        $error = false;
138
+        if ($this->session->exists('two_factor_auth_error')) {
139
+            $this->session->remove('two_factor_auth_error');
140
+            $error = true;
141
+            $errorMessage = $this->session->get("two_factor_auth_error_message");
142
+            $this->session->remove('two_factor_auth_error_message');
143
+        }
144
+        $tmpl = $provider->getTemplate($user);
145
+        $tmpl->assign('redirect_url', $redirect_url);
146
+        $data = [
147
+            'error' => $error,
148
+            'error_message' => $errorMessage,
149
+            'provider' => $provider,
150
+            'backupProvider' => $backupProvider,
151
+            'logout_url' => $this->getLogoutUrl(),
152
+            'redirect_url' => $redirect_url,
153
+            'template' => $tmpl->fetchPage(),
154
+        ];
155
+        $response = new StandaloneTemplateResponse($this->appName, 'twofactorshowchallenge', $data, 'guest');
156
+        if ($provider instanceof IProvidesCustomCSP) {
157
+            $response->setContentSecurityPolicy($provider->getCSP());
158
+        }
159
+        return $response;
160
+    }
161 161
 
162
-	/**
163
-	 * @NoAdminRequired
164
-	 * @NoCSRFRequired
165
-	 * @TwoFactorSetUpDoneRequired
166
-	 *
167
-	 * @UserRateThrottle(limit=5, period=100)
168
-	 *
169
-	 * @param string $challengeProviderId
170
-	 * @param string $challenge
171
-	 * @param string $redirect_url
172
-	 * @return RedirectResponse
173
-	 */
174
-	#[UseSession]
175
-	public function solveChallenge($challengeProviderId, $challenge, $redirect_url = null) {
176
-		$user = $this->userSession->getUser();
177
-		$provider = $this->twoFactorManager->getProvider($user, $challengeProviderId);
178
-		if (is_null($provider)) {
179
-			return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.selectChallenge'));
180
-		}
162
+    /**
163
+     * @NoAdminRequired
164
+     * @NoCSRFRequired
165
+     * @TwoFactorSetUpDoneRequired
166
+     *
167
+     * @UserRateThrottle(limit=5, period=100)
168
+     *
169
+     * @param string $challengeProviderId
170
+     * @param string $challenge
171
+     * @param string $redirect_url
172
+     * @return RedirectResponse
173
+     */
174
+    #[UseSession]
175
+    public function solveChallenge($challengeProviderId, $challenge, $redirect_url = null) {
176
+        $user = $this->userSession->getUser();
177
+        $provider = $this->twoFactorManager->getProvider($user, $challengeProviderId);
178
+        if (is_null($provider)) {
179
+            return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.selectChallenge'));
180
+        }
181 181
 
182
-		try {
183
-			if ($this->twoFactorManager->verifyChallenge($challengeProviderId, $user, $challenge)) {
184
-				if (!is_null($redirect_url)) {
185
-					return new RedirectResponse($this->urlGenerator->getAbsoluteURL(urldecode($redirect_url)));
186
-				}
187
-				return new RedirectResponse($this->urlGenerator->linkToDefaultPageUrl());
188
-			}
189
-		} catch (TwoFactorException $e) {
190
-			/*
182
+        try {
183
+            if ($this->twoFactorManager->verifyChallenge($challengeProviderId, $user, $challenge)) {
184
+                if (!is_null($redirect_url)) {
185
+                    return new RedirectResponse($this->urlGenerator->getAbsoluteURL(urldecode($redirect_url)));
186
+                }
187
+                return new RedirectResponse($this->urlGenerator->linkToDefaultPageUrl());
188
+            }
189
+        } catch (TwoFactorException $e) {
190
+            /*
191 191
 			 * The 2FA App threw an TwoFactorException. Now we display more
192 192
 			 * information to the user. The exception text is stored in the
193 193
 			 * session to be used in showChallenge()
194 194
 			 */
195
-			$this->session->set('two_factor_auth_error_message', $e->getMessage());
196
-		}
195
+            $this->session->set('two_factor_auth_error_message', $e->getMessage());
196
+        }
197 197
 
198
-		$ip = $this->request->getRemoteAddress();
199
-		$uid = $user->getUID();
200
-		$this->logger->warning("Two-factor challenge failed: $uid (Remote IP: $ip)");
201
-		$this->session->set('two_factor_auth_error', true);
202
-		return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.showChallenge', [
203
-			'challengeProviderId' => $provider->getId(),
204
-			'redirect_url' => $redirect_url,
205
-		]));
206
-	}
198
+        $ip = $this->request->getRemoteAddress();
199
+        $uid = $user->getUID();
200
+        $this->logger->warning("Two-factor challenge failed: $uid (Remote IP: $ip)");
201
+        $this->session->set('two_factor_auth_error', true);
202
+        return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.showChallenge', [
203
+            'challengeProviderId' => $provider->getId(),
204
+            'redirect_url' => $redirect_url,
205
+        ]));
206
+    }
207 207
 
208
-	/**
209
-	 * @NoAdminRequired
210
-	 * @NoCSRFRequired
211
-	 */
212
-	public function setupProviders(): StandaloneTemplateResponse {
213
-		$user = $this->userSession->getUser();
214
-		$setupProviders = $this->twoFactorManager->getLoginSetupProviders($user);
208
+    /**
209
+     * @NoAdminRequired
210
+     * @NoCSRFRequired
211
+     */
212
+    public function setupProviders(): StandaloneTemplateResponse {
213
+        $user = $this->userSession->getUser();
214
+        $setupProviders = $this->twoFactorManager->getLoginSetupProviders($user);
215 215
 
216
-		$data = [
217
-			'providers' => $setupProviders,
218
-			'logout_url' => $this->getLogoutUrl(),
219
-		];
216
+        $data = [
217
+            'providers' => $setupProviders,
218
+            'logout_url' => $this->getLogoutUrl(),
219
+        ];
220 220
 
221
-		return new StandaloneTemplateResponse($this->appName, 'twofactorsetupselection', $data, 'guest');
222
-	}
221
+        return new StandaloneTemplateResponse($this->appName, 'twofactorsetupselection', $data, 'guest');
222
+    }
223 223
 
224
-	/**
225
-	 * @NoAdminRequired
226
-	 * @NoCSRFRequired
227
-	 */
228
-	public function setupProvider(string $providerId) {
229
-		$user = $this->userSession->getUser();
230
-		$providers = $this->twoFactorManager->getLoginSetupProviders($user);
224
+    /**
225
+     * @NoAdminRequired
226
+     * @NoCSRFRequired
227
+     */
228
+    public function setupProvider(string $providerId) {
229
+        $user = $this->userSession->getUser();
230
+        $providers = $this->twoFactorManager->getLoginSetupProviders($user);
231 231
 
232
-		$provider = null;
233
-		foreach ($providers as $p) {
234
-			if ($p->getId() === $providerId) {
235
-				$provider = $p;
236
-				break;
237
-			}
238
-		}
232
+        $provider = null;
233
+        foreach ($providers as $p) {
234
+            if ($p->getId() === $providerId) {
235
+                $provider = $p;
236
+                break;
237
+            }
238
+        }
239 239
 
240
-		if ($provider === null) {
241
-			return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.selectChallenge'));
242
-		}
240
+        if ($provider === null) {
241
+            return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.selectChallenge'));
242
+        }
243 243
 
244
-		/** @var IActivatableAtLogin $provider */
245
-		$tmpl = $provider->getLoginSetup($user)->getBody();
246
-		$data = [
247
-			'provider' => $provider,
248
-			'logout_url' => $this->getLogoutUrl(),
249
-			'template' => $tmpl->fetchPage(),
250
-		];
251
-		$response = new StandaloneTemplateResponse($this->appName, 'twofactorsetupchallenge', $data, 'guest');
252
-		return $response;
253
-	}
244
+        /** @var IActivatableAtLogin $provider */
245
+        $tmpl = $provider->getLoginSetup($user)->getBody();
246
+        $data = [
247
+            'provider' => $provider,
248
+            'logout_url' => $this->getLogoutUrl(),
249
+            'template' => $tmpl->fetchPage(),
250
+        ];
251
+        $response = new StandaloneTemplateResponse($this->appName, 'twofactorsetupchallenge', $data, 'guest');
252
+        return $response;
253
+    }
254 254
 
255
-	/**
256
-	 * @NoAdminRequired
257
-	 * @NoCSRFRequired
258
-	 *
259
-	 * @todo handle the extreme edge case of an invalid provider ID and redirect to the provider selection page
260
-	 */
261
-	public function confirmProviderSetup(string $providerId) {
262
-		return new RedirectResponse($this->urlGenerator->linkToRoute(
263
-			'core.TwoFactorChallenge.showChallenge',
264
-			[
265
-				'challengeProviderId' => $providerId,
266
-			]
267
-		));
268
-	}
255
+    /**
256
+     * @NoAdminRequired
257
+     * @NoCSRFRequired
258
+     *
259
+     * @todo handle the extreme edge case of an invalid provider ID and redirect to the provider selection page
260
+     */
261
+    public function confirmProviderSetup(string $providerId) {
262
+        return new RedirectResponse($this->urlGenerator->linkToRoute(
263
+            'core.TwoFactorChallenge.showChallenge',
264
+            [
265
+                'challengeProviderId' => $providerId,
266
+            ]
267
+        ));
268
+    }
269 269
 }
Please login to merge, or discard this patch.
lib/private/AppFramework/Middleware/SessionMiddleware.php 1 patch
Indentation   +50 added lines, -50 removed lines patch added patch discarded remove patch
@@ -37,61 +37,61 @@
 block discarded – undo
37 37
 use ReflectionMethod;
38 38
 
39 39
 class SessionMiddleware extends Middleware {
40
-	/** @var ControllerMethodReflector */
41
-	private $reflector;
40
+    /** @var ControllerMethodReflector */
41
+    private $reflector;
42 42
 
43
-	/** @var ISession */
44
-	private $session;
43
+    /** @var ISession */
44
+    private $session;
45 45
 
46
-	public function __construct(ControllerMethodReflector $reflector,
47
-								ISession $session) {
48
-		$this->reflector = $reflector;
49
-		$this->session = $session;
50
-	}
46
+    public function __construct(ControllerMethodReflector $reflector,
47
+                                ISession $session) {
48
+        $this->reflector = $reflector;
49
+        $this->session = $session;
50
+    }
51 51
 
52
-	/**
53
-	 * @param Controller $controller
54
-	 * @param string $methodName
55
-	 */
56
-	public function beforeController($controller, $methodName) {
57
-		/**
58
-		 * Annotation deprecated with Nextcloud 26
59
-		 */
60
-		$hasAnnotation = $this->reflector->hasAnnotation('UseSession');
61
-		if ($hasAnnotation) {
62
-			$this->session->reopen();
63
-			return;
64
-		}
52
+    /**
53
+     * @param Controller $controller
54
+     * @param string $methodName
55
+     */
56
+    public function beforeController($controller, $methodName) {
57
+        /**
58
+         * Annotation deprecated with Nextcloud 26
59
+         */
60
+        $hasAnnotation = $this->reflector->hasAnnotation('UseSession');
61
+        if ($hasAnnotation) {
62
+            $this->session->reopen();
63
+            return;
64
+        }
65 65
 
66
-		$reflectionMethod = new ReflectionMethod($controller, $methodName);
67
-		$hasAttribute = !empty($reflectionMethod->getAttributes(UseSession::class));
68
-		if ($hasAttribute) {
69
-			$this->session->reopen();
70
-		}
71
-	}
66
+        $reflectionMethod = new ReflectionMethod($controller, $methodName);
67
+        $hasAttribute = !empty($reflectionMethod->getAttributes(UseSession::class));
68
+        if ($hasAttribute) {
69
+            $this->session->reopen();
70
+        }
71
+    }
72 72
 
73
-	/**
74
-	 * @param Controller $controller
75
-	 * @param string $methodName
76
-	 * @param Response $response
77
-	 * @return Response
78
-	 */
79
-	public function afterController($controller, $methodName, Response $response) {
80
-		/**
81
-		 * Annotation deprecated with Nextcloud 26
82
-		 */
83
-		$hasAnnotation = $this->reflector->hasAnnotation('UseSession');
84
-		if ($hasAnnotation) {
85
-			$this->session->close();
86
-			return $response;
87
-		}
73
+    /**
74
+     * @param Controller $controller
75
+     * @param string $methodName
76
+     * @param Response $response
77
+     * @return Response
78
+     */
79
+    public function afterController($controller, $methodName, Response $response) {
80
+        /**
81
+         * Annotation deprecated with Nextcloud 26
82
+         */
83
+        $hasAnnotation = $this->reflector->hasAnnotation('UseSession');
84
+        if ($hasAnnotation) {
85
+            $this->session->close();
86
+            return $response;
87
+        }
88 88
 
89
-		$reflectionMethod = new ReflectionMethod($controller, $methodName);
90
-		$hasAttribute = !empty($reflectionMethod->getAttributes(UseSession::class));
91
-		if ($hasAttribute) {
92
-			$this->session->close();
93
-		}
89
+        $reflectionMethod = new ReflectionMethod($controller, $methodName);
90
+        $hasAttribute = !empty($reflectionMethod->getAttributes(UseSession::class));
91
+        if ($hasAttribute) {
92
+            $this->session->close();
93
+        }
94 94
 
95
-		return $response;
96
-	}
95
+        return $response;
96
+    }
97 97
 }
Please login to merge, or discard this patch.