Completed
Push — master ( 03449d...db6361 )
by Lukas
44s queued 24s
created

Session::isTokenPassword()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 1
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
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\Exceptions\PasswordLoginForbiddenException;
37
use OC\Authentication\Token\IProvider;
38
use OC\Authentication\Token\IToken;
39
use OC\Hooks\Emitter;
40
use OC_User;
41
use OC_Util;
42
use OCA\DAV\Connector\Sabre\Auth;
43
use OCP\AppFramework\Utility\ITimeFactory;
44
use OCP\IConfig;
45
use OCP\IRequest;
46
use OCP\ISession;
47
use OCP\IUser;
48
use OCP\IUserManager;
49
use OCP\IUserSession;
50
use OCP\Session\Exceptions\SessionNotAvailableException;
51
use OCP\Util;
52
53
/**
54
 * Class Session
55
 *
56
 * Hooks available in scope \OC\User:
57
 * - preSetPassword(\OC\User\User $user, string $password, string $recoverPassword)
58
 * - postSetPassword(\OC\User\User $user, string $password, string $recoverPassword)
59
 * - preDelete(\OC\User\User $user)
60
 * - postDelete(\OC\User\User $user)
61
 * - preCreateUser(string $uid, string $password)
62
 * - postCreateUser(\OC\User\User $user)
63
 * - preLogin(string $user, string $password)
64
 * - postLogin(\OC\User\User $user, string $password)
65
 * - preRememberedLogin(string $uid)
66
 * - postRememberedLogin(\OC\User\User $user)
67
 * - logout()
68
 *
69
 * @package OC\User
70
 */
