Completed
Pull Request — master (#25074)
by Christoph
10:03
created

Session::createSessionToken()   B

Complexity

Conditions 5
Paths 13

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 1
Metric Value
cc 5
eloc 14
c 4
b 0
f 1
nc 13
nop 5
dl 0
loc 21
rs 8.7624
1
<?php
2
/**
3
 * @author Arthur Schiwon <[email protected]>
4
 * @author Bernhard Posselt <[email protected]>
5
 * @author Christoph Wurst <[email protected]>
6
 * @author Jörn Friedrich Dreyer <[email protected]>
7
 * @author Lukas Reschke <[email protected]>
8
 * @author Morris Jobke <[email protected]>
9
 * @author Robin Appelman <[email protected]>
10
 * @author Robin McCorkell <[email protected]>
11
 * @author Thomas Müller <[email protected]>
12
 * @author Vincent Petry <[email protected]>
13
 *
14
 * @copyright Copyright (c) 2016, ownCloud, Inc.
15
 * @license AGPL-3.0
16
 *
17
 * This code is free software: you can redistribute it and/or modify
18
 * it under the terms of the GNU Affero General Public License, version 3,
19
 * as published by the Free Software Foundation.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License, version 3,
27
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
28
 *
29
 */
30
31
namespace OC\User;
32
33
use OC;
34
use OC\Authentication\Exceptions\InvalidTokenException;
35
use OC\Authentication\Exceptions\PasswordlessTokenException;
36
use OC\Authentication\Token\IProvider;
37
use OC\Authentication\Token\IToken;
38
use OC\Hooks\Emitter;
39
use OC_User;
40
use OC_Util;
41
use OCA\DAV\Connector\Sabre\Auth;
42
use OCP\AppFramework\Utility\ITimeFactory;
43
use OCP\IConfig;
44
use OCP\IRequest;
45
use OCP\ISession;
46
use OCP\IUser;
47
use OCP\IUserManager;
48
use OCP\IUserSession;
49
use OCP\Session\Exceptions\SessionNotAvailableException;
50
use OCP\Util;
51
52
/**
53
 * Class Session
54
 *
55
 * Hooks available in scope \OC\User:
56
 * - preSetPassword(\OC\User\User $user, string $password, string $recoverPassword)
57
 * - postSetPassword(\OC\User\User $user, string $password, string $recoverPassword)
58
 * - preDelete(\OC\User\User $user)
59
 * - postDelete(\OC\User\User $user)
60
 * - preCreateUser(string $uid, string $password)
61
 * - postCreateUser(\OC\User\User $user)
62
 * - preLogin(string $user, string $password)
63
 * - postLogin(\OC\User\User $user, string $password)
64
 * - preRememberedLogin(string $uid)
65
 * - postRememberedLogin(\OC\User\User $user)
66
 * - logout()
67
 *
68
 * @package OC\User
69
 */
70
class Session implements IUserSession, Emitter {
71
72
	/** @var IUserManager $manager */
73
	private $manager;
74
75
	/** @var ISession $session */
76
	private $session;
77
78
	/** @var ITimeFactory */
79
	private $timeFacory;
80
81
	/** @var IProvider */
82
	private $tokenProvider;
83
84
	/** @var IConfig */
85
	private $config;
86
87
	/** @var User $activeUser */
88
	protected $activeUser;
89
90
	/**
91
	 * @param IUserManager $manager
92
	 * @param ISession $session
93
	 * @param ITimeFactory $timeFacory
94
	 * @param IProvider $tokenProvider
95
	 * @param IConfig $config
96
	 */
97
	public function __construct(IUserManager $manager, ISession $session, ITimeFactory $timeFacory, $tokenProvider, IConfig $config) {
98
		$this->manager = $manager;
99
		$this->session = $session;
100
		$this->timeFacory = $timeFacory;
101
		$this->tokenProvider = $tokenProvider;
102
		$this->config = $config;
103
	}
104
105
	/**
106
	 * @param IProvider $provider
107
	 */
108
	public function setTokenProvider(IProvider $provider) {
109
		$this->tokenProvider = $provider;
110
	}
111
112
	/**
113
	 * @param string $scope
114
	 * @param string $method
115
	 * @param callable $callback
116
	 */
117
	public function listen($scope, $method, callable $callback) {
118
		$this->manager->listen($scope, $method, $callback);
119
	}
120
121
	/**
122
	 * @param string $scope optional
123
	 * @param string $method optional
124
	 * @param callable $callback optional
125
	 */
126
	public function removeListener($scope = null, $method = null, callable $callback = null) {
127
		$this->manager->removeListener($scope, $method, $callback);
128
	}
129
130
	/**
131
	 * get the manager object
132
	 *
133
	 * @return Manager
134
	 */
135
	public function getManager() {
136
		return $this->manager;
137
	}
138
139
	/**
140
	 * get the session object
141
	 *
142
	 * @return ISession
143
	 */
144
	public function getSession() {
145
		return $this->session;
146
	}
147
148
	/**
149
	 * set the session object
150
	 *
151
	 * @param ISession $session
152
	 */
153
	public function setSession(ISession $session) {
154
		if ($this->session instanceof ISession) {
155
			$this->session->close();
156
		}
157
		$this->session = $session;
158
		$this->activeUser = null;
159
	}
160
161
	/**
162
	 * set the currently active user
163
	 *
164
	 * @param User|null $user
165
	 */
166
	public function setUser($user) {
167
		if (is_null($user)) {
168
			$this->session->remove('user_id');
169
		} else {
170
			$this->session->set('user_id', $user->getUID());
171
		}
172
		$this->activeUser = $user;
173
	}
174
175
	/**
176
	 * get the current active user
177
	 *
178
	 * @return IUser|null Current user, otherwise null
179
	 */
180
	public function getUser() {
181
		// FIXME: This is a quick'n dirty work-around for the incognito mode as
182
		// described at https://github.com/owncloud/core/pull/12912#issuecomment-67391155
183
		if (OC_User::isIncognitoMode()) {
184
			return null;
185
		}
186
		if (is_null($this->activeUser)) {
187
			$uid = $this->session->get('user_id');
188
			if (is_null($uid)) {
189
				return null;
190
			}
191
			$this->activeUser = $this->manager->get($uid);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->manager->get($uid) can also be of type object<OCP\IUser>. However, the property $activeUser is declared as type object<OC\User\User>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
192
			if (is_null($this->activeUser)) {
193
				return null;
194
			}
195
			$this->validateSession($this->activeUser);
196
		}
197
		return $this->activeUser;
198
	}
199
200
	protected function validateSession(IUser $user) {
201
		try {
202
			$sessionId = $this->session->getId();
203
		} catch (SessionNotAvailableException $ex) {
204
			return;
205
		}
206
		try {
207
			$token = $this->tokenProvider->getToken($sessionId);
208
		} catch (InvalidTokenException $ex) {
209
			// Session was invalidated
210
			$this->logout();
211
			return;
212
		}
213
214
		// Check whether login credentials are still valid and the user was not disabled
215
		// This check is performed each 5 minutes
216
		$lastCheck = $this->session->get('last_login_check') ? : 0;
217
		$now = $this->timeFacory->getTime();
218
		if ($lastCheck < ($now - 60 * 5)) {
219
			try {
220
				$pwd = $this->tokenProvider->getPassword($token, $sessionId);
221
			} catch (InvalidTokenException $ex) {
222
				// An invalid token password was used -> log user out
223
				$this->logout();
224
				return;
225
			} catch (PasswordlessTokenException $ex) {
226
				// Token has no password, nothing to check
227
				$this->session->set('last_login_check', $now);
228
				return;
229
			}
230
231
			if ($this->manager->checkPassword($token->getLoginName(), $pwd) === false
232
				|| !$user->isEnabled()) {
233
				// Password has changed or user was disabled -> log user out
234
				$this->logout();
235
				return;
236
			}
237
			$this->session->set('last_login_check', $now);
238
		}
239
240
		// Session is valid, so the token can be refreshed
241
		$this->updateToken($token);
242
	}
243
244
	/**
245
	 * Checks whether the user is logged in
246
	 *
247
	 * @return bool if logged in
248
	 */
249
	public function isLoggedIn() {
250
		$user = $this->getUser();
251
		if (is_null($user)) {
252
			return false;
253
		}
254
255
		return $user->isEnabled();
256
	}
257
258
	/**
259
	 * set the login name
260
	 *
261
	 * @param string|null $loginName for the logged in user
262
	 */
263
	public function setLoginName($loginName) {
264
		if (is_null($loginName)) {
265
			$this->session->remove('loginname');
266
		} else {
267
			$this->session->set('loginname', $loginName);
268
		}
269
	}
270
271
	/**
272
	 * get the login name of the current user
273
	 *
274
	 * @return string
275
	 */
276
	public function getLoginName() {
277
		if ($this->activeUser) {
278
			return $this->session->get('loginname');
279
		} else {
280
			$uid = $this->session->get('user_id');
281
			if ($uid) {
282
				$this->activeUser = $this->manager->get($uid);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->manager->get($uid) can also be of type object<OCP\IUser>. However, the property $activeUser is declared as type object<OC\User\User>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
283
				return $this->session->get('loginname');
284
			} else {
285
				return null;
286
			}
287
		}
288
	}
289
290
	/**
291
	 * try to log in with the provided credentials
292
	 *
293
	 * @param string $uid
294
	 * @param string $password
295
	 * @return boolean|null
296
	 * @throws LoginException
297
	 */
298
	public function login($uid, $password) {
299
		$this->session->regenerateId();
300
		if ($this->validateToken($password)) {
301
			$user = $this->getUser();
302
303
			// When logging in with token, the password must be decrypted first before passing to login hook
304
			try {
305
				$token = $this->tokenProvider->getToken($password);
306
				try {
307
					$password = $this->tokenProvider->getPassword($token, $password);
308
					$this->manager->emit('\OC\User', 'preLogin', array($uid, $password));
309
				} catch (PasswordlessTokenException $ex) {
310
					$this->manager->emit('\OC\User', 'preLogin', array($uid, ''));
311
				}
312
			} catch (InvalidTokenException $ex) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
313
				// Invalid token, nothing to do
314
			}
315
		} else {
316
			$this->manager->emit('\OC\User', 'preLogin', array($uid, $password));
317
			$user = $this->manager->checkPassword($uid, $password);
318
		}
319
		if ($user !== false) {
320
			if (!is_null($user)) {
321
				if ($user->isEnabled()) {
322
					$this->setUser($user);
323
					$this->setLoginName($uid);
324
					$this->manager->emit('\OC\User', 'postLogin', array($user, $password));
325
					if ($this->isLoggedIn()) {
326
						$this->prepareUserLogin();
327
						return true;
328
					} else {
329
						// injecting l10n does not work - there is a circular dependency between session and \OCP\L10N\IFactory
330
						$message = \OC::$server->getL10N('lib')->t('Login canceled by app');
331
						throw new LoginException($message);
332
					}
333
				} else {
334
					// injecting l10n does not work - there is a circular dependency between session and \OCP\L10N\IFactory
335
					$message = \OC::$server->getL10N('lib')->t('User disabled');
336
					throw new LoginException($message);
337
				}
338
			}
339
		}
340
		return false;
341
	}
342
343
	/**
344
	 * Tries to log in a client
345
	 *
346
	 * Checks token auth enforced
347
	 * Checks 2FA enabled
348
	 *
349
	 * @param string $user
350
	 * @param string $password
351
	 * @throws LoginException
352
	 * @return boolean
353
	 */
354
	public function logClientIn($user, $password) {
355
		$isTokenPassword = $this->isTokenPassword($password);
356
		if (!$isTokenPassword && $this->isTokenAuthEnforced()) {
357
			// TODO: throw LoginException instead (https://github.com/owncloud/core/pull/24616)
358
			return false;
359
		}
360
		if (!$isTokenPassword && $this->isTwoFactorEnforced($user)) {
361
			// TODO: throw LoginException instead (https://github.com/owncloud/core/pull/24616)
362
			return false;
363
		}
364
		if (!$this->login($user, $password) ) {
365
			$users = $this->manager->getByEmail($user);
366
			if (count($users) === 1) {
367
				return $this->login($users[0]->getUID(), $password);
368
			}
369
			return false;
370
		}
371
372
		// TODO: only create the session token if the client supports cookies
373
		$this->createSessionToken(OC::$server->getRequest(), $this->getUser()->getUID(), $user, $password, !$isTokenPassword);
374
375
		return true;
376
	}
377
378
	private function isTokenAuthEnforced() {
379
		return $this->config->getSystemValue('token_auth_enforced', false);
380
	}
381
382
	protected function isTwoFactorEnforced($username) {
383
		Util::emitHook(
384
			'\OCA\Files_Sharing\API\Server2Server',
385
			'preLoginNameUsedAsUserName',
386
			array('uid' => &$username)
387
		);
388
		$user = $this->manager->get($username);
389
		if (is_null($user)) {
390
			$users = $this->manager->getByEmail($username);
391
			if (count($users) !== 1) {
392
				return true;
393
			}
394
			$user = $users[0];
395
		}
396
		// DI not possible due to cyclic dependencies :'-/
397
		return OC::$server->getTwoFactorAuthManager()->isTwoFactorAuthenticated($user);
398
	}
399
400
	/**
401
	 * Check if the given 'password' is actually a device token
402
	 *
403
	 * @param string $password
404
	 * @return boolean
405
	 */
406
	public function isTokenPassword($password) {
407
		try {
408
			$this->tokenProvider->getToken($password);
409
			return true;
410
		} catch (InvalidTokenException $ex) {
411
			return false;
412
		}
413
	}
414
415
	protected function prepareUserLogin() {
416
		// TODO: mock/inject/use non-static
417
		// Refresh the token
418
		\OC::$server->getCsrfTokenManager()->refreshToken();
419
		//we need to pass the user name, which may differ from login name
420
		$user = $this->getUser()->getUID();
421
		OC_Util::setupFS($user);
422
		//trigger creation of user home and /files folder
423
		\OC::$server->getUserFolder($user);
424
	}
425
426
	/**
427
	 * Tries to login the user with HTTP Basic Authentication
428
	 *
429
	 * @todo do not allow basic auth if the user is 2FA enforced
430
	 * @param IRequest $request
431
	 * @return boolean if the login was successful
432
	 */
433
	public function tryBasicAuthLogin(IRequest $request) {
434
		if (!empty($request->server['PHP_AUTH_USER']) && !empty($request->server['PHP_AUTH_PW'])) {
0 ignored issues
show
Bug introduced by
Accessing server on the interface OCP\IRequest suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
435
			$result = $this->logClientIn($request->server['PHP_AUTH_USER'], $request->server['PHP_AUTH_PW']);
0 ignored issues
show
Bug introduced by
Accessing server on the interface OCP\IRequest suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
436
			if ($result === true) {
437
				/**
438
				 * Add DAV authenticated. This should in an ideal world not be
439
				 * necessary but the iOS App reads cookies from anywhere instead
440
				 * only the DAV endpoint.
441
				 * This makes sure that the cookies will be valid for the whole scope
442
				 * @see https://github.com/owncloud/core/issues/22893
443
				 */
444
				$this->session->set(
445
					Auth::DAV_AUTHENTICATED, $this->getUser()->getUID()
446
				);
447
				return true;
448
			}
449
		}
450
		return false;
451
	}
452
453
	private function loginWithToken($uid) {
454
		// TODO: $this->manager->emit('\OC\User', 'preTokenLogin', array($uid));
455
		$user = $this->manager->get($uid);
456
		if (is_null($user)) {
457
			// user does not exist
458
			return false;
459
		}
460
		if (!$user->isEnabled()) {
461
			// disabled users can not log in
462
			return false;
463
		}
464
465
		//login
466
		$this->setUser($user);
0 ignored issues
show
Documentation introduced by
$user is of type object<OCP\IUser>, but the function expects a object<OC\User\User>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
467
		// TODO: $this->manager->emit('\OC\User', 'postTokenLogin', array($user));
468
		return true;
469
	}
470
471
	/**
472
	 * Create a new session token for the given user credentials
473
	 *
474
	 * @param IRequest $request
475
	 * @param string $uid user UID
476
	 * @param string $loginName login name
477
	 * @param string $password
478
	 * @param boolean $isBrowser false if the token is generated for a client
479
	 * @return boolean
480
	 */
481
	public function createSessionToken(IRequest $request, $uid, $loginName, $password = null, $isBrowser = true) {
482
		if (is_null($this->manager->get($uid))) {
483
			// User does not exist
484
			return false;
485
		}
486
		$name = isset($request->server['HTTP_USER_AGENT']) ? $request->server['HTTP_USER_AGENT'] : 'unknown browser';
0 ignored issues
show
Bug introduced by
Accessing server on the interface OCP\IRequest suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
487
		try {
488
			$sessionId = $this->session->getId();
489
			$pwd = $this->getPassword($password);
490
			if ($isBrowser) {
491
				$this->tokenProvider->generateToken($sessionId, $uid, $loginName, $pwd, $name, IToken::TEMPORARY_TOKEN);
492
			} else {
493
				$this->tokenProvider->generateToken($sessionId, $uid, $loginName, $pwd, $name, IToken::TEMPORARY_CLIENT_TOKEN);
494
			}
495
			return true;
496
		} catch (SessionNotAvailableException $ex) {
497
			// This can happen with OCC, where a memory session is used
498
			// if a memory session is used, we shouldn't create a session token anyway
499
			return false;
500
		}
501
	}
502
503
	/**
504
	 * Checks if the given password is a token.
505
	 * If yes, the password is extracted from the token.
506
	 * If no, the same password is returned.
507
	 *
508
	 * @param string $password either the login password or a device token
509
	 * @return string|null the password or null if none was set in the token
510
	 */
511
	private function getPassword($password) {
512
		if (is_null($password)) {
513
			// This is surely no token ;-)
514
			return null;
515
		}
516
		try {
517
			$token = $this->tokenProvider->getToken($password);
518
			try {
519
				return $this->tokenProvider->getPassword($token, $password);
520
			} catch (PasswordlessTokenException $ex) {
521
				return null;
522
			}
523
		} catch (InvalidTokenException $ex) {
524
			return $password;
525
		}
526
	}
527
528
	/**
529
	 * @param string $token
530
	 * @return boolean
531
	 */
532
	private function validateToken($token) {
533
		try {
534
			$token = $this->tokenProvider->validateToken($token);
535
			if (!is_null($token)) {
536
				$result = $this->loginWithToken($token->getUID());
537
				if ($result) {
538
					// Login success
539
					$this->updateToken($token);
540
					return true;
541
				}
542
			}
543
		} catch (InvalidTokenException $ex) {
544
545
		}
546
		return false;
547
	}
548
549
	/**
550
	 * @param IToken $token
551
	 */
552
	private function updateToken(IToken $token) {
553
		// To save unnecessary DB queries, this is only done once a minute
554
		$lastTokenUpdate = $this->session->get('last_token_update') ? : 0;
555
		$now = $this->timeFacory->getTime();
556
		if ($lastTokenUpdate < ($now - 60)) {
557
			$this->tokenProvider->updateToken($token);
558
			$this->session->set('last_token_update', $now);
559
		}
560
	}
561
562
	/**
563
	 * Tries to login the user with auth token header
564
	 *
565
	 * @todo check remember me cookie
566
	 * @return boolean
567
	 */
568
	public function tryTokenLogin(IRequest $request) {
569
		$authHeader = $request->getHeader('Authorization');
570
		if (strpos($authHeader, 'token ') === false) {
571
			// No auth header, let's try session id
572
			try {
573
				$sessionId = $this->session->getId();
574
				return $this->validateToken($sessionId);
575
			} catch (SessionNotAvailableException $ex) {
576
				return false;
577
			}
578
		} else {
579
			$token = substr($authHeader, 6);
580
			return $this->validateToken($token);
581
		}
582
	}
583
584
	/**
585
	 * perform login using the magic cookie (remember login)
586
	 *
587
	 * @param string $uid the username
588
	 * @param string $currentToken
589
	 * @return bool
590
	 */
591
	public function loginWithCookie($uid, $currentToken) {
592
		$this->session->regenerateId();
593
		$this->manager->emit('\OC\User', 'preRememberedLogin', array($uid));
594
		$user = $this->manager->get($uid);
595
		if (is_null($user)) {
596
			// user does not exist
597
			return false;
598
		}
599
600
		// get stored tokens
601
		$tokens = OC::$server->getConfig()->getUserKeys($uid, 'login_token');
602
		// test cookies token against stored tokens
603
		if (!in_array($currentToken, $tokens, true)) {
604
			return false;
605
		}
606
		// replace successfully used token with a new one
607
		OC::$server->getConfig()->deleteUserValue($uid, 'login_token', $currentToken);
608
		$newToken = OC::$server->getSecureRandom()->generate(32);
609
		OC::$server->getConfig()->setUserValue($uid, 'login_token', $newToken, time());
610
		$this->setMagicInCookie($user->getUID(), $newToken);
611
612
		//login
613
		$this->setUser($user);
0 ignored issues
show
Documentation introduced by
$user is of type object<OCP\IUser>, but the function expects a object<OC\User\User>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
614
		$this->manager->emit('\OC\User', 'postRememberedLogin', array($user));
615
		return true;
616
	}
617
618
	/**
619
	 * logout the user from the session
620
	 */
621
	public function logout() {
622
		$this->manager->emit('\OC\User', 'logout');
623
		$user = $this->getUser();
624
		if (!is_null($user)) {
625
			try {
626
				$this->tokenProvider->invalidateToken($this->session->getId());
627
			} catch (SessionNotAvailableException $ex) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
628
				
629
			}
630
		}
631
		$this->setUser(null);
632
		$this->setLoginName(null);
633
		$this->unsetMagicInCookie();
634
		$this->session->clear();
635
	}
