Passed
Push — master ( a2422c...364e76 )
by Christoph
23:57 queued 10:12
created
lib/private/Memcache/WithLocalCache.php 1 patch
Indentation   +42 added lines, -42 removed lines patch added patch discarded remove patch
@@ -9,46 +9,46 @@
 block discarded – undo
9 9
  * Wrap a cache instance with an extra later of local, in-memory caching
10 10
  */
11 11
 class WithLocalCache implements ICache {
12
-	private ICache $inner;
13
-	private CappedMemoryCache $cached;
14
-
15
-	public function __construct(ICache $inner, int $localCapacity = 512) {
16
-		$this->inner = $inner;
17
-		$this->cached = new CappedMemoryCache($localCapacity);
18
-	}
19
-
20
-	public function get($key) {
21
-		if (isset($this->cached[$key])) {
22
-			return $this->cached[$key];
23
-		} else {
24
-			$value = $this->inner->get($key);
25
-			if (!is_null($value)) {
26
-				$this->cached[$key] = $value;
27
-			}
28
-			return $value;
29
-		}
30
-	}
31
-
32
-	public function set($key, $value, $ttl = 0) {
33
-		$this->cached[$key] = $value;
34
-		return $this->inner->set($key, $value, $ttl);
35
-	}
36
-
37
-	public function hasKey($key) {
38
-		return isset($this->cached[$key]) || $this->inner->hasKey($key);
39
-	}
40
-
41
-	public function remove($key) {
42
-		unset($this->cached[$key]);
43
-		return $this->inner->remove($key);
44
-	}
45
-
46
-	public function clear($prefix = '') {
47
-		$this->cached->clear();
48
-		return $this->inner->clear($prefix);
49
-	}
50
-
51
-	public static function isAvailable(): bool {
52
-		return false;
53
-	}
12
+    private ICache $inner;
13
+    private CappedMemoryCache $cached;
14
+
15
+    public function __construct(ICache $inner, int $localCapacity = 512) {
16
+        $this->inner = $inner;
17
+        $this->cached = new CappedMemoryCache($localCapacity);
18
+    }
19
+
20
+    public function get($key) {
21
+        if (isset($this->cached[$key])) {
22
+            return $this->cached[$key];
23
+        } else {
24
+            $value = $this->inner->get($key);
25
+            if (!is_null($value)) {
26
+                $this->cached[$key] = $value;
27
+            }
28
+            return $value;
29
+        }
30
+    }
31
+
32
+    public function set($key, $value, $ttl = 0) {
33
+        $this->cached[$key] = $value;
34
+        return $this->inner->set($key, $value, $ttl);
35
+    }
36
+
37
+    public function hasKey($key) {
38
+        return isset($this->cached[$key]) || $this->inner->hasKey($key);
39
+    }
40
+
41
+    public function remove($key) {
42
+        unset($this->cached[$key]);
43
+        return $this->inner->remove($key);
44
+    }
45
+
46
+    public function clear($prefix = '') {
47
+        $this->cached->clear();
48
+        return $this->inner->clear($prefix);
49
+    }
50
+
51
+    public static function isAvailable(): bool {
52
+        return false;
53
+    }
54 54
 }
Please login to merge, or discard this patch.
lib/private/User/Manager.php 2 patches
Indentation   +692 added lines, -692 removed lines patch added patch discarded remove patch
@@ -75,696 +75,696 @@
 block discarded – undo
75 75
  * @package OC\User
76 76
  */
