Completed
Pull Request — master (#32039)
by
unknown
11:33
created

Session   F

Complexity

Total Complexity 147

Size/Duplication

Total Lines 1034
Duplicated Lines 3.29 %

Coupling/Cohesion

Components 1
Dependencies 30

Importance

Changes 0
Metric Value
dl 34
loc 1034
rs 1.464
c 0
b 0
f 0
wmc 147
lcom 1
cbo 30

38 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 14 1
A setTokenProvider() 0 3 1
A listen() 0 3 1
A removeListener() 0 3 1
A getSession() 0 3 1
A setSession() 0 7 2
A setUser() 0 8 2
A getUser() 0 18 5
A validateSession() 0 23 5
A isLoggedIn() 0 8 2
A setLoginName() 0 7 2
A getLoginName() 0 13 3
A login() 0 8 2
B logClientIn() 0 30 11
A supportsCookies() 0 7 2
A isTokenAuthEnforced() 0 3 1
A isTwoFactorEnforced() 0 20 4
A isTokenPassword() 0 8 2
A prepareUserLogin() 0 36 5
A tryBasicAuthLogin() 0 22 5
A loginWithPassword() 0 34 4
B loginWithToken() 13 53 6
C loginWithApache() 0 77 14
A createSessionToken() 0 17 4
A getPassword() 0 16 4
C checkTokenCredentials() 0 63 11
A validateToken() 0 23 5
B tryTokenLogin() 10 21 6
A tryAuthModuleLogin() 0 13 3
A loginUser() 11 34 5
A loginWithCookie() 0 28 3
A logout() 0 27 4
A setMagicInCookie() 0 7 1
A unsetMagicInCookie() 0 15 1
A updateSessionTokenPassword() 0 11 3
B verifyAuthHeaders() 0 28 9
A getAuthModules() 0 18 5
A emitFailedLogin() 0 6 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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 Felix Rupp <[email protected]>
7
 * @author Jörn Friedrich Dreyer <[email protected]>
8
 * @author Lukas Reschke <[email protected]>
9
 * @author Morris Jobke <[email protected]>
10
 * @author Robin Appelman <[email protected]>
11
 * @author Robin McCorkell <[email protected]>
12
 * @author Semih Serhat Karakaya <[email protected]>
13
 * @author Thomas Müller <[email protected]>
14
 * @author Vincent Petry <[email protected]>
15
 *
16
 * @copyright Copyright (c) 2018, ownCloud GmbH
17
 * @license AGPL-3.0
18
 *
19
 * This code is free software: you can redistribute it and/or modify
20
 * it under the terms of the GNU Affero General Public License, version 3,
21
 * as published by the Free Software Foundation.
22
 *
23
 * This program is distributed in the hope that it will be useful,
24
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26
 * GNU Affero General Public License for more details.
27
 *
28
 * You should have received a copy of the GNU Affero General Public License, version 3,
29
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
30
 *
31
 */
32
33
namespace OC\User;
34
35
use Exception;
36
use OC;
37
use OC\Authentication\Exceptions\InvalidTokenException;
38
use OC\Authentication\Exceptions\PasswordlessTokenException;
39
use OC\Authentication\Exceptions\PasswordLoginForbiddenException;
40
use OC\Authentication\Token\IProvider;
41
use OC\Authentication\Token\IToken;
42
use OC\HintException;
43
use OC\Hooks\Emitter;
44
use OC\Hooks\PublicEmitter;
45
use OC_User;
46
use OC_Util;
47
use OCA\DAV\Connector\Sabre\Auth;
48
use OCP\App\IServiceLoader;
49
use OCP\AppFramework\Utility\ITimeFactory;
50
use OCP\Authentication\IApacheBackend;
51
use OCP\Authentication\IAuthModule;
52
use OCP\Events\EventEmitterTrait;
53
use OCP\Files\NoReadAccessException;
54
use OCP\Files\NotPermittedException;
55
use OCP\IConfig;
56
use OCP\ILogger;
57
use OCP\IRequest;
58
use OCP\ISession;
59
use OCP\IUser;
60
use OCP\IUserManager;
61
use OCP\IUserSession;
62
use OCP\Session\Exceptions\SessionNotAvailableException;
63
use OCP\UserInterface;
64
use OCP\Util;
65
use Symfony\Component\EventDispatcher\EventDispatcher;
66
use Symfony\Component\EventDispatcher\GenericEvent;
67
68
/**
69
 * Class Session
70
 *
71
 * Hooks available in scope \OC\User:
72
 * - preSetPassword(\OC\User\User $user, string $password, string $recoverPassword)
73
 * - postSetPassword(\OC\User\User $user, string $password, string $recoverPassword)
74
 * - preDelete(\OC\User\User $user)
75
 * - postDelete(\OC\User\User $user)
76
 * - preCreateUser(string $uid, string $password)
77
 * - postCreateUser(\OC\User\User $user)
78
 * - preLogin(string $user, string $password)
79
 * - postLogin(\OC\User\User $user, string $password)
80
 * - failedLogin(string $user)
81
 * - preRememberedLogin(string $uid)
82
 * - postRememberedLogin(\OC\User\User $user)
83
 * - logout()
84
 * - postLogout()
85
 *
86
 * @package OC\User
87
 */
88
class Session implements IUserSession, Emitter {
89
	use EventEmitterTrait;
90
	/** @var IUserManager | PublicEmitter $manager */
91
	private $manager;
92
93
	/** @var ISession $session */
94
	private $session;
95
96
	/** @var ITimeFactory */
97
	private $timeFactory;
98
99
	/** @var IProvider */
100
	private $tokenProvider;
101
102
	/** @var IConfig */
103
	private $config;
104
105
	/** @var ILogger */
106
	private $logger;
107
108
	/** @var User $activeUser */
109
	protected $activeUser;
110
111
	/** @var IServiceLoader */
112
	private $serviceLoader;
113
114
	/** @var SyncService */
115
	protected $userSyncService;
116
117
	/** @var EventDispatcher  */
118
	protected $eventDispatcher;
119
120
	/**
121
	 * @param IUserManager $manager
122
	 * @param ISession $session
123
	 * @param ITimeFactory $timeFactory
124
	 * @param IProvider $tokenProvider
125
	 * @param IConfig $config
126
	 * @param ILogger $logger
127
	 * @param IServiceLoader $serviceLoader
128
	 * @param SyncService $userSyncService
129
	 * @param EventDispatcher $eventDispatcher
130
	 */
131
	public function __construct(IUserManager $manager, ISession $session,
132
								ITimeFactory $timeFactory, IProvider $tokenProvider,
133
								IConfig $config, ILogger $logger, IServiceLoader $serviceLoader,
134
								SyncService $userSyncService, EventDispatcher $eventDispatcher) {
135
		$this->manager = $manager;
136
		$this->session = $session;
137
		$this->timeFactory = $timeFactory;
138
		$this->tokenProvider = $tokenProvider;
139
		$this->config = $config;
140
		$this->logger = $logger;
141
		$this->serviceLoader = $serviceLoader;
142
		$this->userSyncService = $userSyncService;
143
		$this->eventDispatcher = $eventDispatcher;
144
	}
145
146
	/**
147
	 * @param IProvider $provider
148
	 */
149
	public function setTokenProvider(IProvider $provider) {
150
		$this->tokenProvider = $provider;
151
	}
152
153
	/**
154
	 * @param string $scope
155
	 * @param string $method
156
	 * @param callable $callback
157
	 */
158
	public function listen($scope, $method, callable $callback) {
159
		$this->manager->listen($scope, $method, $callback);
160
	}
161
162
	/**
163
	 * @param string $scope optional
164
	 * @param string $method optional
165
	 * @param callable $callback optional
166
	 */
167
	public function removeListener($scope = null, $method = null, callable $callback = null) {
168
		$this->manager->removeListener($scope, $method, $callback);
169
	}
170
171
	/**
172
	 * get the session object
173
	 *
174
	 * @return ISession
175
	 */
176
	public function getSession() {
177
		return $this->session;
178
	}
179
180
	/**
181
	 * set the session object
182
	 *
183
	 * @param ISession $session
184
	 */
185
	public function setSession(ISession $session) {
186
		if ($this->session instanceof ISession) {
187
			$this->session->close();
188
		}
189
		$this->session = $session;
190
		$this->activeUser = null;
191
	}
192
193
	/**
194
	 * set the currently active user
195
	 *
196
	 * @param IUser|null $user
197
	 */
198
	public function setUser($user) {
199
		if ($user === null) {
200
			$this->session->remove('user_id');
201
		} else {
202
			$this->session->set('user_id', $user->getUID());
203
		}
204
		$this->activeUser = $user;
0 ignored issues
show
Documentation Bug introduced by
It seems like $user 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...
205
	}
206
207
	/**
208
	 * Get the current active user. If user is in incognito mode, user is not
209
	 * considered as active
210
	 *
211
	 * @return IUser|null Current user, otherwise null
212
	 */
213
	public function getUser() {
214
		// FIXME: This is a quick'n dirty work-around for the incognito mode as
215
		// described at https://github.com/owncloud/core/pull/12912#issuecomment-67391155
216
		if (OC_User::isIncognitoMode()) {
217
			return null;
218
		}
219
		if ($this->activeUser === null) {
220
			$uid = $this->session->get('user_id');
221
			if ($uid === null) {
222
				return null;
223
			}
224
			$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...
225
			if ($this->activeUser === null) {
226
				return null;
227
			}
228
		}
229
		return $this->activeUser;
230
	}
231
232
	/**
233
	 * Validate whether the current session is valid
234
	 *
235
	 * - For token-authenticated clients, the token validity is checked
236
	 * - For browsers, the session token validity is checked
237
	 */
238
	public function validateSession() {
239
		if (!$this->getUser()) {
240
			return;
241
		}
242
243
		$token = null;
244
		$appPassword = $this->session->get('app_password');
245
246
		if ($appPassword === null) {
247
			try {
248
				$token = $this->session->getId();
249
			} catch (SessionNotAvailableException $ex) {
250
				return;
251
			}
252
		} else {
253
			$token = $appPassword;
254
		}
255
256
		if (!$this->validateToken($token)) {
257
			// Session was invalidated
258
			$this->logout();
259
		}
260
	}
261
262
	/**
263
	 * Checks whether the user is logged in
264
	 *
265
	 * @return bool if logged in
266
	 */
267
	public function isLoggedIn() {
268
		$user = $this->getUser();
269
		if ($user === null) {
270
			return false;
271
		}
272
273
		return $user->isEnabled();
274
	}
275
276
	/**
277
	 * set the login name
278
	 *
279
	 * @param string|null $loginName for the logged in user
280
	 */
281
	public function setLoginName($loginName) {
282
		if ($loginName === null) {
283
			$this->session->remove('loginname');
284
		} else {
285
			$this->session->set('loginname', $loginName);
286
		}
287
	}
288
289
	/**
290
	 * get the login name of the current user
291
	 *
292
	 * @return string
293
	 */
294
	public function getLoginName() {
295
		if ($this->activeUser) {
296
			return $this->session->get('loginname');
297
		}
298
299
		$uid = $this->session->get('user_id');
300
		if ($uid) {
301
			$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...
302
			return $this->session->get('loginname');
303
		}
304
305
		return null;
306
	}
307
308
	/**
309
	 * try to log in with the provided credentials
310
	 *
311
	 * @param string $uid
312
	 * @param string $password
313
	 * @return boolean|null
314
	 * @throws LoginException
315
	 */
316
	public function login($uid, $password) {
317
		$this->session->regenerateId();
318
319
		if ($this->validateToken($password, $uid)) {
320
			return $this->loginWithToken($password);
321
		}
322
		return $this->loginWithPassword($uid, $password);
323
	}
324
325
	/**
326
	 * Tries to log in a client
327
	 *
328
	 * Checks token auth enforced
329
	 * Checks 2FA enabled
330
	 *
331
	 * @param string $user
332
	 * @param string $password
333
	 * @param IRequest $request
334
	 * @throws \InvalidArgumentException
335
	 * @throws LoginException
336
	 * @throws PasswordLoginForbiddenException
337
	 * @return boolean
338
	 */
339
	public function logClientIn($user, $password, IRequest $request) {
340
		$isTokenPassword = $this->isTokenPassword($password);
341
		if ($user === null || \trim($user) === '') {
342
			throw new \InvalidArgumentException('$user cannot be empty');
343
		}
344
		if (!$isTokenPassword && $this->isTokenAuthEnforced()) {
345
			$this->logger->warning("Login failed: '$user' (Remote IP: '{$request->getRemoteAddress()}')", ['app' => 'core']);
346
			$this->emitFailedLogin($user);
347
			throw new PasswordLoginForbiddenException();
348
		}
349
		if (!$isTokenPassword && $this->isTwoFactorEnforced($user)) {
350
			throw new PasswordLoginForbiddenException();
351
		}
352
		if (!$this->login($user, $password)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->login($user, $password) of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
353
			$users = $this->manager->getByEmail($user);
354
			if (\count($users) === 1) {
355
				return $this->login($users[0]->getUID(), $password);
356
			}
357
			return false;
358
		}
359
360
		if ($isTokenPassword) {
361
			$this->session->set('app_password', $password);
362
		} elseif ($this->supportsCookies($request)) {
363
			// Password login, but cookies supported -> create (browser) session token
364
			$this->createSessionToken($request, $this->getUser()->getUID(), $user, $password);
365
		}
366
367
		return true;
368
	}
369
370
	protected function supportsCookies(IRequest $request) {
371
		if ($request->getCookie('cookie_test') !== null) {
372
			return true;
373
		}
374
		\setcookie('cookie_test', 'test', $this->timeFactory->getTime() + 3600);
375
		return false;
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
			['uid' => &$username]
387
		);
388
		$user = $this->manager->get($username);
389
		if ($user === null) {
390
			$users = $this->manager->getByEmail($username);
391
			if (empty($users)) {
392
				return false;
393
			}
394
			if (\count($users) !== 1) {
395
				return true;
396
			}
397
			$user = $users[0];
398
		}
399
		// DI not possible due to cyclic dependencies :'-/
400
		return OC::$server->getTwoFactorAuthManager()->isTwoFactorAuthenticated($user);
401
	}
402
403
	/**
404
	 * Check if the given 'password' is actually a device token
405
	 *
406
	 * @param string $password
407
	 * @return boolean
408
	 */
409
	public function isTokenPassword($password) {
410
		try {
411
			$this->tokenProvider->getToken($password);
412
			return true;
413
		} catch (InvalidTokenException $ex) {
414
			return false;
415
		}
416
	}
417
418
	/**
419
	 * Unintentional public
420
	 *
421
	 * @param bool $firstTimeLogin
422
	 */
423
	public function prepareUserLogin($firstTimeLogin = false) {
424
		// TODO: mock/inject/use non-static
425
		// Refresh the token
426
		\OC::$server->getCsrfTokenManager()->refreshToken();
427
		//we need to pass the user name, which may differ from login name
428
		$user = $this->getUser()->getUID();
429
		OC_Util::setupFS($user);
430
431
		if ($firstTimeLogin) {
432
			// TODO: lock necessary?
433
			//trigger creation of user home and /files folder
434
			$userFolder = \OC::$server->getUserFolder($user);
435
436
			try {
437
				// copy skeleton
438
				\OC_Util::copySkeleton($user, $userFolder);
0 ignored issues
show
Bug introduced by
It seems like $userFolder defined by \OC::$server->getUserFolder($user) on line 434 can be null; however, OC_Util::copySkeleton() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
439
			} catch (NotPermittedException $ex) {
440
				// possible if files directory is in an readonly jail
441
				$this->logger->warning(
442
					'Skeleton not created due to missing write permission'
443
				);
444
			} catch (NoReadAccessException $ex) {
445
				// possible if the skeleton directory does not have read access
446
				$this->logger->warning(
447
					'Skeleton not created due to missing read permission in skeleton directory'
448
				);
449
			} catch (HintException $hintEx) {
450
				// only if Skeleton no existing Dir
451
				$this->logger->error($hintEx->getMessage());
452
			}
453
454
			// trigger any other initialization
455
			$this->eventDispatcher->dispatch(IUser::class . '::firstLogin', new GenericEvent($this->getUser()));
456
			$this->eventDispatcher->dispatch('user.firstlogin', new GenericEvent($this->getUser()));
457
		}
458
	}
459
460
	/**
461
	 * Tries to login the user with HTTP Basic Authentication
462
	 *
463
	 * @todo do not allow basic auth if the user is 2FA enforced
464
	 * @param IRequest $request
465
	 * @return boolean if the login was successful
466
	 * @throws LoginException
467
	 */
468
	public function tryBasicAuthLogin(IRequest $request) {
469
		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...
470
			try {
471
				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...
472
					/**
473
					 * Add DAV authenticated. This should in an ideal world not be
474
					 * necessary but the iOS App reads cookies from anywhere instead
475
					 * only the DAV endpoint.
476
					 * This makes sure that the cookies will be valid for the whole scope
477
					 * @see https://github.com/owncloud/core/issues/22893
478
					 */
479
					$this->session->set(
480
						Auth::DAV_AUTHENTICATED, $this->getUser()->getUID()
481
					);
482
					return true;
483
				}
484
			} catch (PasswordLoginForbiddenException $ex) {
485
				// Nothing to do
486
			}
487
		}