636
637
	/**
638
	 * Set cookie value to use in next page load
639
	 *
640
	 * @param string $username username to be set
641
	 * @param string $token
642
	 */
643
	public function setMagicInCookie($username, $token) {
644
		$secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https';
645
		$expires = time() + OC::$server->getConfig()->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
646
		setcookie('oc_username', $username, $expires, OC::$WEBROOT, '', $secureCookie, true);
647
		setcookie('oc_token', $token, $expires, OC::$WEBROOT, '', $secureCookie, true);
648
		setcookie('oc_remember_login', '1', $expires, OC::$WEBROOT, '', $secureCookie, true);
649
	}
650
651
	/**
652
	 * Remove cookie for "remember username"
653
	 */
654
	public function unsetMagicInCookie() {
655
		//TODO: DI for cookies and IRequest
656
		$secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https';
657
658
		unset($_COOKIE['oc_username']); //TODO: DI
659
		unset($_COOKIE['oc_token']);
660
		unset($_COOKIE['oc_remember_login']);
661
		setcookie('oc_username', '', time() - 3600, OC::$WEBROOT, '', $secureCookie, true);
662
		setcookie('oc_token', '', time() - 3600, OC::$WEBROOT, '', $secureCookie, true);
663
		setcookie('oc_remember_login', '', time() - 3600, OC::$WEBROOT, '', $secureCookie, true);
664
		// old cookies might be stored under /webroot/ instead of /webroot
665
		// and Firefox doesn't like it!
666
		setcookie('oc_username', '', time() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
667
		setcookie('oc_token', '', time() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
668
		setcookie('oc_remember_login', '', time() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
669
	}
670
671
}
672