71
class Session implements IUserSession, Emitter {
72
73
	/** @var IUserManager $manager */
74
	private $manager;
75
76
	/** @var ISession $session */
77
	private $session;
78
79
	/** @var ITimeFactory */
80
	private $timeFacory;
81
82
	/** @var IProvider */
83
	private $tokenProvider;
84
85
	/** @var IConfig */
86
	private $config;
87
88
	/** @var User $activeUser */
89
	protected $activeUser;
90
91
	/**
92
	 * @param IUserManager $manager
93
	 * @param ISession $session
94
	 * @param ITimeFactory $timeFacory
95
	 * @param IProvider $tokenProvider
96
	 * @param IConfig $config
97
	 */
98
	public function __construct(IUserManager $manager, ISession $session, ITimeFactory $timeFacory, $tokenProvider, IConfig $config) {
99
		$this->manager = $manager;
100
		$this->session = $session;
101
		$this->timeFacory = $timeFacory;
102
		$this->tokenProvider = $tokenProvider;
103
		$this->config = $config;
104
	}
105
106
	/**
107
	 * @param IProvider $provider
108
	 */
109
	public function setTokenProvider(IProvider $provider) {
110
		$this->tokenProvider = $provider;
111
	}
112
113
	/**
114
	 * @param string $scope
115
	 * @param string $method
116
	 * @param callable $callback
117
	 */
118
	public function listen($scope, $method, callable $callback) {
119
		$this->manager->listen($scope, $method, $callback);
120
	}
121
122
	/**
123
	 * @param string $scope optional
124
	 * @param string $method optional
125
	 * @param callable $callback optional
126
	 */
127
	public function removeListener($scope = null, $method = null, callable $callback = null) {
128
		$this->manager->removeListener($scope, $method, $callback);
129
	}
130
131
	/**
132
	 * get the manager object
133
	 *
134
	 * @return Manager
135
	 */
136
	public function getManager() {
137
		return $this->manager;
138
	}
139
140
	/**
141
	 * get the session object
142
	 *
143
	 * @return ISession
144
	 */
145
	public function getSession() {
146
		return $this->session;
147
	}
148
149
	/**
150
	 * set the session object
151
	 *
152
	 * @param ISession $session
153
	 */
154
	public function setSession(ISession $session) {
155
		if ($this->session instanceof ISession) {
156
			$this->session->close();
157
		}
158
		$this->session = $session;
159
		$this->activeUser = null;
160
	}
161
162
	/**
163
	 * set the currently active user
164
	 *
165
	 * @param User|null $user
166
	 */
167
	public function setUser($user) {
168
		if (is_null($user)) {
169
			$this->session->remove('user_id');
170
		} else {
171
			$this->session->set('user_id', $user->getUID());
172
		}
173
		$this->activeUser = $user;
174
	}
175
176
	/**
177
	 * get the current active user
178
	 *
179
	 * @return IUser|null Current user, otherwise null
180
	 */
181
	public function getUser() {
182
		// FIXME: This is a quick'n dirty work-around for the incognito mode as
183
		// described at https://github.com/owncloud/core/pull/12912#issuecomment-67391155
184
		if (OC_User::isIncognitoMode()) {
185
			return null;
186
		}
187
		if (is_null($this->activeUser)) {
188
			$uid = $this->session->get('user_id');
189
			if (is_null($uid)) {
190
				return null;
191
			}
192
			$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...
193
			if (is_null($this->activeUser)) {
194
				return null;
195
			}
196
			$this->validateSession();
197
		}
198
		return $this->activeUser;
199
	}
200
201
	/**
202
	 * Validate whether the current session is valid
203
	 *
204
	 * - For token-authenticated clients, the token validity is checked
205
	 * - For browsers, the session token validity is checked
206
	 */
207
	protected function validateSession() {
208
		$token = null;
209
		$appPassword = $this->session->get('app_password');
210
211
		if (is_null($appPassword)) {
212
			try {
213
				$token = $this->session->getId();
214
			} catch (SessionNotAvailableException $ex) {
215
				return;
216
			}
217
		} else {
218
			$token = $appPassword;
219
		}
220
221
		if (!$this->validateToken($token)) {
222
			// Session was invalidated
223
			$this->logout();
224
		}
225
	}
226
227
	/**
228
	 * Checks whether the user is logged in
229
	 *
230
	 * @return bool if logged in
231
	 */
232
	public function isLoggedIn() {
233
		$user = $this->getUser();
234
		if (is_null($user)) {
235
			return false;
236
		}
237
238
		return $user->isEnabled();
239
	}
240
241
	/**
242
	 * set the login name
243
	 *
244
	 * @param string|null $loginName for the logged in user
245
	 */
246
	public function setLoginName($loginName) {
247
		if (is_null($loginName)) {
248
			$this->session->remove('loginname');
249
		} else {
250
			$this->session->set('loginname', $loginName);
251
		}
252
	}
253
254
	/**
255
	 * get the login name of the current user
256
	 *
257
	 * @return string
258
	 */
259
	public function getLoginName() {
260
		if ($this->activeUser) {
261
			return $this->session->get('loginname');
262
		} else {
263
			$uid = $this->session->get('user_id');
264
			if ($uid) {
265
				$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...
266
				return $this->session->get('loginname');
267
			} else {
268
				return null;
269
			}
270
		}
271
	}
272
273
	/**
274
	 * try to log in with the provided credentials
275
	 *
276
	 * @param string $uid
277
	 * @param string $password
278
	 * @return boolean|null
279
	 * @throws LoginException
280
	 */
281
	public function login($uid, $password) {
282
		$this->session->regenerateId();
283
		if ($this->validateToken($password)) {
284
			// When logging in with token, the password must be decrypted first before passing to login hook
285
			try {
286
				$token = $this->tokenProvider->getToken($password);
287
				try {
288
					$loginPassword = $this->tokenProvider->getPassword($token, $password);
289
					$this->manager->emit('\OC\User', 'preLogin', array($uid, $loginPassword));
290
				} catch (PasswordlessTokenException $ex) {
291
					$this->manager->emit('\OC\User', 'preLogin', array($uid, ''));
292
				}
293
			} catch (InvalidTokenException $ex) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
294
				// Invalid token, nothing to do
295
			}
296
297
			$this->loginWithToken($password);
298
			$user = $this->getUser();
299
		} else {
300
			$this->manager->emit('\OC\User', 'preLogin', array($uid, $password));
301
			$user = $this->manager->checkPassword($uid, $password);
302
		}
303
		if ($user !== false) {
304
			if (!is_null($user)) {
305
				if ($user->isEnabled()) {
306
					$this->setUser($user);
307
					$this->setLoginName($uid);
308
					$this->manager->emit('\OC\User', 'postLogin', array($user, $password));
309
					if ($this->isLoggedIn()) {
310
						$this->prepareUserLogin();
311
						return true;
312
					} else {
313
						// injecting l10n does not work - there is a circular dependency between session and \OCP\L10N\IFactory
314
						$message = \OC::$server->getL10N('lib')->t('Login canceled by app');
315
						throw new LoginException($message);
316
					}
317
				} else {
318
					// injecting l10n does not work - there is a circular dependency between session and \OCP\L10N\IFactory
319
					$message = \OC::$server->getL10N('lib')->t('User disabled');
320
					throw new LoginException($message);
321
				}
322
			}
323
		}
324
		return false;
325
	}