488
		return false;
489
	}
490
491
	/**
492
	 * Log an user in via login name and password
493
	 *
494
	 * @param string $login
495
	 * @param string $password
496
	 * @return boolean
497
	 * @throws LoginException if an app canceled the login process or the user is not enabled
498
	 *
499
	 * Two new keys 'login' in the before event and 'user' in the after event
500
	 * are introduced. We should use this keys in future when trying to listen
501
	 * the events emitted from this method. We have kept the key 'uid' for
502
	 * compatibility.
503
	 */
504
	private function loginWithPassword($login, $password) {
505
		$beforeEvent = new GenericEvent(null, ['loginType' => 'password', 'login' => $login, 'uid' => $login, '_uid' => 'deprecated: please use \'login\', the real uid is not yet known', 'password' => $password]);
506
		$this->eventDispatcher->dispatch('user.beforelogin', $beforeEvent);
507
		$this->manager->emit('\OC\User', 'preLogin', [$login, $password]);
508
509
		$user = $this->manager->checkPassword($login, $password);
510
		if ($user === false) {
511
			$this->emitFailedLogin($login);
512
			return false;
513
		}
514
515
		if ($user->isEnabled()) {
516
			$this->setUser($user);
517
			$this->setLoginName($login);
518
			$firstTimeLogin = $user->updateLastLoginTimestamp();
519
			$this->manager->emit('\OC\User', 'postLogin', [$user, $password]);
520
			if ($this->isLoggedIn()) {
521
				$this->prepareUserLogin($firstTimeLogin);
522
523
				$afterEvent = new GenericEvent(null, ['loginType' => 'password', 'user' => $user, 'uid' => $user->getUID(), 'password' => $password]);
524
				$this->eventDispatcher->dispatch('user.afterlogin', $afterEvent);
525
526
				return true;
527
			}
528
529
			// injecting l10n does not work - there is a circular dependency between session and \OCP\L10N\IFactory
530
			$message = \OC::$server->getL10N('lib')->t('Login canceled by app');
531
			throw new LoginException($message);
532
		}
533
534
		// injecting l10n does not work - there is a circular dependency between session and \OCP\L10N\IFactory
535
		$message = \OC::$server->getL10N('lib')->t('User disabled');
536
		throw new LoginException($message);
537
	}
