Completed
Pull Request — master (#7611)
by Blizzz
27:16
created

Manager::createUserFromBackend()   D

Complexity

Conditions 10
Paths 9

Size

Total Lines 42
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 23
nc 9
nop 3
dl 0
loc 42
rs 4.8196
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Joas Schilling <[email protected]>
7
 * @author Jörn Friedrich Dreyer <[email protected]>
8
 * @author Lukas Reschke <[email protected]>
9
 * @author Michael U <[email protected]>
10
 * @author Morris Jobke <[email protected]>
11
 * @author Robin Appelman <[email protected]>
12
 * @author Roeland Jago Douma <[email protected]>
13
 * @author Thomas Müller <[email protected]>
14
 * @author Vincent Chan <[email protected]>
15
 *
16
 * @license AGPL-3.0
17
 *
18
 * This code is free software: you can redistribute it and/or modify
19
 * it under the terms of the GNU Affero General Public License, version 3,
20
 * as published by the Free Software Foundation.
21
 *
22
 * This program is distributed in the hope that it will be useful,
23
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25
 * GNU Affero General Public License for more details.
26
 *
27
 * You should have received a copy of the GNU Affero General Public License, version 3,
28
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
29
 *
30
 */
31
32
namespace OC\User;
33
34
use OC\Hooks\PublicEmitter;
35
use OCP\DB\QueryBuilder\IQueryBuilder;
36
use OCP\IUser;
37
use OCP\IUserBackend;
38
use OCP\IUserManager;
39
use OCP\IConfig;
40
use OCP\UserInterface;
41
42
/**
43
 * Class Manager
44
 *
45
 * Hooks available in scope \OC\User:
46
 * - preSetPassword(\OC\User\User $user, string $password, string $recoverPassword)
47
 * - postSetPassword(\OC\User\User $user, string $password, string $recoverPassword)
48
 * - preDelete(\OC\User\User $user)
49
 * - postDelete(\OC\User\User $user)
50
 * - preCreateUser(string $uid, string $password)
51
 * - postCreateUser(\OC\User\User $user, string $password)
52
 * - change(\OC\User\User $user)
53
 *
54
 * @package OC\User
55
 */
56
class Manager extends PublicEmitter implements IUserManager {
57
	/**
58
	 * @var \OCP\UserInterface[] $backends
59
	 */
60
	private $backends = array();
61
62
	/**
63
	 * @var \OC\User\User[] $cachedUsers
64
	 */
65
	private $cachedUsers = array();
66
67
	/**
68
	 * @var \OCP\IConfig $config
69
	 */
70
	private $config;
71
72
	/**
73
	 * @param \OCP\IConfig $config
74
	 */
75
	public function __construct(IConfig $config) {
76
		$this->config = $config;
77
		$cachedUsers = &$this->cachedUsers;
78
		$this->listen('\OC\User', 'postDelete', function ($user) use (&$cachedUsers) {
79
			/** @var \OC\User\User $user */
80
			unset($cachedUsers[$user->getUID()]);
81
		});
82
	}
83
84
	/**
85
	 * Get the active backends
86
	 * @return \OCP\UserInterface[]
87
	 */
88
	public function getBackends() {
89
		return $this->backends;
90
	}
91
92
	/**
93
	 * register a user backend
94
	 *
95
	 * @param \OCP\UserInterface $backend
96
	 */
97
	public function registerBackend($backend) {
98
		$this->backends[] = $backend;
99
	}
100
101
	/**
102
	 * remove a user backend
103
	 *
104
	 * @param \OCP\UserInterface $backend
105
	 */
106
	public function removeBackend($backend) {
107
		$this->cachedUsers = array();
108
		if (($i = array_search($backend, $this->backends)) !== false) {
109
			unset($this->backends[$i]);
110
		}
111
	}
112
113
	/**
114
	 * remove all user backends
115
	 */
116
	public function clearBackends() {
117
		$this->cachedUsers = array();
118
		$this->backends = array();
119
	}
120
121
	/**
122
	 * get a user by user id
123
	 *
124
	 * @param string $uid
125
	 * @return \OC\User\User|null Either the user or null if the specified user does not exist
126
	 */
127
	public function get($uid) {
128
		if (is_null($uid) || $uid === '' || $uid === false) {
129
			return null;
130
		}
131
		if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends
132
			return $this->cachedUsers[$uid];
133
		}
134
		foreach ($this->backends as $backend) {
135
			if ($backend->userExists($uid)) {
136
				return $this->getUserObject($uid, $backend);
137
			}
138
		}
139
		return null;
140
	}
141
142
	/**
143
	 * get or construct the user object
144
	 *
145
	 * @param string $uid
146
	 * @param \OCP\UserInterface $backend
147
	 * @param bool $cacheUser If false the newly created user object will not be cached
148
	 * @return \OC\User\User
149
	 */
150
	protected function getUserObject($uid, $backend, $cacheUser = true) {
151
		if (isset($this->cachedUsers[$uid])) {
152
			return $this->cachedUsers[$uid];
153
		}
154
155
		$user = new User($uid, $backend, $this, $this->config);
156
		if ($cacheUser) {
157
			$this->cachedUsers[$uid] = $user;
158
		}
159
		return $user;
160
	}
161
162
	/**
163
	 * check if a user exists
164
	 *
165
	 * @param string $uid
166
	 * @return bool
167
	 */
168
	public function userExists($uid) {
169
		$user = $this->get($uid);
170
		return ($user !== null);
171
	}
172
173
	/**
174
	 * Check if the password is valid for the user
175
	 *
176
	 * @param string $loginName
177
	 * @param string $password
178
	 * @return mixed the User object on success, false otherwise
179
	 */
180
	public function checkPassword($loginName, $password) {
181
		$result = $this->checkPasswordNoLogging($loginName, $password);
182
183
		if ($result === false) {
184
			\OC::$server->getLogger()->warning('Login failed: \''. $loginName .'\' (Remote IP: \''. \OC::$server->getRequest()->getRemoteAddress(). '\')', ['app' => 'core']);
185
		}
186
187
		return $result;
188
	}
189
190
	/**
191
	 * Check if the password is valid for the user
192
	 *
193
	 * @internal
194
	 * @param string $loginName
195
	 * @param string $password
196
	 * @return mixed the User object on success, false otherwise
197
	 */
198
	public function checkPasswordNoLogging($loginName, $password) {
199
		$loginName = str_replace("\0", '', $loginName);
200
		$password = str_replace("\0", '', $password);
201
202 View Code Duplication
		foreach ($this->backends as $backend) {
203
			if ($backend->implementsActions(Backend::CHECK_PASSWORD)) {
204
				$uid = $backend->checkPassword($loginName, $password);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface OCP\UserInterface as the method checkPassword() does only exist in the following implementations of said interface: OCA\Testing\AlternativeHomeUserBackend, OCA\User_LDAP\User_LDAP, OCA\User_LDAP\User_Proxy, OC\User\Database.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
205
				if ($uid !== false) {
206
					return $this->getUserObject($uid, $backend);
207
				}
208
			}
209
		}
210
211
		return false;
212
	}
213
214
	/**
215
	 * search by user id
216
	 *
217
	 * @param string $pattern
218
	 * @param int $limit
219
	 * @param int $offset
220
	 * @return \OC\User\User[]
221
	 */
222 View Code Duplication
	public function search($pattern, $limit = null, $offset = null) {
223
		$users = array();
224
		foreach ($this->backends as $backend) {
225
			$backendUsers = $backend->getUsers($pattern, $limit, $offset);
226
			if (is_array($backendUsers)) {
227
				foreach ($backendUsers as $uid) {
228
					$users[$uid] = $this->getUserObject($uid, $backend);
229
				}
230
			}
231
		}
232
233
		uasort($users, function ($a, $b) {
234
			/**
235
			 * @var \OC\User\User $a
236
			 * @var \OC\User\User $b
237
			 */
238
			return strcmp($a->getUID(), $b->getUID());
239
		});
240
		return $users;
241
	}
242
243
	/**
244
	 * search by displayName
245
	 *
246
	 * @param string $pattern
247
	 * @param int $limit
248
	 * @param int $offset
249
	 * @return \OC\User\User[]
250
	 */
251 View Code Duplication
	public function searchDisplayName($pattern, $limit = null, $offset = null) {
252
		$users = array();
253
		foreach ($this->backends as $backend) {
254
			$backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
255
			if (is_array($backendUsers)) {
256
				foreach ($backendUsers as $uid => $displayName) {
257
					$users[] = $this->getUserObject($uid, $backend);
258
				}
259
			}
260
		}
261
262
		usort($users, function ($a, $b) {
263
			/**
264
			 * @var \OC\User\User $a
265
			 * @var \OC\User\User $b
266
			 */
267
			return strcmp(strtolower($a->getDisplayName()), strtolower($b->getDisplayName()));
268
		});
269
		return $users;
270
	}
271
272
	/**
273
	 * @param string $uid
274
	 * @param string $password
275
	 * @throws \InvalidArgumentException
276
	 * @return bool|IUser the created user or false
277
	 */
278
	public function createUser($uid, $password) {
279
		$localBackends = [];
280 View Code Duplication
		foreach ($this->backends as $backend) {
281
			if ($backend instanceof Database) {
282
				// First check if there is another user backend
283
				$localBackends[] = $backend;
284
				continue;
285
			}
286
287
			if ($backend->implementsActions(Backend::CREATE_USER)) {
288
				return $this->createUserFromBackend($uid, $password, $backend);
289
			}
290
		}
291
292
		foreach ($localBackends as $backend) {
293
			if ($backend->implementsActions(Backend::CREATE_USER)) {
294
				return $this->createUserFromBackend($uid, $password, $backend);
295
			}
296
		}
297
298
		return false;
299
	}
300
301
	/**
302
	 * @param string $uid
303
	 * @param string $password
304
	 * @param UserInterface $backend
305
	 * @return IUser|null
306
	 * @throws \InvalidArgumentException
307
	 */
308
	public function createUserFromBackend($uid, $password, UserInterface $backend) {
309
		$l = \OC::$server->getL10N('lib');
310
311
		// Check the name for bad characters
312
		// Allowed are: "a-z", "A-Z", "0-9" and "_.@-'"
0 ignored issues
show
Unused Code Comprehensibility introduced by
44% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
313
		if (preg_match('/[^a-zA-Z0-9 _\.@\-\']/', $uid)) {
314
			throw new \InvalidArgumentException($l->t('Only the following characters are allowed in a username:'
315
				. ' "a-z", "A-Z", "0-9", and "_.@-\'"'));
316
		}
317
		// No empty username
318
		if (trim($uid) === '') {
319
			throw new \InvalidArgumentException($l->t('A valid username must be provided'));
320
		}
321
		// No whitespace at the beginning or at the end
322
		if (trim($uid) !== $uid) {
323
			throw new \InvalidArgumentException($l->t('Username contains whitespace at the beginning or at the end'));
324
		}
325
		// Username only consists of 1 or 2 dots (directory traversal)
326
		if ($uid === '.' || $uid === '..') {
327
			throw new \InvalidArgumentException($l->t('Username must not consist of dots only'));
328
		}
329
		// No empty password
330
		if (trim($password) === '') {
331
			throw new \InvalidArgumentException($l->t('A valid password must be provided'));
332
		}
333
334
		// Check if user already exists
335
		if ($this->userExists($uid)) {
336
			throw new \InvalidArgumentException($l->t('The username is already being used'));
337
		}
338
339
		$this->emit('\OC\User', 'preCreateUser', [$uid, $password]);
340
		$state = $backend->createUser($uid, $password);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface OCP\UserInterface as the method createUser() does only exist in the following implementations of said interface: OCA\Testing\AlternativeHomeUserBackend, OCA\User_LDAP\User_LDAP, OCA\User_LDAP\User_Proxy, OC\User\Database.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
341
		if($state === false) {
342
			throw new \InvalidArgumentException($l->t('Could not create user'));
343
		}
344
		$user = $this->getUserObject($uid, $backend);
345
		if ($user instanceof IUser) {
346
			$this->emit('\OC\User', 'postCreateUser', [$user, $password]);
347
		}
348
		return $user;
349
	}
350
351
	/**
352
	 * returns how many users per backend exist (if supported by backend)
353
	 *
354
	 * @param boolean $hasLoggedIn when true only users that have a lastLogin
355
	 *                entry in the preferences table will be affected
356
	 * @return array|int an array of backend class as key and count number as value
357
	 *                if $hasLoggedIn is true only an int is returned
358
	 */
359
	public function countUsers($hasLoggedIn = false) {
360
		if ($hasLoggedIn) {
361
			return $this->countSeenUsers();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->countSeenUsers(); (integer) is incompatible with the return type declared by the interface OCP\IUserManager::countUsers of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
362
		}
363
		$userCountStatistics = [];
364
		foreach ($this->backends as $backend) {
365
			if ($backend->implementsActions(Backend::COUNT_USERS)) {
366
				$backendUsers = $backend->countUsers();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface OCP\UserInterface as the method countUsers() does only exist in the following implementations of said interface: OCA\Testing\AlternativeHomeUserBackend, OCA\User_LDAP\User_LDAP, OCA\User_LDAP\User_Proxy, OC\User\Database.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
367
				if($backendUsers !== false) {
368
					if($backend instanceof IUserBackend) {
369
						$name = $backend->getBackendName();
370
					} else {
371
						$name = get_class($backend);
372
					}
373
					if(isset($userCountStatistics[$name])) {
374
						$userCountStatistics[$name] += $backendUsers;
375
					} else {
376
						$userCountStatistics[$name] = $backendUsers;
377
					}
378
				}
379
			}
380
		}
381
		return $userCountStatistics;
382
	}
383
384
	/**
385
	 * The callback is executed for each user on each backend.
386
	 * If the callback returns false no further users will be retrieved.
387
	 *
388
	 * @param \Closure $callback
389
	 * @param string $search
390
	 * @param boolean $onlySeen when true only users that have a lastLogin entry
391
	 *                in the preferences table will be affected
392
	 * @since 9.0.0
393
	 */
394
	public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = false) {
395
		if ($onlySeen) {
396
			$this->callForSeenUsers($callback);
397
		} else {
398
			foreach ($this->getBackends() as $backend) {
399
				$limit = 500;
400
				$offset = 0;
401
				do {
402
					$users = $backend->getUsers($search, $limit, $offset);
403 View Code Duplication
					foreach ($users as $uid) {
404
						if (!$backend->userExists($uid)) {
405
							continue;
406
						}
407
						$user = $this->getUserObject($uid, $backend, false);
408
						$return = $callback($user);
409
						if ($return === false) {
410
							break;
411
						}
412
					}
413
					$offset += $limit;
414
				} while (count($users) >= $limit);
415
			}
416
		}
417
	}