326
327
	/**
328
	 * Tries to log in a client
329
	 *
330
	 * Checks token auth enforced
331
	 * Checks 2FA enabled
332
	 *
333
	 * @param string $user
334
	 * @param string $password
335
	 * @param IRequest $request
336
	 * @throws LoginException
337
	 * @throws PasswordLoginForbiddenException
338
	 * @return boolean
339
	 */
340
	public function logClientIn($user, $password, IRequest $request) {
341
		$isTokenPassword = $this->isTokenPassword($password);
342
		if (!$isTokenPassword && $this->isTokenAuthEnforced()) {
343
			throw new PasswordLoginForbiddenException();
344
		}
345
		if (!$isTokenPassword && $this->isTwoFactorEnforced($user)) {
346
			throw new PasswordLoginForbiddenException();
347
		}
348
		if (!$this->login($user, $password) ) {
349
			$users = $this->manager->getByEmail($user);
350
			if (count($users) === 1) {
351
				return $this->login($users[0]->getUID(), $password);
352
			}
353
			return false;
354
		}
355
356
		if ($isTokenPassword) {
357
			$this->session->set('app_password', $password);
358
		} else if($this->supportsCookies($request)) {
359
			// Password login, but cookies supported -> create (browser) session token
360
			$this->createSessionToken($request, $this->getUser()->getUID(), $user, $password);
361
		}
362
363
		return true;
364
	}
365
366
	protected function supportsCookies(IRequest $request) {
367
		if (!is_null($request->getCookie('cookie_test'))) {
368
			return true;
369
		}
370
		setcookie('cookie_test', 'test', $this->timeFacory->getTime() + 3600);
371
		return false;
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
			try {
432
				if ($this->logClientIn($request->server['PHP_AUTH_USER'], $request->server['PHP_AUTH_PW'], $request)) {
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...
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
			} catch (PasswordLoginForbiddenException $ex) {
446
				// Nothing to do
447
			}
448
		}
449
		return false;
450
	}
451
452
	private function loginWithToken($token) {
453
		try {
454
			$dbToken = $this->tokenProvider->getToken($token);
455
		} catch (InvalidTokenException $ex) {
456
			return false;
457
		}
458
		$uid = $dbToken->getUID();
459
460
		$password = '';
461
		try {
462
			$password = $this->tokenProvider->getPassword($dbToken, $token);
463
		} catch (PasswordlessTokenException $ex) {
464
			// Ignore and use empty string instead
465
		}
466
		$this->manager->emit('\OC\User', 'preLogin', array($uid, $password));
467
468
		$user = $this->manager->get($uid);
469
		if (is_null($user)) {
470
			// user does not exist
471
			return false;
472
		}
473
		if (!$user->isEnabled()) {
474
			// disabled users can not log in
475
			return false;
476
		}
477
478
		//login
479
		$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...
480
481
		$this->manager->emit('\OC\User', 'postLogin', array($user, $password));
482
		return true;
483
	}
484
485
	/**
486
	 * Create a new session token for the given user credentials
487
	 *
488
	 * @param IRequest $request
489
	 * @param string $uid user UID
490
	 * @param string $loginName login name
491
	 * @param string $password
492
	 * @return boolean
493
	 */
