Passed
Push — master ( d011df...0d46fa )
by Joas
15:48 queued 11s
created

Manager::searchDisplayName()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 9
nc 4
nop 3
dl 0
loc 19
rs 9.9666
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bjoern Schiessle <[email protected]>
7
 * @author Christoph Wurst <[email protected]>
8
 * @author Georg Ehrke <[email protected]>
9
 * @author Joas Schilling <[email protected]>
10
 * @author John Molakvoæ (skjnldsv) <[email protected]>
11
 * @author Jörn Friedrich Dreyer <[email protected]>
12
 * @author Lukas Reschke <[email protected]>
13
 * @author Morris Jobke <[email protected]>
14
 * @author Robin Appelman <[email protected]>
15
 * @author Roeland Jago Douma <[email protected]>
16
 * @author Thomas Müller <[email protected]>
17
 * @author Vincent Chan <[email protected]>
18
 *
19
 * @license AGPL-3.0
20
 *
21
 * This code is free software: you can redistribute it and/or modify
22
 * it under the terms of the GNU Affero General Public License, version 3,
23
 * as published by the Free Software Foundation.
24
 *
25
 * This program is distributed in the hope that it will be useful,
26
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28
 * GNU Affero General Public License for more details.
29
 *
30
 * You should have received a copy of the GNU Affero General Public License, version 3,
31
 * along with this program. If not, see <http://www.gnu.org/licenses/>
32
 *
33
 */
34
35
namespace OC\User;
36
37
use OC\HintException;
38
use OC\Hooks\PublicEmitter;
39
use OCP\DB\QueryBuilder\IQueryBuilder;
40
use OCP\EventDispatcher\IEventDispatcher;
41
use OCP\ICache;
42
use OCP\ICacheFactory;
43
use OCP\IConfig;
44
use OCP\IGroup;
45
use OCP\IUser;
46
use OCP\IUserBackend;
47
use OCP\IUserManager;
48
use OCP\Support\Subscription\IRegistry;
49
use OCP\User\Backend\IGetRealUIDBackend;
50
use OCP\User\Backend\ISearchKnownUsersBackend;
51
use OCP\User\Events\BeforeUserCreatedEvent;
52
use OCP\User\Events\UserCreatedEvent;
53
use OCP\UserInterface;
54
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
55
56
/**
57
 * Class Manager
58
 *
59
 * Hooks available in scope \OC\User:
60
 * - preSetPassword(\OC\User\User $user, string $password, string $recoverPassword)
61
 * - postSetPassword(\OC\User\User $user, string $password, string $recoverPassword)
62
 * - preDelete(\OC\User\User $user)
63
 * - postDelete(\OC\User\User $user)
64
 * - preCreateUser(string $uid, string $password)
65
 * - postCreateUser(\OC\User\User $user, string $password)
66
 * - change(\OC\User\User $user)
67
 * - assignedUserId(string $uid)
68
 * - preUnassignedUserId(string $uid)
69
 * - postUnassignedUserId(string $uid)
70
 *
71
 * @package OC\User
72
 */
