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