538
539
	/**
540
	 * Log an user in with a given token (id)
541
	 *
542
	 * @param string $token
543
	 * @return boolean
544
	 * @throws LoginException if an app canceled the login process or the user is not enabled
545
	 * @throws InvalidTokenException
546
	 */
547
	private function loginWithToken($token) {
548
		try {
549
			$dbToken = $this->tokenProvider->getToken($token);
550
		} catch (InvalidTokenException $ex) {
551
			return false;
552
		}
553
		$uid = $dbToken->getUID();
554
555
		// When logging in with token, the password must be decrypted first before passing to login hook
556
		$password = '';
557
		try {
558
			$password = $this->tokenProvider->getPassword($dbToken, $token);
559
		} catch (PasswordlessTokenException $ex) {
560
			// Ignore and use empty string instead
561
		}
562
563
		$this->manager->emit('\OC\User', 'preLogin', [$uid, $password]);
564
		$beforeEvent = new GenericEvent(null, ['loginType' => 'token', 'login' => $uid, 'password' => $password]);
565
		$this->eventDispatcher->dispatch('user.beforelogin', $beforeEvent);
566
567
		$user = $this->manager->get($uid);
568
		if ($user === null) {
569
			// user does not exist
570
			$this->emitFailedLogin($uid);
571
			return false;
572
		}
573 View Code Duplication
		if (!$user->isEnabled()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
574
			// disabled users can not log in
575
			// injecting l10n does not work - there is a circular dependency between session and \OCP\L10N\IFactory
576
			$message = \OC::$server->getL10N('lib')->t('User disabled');
577
			throw new LoginException($message);
578
		}
579
580
		//login
581
		$this->setUser($user);
582
		$this->setLoginName($dbToken->getLoginName());
583
		$this->manager->emit('\OC\User', 'postLogin', [$user, $password]);
584
		$afterEvent = new GenericEvent(null, ['loginType' => 'token', 'user' => $user, 'login' => $user->getUID(), 'password' => $password]);
585
		$this->eventDispatcher->dispatch('user.afterlogin', $afterEvent);
586
587 View Code Duplication
		if ($this->isLoggedIn()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
588
			$this->prepareUserLogin();
589
		} else {
590
			// injecting l10n does not work - there is a circular dependency between session and \OCP\L10N\IFactory
591
			$message = \OC::$server->getL10N('lib')->t('Login canceled by app');
592
			throw new LoginException($message);
593
		}
594
595
		// set the app password
596
		$this->session->set('app_password', $token);
597
598
		return true;
599
	}