73
class Manager extends PublicEmitter implements IUserManager {
0 ignored issues
show
Deprecated Code introduced by
The class OC\Hooks\PublicEmitter has been deprecated: 18.0.0 use events and the \OCP\EventDispatcher\IEventDispatcher service ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

73
class Manager extends /** @scrutinizer ignore-deprecated */ PublicEmitter implements IUserManager {
Loading history...
74
	/**
75
	 * @var \OCP\UserInterface[] $backends
76
	 */
77
	private $backends = [];
78
79
	/**
80
	 * @var \OC\User\User[] $cachedUsers
81
	 */
82
	private $cachedUsers = [];
83
84
	/** @var IConfig */
85
	private $config;
86
87
	/** @var EventDispatcherInterface */
88
	private $dispatcher;
89
90
	/** @var ICache */
91
	private $cache;
92
93
	/** @var IEventDispatcher */
94
	private $eventDispatcher;
95
96
	public function __construct(IConfig $config,
97
								EventDispatcherInterface $oldDispatcher,
98
								ICacheFactory $cacheFactory,
99
								IEventDispatcher $eventDispatcher) {
100
		$this->config = $config;
101
		$this->dispatcher = $oldDispatcher;
102
		$this->cache = $cacheFactory->createDistributed('user_backend_map');
103
		$cachedUsers = &$this->cachedUsers;
104
		$this->listen('\OC\User', 'postDelete', function ($user) use (&$cachedUsers) {
105
			/** @var \OC\User\User $user */
106
			unset($cachedUsers[$user->getUID()]);
107
		});
108
		$this->eventDispatcher = $eventDispatcher;
109
	}
110
111
	/**
112
	 * Get the active backends
113
	 * @return \OCP\UserInterface[]
114
	 */
115
	public function getBackends() {
116
		return $this->backends;
117
	}
118
119
	/**
120
	 * register a user backend
121
	 *
122
	 * @param \OCP\UserInterface $backend
123
	 */
124
	public function registerBackend($backend) {
125
		$this->backends[] = $backend;
126
	}
127
128
	/**
129
	 * remove a user backend
130
	 *
131
	 * @param \OCP\UserInterface $backend
132
	 */
133
	public function removeBackend($backend) {
134
		$this->cachedUsers = [];
135
		if (($i = array_search($backend, $this->backends)) !== false) {
136
			unset($this->backends[$i]);
137
		}
138
	}
139
140
	/**
141
	 * remove all user backends
142
	 */
143
	public function clearBackends() {
144
		$this->cachedUsers = [];
145
		$this->backends = [];
146
	}
147
148
	/**
149
	 * get a user by user id
150
	 *
151
	 * @param string $uid
152
	 * @return \OC\User\User|null Either the user or null if the specified user does not exist
153
	 */
154
	public function get($uid) {
155
		if (is_null($uid) || $uid === '' || $uid === false) {
156
			return null;
157
		}
158
		if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends
159
			return $this->cachedUsers[$uid];
160
		}
161
162
		$cachedBackend = $this->cache->get($uid);
163
		if ($cachedBackend !== null && isset($this->backends[$cachedBackend])) {
164
			// Cache has the info of the user backend already, so ask that one directly
165
			$backend = $this->backends[$cachedBackend];
166
			if ($backend->userExists($uid)) {
167
				return $this->getUserObject($uid, $backend);
168
			}
169
		}
170
171
		foreach ($this->backends as $i => $backend) {
172
			if ($i === $cachedBackend) {
173
				// Tried that one already
174
				continue;
175
			}
176
177
			if ($backend->userExists($uid)) {
178
				$this->cache->set($uid, $i, 300);
179
				return $this->getUserObject($uid, $backend);
180
			}
181
		}
182
		return null;
183
	}
184
185
	/**
186
	 * get or construct the user object
187
	 *
188
	 * @param string $uid
189
	 * @param \OCP\UserInterface $backend
190
	 * @param bool $cacheUser If false the newly created user object will not be cached
191
	 * @return \OC\User\User
192
	 */
193
	protected function getUserObject($uid, $backend, $cacheUser = true) {
194
		if ($backend instanceof IGetRealUIDBackend) {
195
			$uid = $backend->getRealUID($uid);
196
		}
197
198
		if (isset($this->cachedUsers[$uid])) {
199
			return $this->cachedUsers[$uid];
200
		}
201
202
		$user = new User($uid, $backend, $this->dispatcher, $this, $this->config);
203
		if ($cacheUser) {
204
			$this->cachedUsers[$uid] = $user;
205
		}
206
		return $user;
207
	}
208
209
	/**
210
	 * check if a user exists
211
	 *
212
	 * @param string $uid
213
	 * @return bool
214
	 */
215
	public function userExists($uid) {
216
		$user = $this->get($uid);
217
		return ($user !== null);
218
	}
219
220
	/**
221
	 * Check if the password is valid for the user
222
	 *
223
	 * @param string $loginName
224
	 * @param string $password
225
	 * @return mixed the User object on success, false otherwise
226
	 */
227
	public function checkPassword($loginName, $password) {
228
		$result = $this->checkPasswordNoLogging($loginName, $password);
229
230
		if ($result === false) {
0 ignored issues
show
introduced by
The condition $result === false is always false.
Loading history...
231
			\OC::$server->getLogger()->warning('Login failed: \''. $loginName .'\' (Remote IP: \''. \OC::$server->getRequest()->getRemoteAddress(). '\')', ['app' => 'core']);
232
		}
233
234
		return $result;
235
	}
236
237
	/**
238
	 * Check if the password is valid for the user
239
	 *
240
	 * @internal
241
	 * @param string $loginName
242
	 * @param string $password
243
	 * @return IUser|false the User object on success, false otherwise
244
	 */
245
	public function checkPasswordNoLogging($loginName, $password) {
246
		$loginName = str_replace("\0", '', $loginName);
247
		$password = str_replace("\0", '', $password);
248
249
		foreach ($this->backends as $backend) {
250
			if ($backend->implementsActions(Backend::CHECK_PASSWORD)) {
251
				$uid = $backend->checkPassword($loginName, $password);
0 ignored issues
show
Bug introduced by
The method checkPassword() does not exist on OCP\UserInterface. It seems like you code against a sub-type of said class. However, the method does not exist in OC\User\Backend or OCP\User\Backend\ABackend. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

251
				/** @scrutinizer ignore-call */ 
252
    $uid = $backend->checkPassword($loginName, $password);
Loading history...
252
				if ($uid !== false) {
253
					return $this->getUserObject($uid, $backend);
254
				}
255
			}
256
		}
257
258
		// since http basic auth doesn't provide a standard way of handling non ascii password we allow password to be urlencoded
259
		// we only do this decoding after using the plain password fails to maintain compatibility with any password that happens
260
		// to contains urlencoded patterns by "accident".
261
		$password = urldecode($password);
262
263
		foreach ($this->backends as $backend) {
264
			if ($backend->implementsActions(Backend::CHECK_PASSWORD)) {
265
				$uid = $backend->checkPassword($loginName, $password);
266
				if ($uid !== false) {
267
					return $this->getUserObject($uid, $backend);
268
				}
269
			}
270
		}
271
272
		return false;
273
	}
274
275
	/**
276
	 * search by user id
277
	 *
278
	 * @param string $pattern
279
	 * @param int $limit
280
	 * @param int $offset
281
	 * @return \OC\User\User[]
282
	 */
283
	public function search($pattern, $limit = null, $offset = null) {
284
		$users = [];
285
		foreach ($this->backends as $backend) {
286
			$backendUsers = $backend->getUsers($pattern, $limit, $offset);
287
			if (is_array($backendUsers)) {
288
				foreach ($backendUsers as $uid) {
289
					$users[$uid] = $this->getUserObject($uid, $backend);
290
				}
291
			}
292
		}
293
294
		uasort($users, function ($a, $b) {
295
			/**
296
			 * @var \OC\User\User $a
297
			 * @var \OC\User\User $b
298
			 */
299
			return strcasecmp($a->getUID(), $b->getUID());
300
		});
301
		return $users;
302
	}
303
304
	/**
305
	 * search by displayName
306
	 *
307
	 * @param string $pattern
308
	 * @param int $limit
309
	 * @param int $offset
310
	 * @return \OC\User\User[]
311
	 */
312
	public function searchDisplayName($pattern, $limit = null, $offset = null) {
313
		$users = [];
314
		foreach ($this->backends as $backend) {
315
			$backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
316
			if (is_array($backendUsers)) {
317
				foreach ($backendUsers as $uid => $displayName) {
318
					$users[] = $this->getUserObject($uid, $backend);
319
				}
320
			}
321
		}
322
323
		usort($users, function ($a, $b) {
324
			/**
325
			 * @var \OC\User\User $a
326
			 * @var \OC\User\User $b
327
			 */
328
			return strcasecmp($a->getDisplayName(), $b->getDisplayName());
329
		});
330
		return $users;
331
	}
332
333
	/**
334
	 * Search known users (from phonebook sync) by displayName
335
	 *
336
	 * @param string $searcher
337
	 * @param string $pattern
338
	 * @param int|null $limit
339
	 * @param int|null $offset
340
	 * @return IUser[]
341
	 */
342
	public function searchKnownUsersByDisplayName(string $searcher, string $pattern, ?int $limit = null, ?int $offset = null): array {
343
		$users = [];
344
		foreach ($this->backends as $backend) {
345
			if ($backend instanceof ISearchKnownUsersBackend) {
346
				$backendUsers = $backend->searchKnownUsersByDisplayName($searcher, $pattern, $limit, $offset);
347
			} else {
348
				// Better than nothing, but filtering after pagination can remove lots of results.
349
				$backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
350
			}
351
			if (is_array($backendUsers)) {
352
				foreach ($backendUsers as $uid => $displayName) {
353
					$users[] = $this->getUserObject($uid, $backend);
354
				}
355
			}
356
		}
357
358
		usort($users, function ($a, $b) {
359
			/**
360
			 * @var IUser $a
361
			 * @var IUser $b
362
			 */
363
			return strcasecmp($a->getDisplayName(), $b->getDisplayName());
364
		});
365
		return $users;
366
	}
367
368
	/**
369
	 * @param string $uid
370
	 * @param string $password
371
	 * @throws \InvalidArgumentException
372
	 * @return bool|IUser the created user or false
373
	 */
374
	public function createUser($uid, $password) {
375
		// DI injection is not used here as IRegistry needs the user manager itself for user count and thus it would create a cyclic dependency
376
		if (\OC::$server->get(IRegistry::class)->delegateIsHardUserLimitReached()) {
377
			$l = \OC::$server->getL10N('lib');
378
			throw new HintException($l->t('The user limit has been reached and the user was not created.'));
379
		}
380
381
		$localBackends = [];
382
		foreach ($this->backends as $backend) {
383
			if ($backend instanceof Database) {
384
				// First check if there is another user backend
385
				$localBackends[] = $backend;
386
				continue;
387
			}
388
389
			if ($backend->implementsActions(Backend::CREATE_USER)) {
390
				return $this->createUserFromBackend($uid, $password, $backend);
391
			}
392
		}
393
394
		foreach ($localBackends as $backend) {
395
			if ($backend->implementsActions(Backend::CREATE_USER)) {
396
				return $this->createUserFromBackend($uid, $password, $backend);
397
			}
398
		}
399
400
		return false;
401
	}
402
403
	/**
404
	 * @param string $uid
405
	 * @param string $password
406
	 * @param UserInterface $backend
407
	 * @return IUser|null
408
	 * @throws \InvalidArgumentException
409
	 */
410
	public function createUserFromBackend($uid, $password, UserInterface $backend) {
411
		$l = \OC::$server->getL10N('lib');
412
413
		// Check the name for bad characters
414
		// Allowed are: "a-z", "A-Z", "0-9" and "_.@-'"
415
		if (preg_match('/[^a-zA-Z0-9 _.@\-\']/', $uid)) {
416
			throw new \InvalidArgumentException($l->t('Only the following characters are allowed in a username:'
417
				. ' "a-z", "A-Z", "0-9", and "_.@-\'"'));
418
		}
419
420
		// No empty username
421
		if (trim($uid) === '') {
422
			throw new \InvalidArgumentException($l->t('A valid username must be provided'));
423
		}
424
425
		// No whitespace at the beginning or at the end
426
		if (trim($uid) !== $uid) {
427
			throw new \InvalidArgumentException($l->t('Username contains whitespace at the beginning or at the end'));
428
		}
429
430
		// Username only consists of 1 or 2 dots (directory traversal)
431
		if ($uid === '.' || $uid === '..') {
432
			throw new \InvalidArgumentException($l->t('Username must not consist of dots only'));
433
		}
434
435
		if (!$this->verifyUid($uid)) {
436
			throw new \InvalidArgumentException($l->t('Username is invalid because files already exist for this user'));
437
		}
438
439
		// No empty password
440
		if (trim($password) === '') {
441
			throw new \InvalidArgumentException($l->t('A valid password must be provided'));
442
		}
443
444
		// Check if user already exists
445
		if ($this->userExists($uid)) {
446
			throw new \InvalidArgumentException($l->t('The username is already being used'));
447
		}
448
449
		/** @deprecated 21.0.0 use BeforeUserCreatedEvent event with the IEventDispatcher instead */
450
		$this->emit('\OC\User', 'preCreateUser', [$uid, $password]);
451
		$this->eventDispatcher->dispatchTyped(new BeforeUserCreatedEvent($uid, $password));
452
		$state = $backend->createUser($uid, $password);
0 ignored issues
show
Bug introduced by
The method createUser() does not exist on OCP\UserInterface. It seems like you code against a sub-type of said class. However, the method does not exist in OC\User\Backend or OCP\User\Backend\ABackend. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

452
		/** @scrutinizer ignore-call */ 
453
  $state = $backend->createUser($uid, $password);
Loading history...
453
		if ($state === false) {
454
			throw new \InvalidArgumentException($l->t('Could not create user'));
455
		}
456
		$user = $this->getUserObject($uid, $backend);
457
		if ($user instanceof IUser) {
0 ignored issues
show
introduced by
$user is always a sub-type of OCP\IUser.
Loading history...
458
			/** @deprecated 21.0.0 use UserCreatedEvent event with the IEventDispatcher instead */
459
			$this->emit('\OC\User', 'postCreateUser', [$user, $password]);
460
			$this->eventDispatcher->dispatchTyped(new UserCreatedEvent($user, $password));
461
		}
462
		return $user;
463
	}
464
465
	/**
466
	 * returns how many users per backend exist (if supported by backend)
467
	 *
468
	 * @param boolean $hasLoggedIn when true only users that have a lastLogin
469
	 *                entry in the preferences table will be affected
470
	 * @return array|int an array of backend class as key and count number as value
471
	 *                if $hasLoggedIn is true only an int is returned
472
	 */
473
	public function countUsers($hasLoggedIn = false) {
474
		if ($hasLoggedIn) {
475
			return $this->countSeenUsers();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->countSeenUsers() returns the type integer which is incompatible with the return type mandated by OCP\IUserManager::countUsers() of array.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
476
		}
477
		$userCountStatistics = [];
478
		foreach ($this->backends as $backend) {
479
			if ($backend->implementsActions(Backend::COUNT_USERS)) {
480
				$backendUsers = $backend->countUsers();
0 ignored issues
show
Bug introduced by
The method countUsers() does not exist on OCP\UserInterface. It seems like you code against a sub-type of said class. However, the method does not exist in OC\User\Backend or OCP\User\Backend\ABackend. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

480
				/** @scrutinizer ignore-call */ 
481
    $backendUsers = $backend->countUsers();
Loading history...
481
				if ($backendUsers !== false) {
482
					if ($backend instanceof IUserBackend) {
483
						$name = $backend->getBackendName();
484
					} else {
485
						$name = get_class($backend);
486
					}
487
					if (isset($userCountStatistics[$name])) {
488
						$userCountStatistics[$name] += $backendUsers;
489
					} else {
490
						$userCountStatistics[$name] = $backendUsers;
491
					}
492
				}
493
			}
494
		}
495
		return $userCountStatistics;
496
	}
497
498
	/**
499
	 * returns how many users per backend exist in the requested groups (if supported by backend)
500
	 *
501
	 * @param IGroup[] $groups an array of gid to search in
502
	 * @return array|int an array of backend class as key and count number as value
503
	 *                if $hasLoggedIn is true only an int is returned
504
	 */
505
	public function countUsersOfGroups(array $groups) {
506
		$users = [];
507
		foreach ($groups as $group) {
508
			$usersIds = array_map(function ($user) {
509
				return $user->getUID();
510
			}, $group->getUsers());
511
			$users = array_merge($users, $usersIds);
512
		}
513
		return count(array_unique($users));
514
	}
515
516
	/**
517
	 * The callback is executed for each user on each backend.
518
	 * If the callback returns false no further users will be retrieved.
519
	 *
520
	 * @param \Closure $callback
521
	 * @param string $search
522
	 * @param boolean $onlySeen when true only users that have a lastLogin entry
523
	 *                in the preferences table will be affected
524
	 * @since 9.0.0
525
	 */
526
	public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = false) {
527
		if ($onlySeen) {
528
			$this->callForSeenUsers($callback);
529
		} else {
530
			foreach ($this->getBackends() as $backend) {
531
				$limit = 500;
532
				$offset = 0;
533
				do {
534
					$users = $backend->getUsers($search, $limit, $offset);
535
					foreach ($users as $uid) {
536
						if (!$backend->userExists($uid)) {
537
							continue;
538
						}
539
						$user = $this->getUserObject($uid, $backend, false);
540
						$return = $callback($user);
541
						if ($return === false) {
542
							break;
543
						}
544
					}
545
					$offset += $limit;
546
				} while (count($users) >= $limit);
547
			}
548
		}
549
	}
