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