600
601
	/**
602
	 * Try to login a user, assuming authentication
603
	 * has already happened (e.g. via Single Sign On).
604
	 *
605
	 * Log in a user and regenerate a new session.
606
	 *
607
	 * @param \OCP\Authentication\IApacheBackend $apacheBackend
608
	 * @return bool
609
	 * @throws LoginException
610
	 */
611
	public function loginWithApache(IApacheBackend $apacheBackend) {
612
		$uidAndBackend = $apacheBackend->getCurrentUserId();
613
		if (\is_array($uidAndBackend)
614
			&& \count($uidAndBackend) === 2
615
			&& $uidAndBackend[0] !== ''
616
			&& $uidAndBackend[0] !== null
617
			&& $uidAndBackend[1] instanceof UserInterface
618
		) {
619
			list($uid, $backend) = $uidAndBackend;
620
		} elseif (\is_string($uidAndBackend)) {
621
			$uid = $uidAndBackend;
622
			if ($apacheBackend instanceof UserInterface) {
623
				$backend = $apacheBackend;
624
			} else {
625
				$this->logger->error('Apache backend failed to provide a valid backend for the user');
626
				return false;
627
			}
628
		} else {
629
			$this->logger->debug('No valid user detected from apache user backend');
630
			return false;
631
		}
632
633
		if ($this->getUser() !== null && $uid === $this->getUser()->getUID()) {
634
			return true; // nothing to do
635
		}
636
		$this->session->regenerateId();
637
638
		$this->manager->emit('\OC\User', 'preLogin', [$uid, '']);
639
		$beforeEvent = new GenericEvent(null, ['loginType' => 'apache', 'login' => $uid, 'password' => '']);
640
		$this->eventDispatcher->dispatch('user.beforelogin', $beforeEvent);
641
642
		// Die here if not valid
643
		if (!$apacheBackend->isSessionActive()) {
644
			return false;
645
		}
646
647
		// Now we try to create the account or sync
648
		$this->userSyncService->createOrSyncAccount($uid, $backend);
649
650
		$user = $this->manager->get($uid);
651
		if ($user === null) {
652
			$this->emitFailedLogin($uid);
653
			return false;
654
		}
655
656
		if ($user->isEnabled()) {
657
			$this->setUser($user);
658
			$this->setLoginName($uid);
659
660
			$request = OC::$server->getRequest();
661
			$this->createSessionToken($request, $uid, $uid);
662
663
			// setup the filesystem
664
			OC_Util::setupFS($uid);
665
			// first call the post_login hooks, the login-process needs to be
666
			// completed before we can safely create the users folder.
667
			// For example encryption needs to initialize the users keys first
668
			// before we can create the user folder with the skeleton files
669
670
			$firstTimeLogin = $user->updateLastLoginTimestamp();
671
			$this->manager->emit('\OC\User', 'postLogin', [$user, '']);
672
			$afterEvent = new GenericEvent(null, ['loginType' => 'apache', 'user' => $user, 'login' => $user->getUID(), 'password' => '']);
673
			$this->eventDispatcher->dispatch('user.afterlogin', $afterEvent);
674
			if ($this->isLoggedIn()) {
675
				$this->prepareUserLogin($firstTimeLogin);
676
				return true;
677
			}
678
679
			// injecting l10n does not work - there is a circular dependency between session and \OCP\L10N\IFactory
680
			$message = \OC::$server->getL10N('lib')->t('Login canceled by app');
681
			throw new LoginException($message);
682
		}
683
684
		// injecting l10n does not work - there is a circular dependency between session and \OCP\L10N\IFactory
685
		$message = \OC::$server->getL10N('lib')->t('User disabled');
686
		throw new LoginException($message);
687
	}
