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