550
551
	/**
552
	 * returns how many users are disabled
553
	 *
554
	 * @return int
555
	 * @since 12.0.0
556
	 */
557
	public function countDisabledUsers(): int {
558
		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
559
		$queryBuilder->select($queryBuilder->func()->count('*'))
560
			->from('preferences')
561
			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
562
			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
563
			->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR));
564
565
566
		$result = $queryBuilder->execute();
567
		$count = $result->fetchOne();
568
		$result->closeCursor();
569
570
		if ($count !== false) {
571
			$count = (int)$count;
572
		} else {
573
			$count = 0;
574
		}
575
576
		return $count;
577
	}
578
579
	/**
580
	 * returns how many users are disabled in the requested groups
581
	 *
582
	 * @param array $groups groupids to search
583
	 * @return int
584
	 * @since 14.0.0
585
	 */
586
	public function countDisabledUsersOfGroups(array $groups): int {
587
		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
588
		$queryBuilder->select($queryBuilder->createFunction('COUNT(DISTINCT ' . $queryBuilder->getColumnName('uid') . ')'))
589
			->from('preferences', 'p')
590
			->innerJoin('p', 'group_user', 'g', $queryBuilder->expr()->eq('p.userid', 'g.uid'))
591
			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
592
			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
593
			->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR))