688
689
	/**
690
	 * Create a new session token for the given user credentials
691
	 *
692
	 * @param IRequest $request
693
	 * @param string $uid user UID
694
	 * @param string $loginName login name
695
	 * @param string $password
696
	 * @return boolean
697
	 */
698
	public function createSessionToken(IRequest $request, $uid, $loginName, $password = null) {
699
		if ($this->manager->get($uid) === null) {
700
			// User does not exist
701
			return false;
702
		}
703
		$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...
704
		try {
705
			$sessionId = $this->session->getId();
706
			$pwd = $this->getPassword($password);
707
			$this->tokenProvider->generateToken($sessionId, $uid, $loginName, $pwd, $name);
708
			return true;
709
		} catch (SessionNotAvailableException $ex) {
710
			// This can happen with OCC, where a memory session is used
711
			// if a memory session is used, we shouldn't create a session token anyway
712
			return false;
713
		}
714
	}
715
716
	/**
717
	 * Checks if the given password is a token.
718
	 * If yes, the password is extracted from the token.
719
	 * If no, the same password is returned.
720
	 *
721
	 * @param string $password either the login password or a device token
722
	 * @return string|null the password or null if none was set in the token
723
	 */
724
	private function getPassword($password) {
725
		if ($password === null) {
726
			// This is surely no token ;-)
727
			return null;
728
		}
729
		try {
730
			$token = $this->tokenProvider->getToken($password);
731
			try {
732
				return $this->tokenProvider->getPassword($token, $password);
733
			} catch (PasswordlessTokenException $ex) {
734
				return null;
735
			}
736
		} catch (InvalidTokenException $ex) {
737
			return $password;
738
		}
739
	}