77 77
 class Manager extends PublicEmitter implements IUserManager {
78
-	/**
79
-	 * @var \OCP\UserInterface[] $backends
80
-	 */
81
-	private $backends = [];
82
-
83
-	/**
84
-	 * @var \OC\User\User[] $cachedUsers
85
-	 */
86
-	private $cachedUsers = [];
87
-
88
-	/** @var IConfig */
89
-	private $config;
90
-
91
-	/** @var EventDispatcherInterface */
92
-	private $dispatcher;
93
-
94
-	/** @var ICache */
95
-	private $cache;
96
-
97
-	/** @var IEventDispatcher */
98
-	private $eventDispatcher;
99
-
100
-	private DisplayNameCache $displayNameCache;
101
-
102
-	public function __construct(IConfig $config,
103
-								EventDispatcherInterface $oldDispatcher,
104
-								ICacheFactory $cacheFactory,
105
-								IEventDispatcher $eventDispatcher) {
106
-		$this->config = $config;
107
-		$this->dispatcher = $oldDispatcher;
108
-		$this->cache = new WithLocalCache($cacheFactory->createDistributed('user_backend_map'));
109
-		$cachedUsers = &$this->cachedUsers;
110
-		$this->listen('\OC\User', 'postDelete', function ($user) use (&$cachedUsers) {
111
-			/** @var \OC\User\User $user */
112
-			unset($cachedUsers[$user->getUID()]);
113
-		});
114
-		$this->eventDispatcher = $eventDispatcher;
115
-		$this->displayNameCache = new DisplayNameCache($cacheFactory, $this);
116
-	}
117
-
118
-	/**
119
-	 * Get the active backends
120
-	 * @return \OCP\UserInterface[]
121
-	 */
122
-	public function getBackends() {
123
-		return $this->backends;
124
-	}
125
-
126
-	/**
127
-	 * register a user backend
128
-	 *
129
-	 * @param \OCP\UserInterface $backend
130
-	 */
131
-	public function registerBackend($backend) {
132
-		$this->backends[] = $backend;
133
-	}
134
-
135
-	/**
136
-	 * remove a user backend
137
-	 *
138
-	 * @param \OCP\UserInterface $backend
139
-	 */
140
-	public function removeBackend($backend) {
141
-		$this->cachedUsers = [];
142
-		if (($i = array_search($backend, $this->backends)) !== false) {
143
-			unset($this->backends[$i]);
144
-		}
145
-	}
146
-
147
-	/**
148
-	 * remove all user backends
149
-	 */
150
-	public function clearBackends() {
151
-		$this->cachedUsers = [];
152
-		$this->backends = [];
153
-	}
154
-
155
-	/**
156
-	 * get a user by user id
157
-	 *
158
-	 * @param string $uid
159
-	 * @return \OC\User\User|null Either the user or null if the specified user does not exist
160
-	 */
161
-	public function get($uid) {
162
-		if (is_null($uid) || $uid === '' || $uid === false) {
163
-			return null;
164
-		}
165
-		if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends
166
-			return $this->cachedUsers[$uid];
167
-		}
168
-
169
-		$cachedBackend = $this->cache->get(sha1($uid));
170
-		if ($cachedBackend !== null && isset($this->backends[$cachedBackend])) {
171
-			// Cache has the info of the user backend already, so ask that one directly
172
-			$backend = $this->backends[$cachedBackend];
173
-			if ($backend->userExists($uid)) {
174
-				return $this->getUserObject($uid, $backend);
175
-			}
176
-		}
177
-
178
-		foreach ($this->backends as $i => $backend) {
179
-			if ($i === $cachedBackend) {
180
-				// Tried that one already
181
-				continue;
182
-			}
183
-
184
-			if ($backend->userExists($uid)) {
185
-				// Hash $uid to ensure that only valid characters are used for the cache key
186
-				$this->cache->set(sha1($uid), $i, 300);
187
-				return $this->getUserObject($uid, $backend);
188
-			}
189
-		}
190
-		return null;
191
-	}
192
-
193
-	public function getDisplayName(string $uid): ?string {
194
-		return $this->displayNameCache->getDisplayName($uid);
195
-	}
196
-
197
-	/**
198
-	 * get or construct the user object
199
-	 *
200
-	 * @param string $uid
201
-	 * @param \OCP\UserInterface $backend
202
-	 * @param bool $cacheUser If false the newly created user object will not be cached
203
-	 * @return \OC\User\User
204
-	 */
205
-	protected function getUserObject($uid, $backend, $cacheUser = true) {
206
-		if ($backend instanceof IGetRealUIDBackend) {
207
-			$uid = $backend->getRealUID($uid);
208
-		}
209
-
210
-		if (isset($this->cachedUsers[$uid])) {
211
-			return $this->cachedUsers[$uid];
212
-		}
213
-
214
-		$user = new User($uid, $backend, $this->dispatcher, $this, $this->config);
215
-		if ($cacheUser) {
216
-			$this->cachedUsers[$uid] = $user;
217
-		}
218
-		return $user;
219
-	}
220
-
221
-	/**
222
-	 * check if a user exists
223
-	 *
224
-	 * @param string $uid
225
-	 * @return bool
226
-	 */
227
-	public function userExists($uid) {
228
-		$user = $this->get($uid);
229
-		return ($user !== null);
230
-	}
231
-
232
-	/**
233
-	 * Check if the password is valid for the user
234
-	 *
235
-	 * @param string $loginName
236
-	 * @param string $password
237
-	 * @return IUser|false the User object on success, false otherwise
238
-	 */
239
-	public function checkPassword($loginName, $password) {
240
-		$result = $this->checkPasswordNoLogging($loginName, $password);
241
-
242
-		if ($result === false) {
243
-			\OC::$server->getLogger()->warning('Login failed: \''. $loginName .'\' (Remote IP: \''. \OC::$server->getRequest()->getRemoteAddress(). '\')', ['app' => 'core']);
244
-		}
245
-
246
-		return $result;
247
-	}
248
-
249
-	/**
250
-	 * Check if the password is valid for the user
251
-	 *
252
-	 * @internal
253
-	 * @param string $loginName
254
-	 * @param string $password
255
-	 * @return IUser|false the User object on success, false otherwise
256
-	 */
257
-	public function checkPasswordNoLogging($loginName, $password) {
258
-		$loginName = str_replace("\0", '', $loginName);
259
-		$password = str_replace("\0", '', $password);
260
-
261
-		$cachedBackend = $this->cache->get($loginName);
262
-		if ($cachedBackend !== null && isset($this->backends[$cachedBackend])) {
263
-			$backends = [$this->backends[$cachedBackend]];
264
-		} else {
265
-			$backends = $this->backends;
266
-		}
267
-		foreach ($backends as $backend) {
268
-			if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) {
269
-				/** @var ICheckPasswordBackend $backend */
270
-				$uid = $backend->checkPassword($loginName, $password);
271
-				if ($uid !== false) {
272
-					return $this->getUserObject($uid, $backend);
273
-				}
274
-			}
275
-		}
276
-
277
-		// since http basic auth doesn't provide a standard way of handling non ascii password we allow password to be urlencoded
278
-		// we only do this decoding after using the plain password fails to maintain compatibility with any password that happens
279
-		// to contain urlencoded patterns by "accident".
280
-		$password = urldecode($password);
281
-
282
-		foreach ($backends as $backend) {
283
-			if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) {
284
-				/** @var ICheckPasswordBackend|UserInterface $backend */
285
-				$uid = $backend->checkPassword($loginName, $password);
286
-				if ($uid !== false) {
287
-					return $this->getUserObject($uid, $backend);
288
-				}
289
-			}
290
-		}
291
-
292
-		return false;
293
-	}
294
-
295
-	/**
296
-	 * search by user id
297
-	 *
298
-	 * @param string $pattern
299
-	 * @param int $limit
300
-	 * @param int $offset
301
-	 * @return \OC\User\User[]
302
-	 */
303
-	public function search($pattern, $limit = null, $offset = null) {
304
-		$users = [];
305
-		foreach ($this->backends as $backend) {
306
-			$backendUsers = $backend->getUsers($pattern, $limit, $offset);
307
-			if (is_array($backendUsers)) {
308
-				foreach ($backendUsers as $uid) {
309
-					$users[$uid] = $this->getUserObject($uid, $backend);
310
-				}
311
-			}
312
-		}
313
-
314
-		uasort($users, function ($a, $b) {
315
-			/**
316
-			 * @var \OC\User\User $a
317
-			 * @var \OC\User\User $b
318
-			 */
319
-			return strcasecmp($a->getUID(), $b->getUID());
320
-		});
321
-		return $users;
322
-	}
323
-
324
-	/**
325
-	 * search by displayName
326
-	 *
327
-	 * @param string $pattern
328
-	 * @param int $limit
329
-	 * @param int $offset
330
-	 * @return \OC\User\User[]
331
-	 */
332
-	public function searchDisplayName($pattern, $limit = null, $offset = null) {
333
-		$users = [];
334
-		foreach ($this->backends as $backend) {
335
-			$backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
336
-			if (is_array($backendUsers)) {
337
-				foreach ($backendUsers as $uid => $displayName) {
338
-					$users[] = $this->getUserObject($uid, $backend);
339
-				}
340
-			}
341
-		}
342
-
343
-		usort($users, function ($a, $b) {
344
-			/**
345
-			 * @var \OC\User\User $a
346
-			 * @var \OC\User\User $b
347
-			 */
348
-			return strcasecmp($a->getDisplayName(), $b->getDisplayName());
349
-		});
350
-		return $users;
351
-	}
352
-
353
-	/**
354
-	 * Search known users (from phonebook sync) by displayName
355
-	 *
356
-	 * @param string $searcher
357
-	 * @param string $pattern
358
-	 * @param int|null $limit
359
-	 * @param int|null $offset
360
-	 * @return IUser[]
361
-	 */
362
-	public function searchKnownUsersByDisplayName(string $searcher, string $pattern, ?int $limit = null, ?int $offset = null): array {
363
-		$users = [];
364
-		foreach ($this->backends as $backend) {
365
-			if ($backend instanceof ISearchKnownUsersBackend) {
366
-				$backendUsers = $backend->searchKnownUsersByDisplayName($searcher, $pattern, $limit, $offset);
367
-			} else {
368
-				// Better than nothing, but filtering after pagination can remove lots of results.
369
-				$backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
370
-			}
371
-			if (is_array($backendUsers)) {
372
-				foreach ($backendUsers as $uid => $displayName) {
373
-					$users[] = $this->getUserObject($uid, $backend);
374
-				}
375
-			}
376
-		}
377
-
378
-		usort($users, function ($a, $b) {
379
-			/**
380
-			 * @var IUser $a
381
-			 * @var IUser $b
382
-			 */
383
-			return strcasecmp($a->getDisplayName(), $b->getDisplayName());
384
-		});
385
-		return $users;
386
-	}
387
-
388
-	/**
389
-	 * @param string $uid
390
-	 * @param string $password
391
-	 * @return false|IUser the created user or false
392
-	 * @throws \InvalidArgumentException
393
-	 * @throws HintException
394
-	 */
395
-	public function createUser($uid, $password) {
396
-		// DI injection is not used here as IRegistry needs the user manager itself for user count and thus it would create a cyclic dependency
397
-		/** @var IAssertion $assertion */
398
-		$assertion = \OC::$server->get(IAssertion::class);
399
-		$assertion->createUserIsLegit();
400
-
401
-		$localBackends = [];
402
-		foreach ($this->backends as $backend) {
403
-			if ($backend instanceof Database) {
404
-				// First check if there is another user backend
405
-				$localBackends[] = $backend;
406
-				continue;
407
-			}
408
-
409
-			if ($backend->implementsActions(Backend::CREATE_USER)) {
410
-				return $this->createUserFromBackend($uid, $password, $backend);
411
-			}
412
-		}
413
-
414
-		foreach ($localBackends as $backend) {
415
-			if ($backend->implementsActions(Backend::CREATE_USER)) {
416
-				return $this->createUserFromBackend($uid, $password, $backend);
417
-			}
418
-		}
419
-
420
-		return false;
421
-	}
422
-
423
-	/**
424
-	 * @param string $uid
425
-	 * @param string $password
426
-	 * @param UserInterface $backend
427
-	 * @return IUser|false
428
-	 * @throws \InvalidArgumentException
429
-	 */
430
-	public function createUserFromBackend($uid, $password, UserInterface $backend) {
431
-		$l = \OC::$server->getL10N('lib');
432
-
433
-		$this->validateUserId($uid, true);
434
-
435
-		// No empty password
436
-		if (trim($password) === '') {
437
-			throw new \InvalidArgumentException($l->t('A valid password must be provided'));
438
-		}
439
-
440
-		// Check if user already exists
441
-		if ($this->userExists($uid)) {
442
-			throw new \InvalidArgumentException($l->t('The username is already being used'));
443
-		}
444
-
445
-		/** @deprecated 21.0.0 use BeforeUserCreatedEvent event with the IEventDispatcher instead */
446
-		$this->emit('\OC\User', 'preCreateUser', [$uid, $password]);
447
-		$this->eventDispatcher->dispatchTyped(new BeforeUserCreatedEvent($uid, $password));
448
-		$state = $backend->createUser($uid, $password);
449
-		if ($state === false) {
450
-			throw new \InvalidArgumentException($l->t('Could not create user'));
451
-		}
452
-		$user = $this->getUserObject($uid, $backend);
453
-		if ($user instanceof IUser) {
454
-			/** @deprecated 21.0.0 use UserCreatedEvent event with the IEventDispatcher instead */
455
-			$this->emit('\OC\User', 'postCreateUser', [$user, $password]);
456
-			$this->eventDispatcher->dispatchTyped(new UserCreatedEvent($user, $password));
457
-			return $user;
458
-		}
459
-		return false;
460
-	}
461
-
462
-	/**
463
-	 * returns how many users per backend exist (if supported by backend)
464
-	 *
465
-	 * @param boolean $hasLoggedIn when true only users that have a lastLogin
466
-	 *                entry in the preferences table will be affected
467
-	 * @return array<string, int> an array of backend class as key and count number as value
468
-	 */
469
-	public function countUsers() {
470
-		$userCountStatistics = [];
471
-		foreach ($this->backends as $backend) {
472
-			if ($backend instanceof ICountUsersBackend || $backend->implementsActions(Backend::COUNT_USERS)) {
473
-				/** @var ICountUsersBackend|IUserBackend $backend */
474
-				$backendUsers = $backend->countUsers();
475
-				if ($backendUsers !== false) {
476
-					if ($backend instanceof IUserBackend) {
477
-						$name = $backend->getBackendName();
478
-					} else {
479
-						$name = get_class($backend);
480
-					}
481
-					if (isset($userCountStatistics[$name])) {
482
-						$userCountStatistics[$name] += $backendUsers;
483
-					} else {
484
-						$userCountStatistics[$name] = $backendUsers;
485
-					}
486
-				}
487
-			}
488
-		}
489
-		return $userCountStatistics;
490
-	}
491
-
492
-	/**
493
-	 * returns how many users per backend exist in the requested groups (if supported by backend)
494
-	 *
495
-	 * @param IGroup[] $groups an array of gid to search in
496
-	 * @return array|int an array of backend class as key and count number as value
497
-	 *                if $hasLoggedIn is true only an int is returned
498
-	 */
499
-	public function countUsersOfGroups(array $groups) {
500
-		$users = [];
501
-		foreach ($groups as $group) {
502
-			$usersIds = array_map(function ($user) {
503
-				return $user->getUID();
504
-			}, $group->getUsers());
505
-			$users = array_merge($users, $usersIds);
506
-		}
507
-		return count(array_unique($users));
508
-	}
509
-
510
-	/**
511
-	 * The callback is executed for each user on each backend.
512
-	 * If the callback returns false no further users will be retrieved.
513
-	 *
514
-	 * @psalm-param \Closure(\OCP\IUser):?bool $callback
515
-	 * @param string $search
516
-	 * @param boolean $onlySeen when true only users that have a lastLogin entry
517
-	 *                in the preferences table will be affected
518
-	 * @since 9.0.0
519
-	 */
520
-	public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = false) {
521
-		if ($onlySeen) {
522
-			$this->callForSeenUsers($callback);
523
-		} else {
524
-			foreach ($this->getBackends() as $backend) {
525
-				$limit = 500;
526
-				$offset = 0;
527
-				do {
528
-					$users = $backend->getUsers($search, $limit, $offset);
529
-					foreach ($users as $uid) {
530
-						if (!$backend->userExists($uid)) {
531
-							continue;
532
-						}
533
-						$user = $this->getUserObject($uid, $backend, false);
534
-						$return = $callback($user);
535
-						if ($return === false) {
536
-							break;
537
-						}
538
-					}
539
-					$offset += $limit;
540
-				} while (count($users) >= $limit);
541
-			}
542
-		}
543
-	}
544
-
545
-	/**
546
-	 * returns how many users are disabled
547
-	 *
548
-	 * @return int
549
-	 * @since 12.0.0
550
-	 */
551
-	public function countDisabledUsers(): int {
552
-		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
553
-		$queryBuilder->select($queryBuilder->func()->count('*'))
554
-			->from('preferences')
555
-			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
556
-			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
557
-			->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR));
558
-
559
-
560
-		$result = $queryBuilder->execute();
561
-		$count = $result->fetchOne();
562
-		$result->closeCursor();
563
-
564
-		if ($count !== false) {
565
-			$count = (int)$count;
566
-		} else {
567
-			$count = 0;
568
-		}
569
-
570
-		return $count;
571
-	}
572
-
573
-	/**
574
-	 * returns how many users are disabled in the requested groups
575
-	 *
576
-	 * @param array $groups groupids to search
577
-	 * @return int
578
-	 * @since 14.0.0
579
-	 */
580
-	public function countDisabledUsersOfGroups(array $groups): int {
581
-		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
582
-		$queryBuilder->select($queryBuilder->createFunction('COUNT(DISTINCT ' . $queryBuilder->getColumnName('uid') . ')'))
583
-			->from('preferences', 'p')
584
-			->innerJoin('p', 'group_user', 'g', $queryBuilder->expr()->eq('p.userid', 'g.uid'))
585
-			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
586
-			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
587
-			->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR))
588
-			->andWhere($queryBuilder->expr()->in('gid', $queryBuilder->createNamedParameter($groups, IQueryBuilder::PARAM_STR_ARRAY)));
589
-
590
-		$result = $queryBuilder->execute();
591
-		$count = $result->fetchOne();
592
-		$result->closeCursor();
593
-
594
-		if ($count !== false) {
595
-			$count = (int)$count;
596
-		} else {
597
-			$count = 0;
598
-		}
599
-
600
-		return $count;
601
-	}
602
-
603
-	/**
604
-	 * returns how many users have logged in once
605
-	 *
606
-	 * @return int
607
-	 * @since 11.0.0
608
-	 */
609
-	public function countSeenUsers() {
610
-		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
611
-		$queryBuilder->select($queryBuilder->func()->count('*'))
612
-			->from('preferences')
613
-			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('login')))
614
-			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('lastLogin')));
615
-
616
-		$query = $queryBuilder->execute();
617
-
618
-		$result = (int)$query->fetchOne();
619
-		$query->closeCursor();
620
-
621
-		return $result;
622
-	}
623
-
624
-	/**
625
-	 * @param \Closure $callback
626
-	 * @psalm-param \Closure(\OCP\IUser):?bool $callback
627
-	 * @since 11.0.0
628
-	 */
629
-	public function callForSeenUsers(\Closure $callback) {
630
-		$limit = 1000;
631
-		$offset = 0;
632
-		do {
633
-			$userIds = $this->getSeenUserIds($limit, $offset);
634
-			$offset += $limit;
635
-			foreach ($userIds as $userId) {
636
-				foreach ($this->backends as $backend) {
637
-					if ($backend->userExists($userId)) {
638
-						$user = $this->getUserObject($userId, $backend, false);
639
-						$return = $callback($user);
640
-						if ($return === false) {
641
-							return;
642
-						}
643
-						break;
644
-					}
645
-				}
646
-			}
647
-		} while (count($userIds) >= $limit);
648
-	}
649
-
650
-	/**
651
-	 * Getting all userIds that have a listLogin value requires checking the
652
-	 * value in php because on oracle you cannot use a clob in a where clause,
653
-	 * preventing us from doing a not null or length(value) > 0 check.
654
-	 *
655
-	 * @param int $limit
656
-	 * @param int $offset
657
-	 * @return string[] with user ids
658
-	 */
659
-	private function getSeenUserIds($limit = null, $offset = null) {
660
-		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
661
-		$queryBuilder->select(['userid'])
662
-			->from('preferences')
663
-			->where($queryBuilder->expr()->eq(
664
-				'appid', $queryBuilder->createNamedParameter('login'))
665
-			)
666
-			->andWhere($queryBuilder->expr()->eq(
667
-				'configkey', $queryBuilder->createNamedParameter('lastLogin'))
668
-			)
669
-			->andWhere($queryBuilder->expr()->isNotNull('configvalue')
670
-			);
671
-
672
-		if ($limit !== null) {
673
-			$queryBuilder->setMaxResults($limit);
674
-		}
675
-		if ($offset !== null) {
676
-			$queryBuilder->setFirstResult($offset);
677
-		}
678
-		$query = $queryBuilder->execute();
679
-		$result = [];
680
-
681
-		while ($row = $query->fetch()) {
682
-			$result[] = $row['userid'];
683
-		}
684
-
685
-		$query->closeCursor();
686
-
687
-		return $result;
688
-	}
689
-
690
-	/**
691
-	 * @param string $email
692
-	 * @return IUser[]
693
-	 * @since 9.1.0
694
-	 */
695
-	public function getByEmail($email) {
696
-		// looking for 'email' only (and not primary_mail) is intentional
697
-		$userIds = $this->config->getUsersForUserValueCaseInsensitive('settings', 'email', $email);
698
-
699
-		$users = array_map(function ($uid) {
700
-			return $this->get($uid);
701
-		}, $userIds);
702
-
703
-		return array_values(array_filter($users, function ($u) {
704
-			return ($u instanceof IUser);
705
-		}));
706
-	}
707
-
708
-	/**
709
-	 * @param string $uid
710
-	 * @param bool $checkDataDirectory
711
-	 * @throws \InvalidArgumentException Message is an already translated string with a reason why the id is not valid
712
-	 * @since 26.0.0
713
-	 */
714
-	public function validateUserId(string $uid, bool $checkDataDirectory = false): void {
715
-		$l = Server::get(IFactory::class)->get('lib');
716
-
717
-		// Check the name for bad characters
718
-		// Allowed are: "a-z", "A-Z", "0-9", spaces and "_.@-'"
719
-		if (preg_match('/[^a-zA-Z0-9 _.@\-\']/', $uid)) {
720
-			throw new \InvalidArgumentException($l->t('Only the following characters are allowed in a username:'
721
-				. ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'));
722
-		}
723
-
724
-		// No empty username
725
-		if (trim($uid) === '') {
726
-			throw new \InvalidArgumentException($l->t('A valid username must be provided'));
727
-		}
728
-
729
-		// No whitespace at the beginning or at the end
730
-		if (trim($uid) !== $uid) {
731
-			throw new \InvalidArgumentException($l->t('Username contains whitespace at the beginning or at the end'));
732
-		}
733
-
734
-		// Username only consists of 1 or 2 dots (directory traversal)
735
-		if ($uid === '.' || $uid === '..') {
736
-			throw new \InvalidArgumentException($l->t('Username must not consist of dots only'));
737
-		}
738
-
739
-		if (!$this->verifyUid($uid, $checkDataDirectory)) {
740
-			throw new \InvalidArgumentException($l->t('Username is invalid because files already exist for this user'));
741
-		}
742
-	}
743
-
744
-	private function verifyUid(string $uid, bool $checkDataDirectory = false): bool {
745
-		$appdata = 'appdata_' . $this->config->getSystemValueString('instanceid');
746
-
747
-		if (\in_array($uid, [
748
-			'.htaccess',
749
-			'files_external',
750
-			'__groupfolders',
751
-			'.ocdata',
752
-			'owncloud.log',
753
-			'nextcloud.log',
754
-			$appdata], true)) {
755
-			return false;
756
-		}
757
-
758
-		if (!$checkDataDirectory) {
759
-			return true;
760
-		}
761
-
762
-		$dataDirectory = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data');
763
-
764
-		return !file_exists(rtrim($dataDirectory, '/') . '/' . $uid);
765
-	}
766
-
767
-	public function getDisplayNameCache(): DisplayNameCache {
768
-		return $this->displayNameCache;
769
-	}
78
+    /**
79
+     * @var \OCP\UserInterface[] $backends
80
+     */
81
+    private $backends = [];
82
+
83
+    /**
84
+     * @var \OC\User\User[] $cachedUsers
85
+     */
86
+    private $cachedUsers = [];
87
+
88
+    /** @var IConfig */
89
+    private $config;
90
+
91
+    /** @var EventDispatcherInterface */
92
+    private $dispatcher;
93
+
94
+    /** @var ICache */
95
+    private $cache;
96
+
97
+    /** @var IEventDispatcher */
98
+    private $eventDispatcher;
99
+
100
+    private DisplayNameCache $displayNameCache;
101
+
102
+    public function __construct(IConfig $config,
103
+                                EventDispatcherInterface $oldDispatcher,
104
+                                ICacheFactory $cacheFactory,
105
+                                IEventDispatcher $eventDispatcher) {
106
+        $this->config = $config;
107
+        $this->dispatcher = $oldDispatcher;
108
+        $this->cache = new WithLocalCache($cacheFactory->createDistributed('user_backend_map'));
109
+        $cachedUsers = &$this->cachedUsers;
110
+        $this->listen('\OC\User', 'postDelete', function ($user) use (&$cachedUsers) {
111
+            /** @var \OC\User\User $user */
112
+            unset($cachedUsers[$user->getUID()]);
113
+        });
114
+        $this->eventDispatcher = $eventDispatcher;
115
+        $this->displayNameCache = new DisplayNameCache($cacheFactory, $this);
116
+    }
117
+
118
+    /**
119
+     * Get the active backends
120
+     * @return \OCP\UserInterface[]
121
+     */
122
+    public function getBackends() {
123
+        return $this->backends;
124
+    }
125
+
126
+    /**
127
+     * register a user backend
128
+     *
129
+     * @param \OCP\UserInterface $backend
130
+     */
131
+    public function registerBackend($backend) {
132
+        $this->backends[] = $backend;
133
+    }
134
+
135
+    /**
136
+     * remove a user backend
137
+     *
138
+     * @param \OCP\UserInterface $backend
139
+     */
140
+    public function removeBackend($backend) {
141
+        $this->cachedUsers = [];
142
+        if (($i = array_search($backend, $this->backends)) !== false) {
143
+            unset($this->backends[$i]);
144
+        }
145
+    }
146
+
147
+    /**
148
+     * remove all user backends
149
+     */
150
+    public function clearBackends() {
151
+        $this->cachedUsers = [];
152
+        $this->backends = [];
153
+    }
154
+
155
+    /**
156
+     * get a user by user id
157
+     *
158
+     * @param string $uid
159
+     * @return \OC\User\User|null Either the user or null if the specified user does not exist
160
+     */
161
+    public function get($uid) {
162
+        if (is_null($uid) || $uid === '' || $uid === false) {
163
+            return null;
164
+        }
165
+        if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends
166
+            return $this->cachedUsers[$uid];
167
+        }
168
+
169
+        $cachedBackend = $this->cache->get(sha1($uid));
170
+        if ($cachedBackend !== null && isset($this->backends[$cachedBackend])) {
171
+            // Cache has the info of the user backend already, so ask that one directly
172
+            $backend = $this->backends[$cachedBackend];
173
+            if ($backend->userExists($uid)) {
174
+                return $this->getUserObject($uid, $backend);
175
+            }
176
+        }
177
+
178
+        foreach ($this->backends as $i => $backend) {
179
+            if ($i === $cachedBackend) {
180
+                // Tried that one already
181
+                continue;
182
+            }
183
+
184
+            if ($backend->userExists($uid)) {
185
+                // Hash $uid to ensure that only valid characters are used for the cache key
186
+                $this->cache->set(sha1($uid), $i, 300);
187
+                return $this->getUserObject($uid, $backend);
188
+            }
189
+        }
190
+        return null;
191
+    }
192
+
193
+    public function getDisplayName(string $uid): ?string {
194
+        return $this->displayNameCache->getDisplayName($uid);
195
+    }
196
+
197
+    /**
198
+     * get or construct the user object
199
+     *
200
+     * @param string $uid
201
+     * @param \OCP\UserInterface $backend
202
+     * @param bool $cacheUser If false the newly created user object will not be cached
203
+     * @return \OC\User\User
204
+     */
205
+    protected function getUserObject($uid, $backend, $cacheUser = true) {
206
+        if ($backend instanceof IGetRealUIDBackend) {
207
+            $uid = $backend->getRealUID($uid);
208
+        }
209
+
210
+        if (isset($this->cachedUsers[$uid])) {
211
+            return $this->cachedUsers[$uid];
212
+        }
213
+
214
+        $user = new User($uid, $backend, $this->dispatcher, $this, $this->config);
215
+        if ($cacheUser) {
216
+            $this->cachedUsers[$uid] = $user;
217
+        }
218
+        return $user;
219
+    }
220
+
221
+    /**
222
+     * check if a user exists
223
+     *
224
+     * @param string $uid
225
+     * @return bool
226
+     */
227
+    public function userExists($uid) {
228
+        $user = $this->get($uid);
229
+        return ($user !== null);
230
+    }
231
+
232
+    /**
233
+     * Check if the password is valid for the user
234
+     *
235
+     * @param string $loginName
236
+     * @param string $password
237
+     * @return IUser|false the User object on success, false otherwise
238
+     */
239
+    public function checkPassword($loginName, $password) {
240
+        $result = $this->checkPasswordNoLogging($loginName, $password);
241
+
242
+        if ($result === false) {
243
+            \OC::$server->getLogger()->warning('Login failed: \''. $loginName .'\' (Remote IP: \''. \OC::$server->getRequest()->getRemoteAddress(). '\')', ['app' => 'core']);
244
+        }
245
+
246
+        return $result;
247
+    }
248
+
249
+    /**
250
+     * Check if the password is valid for the user
251
+     *
252
+     * @internal
253
+     * @param string $loginName
254
+     * @param string $password
255
+     * @return IUser|false the User object on success, false otherwise
256
+     */
257
+    public function checkPasswordNoLogging($loginName, $password) {
258
+        $loginName = str_replace("\0", '', $loginName);
259
+        $password = str_replace("\0", '', $password);
260
+
261
+        $cachedBackend = $this->cache->get($loginName);
262
+        if ($cachedBackend !== null && isset($this->backends[$cachedBackend])) {
263
+            $backends = [$this->backends[$cachedBackend]];
264
+        } else {
265
+            $backends = $this->backends;
266
+        }
267
+        foreach ($backends as $backend) {
268
+            if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) {
269
+                /** @var ICheckPasswordBackend $backend */
270
+                $uid = $backend->checkPassword($loginName, $password);
271
+                if ($uid !== false) {
272
+                    return $this->getUserObject($uid, $backend);
273
+                }
274
+            }
275
+        }
276
+
277
+        // since http basic auth doesn't provide a standard way of handling non ascii password we allow password to be urlencoded
278
+        // we only do this decoding after using the plain password fails to maintain compatibility with any password that happens
279
+        // to contain urlencoded patterns by "accident".
280
+        $password = urldecode($password);
281
+
282
+        foreach ($backends as $backend) {
283
+            if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) {
284
+                /** @var ICheckPasswordBackend|UserInterface $backend */
285
+                $uid = $backend->checkPassword($loginName, $password);
286
+                if ($uid !== false) {
287
+                    return $this->getUserObject($uid, $backend);
288
+                }
289
+            }
290
+        }
291
+
292
+        return false;
293
+    }
294
+
295
+    /**
296
+     * search by user id
297
+     *
298
+     * @param string $pattern
299
+     * @param int $limit
300
+     * @param int $offset
301
+     * @return \OC\User\User[]
302
+     */
303
+    public function search($pattern, $limit = null, $offset = null) {
304
+        $users = [];
305
+        foreach ($this->backends as $backend) {
306
+            $backendUsers = $backend->getUsers($pattern, $limit, $offset);
307
+            if (is_array($backendUsers)) {
308
+                foreach ($backendUsers as $uid) {
309
+                    $users[$uid] = $this->getUserObject($uid, $backend);
310
+                }
311
+            }
312
+        }
313
+
314
+        uasort($users, function ($a, $b) {
315
+            /**
316
+             * @var \OC\User\User $a
317
+             * @var \OC\User\User $b
318
+             */
319
+            return strcasecmp($a->getUID(), $b->getUID());
320
+        });
321
+        return $users;
322
+    }
323
+
324
+    /**
325
+     * search by displayName
326
+     *
327
+     * @param string $pattern
328
+     * @param int $limit
329
+     * @param int $offset
330
+     * @return \OC\User\User[]
331
+     */
332
+    public function searchDisplayName($pattern, $limit = null, $offset = null) {
333
+        $users = [];
334
+        foreach ($this->backends as $backend) {
335
+            $backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
336
+            if (is_array($backendUsers)) {
337
+                foreach ($backendUsers as $uid => $displayName) {
338
+                    $users[] = $this->getUserObject($uid, $backend);
339
+                }
340
+            }
341
+        }
342
+
343
+        usort($users, function ($a, $b) {
344
+            /**
345
+             * @var \OC\User\User $a
346
+             * @var \OC\User\User $b
347
+             */
348
+            return strcasecmp($a->getDisplayName(), $b->getDisplayName());
349
+        });
350
+        return $users;
351
+    }
352
+
353
+    /**
354
+     * Search known users (from phonebook sync) by displayName
355
+     *
356
+     * @param string $searcher
357
+     * @param string $pattern
358
+     * @param int|null $limit
359
+     * @param int|null $offset
360
+     * @return IUser[]
361
+     */
362
+    public function searchKnownUsersByDisplayName(string $searcher, string $pattern, ?int $limit = null, ?int $offset = null): array {
363
+        $users = [];
364
+        foreach ($this->backends as $backend) {
365
+            if ($backend instanceof ISearchKnownUsersBackend) {
366
+                $backendUsers = $backend->searchKnownUsersByDisplayName($searcher, $pattern, $limit, $offset);
367
+            } else {
368
+                // Better than nothing, but filtering after pagination can remove lots of results.
369
+                $backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
370
+            }
371
+            if (is_array($backendUsers)) {
372
+                foreach ($backendUsers as $uid => $displayName) {
373
+                    $users[] = $this->getUserObject($uid, $backend);
374
+                }
375
+            }
376
+        }
377
+
378
+        usort($users, function ($a, $b) {
379
+            /**
380
+             * @var IUser $a
381
+             * @var IUser $b
382
+             */
383
+            return strcasecmp($a->getDisplayName(), $b->getDisplayName());
384
+        });
385
+        return $users;
386
+    }
387
+
388
+    /**
389
+     * @param string $uid
390
+     * @param string $password
391
+     * @return false|IUser the created user or false
392
+     * @throws \InvalidArgumentException
393
+     * @throws HintException
394
+     */
395
+    public function createUser($uid, $password) {
396
+        // DI injection is not used here as IRegistry needs the user manager itself for user count and thus it would create a cyclic dependency
397
+        /** @var IAssertion $assertion */
398
+        $assertion = \OC::$server->get(IAssertion::class);
399
+        $assertion->createUserIsLegit();
400
+
401
+        $localBackends = [];
402
+        foreach ($this->backends as $backend) {
403
+            if ($backend instanceof Database) {
404
+                // First check if there is another user backend
405
+                $localBackends[] = $backend;
406
+                continue;
407
+            }
408
+
409
+            if ($backend->implementsActions(Backend::CREATE_USER)) {
410
+                return $this->createUserFromBackend($uid, $password, $backend);
411
+            }
412
+        }
413
+
414
+        foreach ($localBackends as $backend) {
415
+            if ($backend->implementsActions(Backend::CREATE_USER)) {
416
+                return $this->createUserFromBackend($uid, $password, $backend);
417
+            }
418
+        }
419
+
420
+        return false;
421
+    }
422
+
423
+    /**
424
+     * @param string $uid
425
+     * @param string $password
426
+     * @param UserInterface $backend
427
+     * @return IUser|false
428
+     * @throws \InvalidArgumentException
429
+     */
430
+    public function createUserFromBackend($uid, $password, UserInterface $backend) {
431
+        $l = \OC::$server->getL10N('lib');
432
+
433
+        $this->validateUserId($uid, true);
434
+
435
+        // No empty password
436
+        if (trim($password) === '') {
437
+            throw new \InvalidArgumentException($l->t('A valid password must be provided'));
438
+        }
439
+
440
+        // Check if user already exists
441
+        if ($this->userExists($uid)) {
442
+            throw new \InvalidArgumentException($l->t('The username is already being used'));
443
+        }
444
+
445
+        /** @deprecated 21.0.0 use BeforeUserCreatedEvent event with the IEventDispatcher instead */
446
+        $this->emit('\OC\User', 'preCreateUser', [$uid, $password]);
447
+        $this->eventDispatcher->dispatchTyped(new BeforeUserCreatedEvent($uid, $password));
448
+        $state = $backend->createUser($uid, $password);
449
+        if ($state === false) {
450
+            throw new \InvalidArgumentException($l->t('Could not create user'));
451
+        }
452
+        $user = $this->getUserObject($uid, $backend);
453
+        if ($user instanceof IUser) {
454
+            /** @deprecated 21.0.0 use UserCreatedEvent event with the IEventDispatcher instead */
455
+            $this->emit('\OC\User', 'postCreateUser', [$user, $password]);
456
+            $this->eventDispatcher->dispatchTyped(new UserCreatedEvent($user, $password));
457
+            return $user;
458
+        }
459
+        return false;
460
+    }
461
+
462
+    /**
463
+     * returns how many users per backend exist (if supported by backend)
464
+     *
465
+     * @param boolean $hasLoggedIn when true only users that have a lastLogin
466
+     *                entry in the preferences table will be affected
467
+     * @return array<string, int> an array of backend class as key and count number as value
468
+     */
469
+    public function countUsers() {
470
+        $userCountStatistics = [];
471
+        foreach ($this->backends as $backend) {
472
+            if ($backend instanceof ICountUsersBackend || $backend->implementsActions(Backend::COUNT_USERS)) {
473
+                /** @var ICountUsersBackend|IUserBackend $backend */
474
+                $backendUsers = $backend->countUsers();
475
+                if ($backendUsers !== false) {
476
+                    if ($backend instanceof IUserBackend) {
477
+                        $name = $backend->getBackendName();
478
+                    } else {
479
+                        $name = get_class($backend);
480
+                    }
481
+                    if (isset($userCountStatistics[$name])) {
482
+                        $userCountStatistics[$name] += $backendUsers;
483
+                    } else {
484
+                        $userCountStatistics[$name] = $backendUsers;
485
+                    }
486
+                }
487
+            }
488
+        }
489
+        return $userCountStatistics;
490
+    }
491
+
492
+    /**
493
+     * returns how many users per backend exist in the requested groups (if supported by backend)
494
+     *
495
+     * @param IGroup[] $groups an array of gid to search in
496
+     * @return array|int an array of backend class as key and count number as value
497
+     *                if $hasLoggedIn is true only an int is returned
498
+     */
499
+    public function countUsersOfGroups(array $groups) {
500
+        $users = [];
501
+        foreach ($groups as $group) {
502
+            $usersIds = array_map(function ($user) {
503
+                return $user->getUID();
504
+            }, $group->getUsers());
505
+            $users = array_merge($users, $usersIds);
506
+        }
507
+        return count(array_unique($users));
508
+    }
509
+
510
+    /**
511
+     * The callback is executed for each user on each backend.
512
+     * If the callback returns false no further users will be retrieved.
513
+     *
514
+     * @psalm-param \Closure(\OCP\IUser):?bool $callback
515
+     * @param string $search
516
+     * @param boolean $onlySeen when true only users that have a lastLogin entry
517
+     *                in the preferences table will be affected
518
+     * @since 9.0.0
519
+     */
520
+    public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = false) {
521
+        if ($onlySeen) {
522
+            $this->callForSeenUsers($callback);
523
+        } else {
524
+            foreach ($this->getBackends() as $backend) {
525
+                $limit = 500;
526
+                $offset = 0;
527
+                do {
528
+                    $users = $backend->getUsers($search, $limit, $offset);
529
+                    foreach ($users as $uid) {
530
+                        if (!$backend->userExists($uid)) {
531
+                            continue;
532
+                        }
533
+                        $user = $this->getUserObject($uid, $backend, false);
534
+                        $return = $callback($user);
535
+                        if ($return === false) {
536
+                            break;
537
+                        }
538
+                    }
539
+                    $offset += $limit;
540
+                } while (count($users) >= $limit);
541
+            }
542
+        }
543
+    }
544
+
545
+    /**
546
+     * returns how many users are disabled
547
+     *
548
+     * @return int
549
+     * @since 12.0.0
550
+     */
551
+    public function countDisabledUsers(): int {
552
+        $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
553
+        $queryBuilder->select($queryBuilder->func()->count('*'))
554
+            ->from('preferences')
555
+            ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
556
+            ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
557
+            ->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR));
558
+
559
+
560
+        $result = $queryBuilder->execute();
561
+        $count = $result->fetchOne();
562
+        $result->closeCursor();
563
+
564
+        if ($count !== false) {
565
+            $count = (int)$count;
566
+        } else {
567
+            $count = 0;
568
+        }
569
+
570
+        return $count;
571
+    }
572
+
573
+    /**
574
+     * returns how many users are disabled in the requested groups
575
+     *
576
+     * @param array $groups groupids to search
577
+     * @return int
578
+     * @since 14.0.0
579
+     */
580
+    public function countDisabledUsersOfGroups(array $groups): int {
581
+        $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
582
+        $queryBuilder->select($queryBuilder->createFunction('COUNT(DISTINCT ' . $queryBuilder->getColumnName('uid') . ')'))
583
+            ->from('preferences', 'p')
584
+            ->innerJoin('p', 'group_user', 'g', $queryBuilder->expr()->eq('p.userid', 'g.uid'))
585
+            ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
586
+            ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
587
+            ->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR))
588
+            ->andWhere($queryBuilder->expr()->in('gid', $queryBuilder->createNamedParameter($groups, IQueryBuilder::PARAM_STR_ARRAY)));
589
+
590
+        $result = $queryBuilder->execute();
591
+        $count = $result->fetchOne();
592
+        $result->closeCursor();
593
+
594
+        if ($count !== false) {
595
+            $count = (int)$count;
596
+        } else {
597
+            $count = 0;
598
+        }
599
+
600
+        return $count;
601
+    }
602
+
603
+    /**
604
+     * returns how many users have logged in once
605
+     *
606
+     * @return int
607
+     * @since 11.0.0
608
+     */
609
+    public function countSeenUsers() {
610
+        $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
611
+        $queryBuilder->select($queryBuilder->func()->count('*'))
612
+            ->from('preferences')
613
+            ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('login')))
614
+            ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('lastLogin')));
615
+
616
+        $query = $queryBuilder->execute();
617
+
618
+        $result = (int)$query->fetchOne();
619
+        $query->closeCursor();
620
+
621
+        return $result;
622
+    }
623
+
624
+    /**
625
+     * @param \Closure $callback
626
+     * @psalm-param \Closure(\OCP\IUser):?bool $callback
627
+     * @since 11.0.0
628
+     */
629
+    public function callForSeenUsers(\Closure $callback) {
630
+        $limit = 1000;
631
+        $offset = 0;
632
+        do {
633
+            $userIds = $this->getSeenUserIds($limit, $offset);
634
+            $offset += $limit;
635
+            foreach ($userIds as $userId) {
636
+                foreach ($this->backends as $backend) {
637
+                    if ($backend->userExists($userId)) {
638
+                        $user = $this->getUserObject($userId, $backend, false);
639
+                        $return = $callback($user);
640
+                        if ($return === false) {
641
+                            return;
642
+                        }
643
+                        break;
644
+                    }
645
+                }
646
+            }
647
+        } while (count($userIds) >= $limit);
648
+    }
649
+
650
+    /**
651
+     * Getting all userIds that have a listLogin value requires checking the
652
+     * value in php because on oracle you cannot use a clob in a where clause,
653
+     * preventing us from doing a not null or length(value) > 0 check.
654
+     *
655
+     * @param int $limit
656
+     * @param int $offset
657
+     * @return string[] with user ids
658
+     */
659
+    private function getSeenUserIds($limit = null, $offset = null) {
660
+        $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
661
+        $queryBuilder->select(['userid'])
662
+            ->from('preferences')
663
+            ->where($queryBuilder->expr()->eq(
664
+                'appid', $queryBuilder->createNamedParameter('login'))
665
+            )
666
+            ->andWhere($queryBuilder->expr()->eq(
667
+                'configkey', $queryBuilder->createNamedParameter('lastLogin'))
668
+            )
669
+            ->andWhere($queryBuilder->expr()->isNotNull('configvalue')
670
+            );
671
+
672
+        if ($limit !== null) {
673
+            $queryBuilder->setMaxResults($limit);
674
+        }
675
+        if ($offset !== null) {
676
+            $queryBuilder->setFirstResult($offset);
677
+        }
678
+        $query = $queryBuilder->execute();
679
+        $result = [];
680
+
681
+        while ($row = $query->fetch()) {
682
+            $result[] = $row['userid'];
683
+        }
684
+
685
+        $query->closeCursor();
686
+
687
+        return $result;
688
+    }
689
+
690
+    /**
691
+     * @param string $email
692
+     * @return IUser[]
693
+     * @since 9.1.0
694
+     */
695
+    public function getByEmail($email) {
696
+        // looking for 'email' only (and not primary_mail) is intentional
697
+        $userIds = $this->config->getUsersForUserValueCaseInsensitive('settings', 'email', $email);
698
+
699
+        $users = array_map(function ($uid) {
700
+            return $this->get($uid);
701
+        }, $userIds);
702
+
703
+        return array_values(array_filter($users, function ($u) {
704
+            return ($u instanceof IUser);
705
+        }));
706
+    }
707
+
708
+    /**
709
+     * @param string $uid
710
+     * @param bool $checkDataDirectory
711
+     * @throws \InvalidArgumentException Message is an already translated string with a reason why the id is not valid
712
+     * @since 26.0.0
713
+     */
714
+    public function validateUserId(string $uid, bool $checkDataDirectory = false): void {
715
+        $l = Server::get(IFactory::class)->get('lib');
716
+
717
+        // Check the name for bad characters
718
+        // Allowed are: "a-z", "A-Z", "0-9", spaces and "_.@-'"
719
+        if (preg_match('/[^a-zA-Z0-9 _.@\-\']/', $uid)) {
720
+            throw new \InvalidArgumentException($l->t('Only the following characters are allowed in a username:'
721
+                . ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'));
722
+        }
723
+
724
+        // No empty username
725
+        if (trim($uid) === '') {
726
+            throw new \InvalidArgumentException($l->t('A valid username must be provided'));
727
+        }
728
+
729
+        // No whitespace at the beginning or at the end
730
+        if (trim($uid) !== $uid) {
731
+            throw new \InvalidArgumentException($l->t('Username contains whitespace at the beginning or at the end'));
732
+        }
733
+
734
+        // Username only consists of 1 or 2 dots (directory traversal)
735
+        if ($uid === '.' || $uid === '..') {
736
+            throw new \InvalidArgumentException($l->t('Username must not consist of dots only'));
737
+        }
738
+
739
+        if (!$this->verifyUid($uid, $checkDataDirectory)) {
740
+            throw new \InvalidArgumentException($l->t('Username is invalid because files already exist for this user'));
741
+        }
742
+    }
743
+
744
+    private function verifyUid(string $uid, bool $checkDataDirectory = false): bool {
745
+        $appdata = 'appdata_' . $this->config->getSystemValueString('instanceid');
746
+
747
+        if (\in_array($uid, [
748
+            '.htaccess',
749
+            'files_external',
750
+            '__groupfolders',
751
+            '.ocdata',
752
+            'owncloud.log',
753
+            'nextcloud.log',
754
+            $appdata], true)) {
755
+            return false;
756
+        }
757
+
758
+        if (!$checkDataDirectory) {
759
+            return true;
760
+        }
761
+
762
+        $dataDirectory = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data');
763
+
764
+        return !file_exists(rtrim($dataDirectory, '/') . '/' . $uid);
765
+    }
766
+
767
+    public function getDisplayNameCache(): DisplayNameCache {
768
+        return $this->displayNameCache;
769
+    }
770 770
 }
Please login to merge, or discard this patch.
Spacing   +15 added lines, -15 removed lines patch added patch discarded remove patch
@@ -107,7 +107,7 @@  discard block
 block discarded – undo
107 107
 		$this->dispatcher = $oldDispatcher;
108 108
 		$this->cache = new WithLocalCache($cacheFactory->createDistributed('user_backend_map'));
109 109
 		$cachedUsers = &$this->cachedUsers;
110
-		$this->listen('\OC\User', 'postDelete', function ($user) use (&$cachedUsers) {
110
+		$this->listen('\OC\User', 'postDelete', function($user) use (&$cachedUsers) {
111 111
 			/** @var \OC\User\User $user */
112 112
 			unset($cachedUsers[$user->getUID()]);
113 113
 		});
@@ -240,7 +240,7 @@  discard block
 block discarded – undo
240 240
 		$result = $this->checkPasswordNoLogging($loginName, $password);
241 241
 
242 242
 		if ($result === false) {
243
-			\OC::$server->getLogger()->warning('Login failed: \''. $loginName .'\' (Remote IP: \''. \OC::$server->getRequest()->getRemoteAddress(). '\')', ['app' => 'core']);
243
+			\OC::$server->getLogger()->warning('Login failed: \''.$loginName.'\' (Remote IP: \''.\OC::$server->getRequest()->getRemoteAddress().'\')', ['app' => 'core']);
244 244
 		}
245 245
 
246 246
 		return $result;
@@ -311,7 +311,7 @@  discard block
 block discarded – undo
311 311
 			}
312 312
 		}
313 313
 
314
-		uasort($users, function ($a, $b) {
314
+		uasort($users, function($a, $b) {
315 315
 			/**
316 316
 			 * @var \OC\User\User $a
317 317
 			 * @var \OC\User\User $b
@@ -340,7 +340,7 @@  discard block
 block discarded – undo
340 340
 			}
341 341
 		}
342 342
 
343
-		usort($users, function ($a, $b) {
343
+		usort($users, function($a, $b) {
344 344
 			/**
345 345
 			 * @var \OC\User\User $a
346 346
 			 * @var \OC\User\User $b
@@ -375,7 +375,7 @@  discard block
 block discarded – undo
375 375
 			}
376 376
 		}
377 377
 
378
-		usort($users, function ($a, $b) {
378
+		usort($users, function($a, $b) {
379 379
 			/**
380 380
 			 * @var IUser $a
381 381
 			 * @var IUser $b
@@ -499,7 +499,7 @@  discard block
 block discarded – undo
499 499
 	public function countUsersOfGroups(array $groups) {
500 500
 		$users = [];
501 501
 		foreach ($groups as $group) {
502
-			$usersIds = array_map(function ($user) {
502
+			$usersIds = array_map(function($user) {
503 503
 				return $user->getUID();
504 504
 			}, $group->getUsers());
505 505
 			$users = array_merge($users, $usersIds);
@@ -562,7 +562,7 @@  discard block
 block discarded – undo
562 562
 		$result->closeCursor();
563 563
 
564 564
 		if ($count !== false) {
565
-			$count = (int)$count;
565
+			$count = (int) $count;
566 566
 		} else {
567 567
 			$count = 0;
568 568
 		}
@@ -579,7 +579,7 @@  discard block
 block discarded – undo
579 579
 	 */
580 580
 	public function countDisabledUsersOfGroups(array $groups): int {
581 581
 		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
582
-		$queryBuilder->select($queryBuilder->createFunction('COUNT(DISTINCT ' . $queryBuilder->getColumnName('uid') . ')'))
582
+		$queryBuilder->select($queryBuilder->createFunction('COUNT(DISTINCT '.$queryBuilder->getColumnName('uid').')'))
583 583
 			->from('preferences', 'p')
584 584
 			->innerJoin('p', 'group_user', 'g', $queryBuilder->expr()->eq('p.userid', 'g.uid'))
585 585
 			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
@@ -592,7 +592,7 @@  discard block
 block discarded – undo
592 592
 		$result->closeCursor();
593 593
 
594 594
 		if ($count !== false) {
595
-			$count = (int)$count;
595
+			$count = (int) $count;
596 596
 		} else {
597 597
 			$count = 0;
598 598
 		}
@@ -615,7 +615,7 @@  discard block
 block discarded – undo
615 615
 
616 616
 		$query = $queryBuilder->execute();
617 617
 
618
-		$result = (int)$query->fetchOne();
618
+		$result = (int) $query->fetchOne();
619 619
 		$query->closeCursor();
620 620
 
621 621
 		return $result;
@@ -696,11 +696,11 @@  discard block
 block discarded – undo
696 696
 		// looking for 'email' only (and not primary_mail) is intentional
697 697
 		$userIds = $this->config->getUsersForUserValueCaseInsensitive('settings', 'email', $email);
698 698
 
699
-		$users = array_map(function ($uid) {
699
+		$users = array_map(function($uid) {
700 700
 			return $this->get($uid);
701 701
 		}, $userIds);
702 702
 
703
-		return array_values(array_filter($users, function ($u) {
703
+		return array_values(array_filter($users, function($u) {
704 704
 			return ($u instanceof IUser);
705 705
 		}));
706 706
 	}
@@ -742,7 +742,7 @@  discard block
 block discarded – undo
742 742
 	}
743 743
 
744 744
 	private function verifyUid(string $uid, bool $checkDataDirectory = false): bool {
745
-		$appdata = 'appdata_' . $this->config->getSystemValueString('instanceid');
745
+		$appdata = 'appdata_'.$this->config->getSystemValueString('instanceid');
746 746
 
747 747
 		if (\in_array($uid, [
748 748
 			'.htaccess',
@@ -759,9 +759,9 @@  discard block
 block discarded – undo
759 759
 			return true;
760 760
 		}
761 761
 
762
-		$dataDirectory = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data');
762
+		$dataDirectory = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT.'/data');
763 763
 
764
-		return !file_exists(rtrim($dataDirectory, '/') . '/' . $uid);
764
+		return !file_exists(rtrim($dataDirectory, '/').'/'.$uid);
765 765
 	}
766 766
 
767 767
 	public function getDisplayNameCache(): DisplayNameCache {
Please login to merge, or discard this patch.