594
			->andWhere($queryBuilder->expr()->in('gid', $queryBuilder->createNamedParameter($groups, IQueryBuilder::PARAM_STR_ARRAY)));
595
596
		$result = $queryBuilder->execute();
597
		$count = $result->fetchOne();
598
		$result->closeCursor();
599
600
		if ($count !== false) {
601
			$count = (int)$count;
602
		} else {
603
			$count = 0;
604
		}
605
606
		return $count;
607
	}
608
609
	/**
610
	 * returns how many users have logged in once
611
	 *
612
	 * @return int
613
	 * @since 11.0.0
614
	 */
615
	public function countSeenUsers() {
616
		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
617
		$queryBuilder->select($queryBuilder->func()->count('*'))
618
			->from('preferences')
619
			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('login')))
620
			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('lastLogin')))
621
			->andWhere($queryBuilder->expr()->isNotNull('configvalue'));
622
623
		$query = $queryBuilder->execute();
624
625
		$result = (int)$query->fetchOne();
626
		$query->closeCursor();
627
628
		return $result;
629
	}
630
631
	/**
632
	 * @param \Closure $callback
633
	 * @psalm-param \Closure(\OCP\IUser):?bool $callback
634
	 * @since 11.0.0
635
	 */
636
	public function callForSeenUsers(\Closure $callback) {
637
		$limit = 1000;
638
		$offset = 0;
639
		do {
640
			$userIds = $this->getSeenUserIds($limit, $offset);
641
			$offset += $limit;
642
			foreach ($userIds as $userId) {
643
				foreach ($this->backends as $backend) {
644
					if ($backend->userExists($userId)) {
645
						$user = $this->getUserObject($userId, $backend, false);
646
						$return = $callback($user);
647
						if ($return === false) {
648
							return;
649
						}
650
						break;
651
					}
652
				}
653
			}
654
		} while (count($userIds) >= $limit);
655
	}
