diff options
Diffstat (limited to 'lib/private/User/Manager.php')
-rw-r--r-- | lib/private/User/Manager.php | 841 |
1 files changed, 841 insertions, 0 deletions
diff --git a/lib/private/User/Manager.php b/lib/private/User/Manager.php new file mode 100644 index 00000000000..097fd9a0dc8 --- /dev/null +++ b/lib/private/User/Manager.php @@ -0,0 +1,841 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OC\User; + +use Doctrine\DBAL\Platforms\OraclePlatform; +use OC\Hooks\PublicEmitter; +use OC\Memcache\WithLocalCache; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\HintException; +use OCP\ICache; +use OCP\ICacheFactory; +use OCP\IConfig; +use OCP\IGroup; +use OCP\IUser; +use OCP\IUserBackend; +use OCP\IUserManager; +use OCP\L10N\IFactory; +use OCP\Server; +use OCP\Support\Subscription\IAssertion; +use OCP\User\Backend\ICheckPasswordBackend; +use OCP\User\Backend\ICountMappedUsersBackend; +use OCP\User\Backend\ICountUsersBackend; +use OCP\User\Backend\IGetRealUIDBackend; +use OCP\User\Backend\ILimitAwareCountUsersBackend; +use OCP\User\Backend\IProvideEnabledStateBackend; +use OCP\User\Backend\ISearchKnownUsersBackend; +use OCP\User\Events\BeforeUserCreatedEvent; +use OCP\User\Events\UserCreatedEvent; +use OCP\UserInterface; +use Psr\Log\LoggerInterface; + +/** + * Class Manager + * + * Hooks available in scope \OC\User: + * - preSetPassword(\OC\User\User $user, string $password, string $recoverPassword) + * - postSetPassword(\OC\User\User $user, string $password, string $recoverPassword) + * - preDelete(\OC\User\User $user) + * - postDelete(\OC\User\User $user) + * - preCreateUser(string $uid, string $password) + * - postCreateUser(\OC\User\User $user, string $password) + * - change(\OC\User\User $user) + * - assignedUserId(string $uid) + * - preUnassignedUserId(string $uid) + * - postUnassignedUserId(string $uid) + * + * @package OC\User + */ +class Manager extends PublicEmitter implements IUserManager { + /** + * @var UserInterface[] $backends + */ + private array $backends = []; + + /** + * @var array<string,\OC\User\User> $cachedUsers + */ + private array $cachedUsers = []; + + private ICache $cache; + + private DisplayNameCache $displayNameCache; + + public function __construct( + private IConfig $config, + ICacheFactory $cacheFactory, + private IEventDispatcher $eventDispatcher, + private LoggerInterface $logger, + ) { + $this->cache = new WithLocalCache($cacheFactory->createDistributed('user_backend_map')); + $this->listen('\OC\User', 'postDelete', function (IUser $user): void { + unset($this->cachedUsers[$user->getUID()]); + }); + $this->displayNameCache = new DisplayNameCache($cacheFactory, $this); + } + + /** + * Get the active backends + * @return UserInterface[] + */ + public function getBackends(): array { + return $this->backends; + } + + public function registerBackend(UserInterface $backend): void { + $this->backends[] = $backend; + } + + public function removeBackend(UserInterface $backend): void { + $this->cachedUsers = []; + if (($i = array_search($backend, $this->backends)) !== false) { + unset($this->backends[$i]); + } + } + + public function clearBackends(): void { + $this->cachedUsers = []; + $this->backends = []; + } + + /** + * get a user by user id + * + * @param string $uid + * @return \OC\User\User|null Either the user or null if the specified user does not exist + */ + public function get($uid) { + if (is_null($uid) || $uid === '' || $uid === false) { + return null; + } + if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends + return $this->cachedUsers[$uid]; + } + + if (strlen($uid) > IUser::MAX_USERID_LENGTH) { + return null; + } + + $cachedBackend = $this->cache->get(sha1($uid)); + if ($cachedBackend !== null && isset($this->backends[$cachedBackend])) { + // Cache has the info of the user backend already, so ask that one directly + $backend = $this->backends[$cachedBackend]; + if ($backend->userExists($uid)) { + return $this->getUserObject($uid, $backend); + } + } + + foreach ($this->backends as $i => $backend) { + if ($i === $cachedBackend) { + // Tried that one already + continue; + } + + if ($backend->userExists($uid)) { + // Hash $uid to ensure that only valid characters are used for the cache key + $this->cache->set(sha1($uid), $i, 300); + return $this->getUserObject($uid, $backend); + } + } + return null; + } + + public function getDisplayName(string $uid): ?string { + return $this->displayNameCache->getDisplayName($uid); + } + + /** + * get or construct the user object + * + * @param string $uid + * @param \OCP\UserInterface $backend + * @param bool $cacheUser If false the newly created user object will not be cached + * @return \OC\User\User + */ + public function getUserObject($uid, $backend, $cacheUser = true) { + if ($backend instanceof IGetRealUIDBackend) { + $uid = $backend->getRealUID($uid); + } + + if (isset($this->cachedUsers[$uid])) { + return $this->cachedUsers[$uid]; + } + + $user = new User($uid, $backend, $this->eventDispatcher, $this, $this->config); + if ($cacheUser) { + $this->cachedUsers[$uid] = $user; + } + return $user; + } + + /** + * check if a user exists + * + * @param string $uid + * @return bool + */ + public function userExists($uid) { + if (strlen($uid) > IUser::MAX_USERID_LENGTH) { + return false; + } + + $user = $this->get($uid); + return ($user !== null); + } + + /** + * Check if the password is valid for the user + * + * @param string $loginName + * @param string $password + * @return IUser|false the User object on success, false otherwise + */ + public function checkPassword($loginName, $password) { + $result = $this->checkPasswordNoLogging($loginName, $password); + + if ($result === false) { + $this->logger->warning('Login failed: \'' . $loginName . '\' (Remote IP: \'' . \OC::$server->getRequest()->getRemoteAddress() . '\')', ['app' => 'core']); + } + + return $result; + } + + /** + * Check if the password is valid for the user + * + * @internal + * @param string $loginName + * @param string $password + * @return IUser|false the User object on success, false otherwise + */ + public function checkPasswordNoLogging($loginName, $password) { + $loginName = str_replace("\0", '', $loginName); + $password = str_replace("\0", '', $password); + + $cachedBackend = $this->cache->get($loginName); + if ($cachedBackend !== null && isset($this->backends[$cachedBackend])) { + $backends = [$this->backends[$cachedBackend]]; + } else { + $backends = $this->backends; + } + foreach ($backends as $backend) { + if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) { + /** @var ICheckPasswordBackend $backend */ + $uid = $backend->checkPassword($loginName, $password); + if ($uid !== false) { + return $this->getUserObject($uid, $backend); + } + } + } + + // since http basic auth doesn't provide a standard way of handling non ascii password we allow password to be urlencoded + // we only do this decoding after using the plain password fails to maintain compatibility with any password that happens + // to contain urlencoded patterns by "accident". + $password = urldecode($password); + + foreach ($backends as $backend) { + if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) { + /** @var ICheckPasswordBackend|UserInterface $backend */ + $uid = $backend->checkPassword($loginName, $password); + if ($uid !== false) { + return $this->getUserObject($uid, $backend); + } + } + } + + return false; + } + + /** + * Search by user id + * + * @param string $pattern + * @param int $limit + * @param int $offset + * @return IUser[] + * @deprecated 27.0.0, use searchDisplayName instead + */ + public function search($pattern, $limit = null, $offset = null) { + $users = []; + foreach ($this->backends as $backend) { + $backendUsers = $backend->getUsers($pattern, $limit, $offset); + if (is_array($backendUsers)) { + foreach ($backendUsers as $uid) { + $users[$uid] = new LazyUser($uid, $this, null, $backend); + } + } + } + + uasort($users, function (IUser $a, IUser $b) { + return strcasecmp($a->getUID(), $b->getUID()); + }); + return $users; + } + + /** + * Search by displayName + * + * @param string $pattern + * @param int $limit + * @param int $offset + * @return IUser[] + */ + public function searchDisplayName($pattern, $limit = null, $offset = null) { + $users = []; + foreach ($this->backends as $backend) { + $backendUsers = $backend->getDisplayNames($pattern, $limit, $offset); + if (is_array($backendUsers)) { + foreach ($backendUsers as $uid => $displayName) { + $users[] = new LazyUser($uid, $this, $displayName, $backend); + } + } + } + + usort($users, function (IUser $a, IUser $b) { + return strcasecmp($a->getDisplayName(), $b->getDisplayName()); + }); + return $users; + } + + /** + * @return IUser[] + */ + public function getDisabledUsers(?int $limit = null, int $offset = 0, string $search = ''): array { + $users = $this->config->getUsersForUserValue('core', 'enabled', 'false'); + $users = array_combine( + $users, + array_map( + fn (string $uid): IUser => new LazyUser($uid, $this), + $users + ) + ); + if ($search !== '') { + $users = array_filter( + $users, + function (IUser $user) use ($search): bool { + try { + return mb_stripos($user->getUID(), $search) !== false + || mb_stripos($user->getDisplayName(), $search) !== false + || mb_stripos($user->getEMailAddress() ?? '', $search) !== false; + } catch (NoUserException $ex) { + $this->logger->error('Error while filtering disabled users', ['exception' => $ex, 'userUID' => $user->getUID()]); + return false; + } + }); + } + + $tempLimit = ($limit === null ? null : $limit + $offset); + foreach ($this->backends as $backend) { + if (($tempLimit !== null) && (count($users) >= $tempLimit)) { + break; + } + if ($backend instanceof IProvideEnabledStateBackend) { + $backendUsers = $backend->getDisabledUserList(($tempLimit === null ? null : $tempLimit - count($users)), 0, $search); + foreach ($backendUsers as $uid) { + $users[$uid] = new LazyUser($uid, $this, null, $backend); + } + } + } + + return array_slice($users, $offset, $limit); + } + + /** + * Search known users (from phonebook sync) by displayName + * + * @param string $searcher + * @param string $pattern + * @param int|null $limit + * @param int|null $offset + * @return IUser[] + */ + public function searchKnownUsersByDisplayName(string $searcher, string $pattern, ?int $limit = null, ?int $offset = null): array { + $users = []; + foreach ($this->backends as $backend) { + if ($backend instanceof ISearchKnownUsersBackend) { + $backendUsers = $backend->searchKnownUsersByDisplayName($searcher, $pattern, $limit, $offset); + } else { + // Better than nothing, but filtering after pagination can remove lots of results. + $backendUsers = $backend->getDisplayNames($pattern, $limit, $offset); + } + if (is_array($backendUsers)) { + foreach ($backendUsers as $uid => $displayName) { + $users[] = $this->getUserObject($uid, $backend); + } + } + } + + usort($users, function ($a, $b) { + /** + * @var IUser $a + * @var IUser $b + */ + return strcasecmp($a->getDisplayName(), $b->getDisplayName()); + }); + return $users; + } + + /** + * @param string $uid + * @param string $password + * @return false|IUser the created user or false + * @throws \InvalidArgumentException + * @throws HintException + */ + public function createUser($uid, $password) { + // DI injection is not used here as IRegistry needs the user manager itself for user count and thus it would create a cyclic dependency + /** @var IAssertion $assertion */ + $assertion = \OC::$server->get(IAssertion::class); + $assertion->createUserIsLegit(); + + $localBackends = []; + foreach ($this->backends as $backend) { + if ($backend instanceof Database) { + // First check if there is another user backend + $localBackends[] = $backend; + continue; + } + + if ($backend->implementsActions(Backend::CREATE_USER)) { + return $this->createUserFromBackend($uid, $password, $backend); + } + } + + foreach ($localBackends as $backend) { + if ($backend->implementsActions(Backend::CREATE_USER)) { + return $this->createUserFromBackend($uid, $password, $backend); + } + } + + return false; + } + + /** + * @param string $uid + * @param string $password + * @param UserInterface $backend + * @return IUser|false + * @throws \InvalidArgumentException + */ + public function createUserFromBackend($uid, $password, UserInterface $backend) { + $l = \OCP\Util::getL10N('lib'); + + $this->validateUserId($uid, true); + + // No empty password + if (trim($password) === '') { + throw new \InvalidArgumentException($l->t('A valid password must be provided')); + } + + // Check if user already exists + if ($this->userExists($uid)) { + throw new \InvalidArgumentException($l->t('The Login is already being used')); + } + + /** @deprecated 21.0.0 use BeforeUserCreatedEvent event with the IEventDispatcher instead */ + $this->emit('\OC\User', 'preCreateUser', [$uid, $password]); + $this->eventDispatcher->dispatchTyped(new BeforeUserCreatedEvent($uid, $password)); + $state = $backend->createUser($uid, $password); + if ($state === false) { + throw new \InvalidArgumentException($l->t('Could not create account')); + } + $user = $this->getUserObject($uid, $backend); + if ($user instanceof IUser) { + /** @deprecated 21.0.0 use UserCreatedEvent event with the IEventDispatcher instead */ + $this->emit('\OC\User', 'postCreateUser', [$user, $password]); + $this->eventDispatcher->dispatchTyped(new UserCreatedEvent($user, $password)); + return $user; + } + return false; + } + + /** + * returns how many users per backend exist (if supported by backend) + * + * @param boolean $hasLoggedIn when true only users that have a lastLogin + * entry in the preferences table will be affected + * @return array<string, int> an array of backend class as key and count number as value + */ + public function countUsers() { + $userCountStatistics = []; + foreach ($this->backends as $backend) { + if ($backend instanceof ICountUsersBackend || $backend->implementsActions(Backend::COUNT_USERS)) { + /** @var ICountUsersBackend|IUserBackend $backend */ + $backendUsers = $backend->countUsers(); + if ($backendUsers !== false) { + if ($backend instanceof IUserBackend) { + $name = $backend->getBackendName(); + } else { + $name = get_class($backend); + } + if (isset($userCountStatistics[$name])) { + $userCountStatistics[$name] += $backendUsers; + } else { + $userCountStatistics[$name] = $backendUsers; + } + } + } + } + return $userCountStatistics; + } + + public function countUsersTotal(int $limit = 0, bool $onlyMappedUsers = false): int|false { + $userCount = false; + + foreach ($this->backends as $backend) { + if ($onlyMappedUsers && $backend instanceof ICountMappedUsersBackend) { + $backendUsers = $backend->countMappedUsers(); + } elseif ($backend instanceof ILimitAwareCountUsersBackend) { + $backendUsers = $backend->countUsers($limit); + } elseif ($backend instanceof ICountUsersBackend || $backend->implementsActions(Backend::COUNT_USERS)) { + /** @var ICountUsersBackend $backend */ + $backendUsers = $backend->countUsers(); + } else { + $this->logger->debug('Skip backend for user count: ' . get_class($backend)); + continue; + } + if ($backendUsers !== false) { + $userCount = (int)$userCount + $backendUsers; + if ($limit > 0) { + if ($userCount >= $limit) { + break; + } + $limit -= $userCount; + } + } else { + $this->logger->warning('Can not determine user count for ' . get_class($backend)); + } + } + return $userCount; + } + + /** + * returns how many users per backend exist in the requested groups (if supported by backend) + * + * @param IGroup[] $groups an array of groups to search in + * @param int $limit limit to stop counting + * @return array{int,int} total number of users, and number of disabled users in the given groups, below $limit. If limit is reached, -1 is returned for number of disabled users + */ + public function countUsersAndDisabledUsersOfGroups(array $groups, int $limit): array { + $users = []; + $disabled = []; + foreach ($groups as $group) { + foreach ($group->getUsers() as $user) { + $users[$user->getUID()] = 1; + if (!$user->isEnabled()) { + $disabled[$user->getUID()] = 1; + } + if (count($users) >= $limit) { + return [count($users),-1]; + } + } + } + return [count($users),count($disabled)]; + } + + /** + * The callback is executed for each user on each backend. + * If the callback returns false no further users will be retrieved. + * + * @psalm-param \Closure(\OCP\IUser):?bool $callback + * @param string $search + * @param boolean $onlySeen when true only users that have a lastLogin entry + * in the preferences table will be affected + * @since 9.0.0 + */ + public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = false) { + if ($onlySeen) { + $this->callForSeenUsers($callback); + } else { + foreach ($this->getBackends() as $backend) { + $limit = 500; + $offset = 0; + do { + $users = $backend->getUsers($search, $limit, $offset); + foreach ($users as $uid) { + if (!$backend->userExists($uid)) { + continue; + } + $user = $this->getUserObject($uid, $backend, false); + $return = $callback($user); + if ($return === false) { + break; + } + } + $offset += $limit; + } while (count($users) >= $limit); + } + } + } + + /** + * returns how many users are disabled + * + * @return int + * @since 12.0.0 + */ + public function countDisabledUsers(): int { + $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder(); + $queryBuilder->select($queryBuilder->func()->count('*')) + ->from('preferences') + ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core'))) + ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled'))) + ->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR)); + + + $result = $queryBuilder->execute(); + $count = $result->fetchOne(); + $result->closeCursor(); + + if ($count !== false) { + $count = (int)$count; + } else { + $count = 0; + } + + return $count; + } + + /** + * returns how many users have logged in once + * + * @return int + * @since 11.0.0 + */ + public function countSeenUsers() { + $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder(); + $queryBuilder->select($queryBuilder->func()->count('*')) + ->from('preferences') + ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('login'))) + ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('lastLogin'))); + + $query = $queryBuilder->execute(); + + $result = (int)$query->fetchOne(); + $query->closeCursor(); + + return $result; + } + + public function callForSeenUsers(\Closure $callback) { + $users = $this->getSeenUsers(); + foreach ($users as $user) { + $return = $callback($user); + if ($return === false) { + return; + } + } + } + + /** + * Getting all userIds that have a listLogin value requires checking the + * value in php because on oracle you cannot use a clob in a where clause, + * preventing us from doing a not null or length(value) > 0 check. + * + * @param int $limit + * @param int $offset + * @return string[] with user ids + */ + private function getSeenUserIds($limit = null, $offset = null) { + $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder(); + $queryBuilder->select(['userid']) + ->from('preferences') + ->where($queryBuilder->expr()->eq( + 'appid', $queryBuilder->createNamedParameter('login')) + ) + ->andWhere($queryBuilder->expr()->eq( + 'configkey', $queryBuilder->createNamedParameter('lastLogin')) + ) + ->andWhere($queryBuilder->expr()->isNotNull('configvalue') + ); + + if ($limit !== null) { + $queryBuilder->setMaxResults($limit); + } + if ($offset !== null) { + $queryBuilder->setFirstResult($offset); + } + $query = $queryBuilder->execute(); + $result = []; + + while ($row = $query->fetch()) { + $result[] = $row['userid']; + } + + $query->closeCursor(); + + return $result; + } + + /** + * @param string $email + * @return IUser[] + * @since 9.1.0 + */ + public function getByEmail($email) { + // looking for 'email' only (and not primary_mail) is intentional + $userIds = $this->config->getUsersForUserValueCaseInsensitive('settings', 'email', $email); + + $users = array_map(function ($uid) { + return $this->get($uid); + }, $userIds); + + return array_values(array_filter($users, function ($u) { + return ($u instanceof IUser); + })); + } + + /** + * @param string $uid + * @param bool $checkDataDirectory + * @throws \InvalidArgumentException Message is an already translated string with a reason why the id is not valid + * @since 26.0.0 + */ + public function validateUserId(string $uid, bool $checkDataDirectory = false): void { + $l = Server::get(IFactory::class)->get('lib'); + + // Check the ID for bad characters + // Allowed are: "a-z", "A-Z", "0-9", spaces and "_.@-'" + if (preg_match('/[^a-zA-Z0-9 _.@\-\']/', $uid)) { + throw new \InvalidArgumentException($l->t('Only the following characters are allowed in an Login:' + . ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"')); + } + + // No empty user ID + if (trim($uid) === '') { + throw new \InvalidArgumentException($l->t('A valid Login must be provided')); + } + + // No whitespace at the beginning or at the end + if (trim($uid) !== $uid) { + throw new \InvalidArgumentException($l->t('Login contains whitespace at the beginning or at the end')); + } + + // User ID only consists of 1 or 2 dots (directory traversal) + if ($uid === '.' || $uid === '..') { + throw new \InvalidArgumentException($l->t('Login must not consist of dots only')); + } + + // User ID is too long + if (strlen($uid) > IUser::MAX_USERID_LENGTH) { + // TRANSLATORS User ID is too long + throw new \InvalidArgumentException($l->t('Username is too long')); + } + + if (!$this->verifyUid($uid, $checkDataDirectory)) { + throw new \InvalidArgumentException($l->t('Login is invalid because files already exist for this user')); + } + } + + /** + * Gets the list of user ids sorted by lastLogin, from most recent to least recent + * + * @param int|null $limit how many users to fetch (default: 25, max: 100) + * @param int $offset from which offset to fetch + * @param string $search search users based on search params + * @return list<string> list of user IDs + */ + public function getLastLoggedInUsers(?int $limit = null, int $offset = 0, string $search = ''): array { + // We can't load all users who already logged in + $limit = min(100, $limit ?: 25); + + $connection = \OC::$server->getDatabaseConnection(); + $queryBuilder = $connection->getQueryBuilder(); + $queryBuilder->select('pref_login.userid') + ->from('preferences', 'pref_login') + ->where($queryBuilder->expr()->eq('pref_login.appid', $queryBuilder->expr()->literal('login'))) + ->andWhere($queryBuilder->expr()->eq('pref_login.configkey', $queryBuilder->expr()->literal('lastLogin'))) + ->setFirstResult($offset) + ->setMaxResults($limit) + ; + + // Oracle don't want to run ORDER BY on CLOB column + $loginOrder = $connection->getDatabasePlatform() instanceof OraclePlatform + ? $queryBuilder->expr()->castColumn('pref_login.configvalue', IQueryBuilder::PARAM_INT) + : 'pref_login.configvalue'; + $queryBuilder + ->orderBy($loginOrder, 'DESC') + ->addOrderBy($queryBuilder->func()->lower('pref_login.userid'), 'ASC'); + + if ($search !== '') { + $displayNameMatches = $this->searchDisplayName($search); + $matchedUids = array_map(static fn (IUser $u): string => $u->getUID(), $displayNameMatches); + + $queryBuilder + ->leftJoin('pref_login', 'preferences', 'pref_email', $queryBuilder->expr()->andX( + $queryBuilder->expr()->eq('pref_login.userid', 'pref_email.userid'), + $queryBuilder->expr()->eq('pref_email.appid', $queryBuilder->expr()->literal('settings')), + $queryBuilder->expr()->eq('pref_email.configkey', $queryBuilder->expr()->literal('email')), + )) + ->andWhere($queryBuilder->expr()->orX( + $queryBuilder->expr()->in('pref_login.userid', $queryBuilder->createNamedParameter($matchedUids, IQueryBuilder::PARAM_STR_ARRAY)), + )); + } + + /** @var list<string> */ + $list = $queryBuilder->executeQuery()->fetchAll(\PDO::FETCH_COLUMN); + + return $list; + } + + private function verifyUid(string $uid, bool $checkDataDirectory = false): bool { + $appdata = 'appdata_' . $this->config->getSystemValueString('instanceid'); + + if (\in_array($uid, [ + '.htaccess', + 'files_external', + '__groupfolders', + '.ncdata', + 'owncloud.log', + 'nextcloud.log', + 'updater.log', + 'audit.log', + $appdata], true)) { + return false; + } + + if (!$checkDataDirectory) { + return true; + } + + $dataDirectory = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data'); + + return !file_exists(rtrim($dataDirectory, '/') . '/' . $uid); + } + + public function getDisplayNameCache(): DisplayNameCache { + return $this->displayNameCache; + } + + /** + * Gets the list of users sorted by lastLogin, from most recent to least recent + * + * @param int $offset from which offset to fetch + * @return \Iterator<IUser> list of user IDs + * @since 30.0.0 + */ + public function getSeenUsers(int $offset = 0): \Iterator { + $limit = 1000; + + do { + $userIds = $this->getSeenUserIds($limit, $offset); + $offset += $limit; + + foreach ($userIds as $userId) { + foreach ($this->backends as $backend) { + if ($backend->userExists($userId)) { + $user = $this->getUserObject($userId, $backend, false); + yield $user; + break; + } + } + } + } while (count($userIds) === $limit); + } +} |