diff --git a/apps/files_sharing/lib/Controller/ShareesController.php b/apps/files_sharing/lib/Controller/ShareesController.php index 1a58ce204ad9..7c585e9ce05f 100644 --- a/apps/files_sharing/lib/Controller/ShareesController.php +++ b/apps/files_sharing/lib/Controller/ShareesController.php @@ -3,8 +3,8 @@ * @author Björn Schießle * @author Joas Schilling * @author Roeland Jago Douma - * @author Roeland Jago Douma * @author Thomas Müller + * @author Tom Needham * @author Vincent Petry * * @copyright Copyright (c) 2017, ownCloud GmbH @@ -144,17 +144,17 @@ protected function getUsers($search) { // Search in all the groups this user is part of $userGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser()); foreach ($userGroups as $userGroup) { - $usersTmp = $this->groupManager->displayNamesInGroup($userGroup, $search, $this->limit, $this->offset); - foreach ($usersTmp as $uid => $userDisplayName) { - $users[$uid] = $userDisplayName; + $usersTmp = $this->groupManager->findUsersInGroup($userGroup, $search, $this->limit, $this->offset); + foreach ($usersTmp as $uid => $user) { + $users[$uid] = $user; } } } else { // Search in all users - $usersTmp = $this->userManager->searchDisplayName($search, $this->limit, $this->offset); + $usersTmp = $this->userManager->find($search, $this->limit, $this->offset); foreach ($usersTmp as $user) { - $users[$user->getUID()] = $user->getDisplayName(); + $users[$user->getUID()] = $user; } } @@ -164,13 +164,14 @@ protected function getUsers($search) { $foundUserById = false; $lowerSearch = strtolower($search); - foreach ($users as $uid => $userDisplayName) { - if (strtolower($uid) === $lowerSearch || strtolower($userDisplayName) === $lowerSearch) { + foreach ($users as $uid => $user) { + /* @var $user IUser */ + if (strtolower($uid) === $lowerSearch || strtolower($user->getDisplayName()) === $lowerSearch || strtolower($user->getEMailAddress()) === $lowerSearch) { if (strtolower($uid) === $lowerSearch) { $foundUserById = true; } $this->result['exact']['users'][] = [ - 'label' => $userDisplayName, + 'label' => $user->getDisplayName(), 'value' => [ 'shareType' => Share::SHARE_TYPE_USER, 'shareWith' => $uid, @@ -178,7 +179,7 @@ protected function getUsers($search) { ]; } else { $this->result['users'][] = [ - 'label' => $userDisplayName, + 'label' => $user->getDisplayName(), 'value' => [ 'shareType' => Share::SHARE_TYPE_USER, 'shareWith' => $uid, diff --git a/apps/files_sharing/tests/API/ShareesTest.php b/apps/files_sharing/tests/API/ShareesTest.php index 450b9b871b20..673f69d25016 100644 --- a/apps/files_sharing/tests/API/ShareesTest.php +++ b/apps/files_sharing/tests/API/ShareesTest.php @@ -3,8 +3,8 @@ * @author Björn Schießle * @author Joas Schilling * @author Roeland Jago Douma - * @author Roeland Jago Douma * @author Thomas Müller + * @author Tom Needham * @author Vincent Petry * * @copyright Copyright (c) 2017, ownCloud GmbH @@ -304,7 +304,7 @@ public function dataGetUsers() { true, ['abc', 'xyz'], [ - ['abc', 'test', 2, 0, ['test1' => 'Test One']], + ['abc', 'test', 2, 0, ['test1' => $this->getUserMock('test1', 'Test One')]], ['xyz', 'test', 2, 0, []], ], [], @@ -320,7 +320,7 @@ public function dataGetUsers() { false, ['abc', 'xyz'], [ - ['abc', 'test', 2, 0, ['test1' => 'Test One']], + ['abc', 'test', 2, 0, ['test1' => $this->getUserMock('test1', 'Test One')]], ['xyz', 'test', 2, 0, []], ], [], @@ -335,12 +335,12 @@ public function dataGetUsers() { ['abc', 'xyz'], [ ['abc', 'test', 2, 0, [ - 'test1' => 'Test One', - 'test2' => 'Test Two', + 'test1' => $this->getUserMock('test1', 'Test One'), + 'test2' => $this->getUserMock('test2', 'Test Two'), ]], ['xyz', 'test', 2, 0, [ - 'test1' => 'Test One', - 'test2' => 'Test Two', + 'test1' => $this->getUserMock('test1', 'Test One'), + 'test2' => $this->getUserMock('test2', 'Test Two'), ]], ], [], @@ -358,12 +358,12 @@ public function dataGetUsers() { ['abc', 'xyz'], [ ['abc', 'test', 2, 0, [ - 'test1' => 'Test One', - 'test2' => 'Test Two', + 'test1' => $this->getUserMock('test1', 'Test One'), + 'test2' => $this->getUserMock('test2', 'Test Two'), ]], ['xyz', 'test', 2, 0, [ - 'test1' => 'Test One', - 'test2' => 'Test Two', + 'test1' => $this->getUserMock('test1', 'Test One'), + 'test2' => $this->getUserMock('test2', 'Test Two'), ]], ], [], @@ -378,10 +378,10 @@ public function dataGetUsers() { ['abc', 'xyz'], [ ['abc', 'test', 2, 0, [ - 'test' => 'Test One', + 'test' => $this->getUserMock('test1', 'Test One'), ]], ['xyz', 'test', 2, 0, [ - 'test2' => 'Test Two', + 'test2' => $this->getUserMock('test2', 'Test Two'), ]], ], [ @@ -400,10 +400,10 @@ public function dataGetUsers() { ['abc', 'xyz'], [ ['abc', 'test', 2, 0, [ - 'test' => 'Test One', + 'test' => $this->getUserMock('test1', 'Test One'), ]], ['xyz', 'test', 2, 0, [ - 'test2' => 'Test Two', + 'test2' => $this->getUserMock('test2', 'Test Two'), ]], ], [ @@ -424,7 +424,7 @@ public function dataGetUsers() { // args and user response for "displayNamesInGroup" call [ ['group1', 'ano', 2, 0, [ - 'another1' => 'Another One', + 'another1' => $this->getUserMock('another1', 'Another One'), ]], ['group2', 'ano', 2, 0, [ ]], @@ -504,7 +504,7 @@ public function testGetUsers( if (!$shareWithGroupOnly && !$shareeEnumerationGroupMembers) { $this->userManager->expects($this->once()) - ->method('searchDisplayName') + ->method('find') ->with($searchTerm, $this->invokePrivate($this->sharees, 'limit'), $this->invokePrivate($this->sharees, 'offset')) ->willReturn($userResponse); } else { @@ -527,7 +527,7 @@ public function testGetUsers( } $this->groupManager->expects($this->exactly(sizeof($groupResponse))) - ->method('displayNamesInGroup') + ->method('findUsersInGroup') ->with($this->anything(), $searchTerm, $this->invokePrivate($this->sharees, 'limit'), $this->invokePrivate($this->sharees, 'offset')) ->willReturnMap($userResponse); } diff --git a/config/config.sample.php b/config/config.sample.php index 5f82fee0e407..00f307e46d4b 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -256,6 +256,13 @@ */ 'lost_password_link' => 'https://example.org/link/to/password/reset', +/** + * Allow medial search on account properties like display name, user id, email, + * and other search terms. Allows finding 'Alice' when searching for 'lic'. + * May slow down user search. + */ +'accounts.enable_medial_search' => false, + /** * Mail Parameters * diff --git a/core/Migrations/Version20170221114437.php b/core/Migrations/Version20170221114437.php index 606fba757994..036993e9ed18 100644 --- a/core/Migrations/Version20170221114437.php +++ b/core/Migrations/Version20170221114437.php @@ -3,9 +3,9 @@ use OC\User\Account; use OC\User\AccountMapper; +use OC\User\AccountTermMapper; use OC\User\Database; use OC\User\SyncService; -use OCP\IConfig; use OCP\Migration\ISimpleMigration; use OCP\Migration\IOutput; @@ -16,9 +16,10 @@ class Version20170221114437 implements ISimpleMigration { */ public function run(IOutput $out) { $backend = new Database(); - $accountMapper = new AccountMapper(\OC::$server->getDatabaseConnection()); $config = \OC::$server->getConfig(); $logger = \OC::$server->getLogger(); + $connection = \OC::$server->getDatabaseConnection(); + $accountMapper = new AccountMapper($connection, new AccountTermMapper($connection)); $syncService = new SyncService($accountMapper, $backend, $config, $logger); // insert/update known users diff --git a/core/Migrations/Version20170516100103.php b/core/Migrations/Version20170516100103.php new file mode 100644 index 000000000000..787c76465a4f --- /dev/null +++ b/core/Migrations/Version20170516100103.php @@ -0,0 +1,75 @@ + + * @author Tom Needham + * + * @copyright Copyright (c) 2017, ownCloud GmbH + * @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 + * + */ + +namespace OC\Migrations; + +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Types\Type; +use OCP\Migration\ISchemaMigration; + +/** + * Add account_terms table for account searching + */ +class Version20170516100103 implements ISchemaMigration { + + public function changeSchema(Schema $schema, array $options) { + $prefix = $options['tablePrefix']; + if (!$schema->hasTable("${prefix}account_terms")) { + $table = $schema->createTable("${prefix}account_terms"); + + $table->addColumn('id', Type::BIGINT, [ + 'autoincrement' => true, + 'unsigned' => true, + 'notnull' => true, + ]); + + // Foreign key to oc_accounts.id column + $table->addColumn('account_id', Type::BIGINT, [ + 'notnull' => true, + 'unsigned' => true, + ]); + + $table->addColumn('term', Type::STRING, [ + 'notnull' => true, + 'length' => 256 + ]); + + $table->setPrimaryKey(['id']); + $table->addIndex(['account_id'], 'account_id_index'); + $table->addIndex(['term'], 'term_index'); + + } + + if ($schema->hasTable("${prefix}accounts")) { + $table = $schema->getTable("${prefix}accounts"); + if (!$table->hasIndex('lower_user_id_index')) { + $table->addUniqueIndex(['lower_user_id'], 'lower_user_id_index'); + } + if (!$table->hasIndex('display_name_index')) { + $table->addIndex(['display_name'], 'display_name_index'); + } + if (!$table->hasIndex('email_index')) { + $table->addIndex(['email'], 'email_index'); + } + } + } +} diff --git a/core/register_command.php b/core/register_command.php index 80c4ba73c5ed..590560aec914 100644 --- a/core/register_command.php +++ b/core/register_command.php @@ -143,7 +143,7 @@ $application->add(new OC\Core\Command\User\Report(\OC::$server->getUserManager())); $application->add(new OC\Core\Command\User\ResetPassword(\OC::$server->getUserManager())); $application->add(new OC\Core\Command\User\Setting(\OC::$server->getUserManager(), \OC::$server->getConfig(), \OC::$server->getDatabaseConnection())); - $application->add(new OC\Core\Command\User\SyncBackend(new \OC\User\AccountMapper(\OC::$server->getDatabaseConnection()), \OC::$server->getConfig(), \OC::$server->getUserManager(), \OC::$server->getLogger())); + $application->add(new OC\Core\Command\User\SyncBackend(\OC::$server->getAccountMapper(), \OC::$server->getConfig(), \OC::$server->getUserManager(), \OC::$server->getLogger())); $application->add(new OC\Core\Command\Security\ListCertificates(\OC::$server->getCertificateManager(null), \OC::$server->getL10N('core'))); $application->add(new OC\Core\Command\Security\ImportCertificate(\OC::$server->getCertificateManager(null))); diff --git a/lib/private/Group/Manager.php b/lib/private/Group/Manager.php index 55ecbe845906..4b90edb58ce2 100644 --- a/lib/private/Group/Manager.php +++ b/lib/private/Group/Manager.php @@ -349,6 +349,58 @@ public function getUserGroupIds($user, $scope = null) { }, array_keys($this->getUserGroups($user, $scope))); } + /** + * Finds users in a group + * @param string $gid + * @param string $search + * @param int $limit + * @param int $offset + * @return \OC\User\User[] + */ + public function findUsersInGroup($gid, $search = '', $limit = -1, $offset = 0) { + $group = $this->get($gid); + if(is_null($group)) { + return []; + } + + $search = trim($search); + $groupUsers = []; + + if(!empty($search)) { + // only user backends have the capability to do a complex search for users + $searchOffset = 0; + $searchLimit = $limit * 100; + if($limit === -1) { + $searchLimit = 500; + } + + do { + $filteredUsers = $this->userManager->find($search, $searchLimit, $searchOffset); + foreach($filteredUsers as $filteredUser) { + if($group->inGroup($filteredUser)) { + $groupUsers[]= $filteredUser; + } + } + $searchOffset += $searchLimit; + } while(count($groupUsers) < $searchLimit+$offset && count($filteredUsers) >= $searchLimit); + + if($limit === -1) { + $groupUsers = array_slice($groupUsers, $offset); + } else { + $groupUsers = array_slice($groupUsers, $offset, $limit); + } + } else { + $groupUsers = $group->searchUsers('', $limit, $offset); + } + + $matchingUsers = []; + foreach($groupUsers as $groupUser) { + $matchingUsers[$groupUser->getUID()] = $groupUser; + } + + return $matchingUsers; + } + /** * get a list of all display names in a group * @param string $gid diff --git a/lib/private/Server.php b/lib/private/Server.php index a893f128924e..83b2d73c875a 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -86,6 +86,7 @@ use OC\Tagging\TagMapper; use OC\Theme\ThemeService; use OC\User\AccountMapper; +use OC\User\AccountTermMapper; use OCP\IL10N; use OCP\IServerContainer; use OCP\ISession; @@ -220,11 +221,13 @@ public function __construct($webRoot, \OC\Config $config) { return $c->getRootFolder(); }); }); + $this->registerService('AccountMapper', function(Server $c) { + return new AccountMapper($c->getDatabaseConnection(), new AccountTermMapper($c->getDatabaseConnection())); + }); $this->registerService('UserManager', function (Server $c) { $config = $c->getConfig(); $logger = $c->getLogger(); - $accountMapper = new AccountMapper($c->getDatabaseConnection()); - return new \OC\User\Manager($config, $logger, $accountMapper); + return new \OC\User\Manager($config, $logger, $c->getAccountMapper()); }); $this->registerService('GroupManager', function (Server $c) { $groupManager = new \OC\Group\Manager($this->getUserManager()); @@ -953,6 +956,13 @@ public function getUserManager() { return $this->query('UserManager'); } + /** + * @return \OC\User\AccountMapper + */ + public function getAccountMapper() { + return $this->query('AccountMapper'); + } + /** * @return \OC\Group\Manager */ diff --git a/lib/private/User/Account.php b/lib/private/User/Account.php index 4b927573b058..013233f8f58f 100644 --- a/lib/private/User/Account.php +++ b/lib/private/User/Account.php @@ -22,9 +22,7 @@ namespace OC\User; - use OCP\AppFramework\Db\Entity; -use OCP\AppFramework\QueryException; use OCP\UserInterface; /** @@ -65,6 +63,9 @@ class Account extends Entity { protected $state; protected $home; + private $terms = []; + private $_termsChanged = false; + public function __construct() { $this->addType('state', 'integer'); $this->addType('lastLogin', 'integer'); @@ -86,4 +87,32 @@ public function getBackendInstance() { // actually stupid return \OC::$server->getUserManager()->getBackend($backendClass); } + + public function getUpdatedFields() { + $fields = parent::getUpdatedFields(); + unset($fields['terms']); + return $fields; + } + + public function haveTermsChanged() { + return $this->_termsChanged; + } + + /** + * @param string[] $terms + */ + public function setSearchTerms(array $terms) { + if(array_diff($terms, $this->terms)) { + $this->terms = $terms; + $this->_termsChanged = true; + } + } + + /** + * @return string[] + */ + public function getSearchTerms() { + return $this->terms; + } + } diff --git a/lib/private/User/AccountMapper.php b/lib/private/User/AccountMapper.php index 27a5a2acb76e..d16a5af255e3 100644 --- a/lib/private/User/AccountMapper.php +++ b/lib/private/User/AccountMapper.php @@ -1,6 +1,8 @@ * @author Thomas Müller + * @author Tom Needham * * @copyright Copyright (c) 2017, ownCloud GmbH * @license AGPL-3.0 @@ -19,18 +21,78 @@ * */ - namespace OC\User; use OC\DB\QueryBuilder\Literal; +use OCP\AppFramework\Db\Entity; use OCP\AppFramework\Db\Mapper; +use OCP\IConfig; use OCP\IDBConnection; class AccountMapper extends Mapper { - public function __construct(IDBConnection $db) { + /* @var AccountTermMapper */ + protected $termMapper; + + public function __construct(IDBConnection $db, AccountTermMapper $termMapper) { parent::__construct($db, 'accounts', Account::class); + $this->termMapper = $termMapper; + } + + /** + * Delegate to term mapper to avoid needing to inject term mapper + * @param $account_id + * @param array $terms + */ + public function setTermsForAccount($account_id, array $terms) { + $this->termMapper->setTermsForAccount($account_id, $terms); + } + + /** + * Delegate to term mapper to avoid needing to inject term mapper + * @param $account_id + * @return AccountTerm[] $terms + */ + public function findByAccountId($account_id) { + return $this->termMapper->findByAccountId($account_id); + } + + /** + * @param Account $entity + * @return Entity the saved entity with the set id + */ + public function insert(Entity $entity) { + // run the normal entity insert operation to get an id + $entity = parent::insert($entity); + + /** @var Account $entity */ + if ($entity->haveTermsChanged()) { + $this->termMapper->setTermsForAccount($entity->getId(), $entity->getSearchTerms()); + } + return $entity; + } + + /** + * @param Account $entity + * @return Entity the deleted entity + */ + public function delete(Entity $entity) { + // First delete the search terms for this account + $this->termMapper->deleteTermsForAccount($entity->getId()); + return parent::delete($entity); + } + + /** + * @param Account $entity + * @return Entity the updated entity + */ + public function update(Entity $entity) { + if ($entity->haveTermsChanged()) { + $this->termMapper->setTermsForAccount($entity->getId(), $entity->getSearchTerms()); + } + // Then run the normal entity insert operation + return parent::update($entity); } /** @@ -73,12 +135,36 @@ public function search($fieldName, $pattern, $limit, $offset) { $qb = $this->db->getQueryBuilder(); $qb->select('*') ->from($this->getTableName()) + // TODO check performance on large installs because like with starting % cannot use indexes ->where($qb->expr()->iLike($fieldName, $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%'))) ->orderBy($fieldName); return $this->findEntities($qb->getSQL(), $qb->getParameters(), $limit, $offset); } + /** + * @param string $pattern + * @param integer $limit + * @param integer $offset + * @return Account[] + */ + public function find($pattern, $limit, $offset) { + $lowerPattern = strtolower($pattern); + $qb = $this->db->getQueryBuilder(); + $qb->select(['user_id', 'lower_user_id', 'display_name', 'email', 'last_login', 'backend', 'state', 'quota', 'home']) + ->selectAlias('a.id', 'id') + ->from($this->getTableName(), 'a') + ->leftJoin('a', 'account_terms', 't', $qb->expr()->eq('a.id', 't.account_id')) + ->orderBy('display_name') + ->where($qb->expr()->like('lower_user_id', $qb->createNamedParameter($this->db->escapeLikeParameter($lowerPattern) . '%'))) + ->orWhere($qb->expr()->iLike('display_name', $qb->createNamedParameter($this->db->escapeLikeParameter($pattern) . '%'))) + ->orWhere($qb->expr()->iLike('email', $qb->createNamedParameter($this->db->escapeLikeParameter($pattern) . '%'))) + ->orWhere($qb->expr()->like('t.term', $qb->createNamedParameter($this->db->escapeLikeParameter($lowerPattern) . '%'))); + + + return $this->findEntities($qb->getSQL(), $qb->getParameters(), $limit, $offset); + } + public function getUserCountPerBackend($hasLoggedIn) { $qb = $this->db->getQueryBuilder(); $qb->select(['backend', $qb->createFunction('count(*) as `count`')]) @@ -124,6 +210,7 @@ public function callForAllUsers($callback, $search, $onlySeen) { if ($search) { $qb->where($qb->expr()->iLike('user_id', + // TODO check performance on large installs because like with starting % cannot use indexes $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($search) . '%'))); } if ($onlySeen) { diff --git a/lib/private/User/AccountTerm.php b/lib/private/User/AccountTerm.php new file mode 100644 index 000000000000..9972fbda3291 --- /dev/null +++ b/lib/private/User/AccountTerm.php @@ -0,0 +1,45 @@ + + * @author Tom Needham + * + * @copyright Copyright (c) 2017, ownCloud GmbH + * @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 + * + */ + +namespace OC\User; + + +use OCP\AppFramework\Db\Entity; + +/** + * Class AccountTerm + * + * @method int getAccountId() + * @method void setAccountId(int $accountId) + * @method string getTerm() + * @method void setTerm(string $term) + * + * @package OC\User + */ +class AccountTerm extends Entity { + + protected $accountId; + protected $term; + public function __construct() { + $this->addType('accountId', 'integer'); + } +} \ No newline at end of file diff --git a/lib/private/User/AccountTermMapper.php b/lib/private/User/AccountTermMapper.php new file mode 100644 index 000000000000..b6158dc46c0c --- /dev/null +++ b/lib/private/User/AccountTermMapper.php @@ -0,0 +1,76 @@ + + * @author Tom Needham + * + * @copyright Copyright (c) 2017, ownCloud GmbH + * @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 + * + */ + +namespace OC\User; + + +use OCP\AppFramework\Db\Mapper; +use OCP\IDBConnection; + +class AccountTermMapper extends Mapper { + + public function __construct(IDBConnection $db) { + parent::__construct($db, 'account_terms', AccountTerm::class); + } + + /** + * Sets search terms for a given account id, will always be lowercased + * @param $account_id + * @param string[] $terms + */ + public function setTermsForAccount($account_id, array $terms) { + // Delete all terms for this account + $this->deleteTermsForAccount($account_id); + // Now batch insert the new terms for this account + foreach ($terms as $term) { + $t = new AccountTerm(); + $t->setAccountId($account_id); + $t->setTerm(strtolower($term)); + $this->insert($t); + } + } + + /** + * Removes all search terms for a given account id + * @param $account_id + */ + public function deleteTermsForAccount($account_id) { + $qb = $this->db->getQueryBuilder(); + $qb->delete($this->getTableName()) + ->where($qb->expr()->eq('account_id', $qb->createNamedParameter($account_id))) + ->execute(); + } + + /** + * Retrieves all search terms attached to an account id + * @param $account_id + * @return AccountTerm[] of account search terms + */ + public function findByAccountId($account_id) { + $qb = $this->db->getQueryBuilder(); + $qb->select('term') + ->from($this->getTableName()) + ->where($qb->expr()->eq('account_id', $qb->createNamedParameter($account_id))); + + return $this->findEntities($qb->getSQL(), $qb->getParameters()); + } +} \ No newline at end of file diff --git a/lib/private/User/Manager.php b/lib/private/User/Manager.php index 59dfc26a6e8e..de8c54590379 100644 --- a/lib/private/User/Manager.php +++ b/lib/private/User/Manager.php @@ -8,6 +8,7 @@ * @author Morris Jobke * @author Robin Appelman * @author Thomas Müller + * @author Tom Needham * @author Victor Dubiniuk * @author Vincent Chan * @author Vincent Petry @@ -38,7 +39,9 @@ use OCP\IUser; use OCP\IUserManager; use OCP\IConfig; +use OCP\User\IProvidesExtendedSearchBackend; use OCP\User\IProvidesEMailBackend; +use OCP\User\IProvidesQuotaBackend; use OCP\UserInterface; use Symfony\Component\EventDispatcher\GenericEvent; @@ -213,6 +216,7 @@ public function checkPassword($loginName, $password) { } catch(DoesNotExistException $ex) { $account = $this->newAccount($uid, $backend); } + // TODO always sync account with backend here to update displayname, email, search terms, home etc. user_ldap currently updates user metadata on login, core should take care of updating accounts on a successful login return $this->getUserObject($account); } } @@ -241,6 +245,24 @@ public function search($pattern, $limit = null, $offset = null) { return $users; } + /** + * find a user account by checking user_id, display name and email fields + * + * @param string $pattern + * @param int $limit + * @param int $offset + * @return \OC\User\User[] + */ + public function find($pattern, $limit = null, $offset = null) { + $accounts = $this->accountMapper->find($pattern, $limit, $offset); + $users = []; + foreach ($accounts as $account) { + $user = $this->getUserObject($account); + $users[$user->getUID()] = $user; + } + return $users; + } + /** * search by displayName * @@ -414,6 +436,12 @@ private function newAccount($uid, $backend) { $account->setQuota($quota); } } + if ($backend instanceof IProvidesExtendedSearchBackend) { + $terms = $backend->getSearchTerms($uid); + if (!empty($terms)) { + $account->setSearchTerms($terms); + } + } $home = false; if ($backend->implementsActions(Backend::GET_HOME)) { $home = $backend->getHome($uid); diff --git a/lib/private/User/RemoteUser.php b/lib/private/User/RemoteUser.php index 782f321cf60d..cae025f7bdff 100644 --- a/lib/private/User/RemoteUser.php +++ b/lib/private/User/RemoteUser.php @@ -197,4 +197,17 @@ public function getQuota() { public function setQuota($quota) { } + /** + * @inheritdoc + */ + public function getSearchTerms() { + return []; + } + + /** + * @inheritdoc + */ + public function setSearchTerms(array $terms) { + } + } diff --git a/lib/private/User/SyncService.php b/lib/private/User/SyncService.php index 6fb5e5171511..604f0a5bff77 100644 --- a/lib/private/User/SyncService.php +++ b/lib/private/User/SyncService.php @@ -18,14 +18,12 @@ * along with this program. If not, see * */ - - namespace OC\User; - use OCP\AppFramework\Db\DoesNotExistException; use OCP\IConfig; use OCP\ILogger; +use OCP\User\IProvidesExtendedSearchBackend; use OCP\UserInterface; /** @@ -161,6 +159,10 @@ public function setupAccount(Account $a, $uid) { if ($this->backend->implementsActions(\OC_User_Backend::GET_DISPLAYNAME)) { $a->setDisplayName($this->backend->getDisplayName($uid)); } + // Check if backend supplies an additional search string + if ($this->backend instanceof IProvidesExtendedSearchBackend) { + $a->setSearchTerms($this->backend->getSearchTerms($uid)); + } return $a; } diff --git a/lib/private/User/User.php b/lib/private/User/User.php index 60137157bf16..403aab4a5a23 100644 --- a/lib/private/User/User.php +++ b/lib/private/User/User.php @@ -8,6 +8,7 @@ * @author Morris Jobke * @author Robin Appelman * @author Thomas Müller + * @author Tom Needham * @author Victor Dubiniuk * @author Vincent Petry * @@ -60,7 +61,7 @@ class User implements IUser { /** @var IURLGenerator */ private $urlGenerator; - /** @var EventDispatcher */ + /** @var EventDispatcher */ private $eventDispatcher; /** @var AccountMapper */ @@ -437,4 +438,24 @@ public function triggerChange($feature, $value = null) { $this->emitter->emit('\OC\User', 'changeUser', [$this, $feature, $value]); } } + + /** + * @return string[] + * @since 10.0.1 + */ + public function getSearchTerms() { + $terms = []; + foreach ($this->mapper->findByAccountId($this->account->getId()) as $term) { + $terms[] = $term->getTerm(); + } + return $terms; + } + + /** + * @param string[] $terms + * @since 10.0.1 + */ + public function setSearchTerms(array $terms) { + $this->mapper->setTermsForAccount($this->account->getId(), $terms); + } } diff --git a/lib/public/IGroupManager.php b/lib/public/IGroupManager.php index 24a599f05cd0..7a563f578327 100644 --- a/lib/public/IGroupManager.php +++ b/lib/public/IGroupManager.php @@ -123,6 +123,18 @@ public function getUserGroupIds($user, $scope = null); */ public function displayNamesInGroup($gid, $search = '', $limit = -1, $offset = 0); + /** + * search for users in a specific group + * + * @param string $gid + * @param string $search + * @param int $limit + * @param int $offset + * @return array an array of display names (value) and user objects + * @since 10.0.1 + */ + public function findUsersInGroup($gid, $search = '', $limit = -1, $offset = 0); + /** * Checks if a userId is in the admin group * @param string $userId diff --git a/lib/public/IUser.php b/lib/public/IUser.php index d8f6896c198d..08581f0767d3 100644 --- a/lib/public/IUser.php +++ b/lib/public/IUser.php @@ -199,4 +199,22 @@ public function getQuota(); * @since 9.0.0 */ public function setQuota($quota); + + /** + * set the users' search terms + * + * @param array $terms + * @return void + * @since 10.0.1 + */ + public function setSearchTerms(array $terms); + + /** + * get the users' search terms + * + * @return array + * @since 10.0.1 + */ + public function getSearchTerms(); + } diff --git a/lib/public/IUserManager.php b/lib/public/IUserManager.php index cab2258c4d62..91186e051920 100644 --- a/lib/public/IUserManager.php +++ b/lib/public/IUserManager.php @@ -110,6 +110,17 @@ public function checkPassword($loginName, $password); */ public function search($pattern, $limit = null, $offset = null); + /** + * find a user account by checking user_id, display name and email fields + * + * @param string $pattern + * @param int $limit + * @param int $offset + * @return \OCP\IUser[] + * @since 10.0.1 + */ + public function find($pattern, $limit = null, $offset = null); + /** * search by displayName * diff --git a/lib/public/User/IProvidesExtendedSearchBackend.php b/lib/public/User/IProvidesExtendedSearchBackend.php new file mode 100644 index 000000000000..c23c3a194e1e --- /dev/null +++ b/lib/public/User/IProvidesExtendedSearchBackend.php @@ -0,0 +1,45 @@ + + * + * @copyright Copyright (c) 2017, ownCloud GmbH + * @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 + * + */ + + +// use OCP namespace for all classes that are considered public. +// This means that they should be used by apps instead of the internal ownCloud classes +namespace OCP\User; + +/** + * Interface IProvidesExtendedSearchBackend + * + * TODO update these backend interface names to be consistent and readable + * @package OCP\User + * @since 10.0.1 + */ +interface IProvidesExtendedSearchBackend { + + /** + * Get search terms for a users account for core powered user search + * + * @param string $uid The username + * @return string[] + * @since 10.0.1 + */ + public function getSearchTerms($uid); +} + diff --git a/tests/lib/Files/Config/UserMountCacheTest.php b/tests/lib/Files/Config/UserMountCacheTest.php index 43a603e005f0..5a3e37560ba9 100644 --- a/tests/lib/Files/Config/UserMountCacheTest.php +++ b/tests/lib/Files/Config/UserMountCacheTest.php @@ -18,7 +18,6 @@ use OCP\Files\Config\ICachedMountInfo; use OCP\IConfig; use OCP\IDBConnection; -use OCP\ILogger; use OCP\IUserManager; use Test\TestCase; @@ -65,6 +64,7 @@ public function setUp() { ['u2', $a2], ['u3', $a3], ]); + /** @var Log $log */ $log = $this->createMock(Log::class); $this->userManager = new Manager($config, $log, $accountMapper); diff --git a/tests/lib/Group/ManagerTest.php b/tests/lib/Group/ManagerTest.php index aab23965169c..c49e6f501df4 100644 --- a/tests/lib/Group/ManagerTest.php +++ b/tests/lib/Group/ManagerTest.php @@ -22,6 +22,8 @@ */ namespace Test\Group; +use OC\Group\Database; +use OC\User\Manager; use OCP\IUser; use OCP\GroupInterface; @@ -94,7 +96,7 @@ public function testGet() { /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); + $userManager = $this->createMock(Manager::class); $manager = new \OC\Group\Manager($userManager); $manager->addBackend($backend); @@ -107,7 +109,7 @@ public function testGetNoBackend() { /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); + $userManager = $this->createMock(Manager::class); $manager = new \OC\Group\Manager($userManager); $this->assertNull($manager->get('group1')); @@ -126,7 +128,7 @@ public function testGetNotExists() { /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); + $userManager = $this->createMock(Manager::class); $manager = new \OC\Group\Manager($userManager); $manager->addBackend($backend); @@ -140,7 +142,7 @@ public function testGetDeleted() { /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); + $userManager = $this->createMock(Manager::class); $manager = new \OC\Group\Manager($userManager); $manager->addBackend($backend); @@ -171,7 +173,7 @@ public function testGetMultipleBackends() { /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); + $userManager = $this->createMock(Manager::class); $manager = new \OC\Group\Manager($userManager); $manager->addBackend($backend1); $manager->addBackend($backend2); @@ -202,7 +204,7 @@ public function testCreate() { /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); + $userManager = $this->createMock(Manager::class); $manager = new \OC\Group\Manager($userManager); $manager->addBackend($backend); @@ -225,7 +227,7 @@ public function testCreateExists() { /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); + $userManager = $this->createMock(Manager::class); $manager = new \OC\Group\Manager($userManager); $manager->addBackend($backend); @@ -250,7 +252,7 @@ public function testSearch() { /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); + $userManager = $this->createMock(Manager::class); $manager = new \OC\Group\Manager($userManager); $manager->addBackend($backend); @@ -288,7 +290,7 @@ public function testSearchMultipleBackends() { /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); + $userManager = $this->createMock(Manager::class); $manager = new \OC\Group\Manager($userManager); $manager->addBackend($backend1); $manager->addBackend($backend2); @@ -329,7 +331,7 @@ public function testSearchMultipleBackendsLimitAndOffset() { /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); + $userManager = $this->createMock(Manager::class); $manager = new \OC\Group\Manager($userManager); $manager->addBackend($backend1); $manager->addBackend($backend2); @@ -346,7 +348,7 @@ public function testSearchResultExistsButGroupDoesNot() { /** * @var \PHPUnit_Framework_MockObject_MockObject | \OC\Group\Backend $backend */ - $backend = $this->createMock('\OC\Group\Database'); + $backend = $this->createMock(Database::class); $backend->expects($this->once()) ->method('getGroups') ->with('1') @@ -362,7 +364,7 @@ public function testSearchResultExistsButGroupDoesNot() { /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); + $userManager = $this->createMock(Manager::class); $manager = new \OC\Group\Manager($userManager); $manager->addBackend($backend); @@ -402,7 +404,7 @@ public function testSearchBackendsForScope() { /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); + $userManager = $this->createMock(Manager::class); $manager = new \OC\Group\Manager($userManager); $manager->addBackend($backend1); $manager->addBackend($backend2); @@ -439,8 +441,8 @@ public function testGetUserGroups() { /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); - $userBackend = $this->createMock('\OC_User_Backend'); + $userManager = $this->createMock(Manager::class); + $userBackend = $this->createMock(\OC_User_Backend::class); $manager = new \OC\Group\Manager($userManager); $manager->addBackend($backend); @@ -480,7 +482,7 @@ public function testGetUserGroupsWithDeletedGroup() { /** * @var \PHPUnit_Framework_MockObject_MockObject | \OC\Group\Backend $backend */ - $backend = $this->createMock('\OC\Group\Database'); + $backend = $this->createMock(Database::class); $backend->expects($this->once()) ->method('getUserGroups') ->with('user1') @@ -493,8 +495,8 @@ public function testGetUserGroupsWithDeletedGroup() { /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); - $userBackend = $this->createMock('\OC_User_Backend'); + $userManager = $this->createMock(Manager::class); + $userBackend = $this->createMock(\OC_User_Backend::class); $manager = new \OC\Group\Manager($userManager); $manager->addBackend($backend); @@ -526,8 +528,8 @@ public function testGetUserGroupsWithScope() { /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); - $userBackend = $this->createMock('\OC_User_Backend'); + $userManager = $this->createMock(Manager::class); + $userBackend = $this->createMock(\OC_User_Backend::class); $manager = new \OC\Group\Manager($userManager); $manager->addBackend($backend); @@ -556,8 +558,8 @@ public function testInGroup() { /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); - $userBackend = $this->createMock('\OC_User_Backend'); + $userManager = $this->createMock(Manager::class); + $userBackend = $this->createMock(\OC_User_Backend::class); $manager = new \OC\Group\Manager($userManager); $manager->addBackend($backend); @@ -580,8 +582,8 @@ public function testIsAdmin() { /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); - $userBackend = $this->createMock('\OC_User_Backend'); + $userManager = $this->createMock(Manager::class); + $userBackend = $this->createMock(\OC_User_Backend::class); $manager = new \OC\Group\Manager($userManager); $manager->addBackend($backend); @@ -604,8 +606,8 @@ public function testNotAdmin() { /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); - $userBackend = $this->createMock('\OC_User_Backend'); + $userManager = $this->createMock(Manager::class); + $userBackend = $this->createMock(\OC_User_Backend::class); $manager = new \OC\Group\Manager($userManager); $manager->addBackend($backend); @@ -640,8 +642,8 @@ public function testGetUserGroupsMultipleBackends() { /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); - $userBackend = $this->createMock('\OC_User_Backend'); + $userManager = $this->createMock(Manager::class); + $userBackend = $this->createMock(\OC_User_Backend::class); $manager = new \OC\Group\Manager($userManager); $manager->addBackend($backend1); $manager->addBackend($backend2); @@ -680,8 +682,8 @@ public function testDisplayNamesInGroupWithOneUserBackend() { /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); - $userBackend = $this->createMock('\OC_User_Backend'); + $userManager = $this->createMock(Manager::class); + $userBackend = $this->createMock(\OC_User_Backend::class); $userManager->expects($this->any()) ->method('searchDisplayName') @@ -745,8 +747,8 @@ public function testDisplayNamesInGroupWithOneUserBackendWithLimitSpecified() { /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); - $userBackend = $this->createMock('\OC_User_Backend'); + $userManager = $this->createMock(Manager::class); + $userBackend = $this->createMock(\OC_User_Backend::class); $userManager->expects($this->any()) ->method('searchDisplayName') @@ -812,8 +814,8 @@ public function testDisplayNamesInGroupWithOneUserBackendWithLimitAndOffsetSpeci /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); - $userBackend = $this->createMock('\OC_User_Backend'); + $userManager = $this->createMock(Manager::class); + $userBackend = $this->createMock(\OC_User_Backend::class); $userManager->expects($this->any()) ->method('searchDisplayName') @@ -873,8 +875,8 @@ public function testDisplayNamesInGroupWithOneUserBackendAndSearchEmpty() { /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); - $userBackend = $this->createMock('\OC_User_Backend'); + $userManager = $this->createMock(Manager::class); + $userBackend = $this->createMock(\OC_User_Backend::class); $userManager->expects($this->any()) ->method('get') @@ -917,8 +919,8 @@ public function testDisplayNamesInGroupWithOneUserBackendAndSearchEmptyAndLimitS /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); - $userBackend = $this->createMock('\OC_User_Backend'); + $userManager = $this->createMock(Manager::class); + $userBackend = $this->createMock(\OC_User_Backend::class); $userManager->expects($this->any()) ->method('get') @@ -962,8 +964,8 @@ public function testDisplayNamesInGroupWithOneUserBackendAndSearchEmptyAndLimitA /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); - $userBackend = $this->createMock('\OC_User_Backend'); + $userManager = $this->createMock(Manager::class); + $userBackend = $this->createMock(\OC_User_Backend::class); $userManager->expects($this->any()) ->method('get') @@ -1009,7 +1011,7 @@ public function testGetUserGroupsWithAddUser() { /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); + $userManager = $this->createMock(Manager::class); $manager = new \OC\Group\Manager($userManager); $manager->addBackend($backend); @@ -1056,7 +1058,7 @@ public function testGetUserGroupsWithRemoveUser() { /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); + $userManager = $this->createMock(Manager::class); $manager = new \OC\Group\Manager($userManager); $manager->addBackend($backend); @@ -1090,7 +1092,7 @@ public function testGetUserIdGroups() { /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); + $userManager = $this->createMock(Manager::class); $manager = new \OC\Group\Manager($userManager); $manager->addBackend($backend); @@ -1120,7 +1122,7 @@ public function testGroupDisplayName() { /** * @var \OC\User\Manager $userManager */ - $userManager = $this->createMock('\OC\User\Manager'); + $userManager = $this->createMock(Manager::class); $manager = new \OC\Group\Manager($userManager); $manager->addBackend($backend); @@ -1137,4 +1139,339 @@ public function testGroupDisplayName() { $this->assertEquals('group2', $group->getDisplayName()); } + public function testFindUsersInGroupWithOneUserBackend() { + /** + * @var \PHPUnit_Framework_MockObject_MockObject | \OC\Group\Backend $backend + */ + $backend = $this->getTestBackend(); + $backend->expects($this->exactly(1)) + ->method('groupExists') + ->with('testgroup') + ->will($this->returnValue(true)); + + $backend->expects($this->any()) + ->method('inGroup') + ->will($this->returnCallback(function($uid, $gid) { + switch($uid) { + case 'user1' : return false; + case 'user2' : return true; + case 'user3' : return false; + case 'user33': return true; + default: + return null; + } + })); + + /** + * @var \OC\User\Manager $userManager + */ + $userManager = $this->createMock(Manager::class); + $userBackend = $this->createMock(\OC_User_Backend::class); + + $userManager->expects($this->any()) + ->method('find') + ->with('user3') + ->will($this->returnCallback(function($search, $limit, $offset) use ($userBackend) { + switch($offset) { + case 0 : return ['user3' => $this->getTestUser('user3'), + 'user33' => $this->getTestUser('user33')]; + case 2 : return []; + } + })); + + $userManager->expects($this->any()) + ->method('get') + ->will($this->returnCallback(function($uid) use ($userBackend) { + switch($uid) { + case 'user1' : return $this->getTestUser('user1'); + case 'user2' : return $this->getTestUser('user2'); + case 'user3' : return $this->getTestUser('user3'); + case 'user33': return $this->getTestUser('user33'); + default: + return null; + } + })); + + $manager = new \OC\Group\Manager($userManager); + $manager->addBackend($backend); + + $users = $manager->findUsersInGroup('testgroup', 'user3'); + $this->assertEquals(1, count($users)); + $this->assertFalse(isset($users['user1'])); + $this->assertFalse(isset($users['user2'])); + $this->assertFalse(isset($users['user3'])); + $this->assertTrue(isset($users['user33'])); + } + + public function testFindUsersInGroupWithOneUserBackendWithLimitSpecified() { + /** + * @var \PHPUnit_Framework_MockObject_MockObject | \OC\Group\Backend $backend + */ + $backend = $this->getTestBackend(); + $backend->expects($this->exactly(1)) + ->method('groupExists') + ->with('testgroup') + ->will($this->returnValue(true)); + + $backend->expects($this->any()) + ->method('inGroup') + ->will($this->returnCallback(function($uid, $gid) { + switch($uid) { + case 'user1' : return false; + case 'user2' : return true; + case 'user3' : return false; + case 'user33': return true; + case 'user333': return true; + default: + return null; + } + })); + + /** + * @var \OC\User\Manager $userManager + */ + $userManager = $this->createMock(Manager::class); + $userBackend = $this->createMock(\OC_User_Backend::class); + + $userManager->expects($this->any()) + ->method('find') + ->with('user3') + ->will($this->returnCallback(function($search, $limit, $offset) use ($userBackend) { + switch($offset) { + case 0 : return ['user3' => $this->getTestUser('user3'), + 'user33' => $this->getTestUser('user33')]; + case 2 : return ['user333' => $this->getTestUser('user333')]; + } + })); + + $userManager->expects($this->any()) + ->method('get') + ->will($this->returnCallback(function($uid) use ($userBackend) { + switch($uid) { + case 'user1' : return $this->getTestUser('user1'); + case 'user2' : return $this->getTestUser('user2'); + case 'user3' : return $this->getTestUser('user3'); + case 'user33': return $this->getTestUser('user33'); + case 'user333': return $this->getTestUser('user333'); + default: + return null; + } + })); + + $manager = new \OC\Group\Manager($userManager); + $manager->addBackend($backend); + + $users = $manager->findUsersInGroup('testgroup', 'user3', 1); + $this->assertEquals(1, count($users)); + $this->assertFalse(isset($users['user1'])); + $this->assertFalse(isset($users['user2'])); + $this->assertFalse(isset($users['user3'])); + $this->assertTrue(isset($users['user33'])); + $this->assertFalse(isset($users['user333'])); + } + + public function testFindUsersInGroupWithOneUserBackendWithLimitAndOffsetSpecified() { + /** + * @var \PHPUnit_Framework_MockObject_MockObject | \OC\Group\Backend $backend + */ + $backend = $this->getTestBackend(); + $backend->expects($this->exactly(1)) + ->method('groupExists') + ->with('testgroup') + ->will($this->returnValue(true)); + + $backend->expects($this->any()) + ->method('inGroup') + ->will($this->returnCallback(function($uid) { + switch($uid) { + case 'user1' : return false; + case 'user2' : return true; + case 'user3' : return false; + case 'user33': return true; + case 'user333': return true; + default: + return null; + } + })); + + /** + * @var \OC\User\Manager $userManager + */ + $userManager = $this->createMock(Manager::class); + $userBackend = $this->createMock(\OC_User_Backend::class); + + $userManager->expects($this->any()) + ->method('find') + ->with('user3') + ->will($this->returnCallback(function($search, $limit, $offset) use ($userBackend) { + switch($offset) { + case 0 : + return [ + 'user3' => $this->getTestUser('user3'), + 'user33' => $this->getTestUser('user33'), + 'user333' => $this->getTestUser('user333') + ]; + } + })); + + $userManager->expects($this->any()) + ->method('get') + ->will($this->returnCallback(function($uid) use ($userBackend) { + switch($uid) { + case 'user1' : return $this->getTestUser('user1'); + case 'user2' : return $this->getTestUser('user2'); + case 'user3' : return $this->getTestUser('user3'); + case 'user33': return $this->getTestUser('user33'); + case 'user333': return $this->getTestUser('user333'); + default: + return null; + } + })); + + $manager = new \OC\Group\Manager($userManager); + $manager->addBackend($backend); + + $users = $manager->findUsersInGroup('testgroup', 'user3', 1, 1); + $this->assertEquals(1, count($users)); + $this->assertFalse(isset($users['user1'])); + $this->assertFalse(isset($users['user2'])); + $this->assertFalse(isset($users['user3'])); + $this->assertFalse(isset($users['user33'])); + $this->assertTrue(isset($users['user333'])); + } + + public function testFindUsersInGroupWithOneUserBackendAndSearchEmpty() { + /** + * @var \PHPUnit_Framework_MockObject_MockObject | \OC\Group\Backend $backend + */ + $backend = $this->getTestBackend(); + $backend->expects($this->exactly(1)) + ->method('groupExists') + ->with('testgroup') + ->will($this->returnValue(true)); + + $backend->expects($this->once()) + ->method('usersInGroup') + ->with('testgroup', '', -1, 0) + ->will($this->returnValue(['user2', 'user33'])); + + /** + * @var \OC\User\Manager $userManager + */ + $userManager = $this->createMock(Manager::class); + $userBackend = $this->createMock(\OC_User_Backend::class); + + $userManager->expects($this->any()) + ->method('get') + ->will($this->returnCallback(function($uid) use ($userBackend) { + switch($uid) { + case 'user1' : return $this->getTestUser('user1'); + case 'user2' : return $this->getTestUser('user2'); + case 'user3' : return $this->getTestUser('user3'); + case 'user33': return $this->getTestUser('user33'); + default: + return null; + } + })); + + $manager = new \OC\Group\Manager($userManager); + $manager->addBackend($backend); + + $users = $manager->findUsersInGroup('testgroup', ''); + $this->assertEquals(2, count($users)); + $this->assertFalse(isset($users['user1'])); + $this->assertTrue(isset($users['user2'])); + $this->assertFalse(isset($users['user3'])); + $this->assertTrue(isset($users['user33'])); + } + + public function testFindUsersInGroupWithOneUserBackendAndSearchEmptyAndLimitSpecified() { + /** + * @var \PHPUnit_Framework_MockObject_MockObject | \OC\Group\Backend $backend + */ + $backend = $this->getTestBackend(); + $backend->expects($this->exactly(1)) + ->method('groupExists') + ->with('testgroup') + ->will($this->returnValue(true)); + + $backend->expects($this->once()) + ->method('usersInGroup') + ->with('testgroup', '', 1, 0) + ->will($this->returnValue(['user2'])); + /** + * @var \OC\User\Manager $userManager + */ + $userManager = $this->createMock(Manager::class); + $userBackend = $this->createMock(\OC_User_Backend::class); + + $userManager->expects($this->any()) + ->method('get') + ->will($this->returnCallback(function($uid) use ($userBackend) { + switch($uid) { + case 'user1' : return $this->getTestUser('user1'); + case 'user2' : return $this->getTestUser('user2'); + case 'user3' : return $this->getTestUser('user3'); + case 'user33': return $this->getTestUser('user33'); + default: + return null; + } + })); + + $manager = new \OC\Group\Manager($userManager); + $manager->addBackend($backend); + + $users = $manager->findUsersInGroup('testgroup', '', 1); + $this->assertEquals(1, count($users)); + $this->assertFalse(isset($users['user1'])); + $this->assertTrue(isset($users['user2'])); + $this->assertFalse(isset($users['user3'])); + $this->assertFalse(isset($users['user33'])); + } + + public function testFindUsersInGroupWithOneUserBackendAndSearchEmptyAndLimitAndOffsetSpecified() { + /** + * @var \PHPUnit_Framework_MockObject_MockObject | \OC\Group\Backend $backend + */ + $backend = $this->getTestBackend(); + $backend->expects($this->exactly(1)) + ->method('groupExists') + ->with('testgroup') + ->will($this->returnValue(true)); + + $backend->expects($this->once()) + ->method('usersInGroup') + ->with('testgroup', '', 1, 1) + ->will($this->returnValue(['user33'])); + + /** + * @var \OC\User\Manager $userManager + */ + $userManager = $this->createMock(Manager::class); + $userBackend = $this->createMock(\OC_User_Backend::class); + + $userManager->expects($this->any()) + ->method('get') + ->will($this->returnCallback(function($uid) use ($userBackend) { + switch($uid) { + case 'user1' : return $this->getTestUser('user1'); + case 'user2' : return $this->getTestUser('user2'); + case 'user3' : return $this->getTestUser('user3'); + case 'user33': return $this->getTestUser('user33'); + default: + return null; + } + })); + + $manager = new \OC\Group\Manager($userManager); + $manager->addBackend($backend); + + $users = $manager->findUsersInGroup('testgroup', '', 1, 1); + $this->assertEquals(1, count($users)); + $this->assertFalse(isset($users['user1'])); + $this->assertFalse(isset($users['user2'])); + $this->assertFalse(isset($users['user3'])); + $this->assertTrue(isset($users['user33'])); + } + } diff --git a/tests/lib/Traits/UserTrait.php b/tests/lib/Traits/UserTrait.php index b4a11f43858f..171de3a32c7c 100644 --- a/tests/lib/Traits/UserTrait.php +++ b/tests/lib/Traits/UserTrait.php @@ -8,6 +8,7 @@ namespace Test\Traits; +use OC\User\AccountTermMapper; use OC\User\User; use Test\Util\User\Dummy; use Test\Util\User\MemoryAccountMapper; @@ -38,7 +39,7 @@ protected function createUser($name, $password = null) { protected function setUpUserTrait() { $db = \OC::$server->getDatabaseConnection(); - $accountMapper = new MemoryAccountMapper($db); + $accountMapper = new MemoryAccountMapper($db, new AccountTermMapper($db)); $accountMapper->testCaseName = get_class($this); $this->previousUserManagerInternals = \OC::$server->getUserManager() ->reset($accountMapper, [Dummy::class => new Dummy()]); diff --git a/tests/lib/User/ManagerTest.php b/tests/lib/User/ManagerTest.php index 9acc4bcebdcf..e85ff504afbf 100644 --- a/tests/lib/User/ManagerTest.php +++ b/tests/lib/User/ManagerTest.php @@ -10,6 +10,7 @@ namespace Test\User; use OC\User\Account; use OC\User\AccountMapper; +use OC\User\AccountTermMapper; use OC\User\Backend; use OC\User\Database; use OC\User\Manager; @@ -32,6 +33,8 @@ class ManagerTest extends TestCase { private $manager; /** @var AccountMapper | \PHPUnit_Framework_MockObject_MockObject */ private $accountMapper; + /** @var AccountTermMapper | \PHPUnit_Framework_MockObject_MockObject */ + private $accountTermMapper; public function setUp() { parent::setUp(); @@ -41,7 +44,8 @@ public function setUp() { /** @var ILogger | \PHPUnit_Framework_MockObject_MockObject $logger */ $logger = $this->createMock(ILogger::class); $this->accountMapper = $this->createMock(AccountMapper::class); - $this->manager = new \OC\User\Manager($config, $logger, $this->accountMapper); + $this->accountTermMapper = $this->createMock(AccountTermMapper::class); + $this->manager = new \OC\User\Manager($config, $logger, $this->accountMapper, $this->accountTermMapper); } public function testGetBackends() { @@ -129,6 +133,61 @@ public function testGetOneBackendNotExists() { $this->assertEquals(null, $this->manager->get('foo')); } + public function testFind() { + $a0 = new Account(); + $a0->setUserId('afoo'); + $a1 = new Account(); + $a1->setUserId('foo'); + $this->accountMapper->expects($this->once())->method('find') + ->with('fo')->willReturn([$a0, $a1]); + $result = $this->manager->find('fo'); + $this->assertEquals(2, count($result)); + $this->assertEquals('afoo', array_shift($result)->getUID()); + $this->assertEquals('foo', array_shift($result)->getUID()); + } + + public function testFindWithLimit() { + $a0 = new Account(); + $a0->setUserId('afoo'); + $a1 = new Account(); + $a1->setUserId('foo'); + $this->accountMapper->expects($this->once())->method('find') + ->with('fo', 1)->willReturn([$a0]); + $result = $this->manager->find('fo', 1); + $this->assertEquals(1, count($result)); + $this->assertEquals('afoo', array_shift($result)->getUID()); + } + + public function testFindWithDisplayName() { + $a0 = new Account(); + $a0->setUserId('afoo'); + $a0->setDisplayName('display1'); + $a1 = new Account(); + $a1->setUserId('foo'); + $a0->setDisplayName('display2'); + $this->accountMapper->expects($this->once())->method('find') + ->with('display2')->willReturn([$a1]); + $result = $this->manager->find('display2'); + $this->assertEquals(1, count($result)); + $this->assertEquals('foo', array_shift($result)->getUID()); + } + + public function testFindWithEmail() { + $a0 = new Account(); + $a0->setUserId('afoo'); + $a0->setEmail('test@test.com'); + $a1 = new Account(); + $a1->setUserId('foo'); + $a0->setEmail('test2@test.com'); + $this->accountMapper->expects($this->once())->method('find') + ->with('@test.com')->willReturn([$a0, $a1]); + $result = $this->manager->find('@test.com'); + $this->assertEquals(2, count($result)); + $this->assertEquals('afoo', array_shift($result)->getUID()); + $this->assertEquals('foo', array_shift($result)->getUID()); + } + + public function testSearch() { $a0 = new Account(); $a0->setUserId('afoo');