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