656
657
	/**
658
	 * Getting all userIds that have a listLogin value requires checking the
659
	 * value in php because on oracle you cannot use a clob in a where clause,
660
	 * preventing us from doing a not null or length(value) > 0 check.
661
	 *
662
	 * @param int $limit
663
	 * @param int $offset
664
	 * @return string[] with user ids
665
	 */
666
	private function getSeenUserIds($limit = null, $offset = null) {
667
		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
668
		$queryBuilder->select(['userid'])
669
			->from('preferences')
670
			->where($queryBuilder->expr()->eq(
671
				'appid', $queryBuilder->createNamedParameter('login'))
672
			)
673
			->andWhere($queryBuilder->expr()->eq(
674
				'configkey', $queryBuilder->createNamedParameter('lastLogin'))
675
			)
676
			->andWhere($queryBuilder->expr()->isNotNull('configvalue')
677
			);
678
679
		if ($limit !== null) {
680
			$queryBuilder->setMaxResults($limit);
681
		}
682
		if ($offset !== null) {
683
			$queryBuilder->setFirstResult($offset);
684
		}
685
		$query = $queryBuilder->execute();
686
		$result = [];
687
688
		while ($row = $query->fetch()) {
689
			$result[] = $row['userid'];
690
		}
691
692
		$query->closeCursor();
693
694
		return $result;
695
	}