740
741
	/**
742
	 * @param IToken $dbToken
743
	 * @param string $token
744
	 * @return boolean
745
	 */
746
	private function checkTokenCredentials(IToken $dbToken, $token) {
747
		// Check whether login credentials are still valid and the user was not disabled
748
		// This check is performed each 5 minutes per default
749
		// However, we try to read last_check_timeout from the appconfig table so the
750
		// administrator could change this 5 minutes timeout
751
		$lastCheck = $dbToken->getLastCheck() ? : 0;
752
		$now = $this->timeFactory->getTime();
753
		$last_check_timeout = (int)$this->config->getAppValue('core', 'last_check_timeout', 5);
754
		if ($lastCheck > ($now - 60 * $last_check_timeout)) {
755
			// Checked performed recently, nothing to do now
756
			return true;
757
		}
758
759
		try {
760
			$pwd = $this->tokenProvider->getPassword($dbToken, $token);
761
		} catch (InvalidTokenException $ex) {
762
			// An invalid token password was used -> log user out
763
			return false;
764
		} catch (PasswordlessTokenException $ex) {
765
			// Token has no password
766
767
			if ($this->activeUser !== null && !$this->activeUser->isEnabled()) {
768
				$this->tokenProvider->invalidateToken($token);
769
				return false;
770
			}
771
772
			$dbToken->setLastCheck($now);
773
			$this->tokenProvider->updateToken($dbToken);
774
			return true;
775
		}
776
777
		if ($this->manager->checkPassword($dbToken->getLoginName(), $pwd) === false
778
			|| ($this->activeUser !== null && !$this->activeUser->isEnabled())) {
779
780
			// FIXME: protect debug statement this way to avoid regressions
781
			if ($this->activeUser !== null) {
782
				$this->logger->debug(
783
					'user uid {uid}, email {email}, displayName {displayName} was disabled or password changed',
784
					[
785
						'app' => __METHOD__,
786
						'uid' => $this->activeUser->getUID(),
787
						'email' => $this->activeUser->getEMailAddress(),
788
						'displayName' => $this->activeUser->getDisplayName(),
789
					]
790
				);
791
			} else {
792
				$this->logger->debug(
793
					'user with login name {loginName} was disabled or password changed (no activeUser)',
794
					[
795
						'app' => __METHOD__,
796
						'loginName' => $dbToken->getLoginName(),
797
					]
798
				);
799
			}
800
801
			$this->tokenProvider->invalidateToken($token);
802
			// Password has changed or user was disabled -> log user out
803
			return false;
804
		}
805
		$dbToken->setLastCheck($now);
806
		$this->tokenProvider->updateToken($dbToken);
807
		return true;
808
	}
809
810
	/**
811
	 * Check if the given token exists and performs password/user-enabled checks
812
	 *
813
	 * Invalidates the token if checks fail
814
	 *
815
	 * @param string $token
816
	 * @param string $user login name
817
	 * @return boolean
818
	 */
819
	private function validateToken($token, $user = null) {
820
		try {
821
			$dbToken = $this->tokenProvider->getToken($token);
822
		} catch (InvalidTokenException $ex) {
823
			return false;
824
		}
825
826
		// Check if login names match
827
		if ($user !== null && $dbToken->getLoginName() !== $user) {
828
			// TODO: this makes it impossible to use different login names on browser and client
829
			// e.g. login by e-mail '[email protected]' on browser for generating the token will not
830
			//      allow to use the client token with the login name 'user'.
831
			return false;
832
		}
833
834
		if (!$this->checkTokenCredentials($dbToken, $token)) {
835
			return false;
836
		}
837
838
		$this->tokenProvider->updateTokenActivity($dbToken);
839
840
		return true;
841
	}