494
	public function createSessionToken(IRequest $request, $uid, $loginName, $password = null) {
495
		if (is_null($this->manager->get($uid))) {
496
			// User does not exist
497
			return false;
498
		}
499
		$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...
500
		try {
501
			$sessionId = $this->session->getId();
502
			$pwd = $this->getPassword($password);
503
			$this->tokenProvider->generateToken($sessionId, $uid, $loginName, $pwd, $name);
504
			return true;
505
		} catch (SessionNotAvailableException $ex) {
506
			// This can happen with OCC, where a memory session is used
507
			// if a memory session is used, we shouldn't create a session token anyway
508
			return false;
509
		}
510
	}
511
512
	/**
513
	 * Checks if the given password is a token.
514
	 * If yes, the password is extracted from the token.
515
	 * If no, the same password is returned.
516
	 *
517
	 * @param string $password either the login password or a device token
518
	 * @return string|null the password or null if none was set in the token
519
	 */
520
	private function getPassword($password) {
521
		if (is_null($password)) {
522
			// This is surely no token ;-)
523
			return null;
524
		}
525
		try {
526
			$token = $this->tokenProvider->getToken($password);
527
			try {
528
				return $this->tokenProvider->getPassword($token, $password);
529
			} catch (PasswordlessTokenException $ex) {
530
				return null;
531
			}
532
		} catch (InvalidTokenException $ex) {
533
			return $password;
534
		}
535
	}
536
537
	/**
538
	 * @param IToken $dbToken
539
	 * @param string $token
540
	 * @return boolean
541
	 */
542
	private function checkTokenCredentials(IToken $dbToken, $token) {
543
		// Check whether login credentials are still valid and the user was not disabled
544
		// This check is performed each 5 minutes
545
		$lastCheck = $dbToken->getLastCheck() ? : 0;
546
		$now = $this->timeFacory->getTime();
547
		if ($lastCheck > ($now - 60 * 5)) {
548
			// Checked performed recently, nothing to do now
549
			return true;
550
		}
551
552
		try {
553
			$pwd = $this->tokenProvider->getPassword($dbToken, $token);
554
		} catch (InvalidTokenException $ex) {
555
			// An invalid token password was used -> log user out
556
			return false;
557
		} catch (PasswordlessTokenException $ex) {
558
			// Token has no password
559
560
			if (!is_null($this->activeUser) && !$this->activeUser->isEnabled()) {
561
				$this->tokenProvider->invalidateToken($token);
562
				return false;
563
			}
564
565
			$dbToken->setLastCheck($now);
566
			$this->tokenProvider->updateToken($dbToken);
567
			return true;
568
		}
569
570
		if ($this->manager->checkPassword($dbToken->getLoginName(), $pwd) === false
571
			|| (!is_null($this->activeUser) && !$this->activeUser->isEnabled())) {
572
			$this->tokenProvider->invalidateToken($token);
573
			// Password has changed or user was disabled -> log user out
574
			return false;
575
		}
576
		$dbToken->setLastCheck($now);
577
		$this->tokenProvider->updateToken($dbToken);
578
		return true;
579
	}
580
581
	/**
582
	 * Check if the given token exists and performs password/user-enabled checks
583
	 *
584
	 * Invalidates the token if checks fail
585
	 *
586
	 * @param string $token
587
	 * @return boolean
588
	 */
589
	private function validateToken($token) {
590
		try {
591
			$dbToken = $this->tokenProvider->getToken($token);
592
		} catch (InvalidTokenException $ex) {
593
			return false;
594
		}
595
596
		if (!$this->checkTokenCredentials($dbToken, $token)) {
597
			return false;
598
		}
599
600
		$this->tokenProvider->updateTokenActivity($dbToken);
601
602
		return true;
603
	}
604
605
	/**
606
	 * Tries to login the user with auth token header
607
	 *
608
	 * @todo check remember me cookie
609
	 * @return boolean
610
	 */
611
	public function tryTokenLogin(IRequest $request) {
612
		$authHeader = $request->getHeader('Authorization');
613
		if (strpos($authHeader, 'token ') === false) {
614
			// No auth header, let's try session id
615
			try {
616
				$token = $this->session->getId();
617
			} catch (SessionNotAvailableException $ex) {
618
				return false;
619
			}
620
		} else {
621
			$token = substr($authHeader, 6);
622
		}
623
624
		if (!$this->loginWithToken($token)) {
625
			return false;
626
		}
627
		if(!$this->validateToken($token)) {
628
			return false;
629
		}
630
		return true;
631
	}
