Completed
Pull Request — master (#31)
by Blizzz
13:09 queued 04:36
created

Session   F

Complexity

Total Complexity 86

Size/Duplication

Total Lines 593
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 19

Importance

Changes 4
Bugs 1 Features 0
Metric Value
c 4
b 1
f 0
dl 0
loc 593
rs 1.5789
wmc 86
lcom 1
cbo 19

30 Methods

Rating   Name   Duplication   Size   Complexity  
A setTokenProvider() 0 3 1
A listen() 0 3 1
A removeListener() 0 3 1
A getManager() 0 3 1
A getSession() 0 3 1
A setSession() 0 7 2
A setUser() 0 8 2
B getUser() 0 19 5
D validateSession() 0 43 9
A isLoggedIn() 0 8 2
A setLoginName() 0 7 2
C login() 0 44 8
A __construct() 0 7 1
A getLoginName() 0 13 3
B logClientIn() 0 19 7
A isTokenAuthEnforced() 0 3 1
A isTwoFactorEnforced() 0 17 3
A isTokenPassword() 0 8 2
A prepareUserLogin() 0 10 1
A tryBasicAuthLogin() 0 19 4
A loginWithToken() 0 17 3
A createSessionToken() 0 17 4
A getPassword() 0 16 4
A validateToken() 0 16 4
A updateToken() 0 9 3
A tryTokenLogin() 0 15 3
B loginWithCookie() 0 26 3
A logout() 0 15 3
A setMagicInCookie() 0 7 1
A unsetMagicInCookie() 0 16 1

How to fix   Complexity   

Complex Class

Complex classes like Session often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Session, and based on these observations, apply Extract Interface, too.

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
		return true;
372
	}
373
374
	private function isTokenAuthEnforced() {
375
		return $this->config->getSystemValue('token_auth_enforced', false);
376
	}
377
378
	protected function isTwoFactorEnforced($username) {
379
		Util::emitHook(
380
			'\OCA\Files_Sharing\API\Server2Server',
381
			'preLoginNameUsedAsUserName',
382
			array('uid' => &$username)
383
		);
384
		$user = $this->manager->get($username);
385
		if (is_null($user)) {
386
			$users = $this->manager->getByEmail($username);
387
			if (count($users) !== 1) {
388
				return true;
389
			}
390
			$user = $users[0];
391
		}
392
		// DI not possible due to cyclic dependencies :'-/
393
		return OC::$server->getTwoFactorAuthManager()->isTwoFactorAuthenticated($user);
394
	}
395
396
	/**
397
	 * Check if the given 'password' is actually a device token
398
	 *
399
	 * @param string $password
400
	 * @return boolean
401
	 */
402
	public function isTokenPassword($password) {
403
		try {
404
			$this->tokenProvider->getToken($password);
405
			return true;
406
		} catch (InvalidTokenException $ex) {
407
			return false;
408
		}
409
	}
410
411
	protected function prepareUserLogin() {
412
		// TODO: mock/inject/use non-static
413
		// Refresh the token
414
		\OC::$server->getCsrfTokenManager()->refreshToken();
415
		//we need to pass the user name, which may differ from login name
416
		$user = $this->getUser()->getUID();
417
		OC_Util::setupFS($user);
418
		//trigger creation of user home and /files folder
419
		\OC::$server->getUserFolder($user);
420
	}
421
422
	/**
423
	 * Tries to login the user with HTTP Basic Authentication
424
	 *
425
	 * @todo do not allow basic auth if the user is 2FA enforced
426
	 * @param IRequest $request
427
	 * @return boolean if the login was successful
428
	 */
429
	public function tryBasicAuthLogin(IRequest $request) {
430
		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...
431
			$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...
432
			if ($result === true) {
433
				/**
434
				 * Add DAV authenticated. This should in an ideal world not be
435
				 * necessary but the iOS App reads cookies from anywhere instead
436
				 * only the DAV endpoint.
437
				 * This makes sure that the cookies will be valid for the whole scope
438
				 * @see https://github.com/owncloud/core/issues/22893
439
				 */
440
				$this->session->set(
441
					Auth::DAV_AUTHENTICATED, $this->getUser()->getUID()
442
				);
443
				return true;
444
			}
445
		}