842
843
	/**
844
	 * Tries to login the user with auth token header
845
	 *
846
	 * @param IRequest $request
847
	 * @todo check remember me cookie
848
	 * @return boolean
849
	 * @throws LoginException
850
	 */
851
	public function tryTokenLogin(IRequest $request) {
852
		$authHeader = $request->getHeader('Authorization');
853 View Code Duplication
		if ($authHeader === null || \strpos($authHeader, 'token ') === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
854
			// No auth header, let's try session id
855
			try {
856
				$token = $this->session->getId();
857
			} catch (SessionNotAvailableException $ex) {
858
				return false;
859
			}
860
		} else {
861
			$token = \substr($authHeader, 6);
862
		}
863
864
		if (!$this->loginWithToken($token)) {
865
			return false;
866
		}
867
		if (!$this->validateToken($token)) {
868
			return false;
869
		}
870
		return true;
871
	}
872
873
	/**
874
	 * Tries to login with an AuthModule provided by an app
875
	 *
876
	 * @param IRequest $request The request
877
	 * @return bool True if request can be authenticated, false otherwise
878
	 * @throws Exception If the auth module could not be loaded
879
	 */
880
	public function tryAuthModuleLogin(IRequest $request) {
881
		foreach ($this->getAuthModules(false) as $authModule) {
882
			$user = $authModule->auth($request);
883
			if ($user !== null) {
884
				$uid = $user->getUID();
885
				$password = $authModule->getUserPassword($request);
886
				$this->createSessionToken($request, $uid, $uid, $password);
887
				return $this->loginUser($user, $password);
888
			}
889
		}
890
891
		return false;
892
	}
893
894
	/**
895
	 * Log an user in
896
	 *
897
	 * @param IUser $user The user
898
	 * @param String $password The user's password
899
	 * @return boolean True if the user can be authenticated, false otherwise
900
	 * @throws LoginException if an app canceled the login process or the user is not enabled
901
	 */
902
	protected function loginUser(IUser $user = null, $password) {
903
		$uid = $user === null ? '' : $user->getUID();
904
		return $this->emittingCall(function () use (&$user, &$password) {
905
			if ($user === null) {
906
				//Cannot extract the uid when $user is null, hence pass null
907
				$this->emitFailedLogin(null);
908
				return false;
909
			}
910
911
			$this->manager->emit('\OC\User', 'preLogin', [$user, $password]);
912
913 View Code Duplication
			if (!$user->isEnabled()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
914
				$message = \OC::$server->getL10N('lib')->t('User disabled');
915
				$this->emitFailedLogin($user->getUID());
916
				throw new LoginException($message);
917
			}
918
919
			$this->setUser($user);
920
			$this->setLoginName($user->getDisplayName());
921
922
			$this->manager->emit('\OC\User', 'postLogin', [$user, $password]);
923
924 View Code Duplication
			if ($this->isLoggedIn()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
925
				$this->prepareUserLogin();
926
			} else {
927
				$message = \OC::$server->getL10N('lib')->t('Login canceled by app');
928
				throw new LoginException($message);
929
			}
930
931
			return true;
932
		}, ['before' => ['user' => $user, 'uid' => $uid, 'password' => $password],
933
			'after' => ['user' => $user, 'uid' => $uid, 'password' => $password]],
934
			'user', 'login');
935
	}
936
937
	/**
938
	 * perform login using the magic cookie (remember login)
939
	 *
940
	 * @param string $uid the username
941
	 * @param string $currentToken
942
	 * @return bool
943
	 * @throws \OCP\PreConditionNotMetException
944
	 */
945
	public function loginWithCookie($uid, $currentToken) {
946
		$this->session->regenerateId();
947
		$this->manager->emit('\OC\User', 'preRememberedLogin', [$uid]);
948
		$user = $this->manager->get($uid);
949
		if ($user === null) {
950
			// user does not exist
951
			return false;
952
		}
953
954
		// get stored tokens
955
		$tokens = OC::$server->getConfig()->getUserKeys($uid, 'login_token');
956
		// test cookies token against stored tokens
957
		if (!\in_array($currentToken, $tokens, true)) {
958
			$this->emitFailedLogin($uid);
959
			return false;
960
		}
961
		// replace successfully used token with a new one
962
		OC::$server->getConfig()->deleteUserValue($uid, 'login_token', $currentToken);
963
		$newToken = OC::$server->getSecureRandom()->generate(32);
964
		OC::$server->getConfig()->setUserValue($uid, 'login_token', $newToken, \time());
965
		$this->setMagicInCookie($user->getUID(), $newToken);
966
967
		//login
968
		$this->setUser($user);
969
		$user->updateLastLoginTimestamp();
970
		$this->manager->emit('\OC\User', 'postRememberedLogin', [$user]);
971
		return true;
972
	}
973
974
	/**
975
	 * logout the user from the session
976
	 *
977
	 * @return bool
978
	 */
