diff --git a/apps/dav/lib/CardDAV/AddressBookImpl.php b/apps/dav/lib/CardDAV/AddressBookImpl.php index 030f341c27d6..b5d874f17393 100644 --- a/apps/dav/lib/CardDAV/AddressBookImpl.php +++ b/apps/dav/lib/CardDAV/AddressBookImpl.php @@ -86,11 +86,13 @@ public function getDisplayName() { * @param string $pattern which should match within the $searchProperties * @param array $searchProperties defines the properties within the query pattern should match * @param array $options - for future use. One should always have options! + * @param int $limit + * @param int $offset * @return array an array of contacts which are arrays of key-value-pairs * @since 5.0.0 */ - public function search($pattern, $searchProperties, $options) { - $results = $this->backend->search($this->getKey(), $pattern, $searchProperties); + public function search($pattern, $searchProperties, $options, $limit = null, $offset = null) { + $results = $this->backend->search($this->getKey(), $pattern, $searchProperties, $limit, $offset); $vCards = []; foreach ($results as $result) { diff --git a/apps/dav/lib/CardDAV/CardDavBackend.php b/apps/dav/lib/CardDAV/CardDavBackend.php index e3987f172fc1..199d52a1bdea 100644 --- a/apps/dav/lib/CardDAV/CardDavBackend.php +++ b/apps/dav/lib/CardDAV/CardDavBackend.php @@ -790,9 +790,11 @@ public function updateShares(IShareable $shareable, $add, $remove) { * @param int $addressBookId * @param string $pattern which should match within the $searchProperties * @param array $searchProperties defines the properties within the query pattern should match + * @param int $limit + * @param int $offset * @return array an array of contacts which are arrays of key-value-pairs */ - public function search($addressBookId, $pattern, $searchProperties) { + public function search($addressBookId, $pattern, $searchProperties, $limit = 100, $offset = 0) { $query = $this->db->getQueryBuilder(); $query2 = $this->db->getQueryBuilder(); $query2->selectDistinct('cp.cardid')->from($this->dbCardsPropertiesTable, 'cp'); @@ -809,6 +811,9 @@ public function search($addressBookId, $pattern, $searchProperties) { $query->select('c.carddata', 'c.uri')->from($this->dbCardsTable, 'c') ->where($query->expr()->in('c.id', $query->createFunction($query2->getSQL()))); + $query->setFirstResult($offset)->setMaxResults($limit); + $query->orderBy('c.uri'); + $result = $query->execute(); $cards = $result->fetchAll(); diff --git a/apps/dav/lib/Connector/Sabre/FakeLockerPlugin.php b/apps/dav/lib/Connector/Sabre/FakeLockerPlugin.php index 6c07b51b8720..9025109b334e 100644 --- a/apps/dav/lib/Connector/Sabre/FakeLockerPlugin.php +++ b/apps/dav/lib/Connector/Sabre/FakeLockerPlugin.php @@ -135,6 +135,7 @@ public function fakeLockProvider(RequestInterface $request, new LockDiscovery([$lockInfo]) ]); + $response->setStatus(200); $response->setBody($body); return false; diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php index 649a8d0f63f9..14d285f5de77 100644 --- a/apps/dav/lib/Server.php +++ b/apps/dav/lib/Server.php @@ -143,6 +143,7 @@ public function __construct(IRequest $request, $baseUri) { if($request->isUserAgent([ '/WebDAVFS/', '/Microsoft Office OneNote 2013/', + '/Microsoft-WebDAV-MiniRedir/', ])) { $this->server->addPlugin(new FakeLockerPlugin()); } diff --git a/apps/dav/tests/unit/CardDAV/AddressBookImplTest.php b/apps/dav/tests/unit/CardDAV/AddressBookImplTest.php index 018b3809e831..98f63deafdd7 100644 --- a/apps/dav/tests/unit/CardDAV/AddressBookImplTest.php +++ b/apps/dav/tests/unit/CardDAV/AddressBookImplTest.php @@ -102,10 +102,10 @@ public function testSearch() { ->getMock(); $pattern = 'pattern'; - $searchProperties = 'properties'; + $searchProperties = ['properties']; $this->backend->expects($this->once())->method('search') - ->with($this->addressBookInfo['id'], $pattern, $searchProperties) + ->with($this->addressBookInfo['id'], $pattern, $searchProperties, 10, 0) ->willReturn( [ ['uri' => 'foo.vcf', 'carddata' => 'cardData1'], @@ -121,7 +121,7 @@ public function testSearch() { ['bar.vcf', $this->vCard] )->willReturn('vCard'); - $result = $addressBookImpl->search($pattern, $searchProperties, []); + $result = $addressBookImpl->search($pattern, $searchProperties, [], 10, 0); $this->assertTrue((is_array($result))); $this->assertSame(2, count($result)); } diff --git a/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php b/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php index 892134b66315..04e81e908723 100644 --- a/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php +++ b/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php @@ -466,7 +466,7 @@ public function testGetCardIdFailed() { * @param array $properties * @param array $expected */ - public function testSearch($pattern, $properties, $expected) { + public function testSearch($pattern, $properties, $expected, $limit, $offset) { /** @var VCard $vCards */ $vCards = []; $vCards[0] = new VCard(); @@ -529,7 +529,7 @@ public function testSearch($pattern, $properties, $expected) { ); $query->execute(); - $result = $this->backend->search(0, $pattern, $properties); + $result = $this->backend->search(0, $pattern, $properties, $limit, $offset); // check result $this->assertSame(count($expected), count($result)); @@ -548,11 +548,13 @@ public function testSearch($pattern, $properties, $expected) { public function dataTestSearch() { return [ - ['John', ['FN'], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']]], - ['M. Doe', ['FN'], [['uri1', 'John M. Doe']]], - ['Do', ['FN'], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']]], - 'check if duplicates are handled correctly' => ['John', ['FN', 'CLOUD'], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']]], - 'case insensitive' => ['john', ['FN'], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']]] + ['John', ['FN'], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']], 100, 0], + ['M. Doe', ['FN'], [['uri1', 'John M. Doe']], 100, 0], + ['Do', ['FN'], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']], 100, 0], + 'check if duplicates are handled correctly' => ['John', ['FN', 'CLOUD'], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']], 100, 0], + 'case insensitive' => ['john', ['FN'], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']], 100, 0], + 'search limit' => ['John', ['FN'], [['uri0', 'John Doe']], 1, 0], + 'search offset' => ['John', ['FN'], [['uri1', 'John M. Doe']], 1, 1], ]; } diff --git a/apps/files_sharing/lib/Controller/ShareesController.php b/apps/files_sharing/lib/Controller/ShareesController.php index 7c585e9ce05f..e68ffc90c3cd 100644 --- a/apps/files_sharing/lib/Controller/ShareesController.php +++ b/apps/files_sharing/lib/Controller/ShareesController.php @@ -291,58 +291,102 @@ protected function getGroups($search) { */ protected function getRemote($search) { $this->result['remotes'] = []; - + // Fetch remote search properties from app config + $searchProperties = explode(',', $this->config->getAppValue('dav', 'remote_search_properties', 'CLOUD,FN')); // Search in contacts - //@todo Pagination missing - $addressBookContacts = $this->contactsManager->search($search, ['CLOUD', 'FN']); + $addressBookContacts = $this->contactsManager->search($search, $searchProperties, [], $this->limit, $this->offset); $foundRemoteById = false; foreach ($addressBookContacts as $contact) { + if (isset($contact['isLocalSystemBook'])) { + // We only want remote users continue; } - if (isset($contact['CLOUD'])) { - $cloudIds = $contact['CLOUD']; - if (!is_array($cloudIds)) { - $cloudIds = [$cloudIds]; + if (!isset($contact['CLOUD'])) { + // we need a cloud id to setup a remote share + continue; + } + + // we can have multiple cloud domains, always convert to an array + $cloudIds = $contact['CLOUD']; + if (!is_array($cloudIds)) { + $cloudIds = [$cloudIds]; + } + + $lowerSearch = strtolower($search); + foreach ($cloudIds as $cloudId) { + list(, $serverUrl) = $this->splitUserRemote($cloudId); + + + if (strtolower($cloudId) === $lowerSearch) { + $foundRemoteById = true; + // Save this as an exact match and continue with next CLOUD + $this->result['exact']['remotes'][] = [ + 'label' => $contact['FN'], + 'value' => [ + 'shareType' => Share::SHARE_TYPE_REMOTE, + 'shareWith' => $cloudId, + 'server' => $serverUrl, + ], + ]; + continue; } - $lowerSearch = strtolower($search); - foreach ($cloudIds as $cloudId) { - list(, $serverUrl) = $this->splitUserRemote($cloudId); - if (strtolower($contact['FN']) === $lowerSearch || strtolower($cloudId) === $lowerSearch) { - if (strtolower($cloudId) === $lowerSearch) { - $foundRemoteById = true; + + // CLOUD matching is done above + unset($searchProperties['CLOUD']); + foreach($searchProperties as $property) { + // do we even have this property for this contact/ + if(!isset($contact[$property])) { + // Skip this property since our contact doesnt have it + continue; + } + // check if we have a match + $values = $contact[$property]; + if(!is_array($values)) { + $values = [$values]; + } + foreach($values as $value) { + // check if we have an exact match + if(strtolower($value) === $lowerSearch) { + $this->result['exact']['remotes'][] = [ + 'label' => $contact['FN'], + 'value' => [ + 'shareType' => Share::SHARE_TYPE_REMOTE, + 'shareWith' => $cloudId, + 'server' => $serverUrl, + ], + ]; + + // Now skip to next CLOUD + continue 3; } - $this->result['exact']['remotes'][] = [ - 'label' => $contact['FN'], - 'value' => [ - 'shareType' => Share::SHARE_TYPE_REMOTE, - 'shareWith' => $cloudId, - 'server' => $serverUrl, - ], - ]; - } else { - $this->result['remotes'][] = [ - 'label' => $contact['FN'], - 'value' => [ - 'shareType' => Share::SHARE_TYPE_REMOTE, - 'shareWith' => $cloudId, - 'server' => $serverUrl, - ], - ]; } } + + // If we get here, we didnt find an exact match, so add to other matches + $this->result['remotes'][] = [ + 'label' => $contact['FN'], + 'value' => [ + 'shareType' => Share::SHARE_TYPE_REMOTE, + 'shareWith' => $cloudId, + 'server' => $serverUrl, + ], + ]; + } } + // remove the exact user results if we dont allow autocomplete if (!$this->shareeEnumeration) { $this->result['remotes'] = []; } + if (!$foundRemoteById && substr_count($search, '@') >= 1 && $this->offset === 0 // if an exact local user is found, only keep the remote entry if // its domain does not matches the trusted domains // (if it does, it is a user whose local login domain matches the ownCloud - // instance domain) + // instance domain) && (empty($this->result['exact']['users']) || !$this->isInstanceDomain($search)) ) { diff --git a/apps/files_sharing/tests/API/ShareesTest.php b/apps/files_sharing/tests/API/ShareesTest.php index 673f69d25016..675ff3c5f176 100644 --- a/apps/files_sharing/tests/API/ShareesTest.php +++ b/apps/files_sharing/tests/API/ShareesTest.php @@ -1233,6 +1233,34 @@ public function dataGetRemote() { ] ] ], + // #16 check email property is matched for remote users + [ + 'user@example.com', + [ + [ + 'FN' => 'User3 @ Localhost', + ], + [ + 'FN' => 'User2 @ Localhost', + 'CLOUD' => [ + ], + ], + [ + 'FN' => 'User @ Localhost', + 'CLOUD' => [ + 'username@localhost', + ], + 'EMAIL' => 'user@example.com' + ], + ], + true, + [ + ['label' => 'User @ Localhost', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'username@localhost', 'server' => 'localhost']], + ['label' => 'user@example.com', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'user@example.com']] + ], + [], + true, + ], ]; } @@ -1248,6 +1276,11 @@ public function dataGetRemote() { * @param array $previousExact */ public function testGetRemote($searchTerm, $contacts, $shareeEnumeration, $exactExpected, $expected, $reachedEnd, $previousExact = []) { + + // Set the limit and offset for remote user searching + $this->invokePrivate($this->sharees, 'limit', [2]); + $this->invokePrivate($this->sharees, 'offset', [0]); + $this->config->expects($this->any()) ->method('getSystemValue') ->with('trusted_domains') @@ -1259,10 +1292,15 @@ public function testGetRemote($searchTerm, $contacts, $shareeEnumeration, $exact $this->invokePrivate($this->sharees, 'result', [$result]); } + $this->config->expects($this->once()) + ->method('getAppValue') + ->with('dav', 'remote_search_properties') + ->willReturn('EMAIL,CLOUD,FN'); + $this->invokePrivate($this->sharees, 'shareeEnumeration', [$shareeEnumeration]); $this->contactsManager->expects($this->any()) ->method('search') - ->with($searchTerm, ['CLOUD', 'FN']) + ->with($searchTerm, ['EMAIL', 'CLOUD', 'FN'], [], 2, 0) ->willReturn($contacts); $this->invokePrivate($this->sharees, 'getRemote', [$searchTerm]); diff --git a/core/Migrations/Version20170605143658.php b/core/Migrations/Version20170605143658.php new file mode 100644 index 000000000000..c750eeb943f1 --- /dev/null +++ b/core/Migrations/Version20170605143658.php @@ -0,0 +1,27 @@ +getTable("{$prefix}migrations"); + + // Check column length to see if migration is necessary necessary + if($table->getColumn('app')->getLength() === 177) { + // then this server was installed after the fix + return; + } + + // Need to shorten columns + $table->getColumn('app')->setLength(177); + $table->getColumn('version')->setLength(14); + } +} diff --git a/lib/private/ContactsManager.php b/lib/private/ContactsManager.php index 4ff8c67b6101..cb58159d66a8 100644 --- a/lib/private/ContactsManager.php +++ b/lib/private/ContactsManager.php @@ -26,7 +26,9 @@ namespace OC { - class ContactsManager implements \OCP\Contacts\IManager { + use OCP\Contacts\IManager; + + class ContactsManager implements IManager { /** * This function is used to search and find contacts within the users address books. @@ -35,13 +37,15 @@ class ContactsManager implements \OCP\Contacts\IManager { * @param string $pattern which should match within the $searchProperties * @param array $searchProperties defines the properties within the query pattern should match * @param array $options - for future use. One should always have options! + * @param int $limit + * @param int $offset * @return array an array of contacts which are arrays of key-value-pairs */ - public function search($pattern, $searchProperties = [], $options = []) { + public function search($pattern, $searchProperties = [], $options = [], $limit = 100, $offset = 0) { $this->loadAddressBooks(); $result = []; foreach($this->addressBooks as $addressBook) { - $r = $addressBook->search($pattern, $searchProperties, $options); + $r = $addressBook->search($pattern, $searchProperties, $options, $limit, $offset); $contacts = []; foreach($r as $c){ $c['addressbook-key'] = $addressBook->getKey(); diff --git a/lib/private/DB/MigrationService.php b/lib/private/DB/MigrationService.php index d38faa0443c6..b0ba9b64b6f5 100644 --- a/lib/private/DB/MigrationService.php +++ b/lib/private/DB/MigrationService.php @@ -114,8 +114,10 @@ private function createMigrationTable() { $tableName = $this->connection->getDatabasePlatform()->quoteIdentifier($tableName); $columns = [ - 'app' => new Column($this->connection->getDatabasePlatform()->quoteIdentifier('app'), Type::getType('string'), ['length' => 255]), - 'version' => new Column($this->connection->getDatabasePlatform()->quoteIdentifier('version'), Type::getType('string'), ['length' => 255]), + // Length = max indexable char length - length of other columns = 191 - 14 + 'app' => new Column($this->connection->getDatabasePlatform()->quoteIdentifier('app'), Type::getType('string'), ['length' => 177]), + // Datetime string. Eg: 20172605104128 + 'version' => new Column($this->connection->getDatabasePlatform()->quoteIdentifier('version'), Type::getType('string'), ['length' => 14]), ]; $table = new Table($tableName, $columns); $table->setPrimaryKey([ diff --git a/lib/private/Share/Share.php b/lib/private/Share/Share.php index 3e00ec845cf3..773de34b2bf9 100644 --- a/lib/private/Share/Share.php +++ b/lib/private/Share/Share.php @@ -2864,7 +2864,7 @@ private static function verifyPassword($password) { * @return Group[] */ private static function getGroupsForUser($user) { - $groups = \OC::$server->getGroupManager()->getUserIdGroups($user); + $groups = \OC::$server->getGroupManager()->getUserIdGroups($user, 'sharing'); return array_values(array_map(function(Group $g) { return $g->getGID(); }, $groups)); diff --git a/lib/public/Contacts/IManager.php b/lib/public/Contacts/IManager.php index 2a1657c2cdae..9f733af57e76 100644 --- a/lib/public/Contacts/IManager.php +++ b/lib/public/Contacts/IManager.php @@ -92,10 +92,12 @@ interface IManager { * @param string $pattern which should match within the $searchProperties * @param array $searchProperties defines the properties within the query pattern should match * @param array $options - for future use. One should always have options! + * @param int $limit + * @param int $offset * @return array an array of contacts which are arrays of key-value-pairs * @since 6.0.0 */ - function search($pattern, $searchProperties = [], $options = []); + function search($pattern, $searchProperties = [], $options = [], $limit = null, $offset = null); /** * This function can be used to delete the contact identified by the given id diff --git a/lib/public/IAddressBook.php b/lib/public/IAddressBook.php index 00d458340976..035a5c6c98c5 100644 --- a/lib/public/IAddressBook.php +++ b/lib/public/IAddressBook.php @@ -55,10 +55,12 @@ public function getDisplayName(); * @param string $pattern which should match within the $searchProperties * @param array $searchProperties defines the properties within the query pattern should match * @param array $options - for future use. One should always have options! + * @param int $limit + * @param int $offset * @return array an array of contacts which are arrays of key-value-pairs * @since 5.0.0 */ - public function search($pattern, $searchProperties, $options); + public function search($pattern, $searchProperties, $options, $limit = null, $offset = null); // // dummy results // return array( // array('id' => 0, 'FN' => 'Thomas Müller', 'EMAIL' => 'a@b.c', 'GEO' => '37.386013;-122.082932'), diff --git a/version.php b/version.php index 05c9bc9e25d4..b681411a0629 100644 --- a/version.php +++ b/version.php @@ -25,7 +25,7 @@ // We only can count up. The 4. digit is only for the internal patchlevel to trigger DB upgrades // between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel // when updating major/minor version number. -$OC_Version = [10, 0, 2, 1]; +$OC_Version = [10, 0, 2, 2]; // The human readable string $OC_VersionString = '10.0.2';