632
633
	/**
634
	 * perform login using the magic cookie (remember login)
635
	 *
636
	 * @param string $uid the username
637
	 * @param string $currentToken
638
	 * @return bool
639
	 */
640
	public function loginWithCookie($uid, $currentToken) {
641
		$this->session->regenerateId();
642
		$this->manager->emit('\OC\User', 'preRememberedLogin', array($uid));
643
		$user = $this->manager->get($uid);
644
		if (is_null($user)) {
645
			// user does not exist
646
			return false;
647
		}
648
649
		// get stored tokens
650
		$tokens = OC::$server->getConfig()->getUserKeys($uid, 'login_token');
651
		// test cookies token against stored tokens
652
		if (!in_array($currentToken, $tokens, true)) {
653
			return false;
654
		}
655
		// replace successfully used token with a new one
656
		OC::$server->getConfig()->deleteUserValue($uid, 'login_token', $currentToken);
657
		$newToken = OC::$server->getSecureRandom()->generate(32);
658
		OC::$server->getConfig()->setUserValue($uid, 'login_token', $newToken, time());
659
		$this->setMagicInCookie($user->getUID(), $newToken);
660
661
		//login
662
		$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...
663
		$this->manager->emit('\OC\User', 'postRememberedLogin', array($user));
664
		return true;
665
	}
666
667
	/**
668
	 * logout the user from the session
669
	 */
670
	public function logout() {
671
		$this->manager->emit('\OC\User', 'logout');
672
		$user = $this->getUser();
673
		if (!is_null($user)) {
674
			try {
675
				$this->tokenProvider->invalidateToken($this->session->getId());
676
			} catch (SessionNotAvailableException $ex) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
677
				
678
			}
679
		}
680
		$this->setUser(null);
681
		$this->setLoginName(null);
682
		$this->unsetMagicInCookie();
683
		$this->session->clear();
684
	}
685
686
	/**
687
	 * Set cookie value to use in next page load
688
	 *
689
	 * @param string $username username to be set
690
	 * @param string $token
691
	 */
692
	public function setMagicInCookie($username, $token) {
693
		$secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https';
694
		$expires = time() + OC::$server->getConfig()->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
695
		setcookie('oc_username', $username, $expires, OC::$WEBROOT, '', $secureCookie, true);
696
		setcookie('oc_token', $token, $expires, OC::$WEBROOT, '', $secureCookie, true);
697
		setcookie('oc_remember_login', '1', $expires, OC::$WEBROOT, '', $secureCookie, true);
698
	}
699
700
	/**
701
	 * Remove cookie for "remember username"
702
	 */
703
	public function unsetMagicInCookie() {
704
		//TODO: DI for cookies and IRequest
705
		$secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https';
706
707
		unset($_COOKIE['oc_username']); //TODO: DI
708
		unset($_COOKIE['oc_token']);
709
		unset($_COOKIE['oc_remember_login']);
710
		setcookie('oc_username', '', time() - 3600, OC::$WEBROOT, '', $secureCookie, true);
711
		setcookie('oc_token', '', time() - 3600, OC::$WEBROOT, '', $secureCookie, true);
712
		setcookie('oc_remember_login', '', time() - 3600, OC::$WEBROOT, '', $secureCookie, true);
713
		// old cookies might be stored under /webroot/ instead of /webroot
714
		// and Firefox doesn't like it!
715
		setcookie('oc_username', '', time() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
716
		setcookie('oc_token', '', time() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
717
		setcookie('oc_remember_login', '', time() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
718
	}
719
720
	/**
721
	 * Update password of the browser session token if there is one
722
	 *
723
	 * @param string $password
724
	 */
725
	public function updateSessionTokenPassword($password) {
726
		try {
727
			$sessionId = $this->session->getId();
728
			$token = $this->tokenProvider->getToken($sessionId);
729
			$this->tokenProvider->setPassword($token, $sessionId, $password);
730
		} catch (SessionNotAvailableException $ex) {
731
			// Nothing to do
732
		} catch (InvalidTokenException $ex) {
733
			// Nothing to do
734
		}
735
	}
736
737
}
738