979
	public function logout() {
980
		return $this->emittingCall(function () {
981
			$event = new GenericEvent(null, ['cancel' => false]);
982
			$this->eventDispatcher->dispatch('\OC\User\Session::pre_logout', $event);
983
984
			$this->manager->emit('\OC\User', 'preLogout');
985
986
			if ($event['cancel'] === true) {
987
				return true;
988
			}
989
990
			$this->manager->emit('\OC\User', 'logout');
991
			$user = $this->getUser();
992
			if ($user !== null) {
993
				try {
994
					$this->tokenProvider->invalidateToken($this->session->getId());
995
				} catch (SessionNotAvailableException $ex) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
996
				}
997
			}
998
			$this->setUser(null);
999
			$this->setLoginName(null);
1000
			$this->unsetMagicInCookie();
1001
			$this->session->clear();
1002
			$this->manager->emit('\OC\User', 'postLogout');
1003
			return true;
1004
		}, ['before' => ['uid' => ''], 'after' => ['uid' => '']], 'user', 'logout');
1005
	}
1006
1007
	/**
1008
	 * Set cookie value to use in next page load
1009
	 *
1010
	 * @param string $username username to be set
1011
	 * @param string $token
1012
	 */
1013
	public function setMagicInCookie($username, $token) {
1014
		$secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https';
1015
		$expires = \time() + OC::$server->getConfig()->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
1016
		\setcookie('oc_username', $username, $expires, OC::$WEBROOT, '', $secureCookie, true);
1017
		\setcookie('oc_token', $token, $expires, OC::$WEBROOT, '', $secureCookie, true);
1018
		\setcookie('oc_remember_login', '1', $expires, OC::$WEBROOT, '', $secureCookie, true);
1019
	}
1020
1021
	/**
1022
	 * Remove cookie for "remember username"
1023
	 */
1024
	public function unsetMagicInCookie() {
1025
		//TODO: DI for cookies and IRequest
1026
		$secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https';
1027
1028
		unset($_COOKIE['oc_username'], $_COOKIE['oc_token'], $_COOKIE['oc_remember_login']); //TODO: DI
1029
1030
		\setcookie('oc_username', '', \time() - 3600, OC::$WEBROOT, '', $secureCookie, true);
1031
		\setcookie('oc_token', '', \time() - 3600, OC::$WEBROOT, '', $secureCookie, true);
1032
		\setcookie('oc_remember_login', '', \time() - 3600, OC::$WEBROOT, '', $secureCookie, true);
1033
		// old cookies might be stored under /webroot/ instead of /webroot
1034
		// and Firefox doesn't like it!
1035
		\setcookie('oc_username', '', \time() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
1036
		\setcookie('oc_token', '', \time() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
1037
		\setcookie('oc_remember_login', '', \time() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
1038
	}
1039
1040
	/**
1041
	 * Update password of the browser session token if there is one
1042
	 *
1043
	 * @param string $password
1044
	 */
1045
	public function updateSessionTokenPassword($password) {
1046
		try {
1047
			$sessionId = $this->session->getId();
1048
			$token = $this->tokenProvider->getToken($sessionId);
1049
			$this->tokenProvider->setPassword($token, $sessionId, $password);
1050
		} catch (SessionNotAvailableException $ex) {
1051
			// Nothing to do
1052
		} catch (InvalidTokenException $ex) {
1053
			// Nothing to do
1054
		}
1055
	}
1056
1057
	public function verifyAuthHeaders($request) {
1058
		$shallLogout = false;
1059
		try {
1060
			$lastUser = null;
1061
			foreach ($this->getAuthModules(true) as $module) {
1062
				$user = $module->auth($request);
1063
				if ($user !== null) {
1064
					if ($this->isLoggedIn() && $this->getUser()->getUID() !== $user->getUID()) {
1065
						$shallLogout = true;
1066
						break;
1067
					}
1068
					if ($lastUser !== null && $user->getUID() !== $lastUser->getUID()) {
1069
						$shallLogout = true;
1070
						break;
1071
					}
1072
					$lastUser = $user;
1073
				}
1074
			}
1075
		} catch (Exception $ex) {
1076
			$shallLogout = true;
1077
		}
1078
		if ($shallLogout) {
1079
			// the session is bad -> kill it
1080
			$this->logout();
1081
			return false;
1082
		}
1083
		return true;
1084
	}
1085
1086
	/**
1087
	 * @param $includeBuiltIn
1088
	 * @return \Generator | IAuthModule[]
1089
	 * @throws Exception
1090
	 */
1091
	protected function getAuthModules($includeBuiltIn) {
1092
		if ($includeBuiltIn) {
1093
			yield new TokenAuthModule($this->session, $this->tokenProvider, $this->manager);
1094
		}
1095
1096
		$modules = $this->serviceLoader->load(['auth-modules']);
1097
		foreach ($modules as $module) {
1098
			if ($module instanceof IAuthModule) {
1099
				yield $module;
1100
			} else {
1101
				continue;
1102
			}
1103
		}
1104
1105
		if ($includeBuiltIn) {
1106
			yield new BasicAuthModule($this->config, $this->logger, $this->manager, $this->session, $this->timeFactory);
1107
		}
1108
	}
1109
1110
	/**
1111
	 * This method triggers symfony event for failed login as well as
1112
	 * emits via the emitter in user manager
1113
	 * @param string $user
1114
	 */
1115
	protected function emitFailedLogin($user) {
1116
		$this->manager->emit('\OC\User', 'failedLogin', [$user]);
1117
1118
		$loginFailedEvent = new GenericEvent(null, ['user' => $user]);
1119
		$this->eventDispatcher->dispatch('user.loginfailed', $loginFailedEvent);
1120
	}
1121
}
1122