418
419
	/**
420
	 * returns how many users have logged in once
421
	 *
422
	 * @return int
423
	 * @since 12.0.0
424
	 */
425 View Code Duplication
	public function countDisabledUsers() {
426
		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
427
		$queryBuilder->select($queryBuilder->createFunction('COUNT(*)'))
428
			->from('preferences')
429
			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
430
			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
431
			->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR));
432
433
		$query = $queryBuilder->execute();
434
435
		$result = (int)$query->fetchColumn();
436
		$query->closeCursor();
437
438
		return $result;
439
	}
440
441
	/**
442
	 * returns how many users have logged in once
443
	 *
444
	 * @return int
445
	 * @since 11.0.0
446
	 */
447 View Code Duplication
	public function countSeenUsers() {
448
		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
449
		$queryBuilder->select($queryBuilder->createFunction('COUNT(*)'))
450
			->from('preferences')
451
			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('login')))
452
			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('lastLogin')))
453
			->andWhere($queryBuilder->expr()->isNotNull('configvalue'));
454
455
		$query = $queryBuilder->execute();
456
457
		$result = (int)$query->fetchColumn();
458
		$query->closeCursor();
459
460
		return $result;
461
	}
462
463
	/**
464
	 * @param \Closure $callback
465
	 * @since 11.0.0
466
	 */
