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