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