446
		return false;
447
	}
448
449
	private function loginWithToken($uid) {
450
		// TODO: $this->manager->emit('\OC\User', 'preTokenLogin', array($uid));
451
		$user = $this->manager->get($uid);
452
		if (is_null($user)) {
453
			// user does not exist
454
			return false;
455
		}
456
		if (!$user->isEnabled()) {
457
			// disabled users can not log in
458
			return false;
459
		}
460
461
		//login
462
		$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...
463
		// TODO: $this->manager->emit('\OC\User', 'postTokenLogin', array($user));
464
		return true;
465
	}
466
467
	/**
468
	 * Create a new session token for the given user credentials
469
	 *
470
	 * @param IRequest $request
471
	 * @param string $uid user UID
472
	 * @param string $loginName login name
473
	 * @param string $password
474
	 * @return boolean
475
	 */
476
	public function createSessionToken(IRequest $request, $uid, $loginName, $password = null) {
477
		if (is_null($this->manager->get($uid))) {
478
			// User does not exist
479
			return false;
480
		}
481
		$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...
482
		try {
483
			$sessionId = $this->session->getId();
484
			$pwd = $this->getPassword($password);
485
			$this->tokenProvider->generateToken($sessionId, $uid, $loginName, $pwd, $name);
486
			return true;
487
		} catch (SessionNotAvailableException $ex) {
488
			// This can happen with OCC, where a memory session is used
489
			// if a memory session is used, we shouldn't create a session token anyway
490
			return false;
491
		}
492
	}
493
494
	/**
495
	 * Checks if the given password is a token.
496
	 * If yes, the password is extracted from the token.
497
	 * If no, the same password is returned.
498
	 *
499
	 * @param string $password either the login password or a device token
500
	 * @return string|null the password or null if none was set in the token
501
	 */
502
	private function getPassword($password) {
503
		if (is_null($password)) {
504
			// This is surely no token ;-)
505
			return null;
506
		}
507
		try {
508
			$token = $this->tokenProvider->getToken($password);
509
			try {
510
				return $this->tokenProvider->getPassword($token, $password);
511
			} catch (PasswordlessTokenException $ex) {
512
				return null;
513
			}
514
		} catch (InvalidTokenException $ex) {
515
			return $password;
516
		}
517
	}
518
519
	/**
520
	 * @param string $token
521
	 * @return boolean
522
	 */
523
	private function validateToken($token) {
524
		try {
525
			$token = $this->tokenProvider->validateToken($token);
526
			if (!is_null($token)) {
527
				$result = $this->loginWithToken($token->getUID());
528
				if ($result) {
529
					// Login success
530
					$this->updateToken($token);
531
					return true;
532
				}
533
			}
534
		} catch (InvalidTokenException $ex) {
535
536
		}
537
		return false;
538
	}
539
540
	/**
541
	 * @param IToken $token
542
	 */
543
	private function updateToken(IToken $token) {
544
		// To save unnecessary DB queries, this is only done once a minute
545
		$lastTokenUpdate = $this->session->get('last_token_update') ? : 0;
546
		$now = $this->timeFacory->getTime();
547
		if ($lastTokenUpdate < ($now - 60)) {
548
			$this->tokenProvider->updateToken($token);
549
			$this->session->set('last_token_update', $now);
550
		}
551
	}
552
553
	/**
554
	 * Tries to login the user with auth token header
555
	 *
556
	 * @todo check remember me cookie
557
	 * @return boolean
558
	 */