467
	public function callForSeenUsers(\Closure $callback) {
468
		$limit = 1000;
469
		$offset = 0;
470
		do {
471
			$userIds = $this->getSeenUserIds($limit, $offset);
472
			$offset += $limit;
473 View Code Duplication
			foreach ($userIds as $userId) {
474
				foreach ($this->backends as $backend) {
475
					if ($backend->userExists($userId)) {
476
						$user = $this->getUserObject($userId, $backend, false);
477
						$return = $callback($user);
478
						if ($return === false) {
479
							return;
480
						}
481
					}
482
				}
483
			}
484
		} while (count($userIds) >= $limit);
485
	}
486
487
	/**
488
	 * Getting all userIds that have a listLogin value requires checking the
489
	 * value in php because on oracle you cannot use a clob in a where clause,
490
	 * preventing us from doing a not null or length(value) > 0 check.
491
	 * 
492
	 * @param int $limit
493
	 * @param int $offset
494
	 * @return string[] with user ids
495
	 */
496
	private function getSeenUserIds($limit = null, $offset = null) {
497
		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
498
		$queryBuilder->select(['userid'])
499
			->from('preferences')
500
			->where($queryBuilder->expr()->eq(
501
				'appid', $queryBuilder->createNamedParameter('login'))
502
			)
503
			->andWhere($queryBuilder->expr()->eq(
504
				'configkey', $queryBuilder->createNamedParameter('lastLogin'))
505
			)
506
			->andWhere($queryBuilder->expr()->isNotNull('configvalue')
507
			);
508
509
		if ($limit !== null) {
510
			$queryBuilder->setMaxResults($limit);
511
		}
512
		if ($offset !== null) {
513
			$queryBuilder->setFirstResult($offset);
514
		}
515
		$query = $queryBuilder->execute();
516
		$result = [];
517
518
		while ($row = $query->fetch()) {
519
			$result[] = $row['userid'];
520
		}
521
522
		$query->closeCursor();
523
524
		return $result;
525
	}
526
527
	/**
528
	 * @param string $email
529
	 * @return IUser[]
530
	 * @since 9.1.0
531
	 */
532
	public function getByEmail($email) {
533
		$userIds = $this->config->getUsersForUserValue('settings', 'email', $email);
534
535
		return array_map(function($uid) {
536
			return $this->get($uid);
537
		}, $userIds);
538
	}
539
}
540