Passed
Push — master ( 22a9c5...0b7449 )
by Joas
12:22 queued 12s
created
lib/private/User/Session.php 1 patch
Indentation   +941 added lines, -941 removed lines patch added patch discarded remove patch
@@ -90,945 +90,945 @@
 block discarded – undo
90 90
  */
91 91
 class Session implements IUserSession, Emitter {
92 92
 
93
-	/** @var Manager|PublicEmitter $manager */
94
-	private $manager;
95
-
96
-	/** @var ISession $session */
97
-	private $session;
98
-
99
-	/** @var ITimeFactory */
100
-	private $timeFactory;
101
-
102
-	/** @var IProvider */
103
-	private $tokenProvider;
104
-
105
-	/** @var IConfig */
106
-	private $config;
107
-
108
-	/** @var User $activeUser */
109
-	protected $activeUser;
110
-
111
-	/** @var ISecureRandom */
112
-	private $random;
113
-
114
-	/** @var ILockdownManager  */
115
-	private $lockdownManager;
116
-
117
-	/** @var ILogger */
118
-	private $logger;
119
-	/** @var IEventDispatcher */
120
-	private $dispatcher;
121
-
122
-	/**
123
-	 * @param Manager $manager
124
-	 * @param ISession $session
125
-	 * @param ITimeFactory $timeFactory
126
-	 * @param IProvider|null $tokenProvider
127
-	 * @param IConfig $config
128
-	 * @param ISecureRandom $random
129
-	 * @param ILockdownManager $lockdownManager
130
-	 * @param ILogger $logger
131
-	 */
132
-	public function __construct(Manager $manager,
133
-								ISession $session,
134
-								ITimeFactory $timeFactory,
135
-								?IProvider $tokenProvider,
136
-								IConfig $config,
137
-								ISecureRandom $random,
138
-								ILockdownManager $lockdownManager,
139
-								ILogger $logger,
140
-								IEventDispatcher $dispatcher
141
-	) {
142
-		$this->manager = $manager;
143
-		$this->session = $session;
144
-		$this->timeFactory = $timeFactory;
145
-		$this->tokenProvider = $tokenProvider;
146
-		$this->config = $config;
147
-		$this->random = $random;
148
-		$this->lockdownManager = $lockdownManager;
149
-		$this->logger = $logger;
150
-		$this->dispatcher = $dispatcher;
151
-	}
152
-
153
-	/**
154
-	 * @param IProvider $provider
155
-	 */
156
-	public function setTokenProvider(IProvider $provider) {
157
-		$this->tokenProvider = $provider;
158
-	}
159
-
160
-	/**
161
-	 * @param string $scope
162
-	 * @param string $method
163
-	 * @param callable $callback
164
-	 */
165
-	public function listen($scope, $method, callable $callback) {
166
-		$this->manager->listen($scope, $method, $callback);
167
-	}
168
-
169
-	/**
170
-	 * @param string $scope optional
171
-	 * @param string $method optional
172
-	 * @param callable $callback optional
173
-	 */
174
-	public function removeListener($scope = null, $method = null, callable $callback = null) {
175
-		$this->manager->removeListener($scope, $method, $callback);
176
-	}
177
-
178
-	/**
179
-	 * get the manager object
180
-	 *
181
-	 * @return Manager|PublicEmitter
182
-	 */
183
-	public function getManager() {
184
-		return $this->manager;
185
-	}
186
-
187
-	/**
188
-	 * get the session object
189
-	 *
190
-	 * @return ISession
191
-	 */
192
-	public function getSession() {
193
-		return $this->session;
194
-	}
195
-
196
-	/**
197
-	 * set the session object
198
-	 *
199
-	 * @param ISession $session
200
-	 */
201
-	public function setSession(ISession $session) {
202
-		if ($this->session instanceof ISession) {
203
-			$this->session->close();
204
-		}
205
-		$this->session = $session;
206
-		$this->activeUser = null;
207
-	}
208
-
209
-	/**
210
-	 * set the currently active user
211
-	 *
212
-	 * @param IUser|null $user
213
-	 */
214
-	public function setUser($user) {
215
-		if (is_null($user)) {
216
-			$this->session->remove('user_id');
217
-		} else {
218
-			$this->session->set('user_id', $user->getUID());
219
-		}
220
-		$this->activeUser = $user;
221
-	}
222
-
223
-	/**
224
-	 * get the current active user
225
-	 *
226
-	 * @return IUser|null Current user, otherwise null
227
-	 */
228
-	public function getUser() {
229
-		// FIXME: This is a quick'n dirty work-around for the incognito mode as
230
-		// described at https://github.com/owncloud/core/pull/12912#issuecomment-67391155
231
-		if (OC_User::isIncognitoMode()) {
232
-			return null;
233
-		}
234
-		if (is_null($this->activeUser)) {
235
-			$uid = $this->session->get('user_id');
236
-			if (is_null($uid)) {
237
-				return null;
238
-			}
239
-			$this->activeUser = $this->manager->get($uid);
240
-			if (is_null($this->activeUser)) {
241
-				return null;
242
-			}
243
-			$this->validateSession();
244
-		}
245
-		return $this->activeUser;
246
-	}
247
-
248
-	/**
249
-	 * Validate whether the current session is valid
250
-	 *
251
-	 * - For token-authenticated clients, the token validity is checked
252
-	 * - For browsers, the session token validity is checked
253
-	 */
254
-	protected function validateSession() {
255
-		$token = null;
256
-		$appPassword = $this->session->get('app_password');
257
-
258
-		if (is_null($appPassword)) {
259
-			try {
260
-				$token = $this->session->getId();
261
-			} catch (SessionNotAvailableException $ex) {
262
-				return;
263
-			}
264
-		} else {
265
-			$token = $appPassword;
266
-		}
267
-
268
-		if (!$this->validateToken($token)) {
269
-			// Session was invalidated
270
-			$this->logout();
271
-		}
272
-	}
273
-
274
-	/**
275
-	 * Checks whether the user is logged in
276
-	 *
277
-	 * @return bool if logged in
278
-	 */
279
-	public function isLoggedIn() {
280
-		$user = $this->getUser();
281
-		if (is_null($user)) {
282
-			return false;
283
-		}
284
-
285
-		return $user->isEnabled();
286
-	}
287
-
288
-	/**
289
-	 * set the login name
290
-	 *
291
-	 * @param string|null $loginName for the logged in user
292
-	 */
293
-	public function setLoginName($loginName) {
294
-		if (is_null($loginName)) {
295
-			$this->session->remove('loginname');
296
-		} else {
297
-			$this->session->set('loginname', $loginName);
298
-		}
299
-	}
300
-
301
-	/**
302
-	 * get the login name of the current user
303
-	 *
304
-	 * @return string
305
-	 */
306
-	public function getLoginName() {
307
-		if ($this->activeUser) {
308
-			return $this->session->get('loginname');
309
-		}
310
-
311
-		$uid = $this->session->get('user_id');
312
-		if ($uid) {
313
-			$this->activeUser = $this->manager->get($uid);
314
-			return $this->session->get('loginname');
315
-		}
316
-
317
-		return null;
318
-	}
319
-
320
-	/**
321
-	 * @return null|string
322
-	 */
323
-	public function getImpersonatingUserID(): ?string {
324
-		return $this->session->get('oldUserId');
325
-	}
326
-
327
-	public function setImpersonatingUserID(bool $useCurrentUser = true): void {
328
-		if ($useCurrentUser === false) {
329
-			$this->session->remove('oldUserId');
330
-			return;
331
-		}
332
-
333
-		$currentUser = $this->getUser();
334
-
335
-		if ($currentUser === null) {
336
-			throw new \OC\User\NoUserException();
337
-		}
338
-		$this->session->set('oldUserId', $currentUser->getUID());
339
-	}
340
-	/**
341
-	 * set the token id
342
-	 *
343
-	 * @param int|null $token that was used to log in
344
-	 */
345
-	protected function setToken($token) {
346
-		if ($token === null) {
347
-			$this->session->remove('token-id');
348
-		} else {
349
-			$this->session->set('token-id', $token);
350
-		}
351
-	}
352
-
353
-	/**
354
-	 * try to log in with the provided credentials
355
-	 *
356
-	 * @param string $uid
357
-	 * @param string $password
358
-	 * @return boolean|null
359
-	 * @throws LoginException
360
-	 */
361
-	public function login($uid, $password) {
362
-		$this->session->regenerateId();
363
-		if ($this->validateToken($password, $uid)) {
364
-			return $this->loginWithToken($password);
365
-		}
366
-		return $this->loginWithPassword($uid, $password);
367
-	}
368
-
369
-	/**
370
-	 * @param IUser $user
371
-	 * @param array $loginDetails
372
-	 * @param bool $regenerateSessionId
373
-	 * @return true returns true if login successful or an exception otherwise
374
-	 * @throws LoginException
375
-	 */
376
-	public function completeLogin(IUser $user, array $loginDetails, $regenerateSessionId = true) {
377
-		if (!$user->isEnabled()) {
378
-			// disabled users can not log in
379
-			// injecting l10n does not work - there is a circular dependency between session and \OCP\L10N\IFactory
380
-			$message = \OC::$server->getL10N('lib')->t('User disabled');
381
-			throw new LoginException($message);
382
-		}
383
-
384
-		if ($regenerateSessionId) {
385
-			$this->session->regenerateId();
386
-		}
387
-
388
-		$this->setUser($user);
389
-		$this->setLoginName($loginDetails['loginName']);
390
-
391
-		$isToken = isset($loginDetails['token']) && $loginDetails['token'] instanceof IToken;
392
-		if ($isToken) {
393
-			$this->setToken($loginDetails['token']->getId());
394
-			$this->lockdownManager->setToken($loginDetails['token']);
395
-			$firstTimeLogin = false;
396
-		} else {
397
-			$this->setToken(null);
398
-			$firstTimeLogin = $user->updateLastLoginTimestamp();
399
-		}
400
-
401
-		$this->dispatcher->dispatchTyped(new PostLoginEvent(
402
-			$user,
403
-			$loginDetails['loginName'],
404
-			$loginDetails['password'],
405
-			$isToken
406
-		));
407
-		$this->manager->emit('\OC\User', 'postLogin', [
408
-			$user,
409
-			$loginDetails['loginName'],
410
-			$loginDetails['password'],
411
-			$isToken,
412
-		]);
413
-		if ($this->isLoggedIn()) {
414
-			$this->prepareUserLogin($firstTimeLogin, $regenerateSessionId);
415
-			return true;
416
-		}
417
-
418
-		$message = \OC::$server->getL10N('lib')->t('Login canceled by app');
419
-		throw new LoginException($message);
420
-	}
421
-
422
-	/**
423
-	 * Tries to log in a client
424
-	 *
425
-	 * Checks token auth enforced
426
-	 * Checks 2FA enabled
427
-	 *
428
-	 * @param string $user
429
-	 * @param string $password
430
-	 * @param IRequest $request
431
-	 * @param OC\Security\Bruteforce\Throttler $throttler
432
-	 * @throws LoginException
433
-	 * @throws PasswordLoginForbiddenException
434
-	 * @return boolean
435
-	 */
436
-	public function logClientIn($user,
437
-								$password,
438
-								IRequest $request,
439
-								OC\Security\Bruteforce\Throttler $throttler) {
440
-		$currentDelay = $throttler->sleepDelay($request->getRemoteAddress(), 'login');
441
-
442
-		if ($this->manager instanceof PublicEmitter) {
443
-			$this->manager->emit('\OC\User', 'preLogin', [$user, $password]);
444
-		}
445
-
446
-		try {
447
-			$isTokenPassword = $this->isTokenPassword($password);
448
-		} catch (ExpiredTokenException $e) {
449
-			// Just return on an expired token no need to check further or record a failed login
450
-			return false;
451
-		}
452
-
453
-		if (!$isTokenPassword && $this->isTokenAuthEnforced()) {
454
-			throw new PasswordLoginForbiddenException();
455
-		}
456
-		if (!$isTokenPassword && $this->isTwoFactorEnforced($user)) {
457
-			throw new PasswordLoginForbiddenException();
458
-		}
459
-
460
-		// Try to login with this username and password
461
-		if (!$this->login($user, $password)) {
462
-
463
-			// Failed, maybe the user used their email address
464
-			$users = $this->manager->getByEmail($user);
465
-			if (!(\count($users) === 1 && $this->login($users[0]->getUID(), $password))) {
466
-				$this->logger->warning('Login failed: \'' . $user . '\' (Remote IP: \'' . \OC::$server->getRequest()->getRemoteAddress() . '\')', ['app' => 'core']);
467
-
468
-				$throttler->registerAttempt('login', $request->getRemoteAddress(), ['user' => $user]);
469
-
470
-				$this->dispatcher->dispatchTyped(new OC\Authentication\Events\LoginFailed($user));
471
-
472
-				if ($currentDelay === 0) {
473
-					$throttler->sleepDelay($request->getRemoteAddress(), 'login');
474
-				}
475
-				return false;
476
-			}
477
-		}
478
-
479
-		if ($isTokenPassword) {
480
-			$this->session->set('app_password', $password);
481
-		} elseif ($this->supportsCookies($request)) {
482
-			// Password login, but cookies supported -> create (browser) session token
483
-			$this->createSessionToken($request, $this->getUser()->getUID(), $user, $password);
484
-		}
485
-
486
-		return true;
487
-	}
488
-
489
-	protected function supportsCookies(IRequest $request) {
490
-		if (!is_null($request->getCookie('cookie_test'))) {
491
-			return true;
492
-		}
493
-		setcookie('cookie_test', 'test', $this->timeFactory->getTime() + 3600);
494
-		return false;
495
-	}
496
-
497
-	private function isTokenAuthEnforced() {
498
-		return $this->config->getSystemValue('token_auth_enforced', false);
499
-	}
500
-
501
-	protected function isTwoFactorEnforced($username) {
502
-		Util::emitHook(
503
-			'\OCA\Files_Sharing\API\Server2Server',
504
-			'preLoginNameUsedAsUserName',
505
-			['uid' => &$username]
506
-		);
507
-		$user = $this->manager->get($username);
508
-		if (is_null($user)) {
509
-			$users = $this->manager->getByEmail($username);
510
-			if (empty($users)) {
511
-				return false;
512
-			}
513
-			if (count($users) !== 1) {
514
-				return true;
515
-			}
516
-			$user = $users[0];
517
-		}
518
-		// DI not possible due to cyclic dependencies :'-/
519
-		return OC::$server->getTwoFactorAuthManager()->isTwoFactorAuthenticated($user);
520
-	}
521
-
522
-	/**
523
-	 * Check if the given 'password' is actually a device token
524
-	 *
525
-	 * @param string $password
526
-	 * @return boolean
527
-	 * @throws ExpiredTokenException
528
-	 */
529
-	public function isTokenPassword($password) {
530
-		try {
531
-			$this->tokenProvider->getToken($password);
532
-			return true;
533
-		} catch (ExpiredTokenException $e) {
534
-			throw $e;
535
-		} catch (InvalidTokenException $ex) {
536
-			$this->logger->logException($ex, [
537
-				'level' => ILogger::DEBUG,
538
-				'message' => 'Token is not valid: ' . $ex->getMessage(),
539
-			]);
540
-			return false;
541
-		}
542
-	}
543
-
544
-	protected function prepareUserLogin($firstTimeLogin, $refreshCsrfToken = true) {
545
-		if ($refreshCsrfToken) {
546
-			// TODO: mock/inject/use non-static
547
-			// Refresh the token
548
-			\OC::$server->getCsrfTokenManager()->refreshToken();
549
-		}
550
-
551
-		if ($firstTimeLogin) {
552
-			//we need to pass the user name, which may differ from login name
553
-			$user = $this->getUser()->getUID();
554
-			OC_Util::setupFS($user);
555
-
556
-			// TODO: lock necessary?
557
-			//trigger creation of user home and /files folder
558
-			$userFolder = \OC::$server->getUserFolder($user);
559
-
560
-			try {
561
-				// copy skeleton
562
-				\OC_Util::copySkeleton($user, $userFolder);
563
-			} catch (NotPermittedException $ex) {
564
-				// read only uses
565
-			}
566
-
567
-			// trigger any other initialization
568
-			\OC::$server->getEventDispatcher()->dispatch(IUser::class . '::firstLogin', new GenericEvent($this->getUser()));
569
-		}
570
-	}
571
-
572
-	/**
573
-	 * Tries to login the user with HTTP Basic Authentication
574
-	 *
575
-	 * @todo do not allow basic auth if the user is 2FA enforced
576
-	 * @param IRequest $request
577
-	 * @param OC\Security\Bruteforce\Throttler $throttler
578
-	 * @return boolean if the login was successful
579
-	 */
580
-	public function tryBasicAuthLogin(IRequest $request,
581
-									  OC\Security\Bruteforce\Throttler $throttler) {
582
-		if (!empty($request->server['PHP_AUTH_USER']) && !empty($request->server['PHP_AUTH_PW'])) {
583
-			try {
584
-				if ($this->logClientIn($request->server['PHP_AUTH_USER'], $request->server['PHP_AUTH_PW'], $request, $throttler)) {
585
-					/**
586
-					 * Add DAV authenticated. This should in an ideal world not be
587
-					 * necessary but the iOS App reads cookies from anywhere instead
588
-					 * only the DAV endpoint.
589
-					 * This makes sure that the cookies will be valid for the whole scope
590
-					 * @see https://github.com/owncloud/core/issues/22893
591
-					 */
592
-					$this->session->set(
593
-						Auth::DAV_AUTHENTICATED, $this->getUser()->getUID()
594
-					);
595
-
596
-					// Set the last-password-confirm session to make the sudo mode work
597
-					$this->session->set('last-password-confirm', $this->timeFactory->getTime());
598
-
599
-					return true;
600
-				}
601
-				// If credentials were provided, they need to be valid, otherwise we do boom
602
-				throw new LoginException();
603
-			} catch (PasswordLoginForbiddenException $ex) {
604
-				// Nothing to do
605
-			}
606
-		}
607
-		return false;
608
-	}
609
-
610
-	/**
611
-	 * Log an user in via login name and password
612
-	 *
613
-	 * @param string $uid
614
-	 * @param string $password
615
-	 * @return boolean
616
-	 * @throws LoginException if an app canceld the login process or the user is not enabled
617
-	 */
618
-	private function loginWithPassword($uid, $password) {
619
-		$user = $this->manager->checkPasswordNoLogging($uid, $password);
620
-		if ($user === false) {
621
-			// Password check failed
622
-			return false;
623
-		}
624
-
625
-		return $this->completeLogin($user, ['loginName' => $uid, 'password' => $password], false);
626
-	}
627
-
628
-	/**
629
-	 * Log an user in with a given token (id)
630
-	 *
631
-	 * @param string $token
632
-	 * @return boolean
633
-	 * @throws LoginException if an app canceled the login process or the user is not enabled
634
-	 */
635
-	private function loginWithToken($token) {
636
-		try {
637
-			$dbToken = $this->tokenProvider->getToken($token);
638
-		} catch (InvalidTokenException $ex) {
639
-			return false;
640
-		}
641
-		$uid = $dbToken->getUID();
642
-
643
-		// When logging in with token, the password must be decrypted first before passing to login hook
644
-		$password = '';
645
-		try {
646
-			$password = $this->tokenProvider->getPassword($dbToken, $token);
647
-		} catch (PasswordlessTokenException $ex) {
648
-			// Ignore and use empty string instead
649
-		}
650
-
651
-		$this->manager->emit('\OC\User', 'preLogin', [$dbToken->getLoginName(), $password]);
652
-
653
-		$user = $this->manager->get($uid);
654
-		if (is_null($user)) {
655
-			// user does not exist
656
-			return false;
657
-		}
658
-
659
-		return $this->completeLogin(
660
-			$user,
661
-			[
662
-				'loginName' => $dbToken->getLoginName(),
663
-				'password' => $password,
664
-				'token' => $dbToken
665
-			],
666
-			false);
667
-	}
668
-
669
-	/**
670
-	 * Create a new session token for the given user credentials
671
-	 *
672
-	 * @param IRequest $request
673
-	 * @param string $uid user UID
674
-	 * @param string $loginName login name
675
-	 * @param string $password
676
-	 * @param int $remember
677
-	 * @return boolean
678
-	 */
679
-	public function createSessionToken(IRequest $request, $uid, $loginName, $password = null, $remember = IToken::DO_NOT_REMEMBER) {
680
-		if (is_null($this->manager->get($uid))) {
681
-			// User does not exist
682
-			return false;
683
-		}
684
-		$name = isset($request->server['HTTP_USER_AGENT']) ? utf8_encode($request->server['HTTP_USER_AGENT']) : 'unknown browser';
685
-		try {
686
-			$sessionId = $this->session->getId();
687
-			$pwd = $this->getPassword($password);
688
-			// Make sure the current sessionId has no leftover tokens
689
-			$this->tokenProvider->invalidateToken($sessionId);
690
-			$this->tokenProvider->generateToken($sessionId, $uid, $loginName, $pwd, $name, IToken::TEMPORARY_TOKEN, $remember);
691
-			return true;
692
-		} catch (SessionNotAvailableException $ex) {
693
-			// This can happen with OCC, where a memory session is used
694
-			// if a memory session is used, we shouldn't create a session token anyway
695
-			return false;
696
-		}
697
-	}
698
-
699
-	/**
700
-	 * Checks if the given password is a token.
701
-	 * If yes, the password is extracted from the token.
702
-	 * If no, the same password is returned.
703
-	 *
704
-	 * @param string $password either the login password or a device token
705
-	 * @return string|null the password or null if none was set in the token
706
-	 */
707
-	private function getPassword($password) {
708
-		if (is_null($password)) {
709
-			// This is surely no token ;-)
710
-			return null;
711
-		}
712
-		try {
713
-			$token = $this->tokenProvider->getToken($password);
714
-			try {
715
-				return $this->tokenProvider->getPassword($token, $password);
716
-			} catch (PasswordlessTokenException $ex) {
717
-				return null;
718
-			}
719
-		} catch (InvalidTokenException $ex) {
720
-			return $password;
721
-		}
722
-	}
723
-
724
-	/**
725
-	 * @param IToken $dbToken
726
-	 * @param string $token
727
-	 * @return boolean
728
-	 */
729
-	private function checkTokenCredentials(IToken $dbToken, $token) {
730
-		// Check whether login credentials are still valid and the user was not disabled
731
-		// This check is performed each 5 minutes
732
-		$lastCheck = $dbToken->getLastCheck() ? : 0;
733
-		$now = $this->timeFactory->getTime();
734
-		if ($lastCheck > ($now - 60 * 5)) {
735
-			// Checked performed recently, nothing to do now
736
-			return true;
737
-		}
738
-
739
-		try {
740
-			$pwd = $this->tokenProvider->getPassword($dbToken, $token);
741
-		} catch (InvalidTokenException $ex) {
742
-			// An invalid token password was used -> log user out
743
-			return false;
744
-		} catch (PasswordlessTokenException $ex) {
745
-			// Token has no password
746
-
747
-			if (!is_null($this->activeUser) && !$this->activeUser->isEnabled()) {
748
-				$this->tokenProvider->invalidateToken($token);
749
-				return false;
750
-			}
751
-
752
-			$dbToken->setLastCheck($now);
753
-			$this->tokenProvider->updateToken($dbToken);
754
-			return true;
755
-		}
756
-
757
-		// Invalidate token if the user is no longer active
758
-		if (!is_null($this->activeUser) && !$this->activeUser->isEnabled()) {
759
-			$this->tokenProvider->invalidateToken($token);
760
-			return false;
761
-		}
762
-
763
-		// If the token password is no longer valid mark it as such
764
-		if ($this->manager->checkPassword($dbToken->getLoginName(), $pwd) === false) {
765
-			$this->tokenProvider->markPasswordInvalid($dbToken, $token);
766
-			// User is logged out
767
-			return false;
768
-		}
769
-
770
-		$dbToken->setLastCheck($now);
771
-		$this->tokenProvider->updateToken($dbToken);
772
-		return true;
773
-	}
774
-
775
-	/**
776
-	 * Check if the given token exists and performs password/user-enabled checks
777
-	 *
778
-	 * Invalidates the token if checks fail
779
-	 *
780
-	 * @param string $token
781
-	 * @param string $user login name
782
-	 * @return boolean
783
-	 */
784
-	private function validateToken($token, $user = null) {
785
-		try {
786
-			$dbToken = $this->tokenProvider->getToken($token);
787
-		} catch (InvalidTokenException $ex) {
788
-			return false;
789
-		}
790
-
791
-		// Check if login names match
792
-		if (!is_null($user) && $dbToken->getLoginName() !== $user) {
793
-			// TODO: this makes it impossible to use different login names on browser and client
794
-			// e.g. login by e-mail '[email protected]' on browser for generating the token will not
795
-			//      allow to use the client token with the login name 'user'.
796
-			$this->logger->error('App token login name does not match', [
797
-				'tokenLoginName' => $dbToken->getLoginName(),
798
-				'sessionLoginName' => $user,
799
-			]);
800
-
801
-			return false;
802
-		}
803
-
804
-		if (!$this->checkTokenCredentials($dbToken, $token)) {
805
-			return false;
806
-		}
807
-
808
-		// Update token scope
809
-		$this->lockdownManager->setToken($dbToken);
810
-
811
-		$this->tokenProvider->updateTokenActivity($dbToken);
812
-
813
-		return true;
814
-	}
815
-
816
-	/**
817
-	 * Tries to login the user with auth token header
818
-	 *
819
-	 * @param IRequest $request
820
-	 * @todo check remember me cookie
821
-	 * @return boolean
822
-	 */
823
-	public function tryTokenLogin(IRequest $request) {
824
-		$authHeader = $request->getHeader('Authorization');
825
-		if (strpos($authHeader, 'Bearer ') === 0) {
826
-			$token = substr($authHeader, 7);
827
-		} else {
828
-			// No auth header, let's try session id
829
-			try {
830
-				$token = $this->session->getId();
831
-			} catch (SessionNotAvailableException $ex) {
832
-				return false;
833
-			}
834
-		}
835
-
836
-		if (!$this->loginWithToken($token)) {
837
-			return false;
838
-		}
839
-		if (!$this->validateToken($token)) {
840
-			return false;
841
-		}
842
-
843
-		try {
844
-			$dbToken = $this->tokenProvider->getToken($token);
845
-		} catch (InvalidTokenException $e) {
846
-			// Can't really happen but better save than sorry
847
-			return true;
848
-		}
849
-
850
-		// Remember me tokens are not app_passwords
851
-		if ($dbToken->getRemember() === IToken::DO_NOT_REMEMBER) {
852
-			// Set the session variable so we know this is an app password
853
-			$this->session->set('app_password', $token);
854
-		}
855
-
856
-		return true;
857
-	}
858
-
859
-	/**
860
-	 * perform login using the magic cookie (remember login)
861
-	 *
862
-	 * @param string $uid the username
863
-	 * @param string $currentToken
864
-	 * @param string $oldSessionId
865
-	 * @return bool
866
-	 */
867
-	public function loginWithCookie($uid, $currentToken, $oldSessionId) {
868
-		$this->session->regenerateId();
869
-		$this->manager->emit('\OC\User', 'preRememberedLogin', [$uid]);
870
-		$user = $this->manager->get($uid);
871
-		if (is_null($user)) {
872
-			// user does not exist
873
-			return false;
874
-		}
875
-
876
-		// get stored tokens
877
-		$tokens = $this->config->getUserKeys($uid, 'login_token');
878
-		// test cookies token against stored tokens
879
-		if (!in_array($currentToken, $tokens, true)) {
880
-			return false;
881
-		}
882
-		// replace successfully used token with a new one
883
-		$this->config->deleteUserValue($uid, 'login_token', $currentToken);
884
-		$newToken = $this->random->generate(32);
885
-		$this->config->setUserValue($uid, 'login_token', $newToken, $this->timeFactory->getTime());
886
-
887
-		try {
888
-			$sessionId = $this->session->getId();
889
-			$token = $this->tokenProvider->renewSessionToken($oldSessionId, $sessionId);
890
-		} catch (SessionNotAvailableException $ex) {
891
-			return false;
892
-		} catch (InvalidTokenException $ex) {
893
-			\OC::$server->getLogger()->warning('Renewing session token failed', ['app' => 'core']);
894
-			return false;
895
-		}
896
-
897
-		$this->setMagicInCookie($user->getUID(), $newToken);
898
-
899
-		//login
900
-		$this->setUser($user);
901
-		$this->setLoginName($token->getLoginName());
902
-		$this->setToken($token->getId());
903
-		$this->lockdownManager->setToken($token);
904
-		$user->updateLastLoginTimestamp();
905
-		$password = null;
906
-		try {
907
-			$password = $this->tokenProvider->getPassword($token, $sessionId);
908
-		} catch (PasswordlessTokenException $ex) {
909
-			// Ignore
910
-		}
911
-		$this->manager->emit('\OC\User', 'postRememberedLogin', [$user, $password]);
912
-		return true;
913
-	}
914
-
915
-	/**
916
-	 * @param IUser $user
917
-	 */
918
-	public function createRememberMeToken(IUser $user) {
919
-		$token = $this->random->generate(32);
920
-		$this->config->setUserValue($user->getUID(), 'login_token', $token, $this->timeFactory->getTime());
921
-		$this->setMagicInCookie($user->getUID(), $token);
922
-	}
923
-
924
-	/**
925
-	 * logout the user from the session
926
-	 */
927
-	public function logout() {
928
-		$user = $this->getUser();
929
-		$this->manager->emit('\OC\User', 'logout', [$user]);
930
-		if ($user !== null) {
931
-			try {
932
-				$this->tokenProvider->invalidateToken($this->session->getId());
933
-			} catch (SessionNotAvailableException $ex) {
934
-			}
935
-		}
936
-		$this->setUser(null);
937
-		$this->setLoginName(null);
938
-		$this->setToken(null);
939
-		$this->unsetMagicInCookie();
940
-		$this->session->clear();
941
-		$this->manager->emit('\OC\User', 'postLogout', [$user]);
942
-	}
943
-
944
-	/**
945
-	 * Set cookie value to use in next page load
946
-	 *
947
-	 * @param string $username username to be set
948
-	 * @param string $token
949
-	 */
950
-	public function setMagicInCookie($username, $token) {
951
-		$secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https';
952
-		$webRoot = \OC::$WEBROOT;
953
-		if ($webRoot === '') {
954
-			$webRoot = '/';
955
-		}
956
-
957
-		$maxAge = $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
958
-		\OC\Http\CookieHelper::setCookie(
959
-			'nc_username',
960
-			$username,
961
-			$maxAge,
962
-			$webRoot,
963
-			'',
964
-			$secureCookie,
965
-			true,
966
-			\OC\Http\CookieHelper::SAMESITE_LAX
967
-		);
968
-		\OC\Http\CookieHelper::setCookie(
969
-			'nc_token',
970
-			$token,
971
-			$maxAge,
972
-			$webRoot,
973
-			'',
974
-			$secureCookie,
975
-			true,
976
-			\OC\Http\CookieHelper::SAMESITE_LAX
977
-		);
978
-		try {
979
-			\OC\Http\CookieHelper::setCookie(
980
-				'nc_session_id',
981
-				$this->session->getId(),
982
-				$maxAge,
983
-				$webRoot,
984
-				'',
985
-				$secureCookie,
986
-				true,
987
-				\OC\Http\CookieHelper::SAMESITE_LAX
988
-			);
989
-		} catch (SessionNotAvailableException $ex) {
990
-			// ignore
991
-		}
992
-	}
993
-
994
-	/**
995
-	 * Remove cookie for "remember username"
996
-	 */
997
-	public function unsetMagicInCookie() {
998
-		//TODO: DI for cookies and IRequest
999
-		$secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https';
1000
-
1001
-		unset($_COOKIE['nc_username']); //TODO: DI
1002
-		unset($_COOKIE['nc_token']);
1003
-		unset($_COOKIE['nc_session_id']);
1004
-		setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true);
1005
-		setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true);
1006
-		setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true);
1007
-		// old cookies might be stored under /webroot/ instead of /webroot
1008
-		// and Firefox doesn't like it!
1009
-		setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
1010
-		setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
1011
-		setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
1012
-	}
1013
-
1014
-	/**
1015
-	 * Update password of the browser session token if there is one
1016
-	 *
1017
-	 * @param string $password
1018
-	 */
1019
-	public function updateSessionTokenPassword($password) {
1020
-		try {
1021
-			$sessionId = $this->session->getId();
1022
-			$token = $this->tokenProvider->getToken($sessionId);
1023
-			$this->tokenProvider->setPassword($token, $sessionId, $password);
1024
-		} catch (SessionNotAvailableException $ex) {
1025
-			// Nothing to do
1026
-		} catch (InvalidTokenException $ex) {
1027
-			// Nothing to do
1028
-		}
1029
-	}
1030
-
1031
-	public function updateTokens(string $uid, string $password) {
1032
-		$this->tokenProvider->updatePasswords($uid, $password);
1033
-	}
93
+    /** @var Manager|PublicEmitter $manager */
94
+    private $manager;
95
+
96
+    /** @var ISession $session */
97
+    private $session;
98
+
99
+    /** @var ITimeFactory */
100
+    private $timeFactory;
101
+
102
+    /** @var IProvider */
103
+    private $tokenProvider;
104
+
105
+    /** @var IConfig */
106
+    private $config;
107
+
108
+    /** @var User $activeUser */
109
+    protected $activeUser;
110
+
111
+    /** @var ISecureRandom */
112
+    private $random;
113
+
114
+    /** @var ILockdownManager  */
115
+    private $lockdownManager;
116
+
117
+    /** @var ILogger */
118
+    private $logger;
119
+    /** @var IEventDispatcher */
120
+    private $dispatcher;
121
+
122
+    /**
123
+     * @param Manager $manager
124
+     * @param ISession $session
125
+     * @param ITimeFactory $timeFactory
126
+     * @param IProvider|null $tokenProvider
127
+     * @param IConfig $config
128
+     * @param ISecureRandom $random
129
+     * @param ILockdownManager $lockdownManager
130
+     * @param ILogger $logger
131
+     */
132
+    public function __construct(Manager $manager,
133
+                                ISession $session,
134
+                                ITimeFactory $timeFactory,
135
+                                ?IProvider $tokenProvider,
136
+                                IConfig $config,
137
+                                ISecureRandom $random,
138
+                                ILockdownManager $lockdownManager,
139
+                                ILogger $logger,
140
+                                IEventDispatcher $dispatcher
141
+    ) {
142
+        $this->manager = $manager;
143
+        $this->session = $session;
144
+        $this->timeFactory = $timeFactory;
145
+        $this->tokenProvider = $tokenProvider;
146
+        $this->config = $config;
147
+        $this->random = $random;
148
+        $this->lockdownManager = $lockdownManager;
149
+        $this->logger = $logger;
150
+        $this->dispatcher = $dispatcher;
151
+    }
152
+
153
+    /**
154
+     * @param IProvider $provider
155
+     */
156
+    public function setTokenProvider(IProvider $provider) {
157
+        $this->tokenProvider = $provider;
158
+    }
159
+
160
+    /**
161
+     * @param string $scope
162
+     * @param string $method
163
+     * @param callable $callback
164
+     */
165
+    public function listen($scope, $method, callable $callback) {
166
+        $this->manager->listen($scope, $method, $callback);
167
+    }
168
+
169
+    /**
170
+     * @param string $scope optional
171
+     * @param string $method optional
172
+     * @param callable $callback optional
173
+     */
174
+    public function removeListener($scope = null, $method = null, callable $callback = null) {
175
+        $this->manager->removeListener($scope, $method, $callback);
176
+    }
177
+
178
+    /**
179
+     * get the manager object
180
+     *
181
+     * @return Manager|PublicEmitter
182
+     */
183
+    public function getManager() {
184
+        return $this->manager;
185
+    }
186
+
187
+    /**
188
+     * get the session object
189
+     *
190
+     * @return ISession
191
+     */
192
+    public function getSession() {
193
+        return $this->session;
194
+    }
195
+
196
+    /**
197
+     * set the session object
198
+     *
199
+     * @param ISession $session
200
+     */
201
+    public function setSession(ISession $session) {
202
+        if ($this->session instanceof ISession) {
203
+            $this->session->close();
204
+        }
205
+        $this->session = $session;
206
+        $this->activeUser = null;
207
+    }
208
+
209
+    /**
210
+     * set the currently active user
211
+     *
212
+     * @param IUser|null $user
213
+     */
214
+    public function setUser($user) {
215
+        if (is_null($user)) {
216
+            $this->session->remove('user_id');
217
+        } else {
218
+            $this->session->set('user_id', $user->getUID());
219
+        }
220
+        $this->activeUser = $user;
221
+    }
222
+
223
+    /**
224
+     * get the current active user
225
+     *
226
+     * @return IUser|null Current user, otherwise null
227
+     */
228
+    public function getUser() {
229
+        // FIXME: This is a quick'n dirty work-around for the incognito mode as
230
+        // described at https://github.com/owncloud/core/pull/12912#issuecomment-67391155
231
+        if (OC_User::isIncognitoMode()) {
232
+            return null;
233
+        }
234
+        if (is_null($this->activeUser)) {
235
+            $uid = $this->session->get('user_id');
236
+            if (is_null($uid)) {
237
+                return null;
238
+            }
239
+            $this->activeUser = $this->manager->get($uid);
240
+            if (is_null($this->activeUser)) {
241
+                return null;
242
+            }
243
+            $this->validateSession();
244
+        }
245
+        return $this->activeUser;
246
+    }
247
+
248
+    /**
249
+     * Validate whether the current session is valid
250
+     *
251
+     * - For token-authenticated clients, the token validity is checked
252
+     * - For browsers, the session token validity is checked
253
+     */
254
+    protected function validateSession() {
255
+        $token = null;
256
+        $appPassword = $this->session->get('app_password');
257
+
258
+        if (is_null($appPassword)) {
259
+            try {
260
+                $token = $this->session->getId();
261
+            } catch (SessionNotAvailableException $ex) {
262
+                return;
263
+            }
264
+        } else {
265
+            $token = $appPassword;
266
+        }
267
+
268
+        if (!$this->validateToken($token)) {
269
+            // Session was invalidated
270
+            $this->logout();
271
+        }
272
+    }
273
+
274
+    /**
275
+     * Checks whether the user is logged in
276
+     *
277
+     * @return bool if logged in
278
+     */
279
+    public function isLoggedIn() {
280
+        $user = $this->getUser();
281
+        if (is_null($user)) {
282
+            return false;
283
+        }
284
+
285
+        return $user->isEnabled();
286
+    }
287
+
288
+    /**
289
+     * set the login name
290
+     *
291
+     * @param string|null $loginName for the logged in user
292
+     */
293
+    public function setLoginName($loginName) {
294
+        if (is_null($loginName)) {
295
+            $this->session->remove('loginname');
296
+        } else {
297
+            $this->session->set('loginname', $loginName);
298
+        }
299
+    }
300
+
301
+    /**
302
+     * get the login name of the current user
303
+     *
304
+     * @return string
305
+     */
306
+    public function getLoginName() {
307
+        if ($this->activeUser) {
308
+            return $this->session->get('loginname');
309
+        }
310
+
311
+        $uid = $this->session->get('user_id');
312
+        if ($uid) {
313
+            $this->activeUser = $this->manager->get($uid);
314
+            return $this->session->get('loginname');
315
+        }
316
+
317
+        return null;
318
+    }
319
+
320
+    /**
321
+     * @return null|string
322
+     */
323
+    public function getImpersonatingUserID(): ?string {
324
+        return $this->session->get('oldUserId');
325
+    }
326
+
327
+    public function setImpersonatingUserID(bool $useCurrentUser = true): void {
328
+        if ($useCurrentUser === false) {
329
+            $this->session->remove('oldUserId');
330
+            return;
331
+        }
332
+
333
+        $currentUser = $this->getUser();
334
+
335
+        if ($currentUser === null) {
336
+            throw new \OC\User\NoUserException();
337
+        }
338
+        $this->session->set('oldUserId', $currentUser->getUID());
339
+    }
340
+    /**
341
+     * set the token id
342
+     *
343
+     * @param int|null $token that was used to log in
344
+     */
345
+    protected function setToken($token) {
346
+        if ($token === null) {
347
+            $this->session->remove('token-id');
348
+        } else {
349
+            $this->session->set('token-id', $token);
350
+        }
351
+    }
352
+
353
+    /**
354
+     * try to log in with the provided credentials
355
+     *
356
+     * @param string $uid
357
+     * @param string $password
358
+     * @return boolean|null
359
+     * @throws LoginException
360
+     */
361
+    public function login($uid, $password) {
362
+        $this->session->regenerateId();
363
+        if ($this->validateToken($password, $uid)) {
364
+            return $this->loginWithToken($password);
365
+        }
366
+        return $this->loginWithPassword($uid, $password);
367
+    }
368
+
369
+    /**
370
+     * @param IUser $user
371
+     * @param array $loginDetails
372
+     * @param bool $regenerateSessionId
373
+     * @return true returns true if login successful or an exception otherwise
374
+     * @throws LoginException
375
+     */
376
+    public function completeLogin(IUser $user, array $loginDetails, $regenerateSessionId = true) {
377
+        if (!$user->isEnabled()) {
378
+            // disabled users can not log in
379
+            // injecting l10n does not work - there is a circular dependency between session and \OCP\L10N\IFactory
380
+            $message = \OC::$server->getL10N('lib')->t('User disabled');
381
+            throw new LoginException($message);
382
+        }
383
+
384
+        if ($regenerateSessionId) {
385
+            $this->session->regenerateId();
386
+        }
387
+
388
+        $this->setUser($user);
389
+        $this->setLoginName($loginDetails['loginName']);
390
+
391
+        $isToken = isset($loginDetails['token']) && $loginDetails['token'] instanceof IToken;
392
+        if ($isToken) {
393
+            $this->setToken($loginDetails['token']->getId());
394
+            $this->lockdownManager->setToken($loginDetails['token']);
395
+            $firstTimeLogin = false;
396
+        } else {
397
+            $this->setToken(null);
398
+            $firstTimeLogin = $user->updateLastLoginTimestamp();
399
+        }
400
+
401
+        $this->dispatcher->dispatchTyped(new PostLoginEvent(
402
+            $user,
403
+            $loginDetails['loginName'],
404
+            $loginDetails['password'],
405
+            $isToken
406
+        ));
407
+        $this->manager->emit('\OC\User', 'postLogin', [
408
+            $user,
409
+            $loginDetails['loginName'],
410
+            $loginDetails['password'],
411
+            $isToken,
412
+        ]);
413
+        if ($this->isLoggedIn()) {
414
+            $this->prepareUserLogin($firstTimeLogin, $regenerateSessionId);
415
+            return true;
416
+        }
417
+
418
+        $message = \OC::$server->getL10N('lib')->t('Login canceled by app');
419
+        throw new LoginException($message);
420
+    }
421
+
422
+    /**
423
+     * Tries to log in a client
424
+     *
425
+     * Checks token auth enforced
426
+     * Checks 2FA enabled
427
+     *
428
+     * @param string $user
429
+     * @param string $password
430
+     * @param IRequest $request
431
+     * @param OC\Security\Bruteforce\Throttler $throttler
432
+     * @throws LoginException
433
+     * @throws PasswordLoginForbiddenException
434
+     * @return boolean
435
+     */
436
+    public function logClientIn($user,
437
+                                $password,
438
+                                IRequest $request,
439
+                                OC\Security\Bruteforce\Throttler $throttler) {
440
+        $currentDelay = $throttler->sleepDelay($request->getRemoteAddress(), 'login');
441
+
442
+        if ($this->manager instanceof PublicEmitter) {
443
+            $this->manager->emit('\OC\User', 'preLogin', [$user, $password]);
444
+        }
445
+
446
+        try {
447
+            $isTokenPassword = $this->isTokenPassword($password);
448
+        } catch (ExpiredTokenException $e) {
449
+            // Just return on an expired token no need to check further or record a failed login
450
+            return false;
451
+        }
452
+
453
+        if (!$isTokenPassword && $this->isTokenAuthEnforced()) {
454
+            throw new PasswordLoginForbiddenException();
455
+        }
456
+        if (!$isTokenPassword && $this->isTwoFactorEnforced($user)) {
457
+            throw new PasswordLoginForbiddenException();
458
+        }
459
+
460
+        // Try to login with this username and password
461
+        if (!$this->login($user, $password)) {
462
+
463
+            // Failed, maybe the user used their email address
464
+            $users = $this->manager->getByEmail($user);
465
+            if (!(\count($users) === 1 && $this->login($users[0]->getUID(), $password))) {
466
+                $this->logger->warning('Login failed: \'' . $user . '\' (Remote IP: \'' . \OC::$server->getRequest()->getRemoteAddress() . '\')', ['app' => 'core']);
467
+
468
+                $throttler->registerAttempt('login', $request->getRemoteAddress(), ['user' => $user]);
469
+
470
+                $this->dispatcher->dispatchTyped(new OC\Authentication\Events\LoginFailed($user));
471
+
472
+                if ($currentDelay === 0) {
473
+                    $throttler->sleepDelay($request->getRemoteAddress(), 'login');
474
+                }
475
+                return false;
476
+            }
477
+        }
478
+
479
+        if ($isTokenPassword) {
480
+            $this->session->set('app_password', $password);
481
+        } elseif ($this->supportsCookies($request)) {
482
+            // Password login, but cookies supported -> create (browser) session token
483
+            $this->createSessionToken($request, $this->getUser()->getUID(), $user, $password);
484
+        }
485
+
486
+        return true;
487
+    }
488
+
489
+    protected function supportsCookies(IRequest $request) {
490
+        if (!is_null($request->getCookie('cookie_test'))) {
491
+            return true;
492
+        }
493
+        setcookie('cookie_test', 'test', $this->timeFactory->getTime() + 3600);
494
+        return false;
495
+    }
496
+
497
+    private function isTokenAuthEnforced() {
498
+        return $this->config->getSystemValue('token_auth_enforced', false);
499
+    }
500
+
501
+    protected function isTwoFactorEnforced($username) {
502
+        Util::emitHook(
503
+            '\OCA\Files_Sharing\API\Server2Server',
504
+            'preLoginNameUsedAsUserName',
505
+            ['uid' => &$username]
506
+        );
507
+        $user = $this->manager->get($username);
508
+        if (is_null($user)) {
509
+            $users = $this->manager->getByEmail($username);
510
+            if (empty($users)) {
511
+                return false;
512
+            }
513
+            if (count($users) !== 1) {
514
+                return true;
515
+            }
516
+            $user = $users[0];
517
+        }
518
+        // DI not possible due to cyclic dependencies :'-/
519
+        return OC::$server->getTwoFactorAuthManager()->isTwoFactorAuthenticated($user);
520
+    }
521
+
522
+    /**
523
+     * Check if the given 'password' is actually a device token
524
+     *
525
+     * @param string $password
526
+     * @return boolean
527
+     * @throws ExpiredTokenException
528
+     */
529
+    public function isTokenPassword($password) {
530
+        try {
531
+            $this->tokenProvider->getToken($password);
532
+            return true;
533
+        } catch (ExpiredTokenException $e) {
534
+            throw $e;
535
+        } catch (InvalidTokenException $ex) {
536
+            $this->logger->logException($ex, [
537
+                'level' => ILogger::DEBUG,
538
+                'message' => 'Token is not valid: ' . $ex->getMessage(),
539
+            ]);
540
+            return false;
541
+        }
542
+    }
543
+
544
+    protected function prepareUserLogin($firstTimeLogin, $refreshCsrfToken = true) {
545
+        if ($refreshCsrfToken) {
546
+            // TODO: mock/inject/use non-static
547
+            // Refresh the token
548
+            \OC::$server->getCsrfTokenManager()->refreshToken();
549
+        }
550
+
551
+        if ($firstTimeLogin) {
552
+            //we need to pass the user name, which may differ from login name
553
+            $user = $this->getUser()->getUID();
554
+            OC_Util::setupFS($user);
555
+
556
+            // TODO: lock necessary?
557
+            //trigger creation of user home and /files folder
558
+            $userFolder = \OC::$server->getUserFolder($user);
559
+
560
+            try {
561
+                // copy skeleton
562
+                \OC_Util::copySkeleton($user, $userFolder);
563
+            } catch (NotPermittedException $ex) {
564
+                // read only uses
565
+            }
566
+
567
+            // trigger any other initialization
568
+            \OC::$server->getEventDispatcher()->dispatch(IUser::class . '::firstLogin', new GenericEvent($this->getUser()));
569
+        }
570
+    }
571
+
572
+    /**
573
+     * Tries to login the user with HTTP Basic Authentication
574
+     *
575
+     * @todo do not allow basic auth if the user is 2FA enforced
576
+     * @param IRequest $request
577
+     * @param OC\Security\Bruteforce\Throttler $throttler
578
+     * @return boolean if the login was successful
579
+     */
580
+    public function tryBasicAuthLogin(IRequest $request,
581
+                                        OC\Security\Bruteforce\Throttler $throttler) {
582
+        if (!empty($request->server['PHP_AUTH_USER']) && !empty($request->server['PHP_AUTH_PW'])) {
583
+            try {
584
+                if ($this->logClientIn($request->server['PHP_AUTH_USER'], $request->server['PHP_AUTH_PW'], $request, $throttler)) {
585
+                    /**
586
+                     * Add DAV authenticated. This should in an ideal world not be
587
+                     * necessary but the iOS App reads cookies from anywhere instead
588
+                     * only the DAV endpoint.
589
+                     * This makes sure that the cookies will be valid for the whole scope
590
+                     * @see https://github.com/owncloud/core/issues/22893
591
+                     */
592
+                    $this->session->set(
593
+                        Auth::DAV_AUTHENTICATED, $this->getUser()->getUID()
594
+                    );
595
+
596
+                    // Set the last-password-confirm session to make the sudo mode work
597
+                    $this->session->set('last-password-confirm', $this->timeFactory->getTime());
598
+
599
+                    return true;
600
+                }
601
+                // If credentials were provided, they need to be valid, otherwise we do boom
602
+                throw new LoginException();
603
+            } catch (PasswordLoginForbiddenException $ex) {
604
+                // Nothing to do
605
+            }
606
+        }
607
+        return false;
608
+    }
609
+
610
+    /**
611
+     * Log an user in via login name and password
612
+     *
613
+     * @param string $uid
614
+     * @param string $password
615
+     * @return boolean
616
+     * @throws LoginException if an app canceld the login process or the user is not enabled
617
+     */
618
+    private function loginWithPassword($uid, $password) {
619
+        $user = $this->manager->checkPasswordNoLogging($uid, $password);
620
+        if ($user === false) {
621
+            // Password check failed
622
+            return false;
623
+        }
624
+
625
+        return $this->completeLogin($user, ['loginName' => $uid, 'password' => $password], false);
626
+    }
627
+
628
+    /**
629
+     * Log an user in with a given token (id)
630
+     *
631
+     * @param string $token
632
+     * @return boolean
633
+     * @throws LoginException if an app canceled the login process or the user is not enabled
634
+     */
635
+    private function loginWithToken($token) {
636
+        try {
637
+            $dbToken = $this->tokenProvider->getToken($token);
638
+        } catch (InvalidTokenException $ex) {
639
+            return false;
640
+        }
641
+        $uid = $dbToken->getUID();
642
+
643
+        // When logging in with token, the password must be decrypted first before passing to login hook
644
+        $password = '';
645
+        try {
646
+            $password = $this->tokenProvider->getPassword($dbToken, $token);
647
+        } catch (PasswordlessTokenException $ex) {
648
+            // Ignore and use empty string instead
649
+        }
650
+
651
+        $this->manager->emit('\OC\User', 'preLogin', [$dbToken->getLoginName(), $password]);
652
+
653
+        $user = $this->manager->get($uid);
654
+        if (is_null($user)) {
655
+            // user does not exist
656
+            return false;
657
+        }
658
+
659
+        return $this->completeLogin(
660
+            $user,
661
+            [
662
+                'loginName' => $dbToken->getLoginName(),
663
+                'password' => $password,
664
+                'token' => $dbToken
665
+            ],
666
+            false);
667
+    }
668
+
669
+    /**
670
+     * Create a new session token for the given user credentials
671
+     *
672
+     * @param IRequest $request
673
+     * @param string $uid user UID
674
+     * @param string $loginName login name
675
+     * @param string $password
676
+     * @param int $remember
677
+     * @return boolean
678
+     */
679
+    public function createSessionToken(IRequest $request, $uid, $loginName, $password = null, $remember = IToken::DO_NOT_REMEMBER) {
680
+        if (is_null($this->manager->get($uid))) {
681
+            // User does not exist
682
+            return false;
683
+        }
684
+        $name = isset($request->server['HTTP_USER_AGENT']) ? utf8_encode($request->server['HTTP_USER_AGENT']) : 'unknown browser';
685
+        try {
686
+            $sessionId = $this->session->getId();
687
+            $pwd = $this->getPassword($password);
688
+            // Make sure the current sessionId has no leftover tokens
689
+            $this->tokenProvider->invalidateToken($sessionId);
690
+            $this->tokenProvider->generateToken($sessionId, $uid, $loginName, $pwd, $name, IToken::TEMPORARY_TOKEN, $remember);
691
+            return true;
692
+        } catch (SessionNotAvailableException $ex) {
693
+            // This can happen with OCC, where a memory session is used
694
+            // if a memory session is used, we shouldn't create a session token anyway
695
+            return false;
696
+        }
697
+    }
698
+
699
+    /**
700
+     * Checks if the given password is a token.
701
+     * If yes, the password is extracted from the token.
702
+     * If no, the same password is returned.
703
+     *
704
+     * @param string $password either the login password or a device token
705
+     * @return string|null the password or null if none was set in the token
706
+     */
707
+    private function getPassword($password) {
708
+        if (is_null($password)) {
709
+            // This is surely no token ;-)
710
+            return null;
711
+        }
712
+        try {
713
+            $token = $this->tokenProvider->getToken($password);
714
+            try {
715
+                return $this->tokenProvider->getPassword($token, $password);
716
+            } catch (PasswordlessTokenException $ex) {
717
+                return null;
718
+            }
719
+        } catch (InvalidTokenException $ex) {
720
+            return $password;
721
+        }
722
+    }
723
+
724
+    /**
725
+     * @param IToken $dbToken
726
+     * @param string $token
727
+     * @return boolean
728
+     */
729
+    private function checkTokenCredentials(IToken $dbToken, $token) {
730
+        // Check whether login credentials are still valid and the user was not disabled
731
+        // This check is performed each 5 minutes
732
+        $lastCheck = $dbToken->getLastCheck() ? : 0;
733
+        $now = $this->timeFactory->getTime();
734
+        if ($lastCheck > ($now - 60 * 5)) {
735
+            // Checked performed recently, nothing to do now
736
+            return true;
737
+        }
738
+
739
+        try {
740
+            $pwd = $this->tokenProvider->getPassword($dbToken, $token);
741
+        } catch (InvalidTokenException $ex) {
742
+            // An invalid token password was used -> log user out
743
+            return false;
744
+        } catch (PasswordlessTokenException $ex) {
745
+            // Token has no password
746
+
747
+            if (!is_null($this->activeUser) && !$this->activeUser->isEnabled()) {
748
+                $this->tokenProvider->invalidateToken($token);
749
+                return false;
750
+            }
751
+
752
+            $dbToken->setLastCheck($now);
753
+            $this->tokenProvider->updateToken($dbToken);
754
+            return true;
755
+        }
756
+
757
+        // Invalidate token if the user is no longer active
758
+        if (!is_null($this->activeUser) && !$this->activeUser->isEnabled()) {
759
+            $this->tokenProvider->invalidateToken($token);
760
+            return false;
761
+        }
762
+
763
+        // If the token password is no longer valid mark it as such
764
+        if ($this->manager->checkPassword($dbToken->getLoginName(), $pwd) === false) {
765
+            $this->tokenProvider->markPasswordInvalid($dbToken, $token);
766
+            // User is logged out
767
+            return false;
768
+        }
769
+
770
+        $dbToken->setLastCheck($now);
771
+        $this->tokenProvider->updateToken($dbToken);
772
+        return true;
773
+    }
774
+
775
+    /**
776
+     * Check if the given token exists and performs password/user-enabled checks
777
+     *
778
+     * Invalidates the token if checks fail
779
+     *
780
+     * @param string $token
781
+     * @param string $user login name
782
+     * @return boolean
783
+     */
784
+    private function validateToken($token, $user = null) {
785
+        try {
786
+            $dbToken = $this->tokenProvider->getToken($token);
787
+        } catch (InvalidTokenException $ex) {
788
+            return false;
789
+        }
790
+
791
+        // Check if login names match
792
+        if (!is_null($user) && $dbToken->getLoginName() !== $user) {
793
+            // TODO: this makes it impossible to use different login names on browser and client
794
+            // e.g. login by e-mail '[email protected]' on browser for generating the token will not
795
+            //      allow to use the client token with the login name 'user'.
796
+            $this->logger->error('App token login name does not match', [
797
+                'tokenLoginName' => $dbToken->getLoginName(),
798
+                'sessionLoginName' => $user,
799
+            ]);
800
+
801
+            return false;
802
+        }
803
+
804
+        if (!$this->checkTokenCredentials($dbToken, $token)) {
805
+            return false;
806
+        }
807
+
808
+        // Update token scope
809
+        $this->lockdownManager->setToken($dbToken);
810
+
811
+        $this->tokenProvider->updateTokenActivity($dbToken);
812
+
813
+        return true;
814
+    }
815
+
816
+    /**
817
+     * Tries to login the user with auth token header
818
+     *
819
+     * @param IRequest $request
820
+     * @todo check remember me cookie
821
+     * @return boolean
822
+     */
823
+    public function tryTokenLogin(IRequest $request) {
824
+        $authHeader = $request->getHeader('Authorization');
825
+        if (strpos($authHeader, 'Bearer ') === 0) {
826
+            $token = substr($authHeader, 7);
827
+        } else {
828
+            // No auth header, let's try session id
829
+            try {
830
+                $token = $this->session->getId();
831
+            } catch (SessionNotAvailableException $ex) {
832
+                return false;
833
+            }
834
+        }
835
+
836
+        if (!$this->loginWithToken($token)) {
837
+            return false;
838
+        }
839
+        if (!$this->validateToken($token)) {
840
+            return false;
841
+        }
842
+
843
+        try {
844
+            $dbToken = $this->tokenProvider->getToken($token);
845
+        } catch (InvalidTokenException $e) {
846
+            // Can't really happen but better save than sorry
847
+            return true;
848
+        }
849
+
850
+        // Remember me tokens are not app_passwords
851
+        if ($dbToken->getRemember() === IToken::DO_NOT_REMEMBER) {
852
+            // Set the session variable so we know this is an app password
853
+            $this->session->set('app_password', $token);
854
+        }
855
+
856
+        return true;
857
+    }
858
+
859
+    /**
860
+     * perform login using the magic cookie (remember login)
861
+     *
862
+     * @param string $uid the username
863
+     * @param string $currentToken
864
+     * @param string $oldSessionId
865
+     * @return bool
866
+     */
867
+    public function loginWithCookie($uid, $currentToken, $oldSessionId) {
868
+        $this->session->regenerateId();
869
+        $this->manager->emit('\OC\User', 'preRememberedLogin', [$uid]);
870
+        $user = $this->manager->get($uid);
871
+        if (is_null($user)) {
872
+            // user does not exist
873
+            return false;
874
+        }
875
+
876
+        // get stored tokens
877
+        $tokens = $this->config->getUserKeys($uid, 'login_token');
878
+        // test cookies token against stored tokens
879
+        if (!in_array($currentToken, $tokens, true)) {
880
+            return false;
881
+        }
882
+        // replace successfully used token with a new one
883
+        $this->config->deleteUserValue($uid, 'login_token', $currentToken);
884
+        $newToken = $this->random->generate(32);
885
+        $this->config->setUserValue($uid, 'login_token', $newToken, $this->timeFactory->getTime());
886
+
887
+        try {
888
+            $sessionId = $this->session->getId();
889
+            $token = $this->tokenProvider->renewSessionToken($oldSessionId, $sessionId);
890
+        } catch (SessionNotAvailableException $ex) {
891
+            return false;
892
+        } catch (InvalidTokenException $ex) {
893
+            \OC::$server->getLogger()->warning('Renewing session token failed', ['app' => 'core']);
894
+            return false;
895
+        }
896
+
897
+        $this->setMagicInCookie($user->getUID(), $newToken);
898
+
899
+        //login
900
+        $this->setUser($user);
901
+        $this->setLoginName($token->getLoginName());
902
+        $this->setToken($token->getId());
903
+        $this->lockdownManager->setToken($token);
904
+        $user->updateLastLoginTimestamp();
905
+        $password = null;
906
+        try {
907
+            $password = $this->tokenProvider->getPassword($token, $sessionId);
908
+        } catch (PasswordlessTokenException $ex) {
909
+            // Ignore
910
+        }
911
+        $this->manager->emit('\OC\User', 'postRememberedLogin', [$user, $password]);
912
+        return true;
913
+    }
914
+
915
+    /**
916
+     * @param IUser $user
917
+     */
918
+    public function createRememberMeToken(IUser $user) {
919
+        $token = $this->random->generate(32);
920
+        $this->config->setUserValue($user->getUID(), 'login_token', $token, $this->timeFactory->getTime());
921
+        $this->setMagicInCookie($user->getUID(), $token);
922
+    }
923
+
924
+    /**
925
+     * logout the user from the session
926
+     */
927
+    public function logout() {
928
+        $user = $this->getUser();
929
+        $this->manager->emit('\OC\User', 'logout', [$user]);
930
+        if ($user !== null) {
931
+            try {
932
+                $this->tokenProvider->invalidateToken($this->session->getId());
933
+            } catch (SessionNotAvailableException $ex) {
934
+            }
935
+        }
936
+        $this->setUser(null);
937
+        $this->setLoginName(null);
938
+        $this->setToken(null);
939
+        $this->unsetMagicInCookie();
940
+        $this->session->clear();
941
+        $this->manager->emit('\OC\User', 'postLogout', [$user]);
942
+    }
943
+
944
+    /**
945
+     * Set cookie value to use in next page load
946
+     *
947
+     * @param string $username username to be set
948
+     * @param string $token
949
+     */
950
+    public function setMagicInCookie($username, $token) {
951
+        $secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https';
952
+        $webRoot = \OC::$WEBROOT;
953
+        if ($webRoot === '') {
954
+            $webRoot = '/';
955
+        }
956
+
957
+        $maxAge = $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
958
+        \OC\Http\CookieHelper::setCookie(
959
+            'nc_username',
960
+            $username,
961
+            $maxAge,
962
+            $webRoot,
963
+            '',
964
+            $secureCookie,
965
+            true,
966
+            \OC\Http\CookieHelper::SAMESITE_LAX
967
+        );
968
+        \OC\Http\CookieHelper::setCookie(
969
+            'nc_token',
970
+            $token,
971
+            $maxAge,
972
+            $webRoot,
973
+            '',
974
+            $secureCookie,
975
+            true,
976
+            \OC\Http\CookieHelper::SAMESITE_LAX
977
+        );
978
+        try {
979
+            \OC\Http\CookieHelper::setCookie(
980
+                'nc_session_id',
981
+                $this->session->getId(),
982
+                $maxAge,
983
+                $webRoot,
984
+                '',
985
+                $secureCookie,
986
+                true,
987
+                \OC\Http\CookieHelper::SAMESITE_LAX
988
+            );
989
+        } catch (SessionNotAvailableException $ex) {
990
+            // ignore
991
+        }
992
+    }
993
+
994
+    /**
995
+     * Remove cookie for "remember username"
996
+     */
997
+    public function unsetMagicInCookie() {
998
+        //TODO: DI for cookies and IRequest
999
+        $secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https';
1000
+
1001
+        unset($_COOKIE['nc_username']); //TODO: DI
1002
+        unset($_COOKIE['nc_token']);
1003
+        unset($_COOKIE['nc_session_id']);
1004
+        setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true);
1005
+        setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true);
1006
+        setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true);
1007
+        // old cookies might be stored under /webroot/ instead of /webroot
1008
+        // and Firefox doesn't like it!
1009
+        setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
1010
+        setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
1011
+        setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
1012
+    }
1013
+
1014
+    /**
1015
+     * Update password of the browser session token if there is one
1016
+     *
1017
+     * @param string $password
1018
+     */
1019
+    public function updateSessionTokenPassword($password) {
1020
+        try {
1021
+            $sessionId = $this->session->getId();
1022
+            $token = $this->tokenProvider->getToken($sessionId);
1023
+            $this->tokenProvider->setPassword($token, $sessionId, $password);
1024
+        } catch (SessionNotAvailableException $ex) {
1025
+            // Nothing to do
1026
+        } catch (InvalidTokenException $ex) {
1027
+            // Nothing to do
1028
+        }
1029
+    }
1030
+
1031
+    public function updateTokens(string $uid, string $password) {
1032
+        $this->tokenProvider->updatePasswords($uid, $password);
1033
+    }
1034 1034
 }
Please login to merge, or discard this patch.