559
	public function tryTokenLogin(IRequest $request) {
560
		$authHeader = $request->getHeader('Authorization');
561
		if (strpos($authHeader, 'token ') === false) {
562
			// No auth header, let's try session id
563
			try {
564
				$sessionId = $this->session->getId();
565
				return $this->validateToken($sessionId);
566
			} catch (SessionNotAvailableException $ex) {
567
				return false;
568
			}
569
		} else {
570
			$token = substr($authHeader, 6);
571
			return $this->validateToken($token);
572
		}
573
	}
574
575
	/**
576
	 * perform login using the magic cookie (remember login)
577
	 *
578
	 * @param string $uid the username
579
	 * @param string $currentToken
580
	 * @return bool
581
	 */
582
	public function loginWithCookie($uid, $currentToken) {
583
		$this->session->regenerateId();
584
		$this->manager->emit('\OC\User', 'preRememberedLogin', array($uid));
585
		$user = $this->manager->get($uid);
586
		if (is_null($user)) {
587
			// user does not exist
588
			return false;
589
		}
590
591
		// get stored tokens
592
		$tokens = OC::$server->getConfig()->getUserKeys($uid, 'login_token');
593
		// test cookies token against stored tokens
594
		if (!in_array($currentToken, $tokens, true)) {
595
			return false;
596
		}
597
		// replace successfully used token with a new one
598
		OC::$server->getConfig()->deleteUserValue($uid, 'login_token', $currentToken);
599
		$newToken = OC::$server->getSecureRandom()->generate(32);
600
		OC::$server->getConfig()->setUserValue($uid, 'login_token', $newToken, time());
601
		$this->setMagicInCookie($user->getUID(), $newToken);
602
603
		//login
604
		$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...
605
		$this->manager->emit('\OC\User', 'postRememberedLogin', array($user));
606
		return true;
607
	}
608
609
	/**
610
	 * logout the user from the session
611
	 */
612
	public function logout() {
613
		$this->manager->emit('\OC\User', 'logout');
614
		$user = $this->getUser();
615
		if (!is_null($user)) {
616
			try {
617
				$this->tokenProvider->invalidateToken($this->session->getId());
618
			} catch (SessionNotAvailableException $ex) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
619
				
620
			}
621
		}
622
		$this->setUser(null);
623
		$this->setLoginName(null);
624
		$this->unsetMagicInCookie();
625
		$this->session->clear();
626
	}
627
628
	/**
629
	 * Set cookie value to use in next page load
630
	 *
631
	 * @param string $username username to be set
632
	 * @param string $token
633
	 */
634
	public function setMagicInCookie($username, $token) {
635
		$secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https';
636
		$expires = time() + OC::$server->getConfig()->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
637
		setcookie('oc_username', $username, $expires, OC::$WEBROOT, '', $secureCookie, true);
638
		setcookie('oc_token', $token, $expires, OC::$WEBROOT, '', $secureCookie, true);
639
		setcookie('oc_remember_login', '1', $expires, OC::$WEBROOT, '', $secureCookie, true);
640
	}
641
642
	/**
643
	 * Remove cookie for "remember username"
644
	 */
645
	public function unsetMagicInCookie() {
646
		//TODO: DI for cookies and IRequest
647
		$secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https';
648
649
		unset($_COOKIE['oc_username']); //TODO: DI
650
		unset($_COOKIE['oc_token']);
651
		unset($_COOKIE['oc_remember_login']);
652
		setcookie('oc_username', '', time() - 3600, OC::$WEBROOT, '', $secureCookie, true);
653
		setcookie('oc_token', '', time() - 3600, OC::$WEBROOT, '', $secureCookie, true);
654
		setcookie('oc_remember_login', '', time() - 3600, OC::$WEBROOT, '', $secureCookie, true);
655
		// old cookies might be stored under /webroot/ instead of /webroot
656
		// and Firefox doesn't like it!
657
		setcookie('oc_username', '', time() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
658
		setcookie('oc_token', '', time() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
659
		setcookie('oc_remember_login', '', time() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
660
	}
661
662
}
663