diff options
Diffstat (limited to 'lib/private/Group/Database.php')
-rw-r--r-- | lib/private/Group/Database.php | 279 |
1 files changed, 177 insertions, 102 deletions
diff --git a/lib/private/Group/Database.php b/lib/private/Group/Database.php index c49f3bce596..0cb571a3935 100644 --- a/lib/private/Group/Database.php +++ b/lib/private/Group/Database.php @@ -1,89 +1,58 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> - * @author Loki3000 <github@labcms.ru> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * - */ -/* - * - * The following SQL statement is just a help for developers and will not be - * executed! - * - * CREATE TABLE `groups` ( - * `gid` varchar(64) COLLATE utf8_unicode_ci NOT NULL, - * PRIMARY KEY (`gid`) - * ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; - * - * CREATE TABLE `group_user` ( - * `gid` varchar(64) COLLATE utf8_unicode_ci NOT NULL, - * `uid` varchar(64) COLLATE utf8_unicode_ci NOT NULL - * ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ - namespace OC\Group; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; +use OC\User\LazyUser; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Group\Backend\ABackend; use OCP\Group\Backend\IAddToGroupBackend; +use OCP\Group\Backend\IBatchMethodsBackend; use OCP\Group\Backend\ICountDisabledInGroup; use OCP\Group\Backend\ICountUsersBackend; -use OCP\Group\Backend\ICreateGroupBackend; +use OCP\Group\Backend\ICreateNamedGroupBackend; use OCP\Group\Backend\IDeleteGroupBackend; use OCP\Group\Backend\IGetDisplayNameBackend; use OCP\Group\Backend\IGroupDetailsBackend; +use OCP\Group\Backend\INamedBackend; use OCP\Group\Backend\IRemoveFromGroupBackend; +use OCP\Group\Backend\ISearchableGroupBackend; use OCP\Group\Backend\ISetDisplayNameBackend; use OCP\IDBConnection; +use OCP\IUserManager; /** * Class for group management in a SQL Database (e.g. MySQL, SQLite) */ class Database extends ABackend implements IAddToGroupBackend, - ICountDisabledInGroup, - ICountUsersBackend, - ICreateGroupBackend, - IDeleteGroupBackend, - IGetDisplayNameBackend, - IGroupDetailsBackend, - IRemoveFromGroupBackend, - ISetDisplayNameBackend { - - /** @var string[] */ + ICountDisabledInGroup, + ICountUsersBackend, + ICreateNamedGroupBackend, + IDeleteGroupBackend, + IGetDisplayNameBackend, + IGroupDetailsBackend, + IRemoveFromGroupBackend, + ISetDisplayNameBackend, + ISearchableGroupBackend, + IBatchMethodsBackend, + INamedBackend { + /** @var array<string, array{gid: string, displayname: string}> */ private $groupCache = []; - /** @var IDBConnection */ - private $dbConn; - /** * \OC\Group\Database constructor. * * @param IDBConnection|null $dbConn */ - public function __construct(IDBConnection $dbConn = null) { - $this->dbConn = $dbConn; + public function __construct( + private ?IDBConnection $dbConn = null, + ) { } /** @@ -95,35 +64,28 @@ class Database extends ABackend implements } } - /** - * Try to create a new group - * @param string $gid The name of the group to create - * @return bool - * - * Tries to create a new group. If the group name already exists, false will - * be returned. - */ - public function createGroup(string $gid): bool { + public function createGroup(string $name): ?string { $this->fixDI(); + $gid = $this->computeGid($name); try { // Add group $builder = $this->dbConn->getQueryBuilder(); $result = $builder->insert('groups') ->setValue('gid', $builder->createNamedParameter($gid)) - ->setValue('displayname', $builder->createNamedParameter($gid)) + ->setValue('displayname', $builder->createNamedParameter($name)) ->execute(); } catch (UniqueConstraintViolationException $e) { - $result = 0; + return null; } // Add to cache $this->groupCache[$gid] = [ 'gid' => $gid, - 'displayname' => $gid + 'displayname' => $name ]; - return $result === 1; + return $gid; } /** @@ -140,19 +102,19 @@ class Database extends ABackend implements $qb = $this->dbConn->getQueryBuilder(); $qb->delete('groups') ->where($qb->expr()->eq('gid', $qb->createNamedParameter($gid))) - ->execute(); + ->executeStatement(); // Delete the group-user relation $qb = $this->dbConn->getQueryBuilder(); $qb->delete('group_user') ->where($qb->expr()->eq('gid', $qb->createNamedParameter($gid))) - ->execute(); + ->executeStatement(); // Delete the group-groupadmin relation $qb = $this->dbConn->getQueryBuilder(); $qb->delete('group_admin') ->where($qb->expr()->eq('gid', $qb->createNamedParameter($gid))) - ->execute(); + ->executeStatement(); // Delete from cache unset($this->groupCache[$gid]); @@ -177,7 +139,7 @@ class Database extends ABackend implements ->from('group_user') ->where($qb->expr()->eq('gid', $qb->createNamedParameter($gid))) ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($uid))) - ->execute(); + ->executeQuery(); $result = $cursor->fetch(); $cursor->closeCursor(); @@ -202,7 +164,7 @@ class Database extends ABackend implements $qb->insert('group_user') ->setValue('uid', $qb->createNamedParameter($uid)) ->setValue('gid', $qb->createNamedParameter($gid)) - ->execute(); + ->executeStatement(); return true; } else { return false; @@ -224,7 +186,7 @@ class Database extends ABackend implements $qb->delete('group_user') ->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid))) ->andWhere($qb->expr()->eq('gid', $qb->createNamedParameter($gid))) - ->execute(); + ->executeStatement(); return true; } @@ -232,7 +194,7 @@ class Database extends ABackend implements /** * Get all groups a user belongs to * @param string $uid Name of the user - * @return array an array of group names + * @return list<string> an array of group names * * This function fetches all groups a user belongs to. It does not check * if the user exists at all. @@ -251,7 +213,7 @@ class Database extends ABackend implements ->from('group_user', 'gu') ->leftJoin('gu', 'groups', 'g', $qb->expr()->eq('gu.gid', 'g.gid')) ->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid))) - ->execute(); + ->executeQuery(); $groups = []; while ($row = $cursor->fetch()) { @@ -275,11 +237,11 @@ class Database extends ABackend implements * * Returns a list with all groups */ - public function getGroups($search = '', $limit = null, $offset = null) { + public function getGroups(string $search = '', int $limit = -1, int $offset = 0) { $this->fixDI(); $query = $this->dbConn->getQueryBuilder(); - $query->select('gid') + $query->select('gid', 'displayname') ->from('groups') ->orderBy('gid', 'ASC'); @@ -292,12 +254,20 @@ class Database extends ABackend implements ))); } - $query->setMaxResults($limit) - ->setFirstResult($offset); - $result = $query->execute(); + if ($limit > 0) { + $query->setMaxResults($limit); + } + if ($offset > 0) { + $query->setFirstResult($offset); + } + $result = $query->executeQuery(); $groups = []; while ($row = $result->fetch()) { + $this->groupCache[$row['gid']] = [ + 'displayname' => $row['displayname'], + 'gid' => $row['gid'], + ]; $groups[] = $row['gid']; } $result->closeCursor(); @@ -322,7 +292,7 @@ class Database extends ABackend implements $cursor = $qb->select('gid', 'displayname') ->from('groups') ->where($qb->expr()->eq('gid', $qb->createNamedParameter($gid))) - ->execute(); + ->executeQuery(); $result = $cursor->fetch(); $cursor->closeCursor(); @@ -337,29 +307,72 @@ class Database extends ABackend implements } /** - * get a list of all users in a group + * {@inheritdoc} + */ + public function groupsExists(array $gids): array { + $notFoundGids = []; + $existingGroups = []; + + // In case the data is already locally accessible, not need to do SQL query + // or do a SQL query but with a smaller in clause + foreach ($gids as $gid) { + if (isset($this->groupCache[$gid])) { + $existingGroups[] = $gid; + } else { + $notFoundGids[] = $gid; + } + } + + $qb = $this->dbConn->getQueryBuilder(); + $qb->select('gid', 'displayname') + ->from('groups') + ->where($qb->expr()->in('gid', $qb->createParameter('ids'))); + foreach (array_chunk($notFoundGids, 1000) as $chunk) { + $qb->setParameter('ids', $chunk, IQueryBuilder::PARAM_STR_ARRAY); + $result = $qb->executeQuery(); + while ($row = $result->fetch()) { + $this->groupCache[(string)$row['gid']] = [ + 'displayname' => (string)$row['displayname'], + 'gid' => (string)$row['gid'], + ]; + $existingGroups[] = (string)$row['gid']; + } + $result->closeCursor(); + } + + return $existingGroups; + } + + /** + * Get a list of all users in a group * @param string $gid * @param string $search * @param int $limit * @param int $offset - * @return array an array of user ids + * @return array<int,string> an array of user ids */ - public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) { + public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0): array { + return array_values(array_map(fn ($user) => $user->getUid(), $this->searchInGroup($gid, $search, $limit, $offset))); + } + + public function searchInGroup(string $gid, string $search = '', int $limit = -1, int $offset = 0): array { $this->fixDI(); $query = $this->dbConn->getQueryBuilder(); - $query->select('g.uid') - ->from('group_user', 'g') + $query->select('g.uid', 'u.displayname'); + + $query->from('group_user', 'g') ->where($query->expr()->eq('gid', $query->createNamedParameter($gid))) ->orderBy('g.uid', 'ASC'); + $query->leftJoin('g', 'users', 'u', $query->expr()->eq('g.uid', 'u.uid')); + if ($search !== '') { - $query->leftJoin('g', 'users', 'u', $query->expr()->eq('g.uid', 'u.uid')) - ->leftJoin('u', 'preferences', 'p', $query->expr()->andX( - $query->expr()->eq('p.userid', 'u.uid'), - $query->expr()->eq('p.appid', $query->expr()->literal('settings')), - $query->expr()->eq('p.configkey', $query->expr()->literal('email'))) - ) + $query->leftJoin('u', 'preferences', 'p', $query->expr()->andX( + $query->expr()->eq('p.userid', 'u.uid'), + $query->expr()->eq('p.appid', $query->expr()->literal('settings')), + $query->expr()->eq('p.configkey', $query->expr()->literal('email')) + )) // sqlite doesn't like re-using a single named parameter here ->andWhere( $query->expr()->orX( @@ -378,11 +391,12 @@ class Database extends ABackend implements $query->setFirstResult($offset); } - $result = $query->execute(); + $result = $query->executeQuery(); $users = []; + $userManager = \OCP\Server::get(IUserManager::class); while ($row = $result->fetch()) { - $users[] = $row['uid']; + $users[$row['uid']] = new LazyUser($row['uid'], $userManager, $row['displayname'] ?? null); } $result->closeCursor(); @@ -409,7 +423,7 @@ class Database extends ABackend implements ))); } - $result = $query->execute(); + $result = $query->executeQuery(); $count = $result->fetchOne(); $result->closeCursor(); @@ -441,7 +455,7 @@ class Database extends ABackend implements ->andWhere($query->expr()->eq('configvalue', $query->createNamedParameter('false'), IQueryBuilder::PARAM_STR)) ->andWhere($query->expr()->eq('gid', $query->createNamedParameter($gid), IQueryBuilder::PARAM_STR)); - $result = $query->execute(); + $result = $query->executeQuery(); $count = $result->fetchOne(); $result->closeCursor(); @@ -456,7 +470,11 @@ class Database extends ABackend implements public function getDisplayName(string $gid): string { if (isset($this->groupCache[$gid])) { - return $this->groupCache[$gid]['displayname']; + $displayName = $this->groupCache[$gid]['displayname']; + + if (isset($displayName) && trim($displayName) !== '') { + return $displayName; + } } $this->fixDI(); @@ -466,11 +484,11 @@ class Database extends ABackend implements ->from('groups') ->where($query->expr()->eq('gid', $query->createNamedParameter($gid))); - $result = $query->execute(); + $result = $query->executeQuery(); $displayName = $result->fetchOne(); $result->closeCursor(); - return (string) $displayName; + return (string)$displayName; } public function getGroupDetails(string $gid): array { @@ -482,6 +500,45 @@ class Database extends ABackend implements return []; } + /** + * {@inheritdoc} + */ + public function getGroupsDetails(array $gids): array { + $notFoundGids = []; + $details = []; + + $this->fixDI(); + + // In case the data is already locally accessible, not need to do SQL query + // or do a SQL query but with a smaller in clause + foreach ($gids as $gid) { + if (isset($this->groupCache[$gid])) { + $details[$gid] = ['displayName' => $this->groupCache[$gid]['displayname']]; + } else { + $notFoundGids[] = $gid; + } + } + + foreach (array_chunk($notFoundGids, 1000) as $chunk) { + $query = $this->dbConn->getQueryBuilder(); + $query->select('gid', 'displayname') + ->from('groups') + ->where($query->expr()->in('gid', $query->createNamedParameter($chunk, IQueryBuilder::PARAM_STR_ARRAY))); + + $result = $query->executeQuery(); + while ($row = $result->fetch()) { + $details[(string)$row['gid']] = ['displayName' => (string)$row['displayname']]; + $this->groupCache[(string)$row['gid']] = [ + 'displayname' => (string)$row['displayname'], + 'gid' => (string)$row['gid'], + ]; + } + $result->closeCursor(); + } + + return $details; + } + public function setDisplayName(string $gid, string $displayName): bool { if (!$this->groupExists($gid)) { return false; @@ -498,8 +555,26 @@ class Database extends ABackend implements $query->update('groups') ->set('displayname', $query->createNamedParameter($displayName)) ->where($query->expr()->eq('gid', $query->createNamedParameter($gid))); - $query->execute(); + $query->executeStatement(); return true; } + + /** + * Backend name to be shown in group management + * @return string the name of the backend to be shown + * @since 21.0.0 + */ + public function getBackendName(): string { + return 'Database'; + } + + /** + * Compute group ID from display name (GIDs are limited to 64 characters in database) + */ + private function computeGid(string $displayName): string { + return mb_strlen($displayName) > 64 + ? hash('sha256', $displayName) + : $displayName; + } } |