696
697
	/**
698
	 * @param string $email
699
	 * @return IUser[]
700
	 * @since 9.1.0
701
	 */
702
	public function getByEmail($email) {
703
		$userIds = $this->config->getUsersForUserValueCaseInsensitive('settings', 'email', $email);
0 ignored issues
show
Bug introduced by
The method getUsersForUserValueCaseInsensitive() does not exist on OCP\IConfig. Did you maybe mean getUsersForUserValue()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

703
		/** @scrutinizer ignore-call */ 
704
  $userIds = $this->config->getUsersForUserValueCaseInsensitive('settings', 'email', $email);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
704
705
		$users = array_map(function ($uid) {
706
			return $this->get($uid);
707
		}, $userIds);
708
709
		return array_values(array_filter($users, function ($u) {
710
			return ($u instanceof IUser);
711
		}));
712
	}
713
714
	private function verifyUid(string $uid): bool {
715
		$appdata = 'appdata_' . $this->config->getSystemValueString('instanceid');
716
717
		if (\in_array($uid, [
718
			'.htaccess',
719
			'files_external',
720
			'__groupfolders',
721
			'.ocdata',
722
			'owncloud.log',
723
			'nextcloud.log',
724
			$appdata], true)) {
725
			return false;
726
		}
727
728
		$dataDirectory = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data');
729
730
		return !file_exists(rtrim($dataDirectory, '/') . '/' . $uid);
731
	}
732
}
733