From df835ed3502ac1c283abffe00f35b150ecbefe9c Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Thu, 1 Jul 2021 17:26:00 +0200 Subject: Extend pending shares list to include remote shares And adjust the accept/decline actions to use the right endpoint for remote shares. Signed-off-by: Vincent Petry --- apps/files_sharing/js/app.js | 13 ++++++++++-- apps/files_sharing/js/sharedfilelist.js | 35 ++++++++++++++++++++++++++++++++- apps/files_sharing/src/share.js | 3 +++ 3 files changed, 48 insertions(+), 3 deletions(-) (limited to 'apps/files_sharing') diff --git a/apps/files_sharing/js/app.js b/apps/files_sharing/js/app.js index 713560e556b..c25ae7c8b1b 100644 --- a/apps/files_sharing/js/app.js +++ b/apps/files_sharing/js/app.js @@ -296,7 +296,11 @@ OCA.Sharing.App = { type: OCA.Files.FileActions.TYPE_INLINE, actionHandler(fileName, context) { const shareId = context.$file.data('shareId') - $.post(OC.linkToOCS('apps/files_sharing/api/v1/shares/pending', 2) + shareId) + let shareBase = 'shares/pending' + if (context.$file.attr('data-remote-id')) { + shareBase = 'remote_shares/pending' + } + $.post(OC.linkToOCS('apps/files_sharing/api/v1/' + shareBase, 2) + shareId) .success(function(result) { context.fileList.remove(context.fileInfoModel.attributes.name) }).fail(function() { @@ -313,8 +317,13 @@ OCA.Sharing.App = { type: OCA.Files.FileActions.TYPE_INLINE, actionHandler(fileName, context) { const shareId = context.$file.data('shareId') + let shareBase = 'shares' + if (context.$file.attr('data-remote-id')) { + shareBase = 'remote_shares/pending' + } + $.ajax({ - url: OC.linkToOCS('apps/files_sharing/api/v1/shares', 2) + shareId, + url: OC.linkToOCS('apps/files_sharing/api/v1/' + shareBase, 2) + shareId, type: 'DELETE', }).success(function(result) { context.fileList.remove(context.fileInfoModel.attributes.name) diff --git a/apps/files_sharing/js/sharedfilelist.js b/apps/files_sharing/js/sharedfilelist.js index 962f8a16915..a2239941d6b 100644 --- a/apps/files_sharing/js/sharedfilelist.js +++ b/apps/files_sharing/js/sharedfilelist.js @@ -212,6 +212,18 @@ } } + var pendingRemoteShares = { + url: OC.linkToOCS('apps/files_sharing/api/v1/remote_shares', 2) + 'pending', + /* jshint camelcase: false */ + data: { + format: 'json' + }, + type: 'GET', + beforeSend: function(xhr) { + xhr.setRequestHeader('OCS-APIREQUEST', 'true') + } + } + var shares = { url: OC.linkToOCS('apps/files_sharing/api/v1') + 'shares', /* jshint camelcase: false */ @@ -245,6 +257,7 @@ promises.push($.ajax(deletedShares)) } else if (this._showPending) { promises.push($.ajax(pendingShares)) + promises.push($.ajax(pendingRemoteShares)) } else { promises.push($.ajax(shares)) @@ -292,7 +305,12 @@ } if (additionalShares && additionalShares.ocs && additionalShares.ocs.data) { - files = files.concat(this._makeFilesFromShares(additionalShares.ocs.data, !this._sharedWithUser)) + if (this._showPending) { + // in this case the second callback is about pending remote shares + files = files.concat(this._makeFilesFromRemoteShares(additionalShares.ocs.data)) + } else { + files = files.concat(this._makeFilesFromShares(additionalShares.ocs.data, !this._sharedWithUser)) + } } this.setFiles(files) @@ -317,6 +335,21 @@ tags: share.tags || [] } + if (share.remote_id) { + // remote share + if (share.accepted !== '1') { + file.name = OC.basename(share.name) + file.path = '/' + } + file.remoteId = share.remote_id + file.shareOwnerId = share.owner + } + + if (!file.type) { + // pending shares usually have no type, so default to showing a directory icon + file.mimetype = 'httpd/unix-directory' + } + file.shares = [{ id: share.id, type: OC.Share.SHARE_TYPE_REMOTE diff --git a/apps/files_sharing/src/share.js b/apps/files_sharing/src/share.js index 385d42eaaea..ab41db7e8cd 100644 --- a/apps/files_sharing/src/share.js +++ b/apps/files_sharing/src/share.js @@ -104,6 +104,9 @@ import escapeHTML from 'escape-html' if (fileData.shareTypes) { tr.attr('data-share-types', fileData.shareTypes.join(',')) } + if (fileData.remoteId) { + tr.attr('data-remote-id', fileData.remoteId) + } return tr } -- cgit v1.2.3 From 08ab5d97dab170d642d98c0be20adf6757908315 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Fri, 2 Jul 2021 09:58:54 +0200 Subject: Fix received federated group shares Fix pending shares endpoint to consider user-specific sub-entries for group shares whenever a share was accepted or declined. Added unit test for adding remote group shares. Fixed "removeUserShares" to not send a remote request as we never send remote requests for group shares. Signed-off-by: Vincent Petry --- apps/files_sharing/lib/External/Manager.php | 50 ++++- apps/files_sharing/tests/External/ManagerTest.php | 226 ++++++++++++++-------- 2 files changed, 184 insertions(+), 92 deletions(-) (limited to 'apps/files_sharing') diff --git a/apps/files_sharing/lib/External/Manager.php b/apps/files_sharing/lib/External/Manager.php index e51bd64cf38..247be47ac90 100644 --- a/apps/files_sharing/lib/External/Manager.php +++ b/apps/files_sharing/lib/External/Manager.php @@ -571,15 +571,21 @@ class Manager { */ public function removeUserShares($uid): bool { try { + // TODO: use query builder $getShare = $this->connection->prepare(' - SELECT `remote`, `share_token`, `remote_id` + SELECT `id`, `remote`, `share_type`, `share_token`, `remote_id` FROM `*PREFIX*share_external` WHERE `user` = ?'); $result = $getShare->execute([$uid]); $shares = $result->fetchAll(); $result->closeCursor(); + $deletedGroupShares = []; foreach ($shares as $share) { - $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline'); + if ((int)$share['share_type'] === IShare::TYPE_GROUP) { + $deletedGroupShares[] = $share['id']; + } else { + $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline'); + } } $query = $this->connection->prepare(' @@ -588,6 +594,17 @@ class Manager { '); $deleteResult = $query->execute([$uid]); $deleteResult->closeCursor(); + + // delete sub-entries from deleted parents + foreach ($deletedGroupShares as $deletedId) { + // TODO: batch this with query builder + $query = $this->connection->prepare(' + DELETE FROM `*PREFIX*share_external` + WHERE `parent` = ? + '); + $deleteResult = $query->execute([$deletedId]); + $deleteResult->closeCursor(); + } } catch (\Doctrine\DBAL\Exception $ex) { return false; } @@ -629,14 +646,11 @@ class Manager { $userGroups[] = $group->getGID(); } - $query = 'SELECT `id`, `remote`, `remote_id`, `share_token`, `name`, `owner`, `user`, `mountpoint`, `accepted` + // FIXME: use query builder + $query = 'SELECT `id`, `share_type`, `parent`, `remote`, `remote_id`, `share_token`, `name`, `owner`, `user`, `mountpoint`, `accepted` FROM `*PREFIX*share_external` WHERE (`user` = ? OR `user` IN (?))'; - $parameters = [$this->uid, implode(',',$userGroups)]; - if (!is_null($accepted)) { - $query .= ' AND `accepted` = ?'; - $parameters[] = (int) $accepted; - } + $parameters = [$this->uid, implode(',', $userGroups)]; $query .= ' ORDER BY `id` ASC'; $sharesQuery = $this->connection->prepare($query); @@ -644,8 +658,26 @@ class Manager { $result = $sharesQuery->execute($parameters); $shares = $result->fetchAll(); $result->closeCursor(); - return $shares; + + // remove parent group share entry if we have a specific user share entry for the user + $toRemove = []; + foreach ($shares as $share) { + if ((int)$share['share_type'] === IShare::TYPE_GROUP && (int)$share['parent'] > 0) { + $toRemove[] = $share['parent']; + } + } + $shares = array_filter($shares, function ($share) use ($toRemove) { + return !in_array($share['id'], $toRemove, true); + }); + + if (!is_null($accepted)) { + $shares = array_filter($shares, function ($share) use ($accepted) { + return (bool)$share['accepted'] === $accepted; + }); + } + return array_values($shares); } catch (\Doctrine\DBAL\Exception $e) { + // FIXME return []; } } diff --git a/apps/files_sharing/tests/External/ManagerTest.php b/apps/files_sharing/tests/External/ManagerTest.php index 0098f67b2fb..850265cb9de 100644 --- a/apps/files_sharing/tests/External/ManagerTest.php +++ b/apps/files_sharing/tests/External/ManagerTest.php @@ -41,6 +41,7 @@ use OCP\Federation\ICloudFederationFactory; use OCP\Federation\ICloudFederationProviderManager; use OCP\Http\Client\IClientService; use OCP\Http\Client\IResponse; +use OCP\IGroup; use OCP\IGroupManager; use OCP\IUserManager; use OCP\Share\IShare; @@ -132,6 +133,23 @@ class ManagerTest extends TestCase { $this->testMountProvider = new MountProvider(\OC::$server->getDatabaseConnection(), function () { return $this->manager; }, new CloudIdManager($this->contactsManager)); + + $group1 = $this->createMock(IGroup::class); + $group1->expects($this->any())->method('getGID')->willReturn('group1'); + $group1->expects($this->any())->method('inGroup')->with($this->user)->willReturn(true); + + $this->userManager->expects($this->any())->method('get')->willReturn($this->user); + $this->groupManager->expects($this->any())->method(('getUserGroups'))->willReturn([$group1]); + $this->groupManager->expects($this->any())->method(('get'))->with('group1')->willReturn($group1); + } + + protected function tearDown(): void { + // clear the share external table to avoid side effects + $query = \OC::$server->getDatabaseConnection()->prepare('DELETE FROM `*PREFIX*share_external`'); + $result = $query->execute(); + $result->closeCursor(); + + parent::tearDown(); } private function setupMounts() { @@ -141,8 +159,8 @@ class ManagerTest extends TestCase { } } - public function testAddShare() { - $shareData1 = [ + public function testAddUserShare() { + $this->doTestAddShare([ 'remote' => 'http://localhost', 'token' => 'token1', 'password' => '', @@ -152,23 +170,41 @@ class ManagerTest extends TestCase { 'accepted' => false, 'user' => $this->uid, 'remoteId' => '2342' - ]; + ], false); + } + + public function testAddGroupShare() { + $this->doTestAddShare([ + 'remote' => 'http://localhost', + 'token' => 'token1', + 'password' => '', + 'name' => '/SharedFolder', + 'owner' => 'foobar', + 'shareType' => IShare::TYPE_GROUP, + 'accepted' => false, + 'user' => 'group1', + 'remoteId' => '2342' + ], true); + } + + public function doTestAddShare($shareData1, $isGroup = false) { $shareData2 = $shareData1; $shareData2['token'] = 'token2'; $shareData3 = $shareData1; $shareData3['token'] = 'token3'; - $this->userManager->expects($this->any())->method('get')->willReturn($this->user); - $this->groupManager->expects($this->any())->method(('getUserGroups'))->willReturn([]); - - $this->manager->expects($this->at(0))->method('tryOCMEndPoint')->with('http://localhost', 'token1', '2342', 'accept')->willReturn(false); - $this->manager->expects($this->at(1))->method('tryOCMEndPoint')->with('http://localhost', 'token3', '2342', 'decline')->willReturn(false); + if ($isGroup) { + $this->manager->expects($this->never())->method('tryOCMEndPoint'); + } else { + $this->manager->expects($this->at(0))->method('tryOCMEndPoint')->with('http://localhost', 'token1', '2342', 'accept')->willReturn(false); + $this->manager->expects($this->at(1))->method('tryOCMEndPoint')->with('http://localhost', 'token3', '2342', 'decline')->willReturn(false); + } // Add a share for "user" $this->assertSame(null, call_user_func_array([$this->manager, 'addShare'], $shareData1)); $openShares = $this->manager->getOpenShares(); $this->assertCount(1, $openShares); - $this->assertExternalShareEntry($shareData1, $openShares[0], 1, '{{TemporaryMountPointName#' . $shareData1['name'] . '}}'); + $this->assertExternalShareEntry($shareData1, $openShares[0], 1, '{{TemporaryMountPointName#' . $shareData1['name'] . '}}', $shareData1['user']); $this->setupMounts(); $this->assertNotMount('SharedFolder'); @@ -178,33 +214,35 @@ class ManagerTest extends TestCase { $this->assertSame(null, call_user_func_array([$this->manager, 'addShare'], $shareData2)); $openShares = $this->manager->getOpenShares(); $this->assertCount(2, $openShares); - $this->assertExternalShareEntry($shareData1, $openShares[0], 1, '{{TemporaryMountPointName#' . $shareData1['name'] . '}}'); + $this->assertExternalShareEntry($shareData1, $openShares[0], 1, '{{TemporaryMountPointName#' . $shareData1['name'] . '}}', $shareData1['user']); // New share falls back to "-1" appendix, because the name is already taken - $this->assertExternalShareEntry($shareData2, $openShares[1], 2, '{{TemporaryMountPointName#' . $shareData2['name'] . '}}-1'); + $this->assertExternalShareEntry($shareData2, $openShares[1], 2, '{{TemporaryMountPointName#' . $shareData2['name'] . '}}-1', $shareData2['user']); $this->setupMounts(); $this->assertNotMount('SharedFolder'); $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}'); $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}-1'); - $client = $this->getMockBuilder('OCP\Http\Client\IClient') - ->disableOriginalConstructor()->getMock(); - $this->clientService->expects($this->at(0)) - ->method('newClient') - ->willReturn($client); - $response = $this->createMock(IResponse::class); - $response->method('getBody') - ->willReturn(json_encode([ - 'ocs' => [ - 'meta' => [ - 'statuscode' => 200, + if (!$isGroup) { + $client = $this->getMockBuilder('OCP\Http\Client\IClient') + ->disableOriginalConstructor()->getMock(); + $this->clientService->expects($this->at(0)) + ->method('newClient') + ->willReturn($client); + $response = $this->createMock(IResponse::class); + $response->method('getBody') + ->willReturn(json_encode([ + 'ocs' => [ + 'meta' => [ + 'statuscode' => 200, + ] ] - ] - ])); - $client->expects($this->once()) - ->method('post') - ->with($this->stringStartsWith('http://localhost/ocs/v2.php/cloud/shares/' . $openShares[0]['remote_id']), $this->anything()) - ->willReturn($response); + ])); + $client->expects($this->once()) + ->method('post') + ->with($this->stringStartsWith('http://localhost/ocs/v2.php/cloud/shares/' . $openShares[0]['remote_id']), $this->anything()) + ->willReturn($response); + } // Accept the first share $this->manager->acceptShare($openShares[0]['id']); @@ -213,11 +251,11 @@ class ManagerTest extends TestCase { $acceptedShares = self::invokePrivate($this->manager, 'getShares', [true]); $this->assertCount(1, $acceptedShares); $shareData1['accepted'] = true; - $this->assertExternalShareEntry($shareData1, $acceptedShares[0], 1, $shareData1['name']); + $this->assertExternalShareEntry($shareData1, $acceptedShares[0], 1, $shareData1['name'], $this->uid); // Check remaining shares - Open $openShares = $this->manager->getOpenShares(); $this->assertCount(1, $openShares); - $this->assertExternalShareEntry($shareData2, $openShares[0], 2, '{{TemporaryMountPointName#' . $shareData2['name'] . '}}-1'); + $this->assertExternalShareEntry($shareData2, $openShares[0], 2, '{{TemporaryMountPointName#' . $shareData2['name'] . '}}-1', $shareData2['user']); $this->setupMounts(); $this->assertMount($shareData1['name']); @@ -228,33 +266,39 @@ class ManagerTest extends TestCase { $this->assertSame(null, call_user_func_array([$this->manager, 'addShare'], $shareData3)); $openShares = $this->manager->getOpenShares(); $this->assertCount(2, $openShares); - $this->assertExternalShareEntry($shareData2, $openShares[0], 2, '{{TemporaryMountPointName#' . $shareData2['name'] . '}}-1'); - // New share falls back to the original name (no "-\d", because the name is not taken) - $this->assertExternalShareEntry($shareData3, $openShares[1], 3, '{{TemporaryMountPointName#' . $shareData3['name'] . '}}'); + $this->assertExternalShareEntry($shareData2, $openShares[0], 2, '{{TemporaryMountPointName#' . $shareData2['name'] . '}}-1', $shareData2['user']); + if (!$isGroup) { + // New share falls back to the original name (no "-\d", because the name is not taken) + $this->assertExternalShareEntry($shareData3, $openShares[1], 3, '{{TemporaryMountPointName#' . $shareData3['name'] . '}}', $shareData3['user']); + } else { + $this->assertExternalShareEntry($shareData3, $openShares[1], 3, '{{TemporaryMountPointName#' . $shareData3['name'] . '}}-2', $shareData3['user']); + } $this->setupMounts(); $this->assertMount($shareData1['name']); $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}'); $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}-1'); - $client = $this->getMockBuilder('OCP\Http\Client\IClient') - ->disableOriginalConstructor()->getMock(); - $this->clientService->expects($this->at(0)) - ->method('newClient') - ->willReturn($client); - $response = $this->createMock(IResponse::class); - $response->method('getBody') - ->willReturn(json_encode([ - 'ocs' => [ - 'meta' => [ - 'statuscode' => 200, + if (!$isGroup) { + $client = $this->getMockBuilder('OCP\Http\Client\IClient') + ->disableOriginalConstructor()->getMock(); + $this->clientService->expects($this->at(0)) + ->method('newClient') + ->willReturn($client); + $response = $this->createMock(IResponse::class); + $response->method('getBody') + ->willReturn(json_encode([ + 'ocs' => [ + 'meta' => [ + 'statuscode' => 200, + ] ] - ] - ])); - $client->expects($this->once()) - ->method('post') - ->with($this->stringStartsWith('http://localhost/ocs/v2.php/cloud/shares/' . $openShares[1]['remote_id'] . '/decline'), $this->anything()) - ->willReturn($response); + ])); + $client->expects($this->once()) + ->method('post') + ->with($this->stringStartsWith('http://localhost/ocs/v2.php/cloud/shares/' . $openShares[1]['remote_id'] . '/decline'), $this->anything()) + ->willReturn($response); + } // Decline the third share $this->manager->declineShare($openShares[1]['id']); @@ -268,46 +312,62 @@ class ManagerTest extends TestCase { $acceptedShares = self::invokePrivate($this->manager, 'getShares', [true]); $this->assertCount(1, $acceptedShares); $shareData1['accepted'] = true; - $this->assertExternalShareEntry($shareData1, $acceptedShares[0], 1, $shareData1['name']); + $this->assertExternalShareEntry($shareData1, $acceptedShares[0], 1, $shareData1['name'], $this->uid); // Check remaining shares - Open $openShares = $this->manager->getOpenShares(); - $this->assertCount(1, $openShares); - $this->assertExternalShareEntry($shareData2, $openShares[0], 2, '{{TemporaryMountPointName#' . $shareData2['name'] . '}}-1'); + if ($isGroup) { + // declining a group share adds it back to pending instead of deleting it + $this->assertCount(2, $openShares); + // this is a group share that is still open + $this->assertExternalShareEntry($shareData2, $openShares[0], 2, '{{TemporaryMountPointName#' . $shareData2['name'] . '}}-1', $shareData2['user']); + // this is the user share sub-entry matching the group share which got declined + $this->assertExternalShareEntry($shareData3, $openShares[1], 2, '{{TemporaryMountPointName#' . $shareData3['name'] . '}}-2', $this->uid); + } else { + $this->assertCount(1, $openShares); + $this->assertExternalShareEntry($shareData2, $openShares[0], 2, '{{TemporaryMountPointName#' . $shareData2['name'] . '}}-1', $this->uid); + } $this->setupMounts(); $this->assertMount($shareData1['name']); $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}'); $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}-1'); - $client1 = $this->getMockBuilder('OCP\Http\Client\IClient') - ->disableOriginalConstructor()->getMock(); - $client2 = $this->getMockBuilder('OCP\Http\Client\IClient') - ->disableOriginalConstructor()->getMock(); - $this->clientService->expects($this->at(0)) - ->method('newClient') - ->willReturn($client1); - $this->clientService->expects($this->at(1)) - ->method('newClient') - ->willReturn($client2); - $response = $this->createMock(IResponse::class); - $response->method('getBody') - ->willReturn(json_encode([ - 'ocs' => [ - 'meta' => [ - 'statuscode' => 200, + if ($isGroup) { + // no http requests here + $this->manager->removeUserShares('group1'); + } else { + $client1 = $this->getMockBuilder('OCP\Http\Client\IClient') + ->disableOriginalConstructor()->getMock(); + $client2 = $this->getMockBuilder('OCP\Http\Client\IClient') + ->disableOriginalConstructor()->getMock(); + $this->clientService->expects($this->at(0)) + ->method('newClient') + ->willReturn($client1); + $this->clientService->expects($this->at(1)) + ->method('newClient') + ->willReturn($client2); + $response = $this->createMock(IResponse::class); + $response->method('getBody') + ->willReturn(json_encode([ + 'ocs' => [ + 'meta' => [ + 'statuscode' => 200, + ] ] - ] - ])); - $client1->expects($this->once()) - ->method('post') - ->with($this->stringStartsWith('http://localhost/ocs/v2.php/cloud/shares/' . $openShares[0]['remote_id'] . '/decline'), $this->anything()) - ->willReturn($response); - $client2->expects($this->once()) - ->method('post') - ->with($this->stringStartsWith('http://localhost/ocs/v2.php/cloud/shares/' . $acceptedShares[0]['remote_id'] . '/decline'), $this->anything()) - ->willReturn($response); - - $this->manager->removeUserShares($this->uid); + ])); + + $client1->expects($this->once()) + ->method('post') + ->with($this->stringStartsWith('http://localhost/ocs/v2.php/cloud/shares/' . $openShares[0]['remote_id'] . '/decline'), $this->anything()) + ->willReturn($response); + $client2->expects($this->once()) + ->method('post') + ->with($this->stringStartsWith('http://localhost/ocs/v2.php/cloud/shares/' . $acceptedShares[0]['remote_id'] . '/decline'), $this->anything()) + ->willReturn($response); + + $this->manager->removeUserShares($this->uid); + } + $this->assertEmpty(self::invokePrivate($this->manager, 'getShares', [null]), 'Asserting all shares for the user have been deleted'); $this->mountManager->clear(); @@ -323,13 +383,13 @@ class ManagerTest extends TestCase { * @param int $share * @param string $mountPoint */ - protected function assertExternalShareEntry($expected, $actual, $share, $mountPoint) { + protected function assertExternalShareEntry($expected, $actual, $share, $mountPoint, $targetEntity) { $this->assertEquals($expected['remote'], $actual['remote'], 'Asserting remote of a share #' . $share); $this->assertEquals($expected['token'], $actual['share_token'], 'Asserting token of a share #' . $share); $this->assertEquals($expected['name'], $actual['name'], 'Asserting name of a share #' . $share); $this->assertEquals($expected['owner'], $actual['owner'], 'Asserting owner of a share #' . $share); $this->assertEquals($expected['accepted'], (int) $actual['accepted'], 'Asserting accept of a share #' . $share); - $this->assertEquals($expected['user'], $actual['user'], 'Asserting user of a share #' . $share); + $this->assertEquals($targetEntity, $actual['user'], 'Asserting user of a share #' . $share); $this->assertEquals($mountPoint, $actual['mountpoint'], 'Asserting mountpoint of a share #' . $share); } -- cgit v1.2.3 From 13781b0425391655daf94067a937d92103145a7d Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Fri, 2 Jul 2021 17:42:55 +0200 Subject: Remove "Reject share" for pending remote shares In the list of pending shares, the option for rejecting the share has been removed. Signed-off-by: Vincent Petry --- apps/files/js/fileactions.js | 8 ++++++++ apps/files_sharing/js/app.js | 6 ++++++ apps/files_sharing/js/sharedfilelist.js | 4 ++++ apps/files_sharing/src/share.js | 3 --- 4 files changed, 18 insertions(+), 3 deletions(-) (limited to 'apps/files_sharing') diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js index c7883e4d2a6..5d9e8578e19 100644 --- a/apps/files/js/fileactions.js +++ b/apps/files/js/fileactions.js @@ -155,6 +155,9 @@ if (_.isFunction(action.render)) { actionSpec.render = action.render; } + if (_.isFunction(action.shouldRender)) { + actionSpec.shouldRender = action.shouldRender; + } if (!this.actions[mime]) { this.actions[mime] = {}; } @@ -397,6 +400,11 @@ * @param {OCA.Files.FileActionContext} context rendering context */ _renderInlineAction: function(actionSpec, isDefault, context) { + if (actionSpec.shouldRender) { + if (!actionSpec.shouldRender(context)) { + return; + } + } var renderFunc = actionSpec.render || _.bind(this._defaultRenderAction, this); var $actionEl = renderFunc(actionSpec, isDefault, context); if (!$actionEl || !$actionEl.length) { diff --git a/apps/files_sharing/js/app.js b/apps/files_sharing/js/app.js index c25ae7c8b1b..64db7fac692 100644 --- a/apps/files_sharing/js/app.js +++ b/apps/files_sharing/js/app.js @@ -315,6 +315,12 @@ OCA.Sharing.App = { permissions: OC.PERMISSION_ALL, iconClass: 'icon-close', type: OCA.Files.FileActions.TYPE_INLINE, + shouldRender(context) { + if (context.$file.attr('data-remote-id')) { + return false + } + return true + }, actionHandler(fileName, context) { const shareId = context.$file.data('shareId') let shareBase = 'shares' diff --git a/apps/files_sharing/js/sharedfilelist.js b/apps/files_sharing/js/sharedfilelist.js index a2239941d6b..bffc71d7a07 100644 --- a/apps/files_sharing/js/sharedfilelist.js +++ b/apps/files_sharing/js/sharedfilelist.js @@ -96,6 +96,10 @@ $tr.attr('data-share-permissions', permission) } + if (fileData.remoteId) { + $tr.attr('data-remote-id', fileData.remoteId) + } + // add row with expiration date for link only shares - influenced by _createRow of filelist if (this._linksOnly) { var expirationTimestamp = 0 diff --git a/apps/files_sharing/src/share.js b/apps/files_sharing/src/share.js index ab41db7e8cd..385d42eaaea 100644 --- a/apps/files_sharing/src/share.js +++ b/apps/files_sharing/src/share.js @@ -104,9 +104,6 @@ import escapeHTML from 'escape-html' if (fileData.shareTypes) { tr.attr('data-share-types', fileData.shareTypes.join(',')) } - if (fileData.remoteId) { - tr.attr('data-remote-id', fileData.remoteId) - } return tr } -- cgit v1.2.3 From 4d0f0095eafe53f275e3a39b857212d89461f8e9 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Fri, 2 Jul 2021 17:45:03 +0200 Subject: Add logging to external shares manager Instead of just returning false, also log the exception to make debugging database issues easier. Signed-off-by: Vincent Petry --- .../lib/OCM/CloudFederationProviderFiles.php | 3 +- apps/files_sharing/lib/AppInfo/Application.php | 3 +- apps/files_sharing/lib/External/Manager.php | 37 ++++++++++++++-------- apps/files_sharing/lib/Hooks.php | 3 +- apps/files_sharing/tests/External/ManagerTest.php | 2 ++ 5 files changed, 32 insertions(+), 16 deletions(-) (limited to 'apps/files_sharing') diff --git a/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php b/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php index 26b0288c354..d9ae9f6d733 100644 --- a/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php +++ b/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php @@ -251,7 +251,8 @@ class CloudFederationProviderFiles implements ICloudFederationProvider { \OC::$server->getGroupManager(), \OC::$server->getUserManager(), $shareWith, - \OC::$server->query(IEventDispatcher::class) + \OC::$server->query(IEventDispatcher::class), + \OC::$server->getLogger() ); try { diff --git a/apps/files_sharing/lib/AppInfo/Application.php b/apps/files_sharing/lib/AppInfo/Application.php index 7f234e63660..3975a8a3bde 100644 --- a/apps/files_sharing/lib/AppInfo/Application.php +++ b/apps/files_sharing/lib/AppInfo/Application.php @@ -98,7 +98,8 @@ class Application extends App { $server->getGroupManager(), $server->getUserManager(), $uid, - $server->query(IEventDispatcher::class) + $server->query(IEventDispatcher::class), + $server->getLogger() ); }); diff --git a/apps/files_sharing/lib/External/Manager.php b/apps/files_sharing/lib/External/Manager.php index 247be47ac90..de566692d6b 100644 --- a/apps/files_sharing/lib/External/Manager.php +++ b/apps/files_sharing/lib/External/Manager.php @@ -14,6 +14,7 @@ * @author Robin Appelman * @author Roeland Jago Douma * @author Stefan Weil + * @author Vincent Petry * * @license AGPL-3.0 * @@ -44,6 +45,7 @@ use OCP\Files\Storage\IStorageFactory; use OCP\Http\Client\IClientService; use OCP\IDBConnection; use OCP\IGroupManager; +use OCP\ILogger; use OCP\IUserManager; use OCP\Notification\IManager; use OCP\OCS\IDiscoveryService; @@ -89,18 +91,24 @@ class Manager { /** @var IEventDispatcher */ private $eventDispatcher; - public function __construct(IDBConnection $connection, - \OC\Files\Mount\Manager $mountManager, - IStorageFactory $storageLoader, - IClientService $clientService, - IManager $notificationManager, - IDiscoveryService $discoveryService, - ICloudFederationProviderManager $cloudFederationProviderManager, - ICloudFederationFactory $cloudFederationFactory, - IGroupManager $groupManager, - IUserManager $userManager, - ?string $uid, - IEventDispatcher $eventDispatcher) { + /** @var ILogger */ + private $logger; + + public function __construct( + IDBConnection $connection, + \OC\Files\Mount\Manager $mountManager, + IStorageFactory $storageLoader, + IClientService $clientService, + IManager $notificationManager, + IDiscoveryService $discoveryService, + ICloudFederationProviderManager $cloudFederationProviderManager, + ICloudFederationFactory $cloudFederationFactory, + IGroupManager $groupManager, + IUserManager $userManager, + ?string $uid, + IEventDispatcher $eventDispatcher, + ILogger $logger + ) { $this->connection = $connection; $this->mountManager = $mountManager; $this->storageLoader = $storageLoader; @@ -113,6 +121,7 @@ class Manager { $this->groupManager = $groupManager; $this->userManager = $userManager; $this->eventDispatcher = $eventDispatcher; + $this->logger = $logger; } /** @@ -535,6 +544,7 @@ class Manager { $this->removeReShares($id); } catch (\Doctrine\DBAL\Exception $ex) { + $this->logger->logException($ex); return false; } @@ -606,6 +616,7 @@ class Manager { $deleteResult->closeCursor(); } } catch (\Doctrine\DBAL\Exception $ex) { + $this->logger->logException($ex); return false; } @@ -677,7 +688,7 @@ class Manager { } return array_values($shares); } catch (\Doctrine\DBAL\Exception $e) { - // FIXME + $this->logger->logException($e); return []; } } diff --git a/apps/files_sharing/lib/Hooks.php b/apps/files_sharing/lib/Hooks.php index ff4ca59339a..26e799297ff 100644 --- a/apps/files_sharing/lib/Hooks.php +++ b/apps/files_sharing/lib/Hooks.php @@ -43,7 +43,8 @@ class Hooks { \OC::$server->getGroupManager(), \OC::$server->getUserManager(), $params['uid'], - \OC::$server->query(IEventDispatcher::class) + \OC::$server->query(IEventDispatcher::class), + \OC::$server->getLogger() ); $manager->removeUserShares($params['uid']); diff --git a/apps/files_sharing/tests/External/ManagerTest.php b/apps/files_sharing/tests/External/ManagerTest.php index 850265cb9de..b80b9d04e18 100644 --- a/apps/files_sharing/tests/External/ManagerTest.php +++ b/apps/files_sharing/tests/External/ManagerTest.php @@ -43,6 +43,7 @@ use OCP\Http\Client\IClientService; use OCP\Http\Client\IResponse; use OCP\IGroup; use OCP\IGroupManager; +use OCP\ILogger; use OCP\IUserManager; use OCP\Share\IShare; use Test\Traits\UserTrait; @@ -127,6 +128,7 @@ class ManagerTest extends TestCase { $this->userManager, $this->uid, $this->eventDispatcher, + $this->createMock(ILogger::class), ] )->setMethods(['tryOCMEndPoint'])->getMock(); -- cgit v1.2.3 From 37a65237ea1bfbfdc33c8af250682267327489c3 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Fri, 2 Jul 2021 17:46:16 +0200 Subject: Fix re-accepting or re-rejecting remote group shares When accepting a group share, a sub-share entry is created which also has a different id. When accepting or rejecting the sub-share, simply update the "accepted" flag instead of trying to re-insert the entry. Adjust getShare to also properly validate group share membership when called on a sub-share id. Signed-off-by: Vincent Petry --- apps/files_sharing/lib/External/Manager.php | 139 +++++++++++++++++++--------- 1 file changed, 97 insertions(+), 42 deletions(-) (limited to 'apps/files_sharing') diff --git a/apps/files_sharing/lib/External/Manager.php b/apps/files_sharing/lib/External/Manager.php index de566692d6b..b67ca675c31 100644 --- a/apps/files_sharing/lib/External/Manager.php +++ b/apps/files_sharing/lib/External/Manager.php @@ -227,7 +227,7 @@ class Manager { * @param int $id share id * @return mixed share of false */ - public function getShare($id) { + private function fetchShare($id) { $getShare = $this->connection->prepare(' SELECT `id`, `remote`, `remote_id`, `share_token`, `name`, `owner`, `user`, `mountpoint`, `accepted`, `parent`, `share_type`, `password`, `mountpoint_hash` FROM `*PREFIX*share_external` @@ -235,14 +235,32 @@ class Manager { $result = $getShare->execute([$id]); $share = $result->fetch(); $result->closeCursor(); + return $share; + } + + /** + * get share + * + * @param int $id share id + * @return mixed share of false + */ + public function getShare($id) { + $share = $this->fetchShare($id); $validShare = is_array($share) && isset($share['share_type']) && isset($share['user']); // check if the user is allowed to access it if ($validShare && (int)$share['share_type'] === IShare::TYPE_USER && $share['user'] === $this->uid) { return $share; } elseif ($validShare && (int)$share['share_type'] === IShare::TYPE_GROUP) { + $parentId = (int)$share['parent']; + if ($parentId !== -1) { + // we just retrieved a sub-share, switch to the parent entry for verification + $groupShare = $this->fetchShare($parentId); + } else { + $groupShare = $share; + } $user = $this->userManager->get($this->uid); - if ($this->groupManager->get($share['user'])->inGroup($user)) { + if ($this->groupManager->get($groupShare['user'])->inGroup($user)) { return $share; } } @@ -250,6 +268,20 @@ class Manager { return false; } + /** + * Updates accepted flag in the database + * + * @param int $id + */ + private function updateAccepted(int $shareId, bool $accepted) : void { + $query = $this->connection->prepare(' + UPDATE `*PREFIX*share_external` + SET `accepted` = ? + WHERE `id` = ?'); + $updateResult = $query->execute([$accepted ? 1 : 0, $shareId]); + $updateResult->closeCursor(); + } + /** * accept server-to-server share * @@ -277,21 +309,34 @@ class Manager { WHERE `id` = ? AND `user` = ?'); $userShareAccepted = $acceptShare->execute([1, $mountPoint, $hash, $id, $this->uid]); } else { - try { - $this->writeShareToDb( - $share['remote'], - $share['share_token'], - $share['password'], - $share['name'], - $share['owner'], - $this->uid, - $mountPoint, $hash, 1, - $share['remote_id'], - $id, - $share['share_type']); - $result = true; - } catch (Exception $e) { - $result = false; + $parentId = (int)$share['parent']; + if ($parentId !== -1) { + // this is the sub-share, simply update it to re-accept + try { + $this->updateAccepted((int)$share['id'], true); + $result = true; + } catch (Exception $e) { + $this->logger->logException($e); + $result = false; + } + } else { + try { + $this->writeShareToDb( + $share['remote'], + $share['share_token'], + $share['password'], + $share['name'], + $share['owner'], + $this->uid, + $mountPoint, $hash, 1, + $share['remote_id'], + $id, + $share['share_type']); + $result = true; + } catch (Exception $e) { + $this->logger->logException($e); + $result = false; + } } } if ($userShareAccepted !== false) { @@ -327,23 +372,38 @@ class Manager { $this->processNotification($id); $result = true; } elseif ($share && (int)$share['share_type'] === IShare::TYPE_GROUP) { - try { - $this->writeShareToDb( - $share['remote'], - $share['share_token'], - $share['password'], - $share['name'], - $share['owner'], - $this->uid, - $share['mountpoint'], - $share['mountpoint_hash'], - 0, - $share['remote_id'], - $id, - $share['share_type']); - $result = true; - } catch (Exception $e) { - $result = false; + $parent = (int)$share['parent']; + // can only decline an already accepted/mounted group share, + // check if this is the sub-share entry + if ($parent !== -1) { + try { + // this is the sub-share, simply update it to decline + $this->updateAccepted((int)$share['id'], false); + $result = true; + } catch (Exception $e) { + $this->logger->logException($e); + $result = false; + } + } else { + try { + $this->writeShareToDb( + $share['remote'], + $share['share_token'], + $share['password'], + $share['name'], + $share['owner'], + $this->uid, + $share['mountpoint'], + $share['mountpoint_hash'], + 0, + $share['remote_id'], + $id, + $share['share_type']); + $result = true; + } catch (Exception $e) { + $this->logger->logException($e); + $result = false; + } } $this->processNotification($id); } @@ -528,18 +588,13 @@ class Manager { } $query = $this->connection->prepare(' - DELETE FROM `*PREFIX*share_external` - WHERE `id` = ? + DELETE FROM `*PREFIX*share_external` + WHERE `id` = ? '); $deleteResult = $query->execute([(int)$share['id']]); $deleteResult->closeCursor(); } elseif ($share !== false && (int)$share['share_type'] === IShare::TYPE_GROUP) { - $query = $this->connection->prepare(' - UPDATE `*PREFIX*share_external` - SET `accepted` = ? - WHERE `id` = ?'); - $updateResult = $query->execute([0, (int)$share['id']]); - $updateResult->closeCursor(); + $this->updateAccepted((int)$share['id'], false); } $this->removeReShares($id); -- cgit v1.2.3 From 9c0e1d8252b15ec6319cabf3b0b46f19aa015669 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Fri, 2 Jul 2021 18:16:34 +0200 Subject: Pending remote group share fixes Only remove reject share for remote group shares Also fix share indicator to appear for remote group shares as well. Fix pending remote share icon to be the one of a share. Signed-off-by: Vincent Petry --- apps/files_sharing/js/app.js | 4 +++- apps/files_sharing/js/sharedfilelist.js | 8 +++++++- apps/files_sharing/src/share.js | 2 ++ 3 files changed, 12 insertions(+), 2 deletions(-) (limited to 'apps/files_sharing') diff --git a/apps/files_sharing/js/app.js b/apps/files_sharing/js/app.js index 64db7fac692..b384946e5c8 100644 --- a/apps/files_sharing/js/app.js +++ b/apps/files_sharing/js/app.js @@ -316,7 +316,9 @@ OCA.Sharing.App = { iconClass: 'icon-close', type: OCA.Files.FileActions.TYPE_INLINE, shouldRender(context) { - if (context.$file.attr('data-remote-id')) { + // disable rejecting group shares from the pending list because they anyway + // land back into that same list + if (context.$file.attr('data-remote-id') && parseInt(context.$file.attr('data-share-type'), 10) === OC.Share.SHARE_TYPE_REMOTE_GROUP) { return false } return true diff --git a/apps/files_sharing/js/sharedfilelist.js b/apps/files_sharing/js/sharedfilelist.js index bffc71d7a07..17c72db1ffc 100644 --- a/apps/files_sharing/js/sharedfilelist.js +++ b/apps/files_sharing/js/sharedfilelist.js @@ -100,6 +100,10 @@ $tr.attr('data-remote-id', fileData.remoteId) } + if (fileData.shareType) { + $tr.attr('data-share-type', fileData.shareType) + } + // add row with expiration date for link only shares - influenced by _createRow of filelist if (this._linksOnly) { var expirationTimestamp = 0 @@ -333,6 +337,8 @@ mtime: share.mtime * 1000, mimetype: share.mimetype, type: share.type, + // remote share types are different and need to be mapped + shareType: (parseInt(share.share_type, 10) === 1) ? OC.Share.SHARE_TYPE_REMOTE_GROUP : OC.Share.SHARE_TYPE_REMOTE, id: share.file_id, path: OC.dirname(share.mountpoint), permissions: share.permissions, @@ -351,7 +357,7 @@ if (!file.type) { // pending shares usually have no type, so default to showing a directory icon - file.mimetype = 'httpd/unix-directory' + file.mimetype = 'dir-shared' } file.shares = [{ diff --git a/apps/files_sharing/src/share.js b/apps/files_sharing/src/share.js index 385d42eaaea..bb160f8715d 100644 --- a/apps/files_sharing/src/share.js +++ b/apps/files_sharing/src/share.js @@ -181,6 +181,8 @@ import escapeHTML from 'escape-html' hasShares = true } else if (shareType === OC.Share.SHARE_TYPE_REMOTE) { hasShares = true + } else if (shareType === OC.Share.SHARE_TYPE_REMOTE_GROUP) { + hasShares = true } else if (shareType === OC.Share.SHARE_TYPE_CIRCLE) { hasShares = true } else if (shareType === OC.Share.SHARE_TYPE_ROOM) { -- cgit v1.2.3 From dde4c9d9dfd5e4f241b0464667c0f2410d9851ed Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Mon, 5 Jul 2021 12:13:53 +0200 Subject: Disable default actions in pending file list Signed-off-by: Vincent Petry --- apps/files/js/gotoplugin.js | 5 +++++ apps/files_sharing/js/app.js | 2 ++ apps/files_sharing/src/share.js | 4 ++++ 3 files changed, 11 insertions(+) (limited to 'apps/files_sharing') diff --git a/apps/files/js/gotoplugin.js b/apps/files/js/gotoplugin.js index 4793420ed2d..d686c7850cf 100644 --- a/apps/files/js/gotoplugin.js +++ b/apps/files/js/gotoplugin.js @@ -29,6 +29,11 @@ if (this.disallowedLists.indexOf(fileList.id) !== -1) { return; } + // lists where the "Open" default action is disabled should + // also have the goto action disabled + if (fileList._defaultFileActionsDisabled) { + return + } var fileActions = fileList.fileActions; fileActions.registerAction({ diff --git a/apps/files_sharing/js/app.js b/apps/files_sharing/js/app.js index b384946e5c8..89f72ee8209 100644 --- a/apps/files_sharing/js/app.js +++ b/apps/files_sharing/js/app.js @@ -141,6 +141,8 @@ OCA.Sharing.App = { { id: 'shares.pending', showPending: true, + detailsViewEnabled: false, + defaultFileActionsDisabled: true, sharedWithUser: true, fileActions: this._acceptShareAction(), config: OCA.Files.App.getFilesConfig(), diff --git a/apps/files_sharing/src/share.js b/apps/files_sharing/src/share.js index bb160f8715d..a7660a29f58 100644 --- a/apps/files_sharing/src/share.js +++ b/apps/files_sharing/src/share.js @@ -232,6 +232,10 @@ import escapeHTML from 'escape-html' }, type: OCA.Files.FileActions.TYPE_INLINE, actionHandler: function(fileName, context) { + // details view disabled in some share lists + if (!fileList._detailsView) { + return + } // do not open sidebar if permission is set and equal to 0 var permissions = parseInt(context.$file.data('share-permissions'), 10) if (isNaN(permissions) || permissions > 0) { -- cgit v1.2.3 From 701de2385742245628296428b63677fd64d9f87a Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Mon, 5 Jul 2021 16:07:02 +0200 Subject: Fix remote group share API interactions Accepting and declining can now be done repeatedly on both the parent group share and sub-share with the same effects. Added unit tests to cover these cases, and also when the same operation is repeated. Signed-off-by: Vincent Petry --- apps/files_sharing/lib/External/Manager.php | 44 ++++-- apps/files_sharing/tests/External/ManagerTest.php | 166 +++++++++++++++++++++- 2 files changed, 199 insertions(+), 11 deletions(-) (limited to 'apps/files_sharing') diff --git a/apps/files_sharing/lib/External/Manager.php b/apps/files_sharing/lib/External/Manager.php index b67ca675c31..082e1adef8a 100644 --- a/apps/files_sharing/lib/External/Manager.php +++ b/apps/files_sharing/lib/External/Manager.php @@ -238,6 +238,20 @@ class Manager { return $share; } + private function fetchUserShare($parentId, $uid) { + $getShare = $this->connection->prepare(' + SELECT `id`, `remote`, `remote_id`, `share_token`, `name`, `owner`, `user`, `mountpoint`, `accepted`, `parent`, `share_type`, `password`, `mountpoint_hash` + FROM `*PREFIX*share_external` + WHERE `parent` = ? AND `user` = ?'); + $result = $getShare->execute([$parentId, $uid]); + $share = $result->fetch(); + $result->closeCursor(); + if ($share !== false) { + return $share; + } + return null; + } + /** * get share * @@ -311,9 +325,15 @@ class Manager { } else { $parentId = (int)$share['parent']; if ($parentId !== -1) { - // this is the sub-share, simply update it to re-accept + // this is the sub-share + $subshare = $share; + } else { + $subshare = $this->fetchUserShare($id, $this->uid); + } + + if ($subshare !== null) { try { - $this->updateAccepted((int)$share['id'], true); + $this->updateAccepted((int)$subshare['id'], true); $result = true; } catch (Exception $e) { $this->logger->logException($e); @@ -372,13 +392,17 @@ class Manager { $this->processNotification($id); $result = true; } elseif ($share && (int)$share['share_type'] === IShare::TYPE_GROUP) { - $parent = (int)$share['parent']; - // can only decline an already accepted/mounted group share, - // check if this is the sub-share entry - if ($parent !== -1) { + $parentId = (int)$share['parent']; + if ($parentId !== -1) { + // this is the sub-share + $subshare = $share; + } else { + $subshare = $this->fetchUserShare($id, $this->uid); + } + + if ($subshare !== null) { try { - // this is the sub-share, simply update it to decline - $this->updateAccepted((int)$share['id'], false); + $this->updateAccepted((int)$subshare['id'], false); $result = true; } catch (Exception $e) { $this->logger->logException($e); @@ -566,6 +590,10 @@ class Manager { public function removeShare($mountPoint): bool { $mountPointObj = $this->mountManager->find($mountPoint); + if ($mountPointObj === null) { + $this->logger->error('Mount point to remove share not found', ['mountPoint' => $mountPoint]); + return false; + } $id = $mountPointObj->getStorage()->getCache()->getId(''); $mountPoint = $this->stripPath($mountPoint); diff --git a/apps/files_sharing/tests/External/ManagerTest.php b/apps/files_sharing/tests/External/ManagerTest.php index b80b9d04e18..2340d057dd7 100644 --- a/apps/files_sharing/tests/External/ManagerTest.php +++ b/apps/files_sharing/tests/External/ManagerTest.php @@ -113,6 +113,9 @@ class ManagerTest extends TestCase { ->method('search') ->willReturn([]); + $logger = $this->createMock(ILogger::class); + $logger->expects($this->never())->method('logException'); + $this->manager = $this->getMockBuilder(Manager::class) ->setConstructorArgs( [ @@ -128,7 +131,7 @@ class ManagerTest extends TestCase { $this->userManager, $this->uid, $this->eventDispatcher, - $this->createMock(ILogger::class), + $logger, ] )->setMethods(['tryOCMEndPoint'])->getMock(); @@ -155,6 +158,7 @@ class ManagerTest extends TestCase { } private function setupMounts() { + $this->mountManager->clear(); $mounts = $this->testMountProvider->getMountsForUser($this->user, new StorageFactory()); foreach ($mounts as $mount) { $this->mountManager->addMount($mount); @@ -247,7 +251,7 @@ class ManagerTest extends TestCase { } // Accept the first share - $this->manager->acceptShare($openShares[0]['id']); + $this->assertTrue($this->manager->acceptShare($openShares[0]['id'])); // Check remaining shares - Accepted $acceptedShares = self::invokePrivate($this->manager, 'getShares', [true]); @@ -303,7 +307,7 @@ class ManagerTest extends TestCase { } // Decline the third share - $this->manager->declineShare($openShares[1]['id']); + $this->assertTrue($this->manager->declineShare($openShares[1]['id'])); $this->setupMounts(); $this->assertMount($shareData1['name']); @@ -379,6 +383,162 @@ class ManagerTest extends TestCase { $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}-1'); } + private function verifyAcceptedGroupShare($shareData) { + $openShares = $this->manager->getOpenShares(); + $this->assertCount(0, $openShares); + $acceptedShares = self::invokePrivate($this->manager, 'getShares', [true]); + $this->assertCount(1, $acceptedShares); + $shareData['accepted'] = true; + $this->assertExternalShareEntry($shareData, $acceptedShares[0], 0, $shareData['name'], $this->uid); + $this->setupMounts(); + $this->assertMount($shareData['name']); + } + + private function verifyDeclinedGroupShare($shareData, $tempMount = null) { + if ($tempMount === null) { + $tempMount = '{{TemporaryMountPointName#/SharedFolder}}'; + } + $openShares = $this->manager->getOpenShares(); + $this->assertCount(1, $openShares); + $acceptedShares = self::invokePrivate($this->manager, 'getShares', [true]); + $this->assertCount(0, $acceptedShares); + $this->assertExternalShareEntry($shareData, $openShares[0], 0, $tempMount, $this->uid); + $this->setupMounts(); + $this->assertNotMount($shareData['name']); + $this->assertNotMount($tempMount); + } + + private function createTestGroupShare() { + $shareData = [ + 'remote' => 'http://localhost', + 'token' => 'token1', + 'password' => '', + 'name' => '/SharedFolder', + 'owner' => 'foobar', + 'shareType' => IShare::TYPE_GROUP, + 'accepted' => false, + 'user' => 'group1', + 'remoteId' => '2342' + ]; + + $this->assertSame(null, call_user_func_array([$this->manager, 'addShare'], $shareData)); + + $allShares = self::invokePrivate($this->manager, 'getShares', [null]); + $this->assertCount(1, $allShares); + + // this will hold the main group entry + $groupShare = $allShares[0]; + + return [$shareData, $groupShare]; + } + + public function testAcceptOriginalGroupShare() { + [$shareData, $groupShare] = $this->createTestGroupShare(); + $this->assertTrue($this->manager->acceptShare($groupShare['id'])); + $this->verifyAcceptedGroupShare($shareData); + + // a second time + $this->assertTrue($this->manager->acceptShare($groupShare['id'])); + $this->verifyAcceptedGroupShare($shareData); + } + + public function testAcceptGroupShareAgainThroughGroupShare() { + [$shareData, $groupShare] = $this->createTestGroupShare(); + $this->assertTrue($this->manager->acceptShare($groupShare['id'])); + $this->verifyAcceptedGroupShare($shareData); + + // decline again, this keeps the sub-share + $this->assertTrue($this->manager->declineShare($groupShare['id'])); + $this->verifyDeclinedGroupShare($shareData, '/SharedFolder'); + + // this will return sub-entries + $openShares = $this->manager->getOpenShares(); + + // accept through sub-share + $this->assertTrue($this->manager->acceptShare($groupShare['id'])); + $this->verifyAcceptedGroupShare($shareData, '/SharedFolder'); + + // accept a second time + $this->assertTrue($this->manager->acceptShare($groupShare['id'])); + $this->verifyAcceptedGroupShare($shareData, '/SharedFolder'); + } + + public function testAcceptGroupShareAgainThroughSubShare() { + [$shareData, $groupShare] = $this->createTestGroupShare(); + $this->assertTrue($this->manager->acceptShare($groupShare['id'])); + $this->verifyAcceptedGroupShare($shareData); + + // decline again, this keeps the sub-share + $this->assertTrue($this->manager->declineShare($groupShare['id'])); + $this->verifyDeclinedGroupShare($shareData, '/SharedFolder'); + + // this will return sub-entries + $openShares = $this->manager->getOpenShares(); + + // accept through sub-share + $this->assertTrue($this->manager->acceptShare($openShares[0]['id'])); + $this->verifyAcceptedGroupShare($shareData); + + // accept a second time + $this->assertTrue($this->manager->acceptShare($openShares[0]['id'])); + $this->verifyAcceptedGroupShare($shareData); + } + + public function testDeclineOriginalGroupShare() { + [$shareData, $groupShare] = $this->createTestGroupShare(); + $this->assertTrue($this->manager->declineShare($groupShare['id'])); + $this->verifyDeclinedGroupShare($shareData); + + // a second time + $this->assertTrue($this->manager->declineShare($groupShare['id'])); + $this->verifyDeclinedGroupShare($shareData); + } + + public function testDeclineGroupShareAgainThroughGroupShare() { + [$shareData, $groupShare] = $this->createTestGroupShare(); + $this->assertTrue($this->manager->acceptShare($groupShare['id'])); + $this->verifyAcceptedGroupShare($shareData); + + // decline again, this keeps the sub-share + $this->assertTrue($this->manager->declineShare($groupShare['id'])); + $this->verifyDeclinedGroupShare($shareData, '/SharedFolder'); + + // a second time + $this->assertTrue($this->manager->declineShare($groupShare['id'])); + $this->verifyDeclinedGroupShare($shareData, '/SharedFolder'); + } + + public function testDeclineGroupShareAgainThroughSubshare() { + [$shareData, $groupShare] = $this->createTestGroupShare(); + $this->assertTrue($this->manager->acceptShare($groupShare['id'])); + $this->verifyAcceptedGroupShare($shareData); + + // this will return sub-entries + $allShares = self::invokePrivate($this->manager, 'getShares', [null]); + $this->assertCount(1, $allShares); + + // decline again through sub-share + $this->assertTrue($this->manager->declineShare($allShares[0]['id'])); + $this->verifyDeclinedGroupShare($shareData, '/SharedFolder'); + + // a second time + $this->assertTrue($this->manager->declineShare($allShares[0]['id'])); + $this->verifyDeclinedGroupShare($shareData, '/SharedFolder'); + } + + public function testDeclineGroupShareAgainThroughMountPoint() { + [$shareData, $groupShare] = $this->createTestGroupShare(); + $this->assertTrue($this->manager->acceptShare($groupShare['id'])); + $this->verifyAcceptedGroupShare($shareData); + + // decline through mount point name + $this->assertTrue($this->manager->removeShare($this->uid . '/files/' . $shareData['name'])); + $this->verifyDeclinedGroupShare($shareData, '/SharedFolder'); + + // second time must fail as the mount point is gone + $this->assertFalse($this->manager->removeShare($this->uid . '/files/' . $shareData['name'])); + } + /** * @param array $expected * @param array $actual -- cgit v1.2.3 From df0ca2f1db8b3bacf8fd6b9b927f370b377acdee Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Tue, 13 Jul 2021 17:51:52 +0200 Subject: Fix external share manager with multiple user groups Use query builder with proper matching for finding the group names. Signed-off-by: Vincent Petry --- apps/files_sharing/lib/External/Manager.php | 23 +++++++++++++++-------- apps/files_sharing/tests/External/ManagerTest.php | 12 ++++++++++-- 2 files changed, 25 insertions(+), 10 deletions(-) (limited to 'apps/files_sharing') diff --git a/apps/files_sharing/lib/External/Manager.php b/apps/files_sharing/lib/External/Manager.php index 082e1adef8a..fdc02f104af 100644 --- a/apps/files_sharing/lib/External/Manager.php +++ b/apps/files_sharing/lib/External/Manager.php @@ -37,6 +37,7 @@ use Doctrine\DBAL\Driver\Exception; use OC\Files\Filesystem; use OCA\FederatedFileSharing\Events\FederatedShareAddedEvent; use OCA\Files_Sharing\Helper; +use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\EventDispatcher\IEventDispatcher; use OCP\Federation\ICloudFederationFactory; use OCP\Federation\ICloudFederationProviderManager; @@ -740,16 +741,22 @@ class Manager { $userGroups[] = $group->getGID(); } - // FIXME: use query builder - $query = 'SELECT `id`, `share_type`, `parent`, `remote`, `remote_id`, `share_token`, `name`, `owner`, `user`, `mountpoint`, `accepted` - FROM `*PREFIX*share_external` - WHERE (`user` = ? OR `user` IN (?))'; - $parameters = [$this->uid, implode(',', $userGroups)]; - $query .= ' ORDER BY `id` ASC'; + $qb = $this->connection->getQueryBuilder(); + $qb->select('id', 'share_type', 'parent', 'remote', 'remote_id', 'share_token', 'name', 'owner', 'user', 'mountpoint', 'accepted') + ->from('share_external') + ->where( + $qb->expr()->orX( + $qb->expr()->eq('user', $qb->createNamedParameter($this->uid)), + $qb->expr()->in( + 'user', + $qb->createNamedParameter($userGroups, IQueryBuilder::PARAM_STR_ARRAY) + ) + ) + ) + ->orderBy('id', 'ASC'); - $sharesQuery = $this->connection->prepare($query); try { - $result = $sharesQuery->execute($parameters); + $result = $qb->execute(); $shares = $result->fetchAll(); $result->closeCursor(); diff --git a/apps/files_sharing/tests/External/ManagerTest.php b/apps/files_sharing/tests/External/ManagerTest.php index 2340d057dd7..1a6d83ce719 100644 --- a/apps/files_sharing/tests/External/ManagerTest.php +++ b/apps/files_sharing/tests/External/ManagerTest.php @@ -143,9 +143,17 @@ class ManagerTest extends TestCase { $group1->expects($this->any())->method('getGID')->willReturn('group1'); $group1->expects($this->any())->method('inGroup')->with($this->user)->willReturn(true); + $group2 = $this->createMock(IGroup::class); + $group2->expects($this->any())->method('getGID')->willReturn('group2'); + $group2->expects($this->any())->method('inGroup')->with($this->user)->willReturn(true); + $this->userManager->expects($this->any())->method('get')->willReturn($this->user); - $this->groupManager->expects($this->any())->method(('getUserGroups'))->willReturn([$group1]); - $this->groupManager->expects($this->any())->method(('get'))->with('group1')->willReturn($group1); + $this->groupManager->expects($this->any())->method(('getUserGroups'))->willReturn([$group1, $group2]); + $this->groupManager->expects($this->any())->method(('get')) + ->will($this->returnValueMap([ + ['group1', $group1], + ['group2', $group2], + ])); } protected function tearDown(): void { -- cgit v1.2.3 From 5b111961ae68b1334f2dbef028627adc4b8c9341 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Mon, 19 Jul 2021 23:14:06 +0200 Subject: Use mimetype instead of type when defaulting in remote shares Signed-off-by: Vincent Petry --- apps/files_sharing/js/sharedfilelist.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'apps/files_sharing') diff --git a/apps/files_sharing/js/sharedfilelist.js b/apps/files_sharing/js/sharedfilelist.js index 17c72db1ffc..4e364f92077 100644 --- a/apps/files_sharing/js/sharedfilelist.js +++ b/apps/files_sharing/js/sharedfilelist.js @@ -355,7 +355,7 @@ file.shareOwnerId = share.owner } - if (!file.type) { + if (!file.mimetype) { // pending shares usually have no type, so default to showing a directory icon file.mimetype = 'dir-shared' } -- cgit v1.2.3 From 2e76d98e1364f386f901e0698029d76fef67565f Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Tue, 20 Jul 2021 16:42:19 +0200 Subject: Fix remote group share decline+accept code path When declining a remote group share through the dialog that appears when notifications are off, the mount point is now correctly saved when re-accepting. Signed-off-by: Vincent Petry --- apps/files_sharing/lib/External/Manager.php | 8 ++++- apps/files_sharing/tests/External/ManagerTest.php | 36 +++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) (limited to 'apps/files_sharing') diff --git a/apps/files_sharing/lib/External/Manager.php b/apps/files_sharing/lib/External/Manager.php index fdc02f104af..b74198c8793 100644 --- a/apps/files_sharing/lib/External/Manager.php +++ b/apps/files_sharing/lib/External/Manager.php @@ -334,7 +334,13 @@ class Manager { if ($subshare !== null) { try { - $this->updateAccepted((int)$subshare['id'], true); + $acceptShare = $this->connection->prepare(' + UPDATE `*PREFIX*share_external` + SET `accepted` = ?, + `mountpoint` = ?, + `mountpoint_hash` = ? + WHERE `id` = ? AND `user` = ?'); + $acceptShare->execute([1, $mountPoint, $hash, $subshare['id'], $this->uid]); $result = true; } catch (Exception $e) { $this->logger->logException($e); diff --git a/apps/files_sharing/tests/External/ManagerTest.php b/apps/files_sharing/tests/External/ManagerTest.php index 1a6d83ce719..f54d089827c 100644 --- a/apps/files_sharing/tests/External/ManagerTest.php +++ b/apps/files_sharing/tests/External/ManagerTest.php @@ -547,6 +547,42 @@ class ManagerTest extends TestCase { $this->assertFalse($this->manager->removeShare($this->uid . '/files/' . $shareData['name'])); } + public function testDeclineThenAcceptGroupShareAgainThroughGroupShare() { + [$shareData, $groupShare] = $this->createTestGroupShare(); + // decline, this creates a declined sub-share + $this->assertTrue($this->manager->declineShare($groupShare['id'])); + $this->verifyDeclinedGroupShare($shareData); + + // this will return sub-entries + $openShares = $this->manager->getOpenShares(); + + // accept through sub-share + $this->assertTrue($this->manager->acceptShare($groupShare['id'])); + $this->verifyAcceptedGroupShare($shareData, '/SharedFolder'); + + // accept a second time + $this->assertTrue($this->manager->acceptShare($groupShare['id'])); + $this->verifyAcceptedGroupShare($shareData, '/SharedFolder'); + } + + public function testDeclineThenAcceptGroupShareAgainThroughSubShare() { + [$shareData, $groupShare] = $this->createTestGroupShare(); + // decline, this creates a declined sub-share + $this->assertTrue($this->manager->declineShare($groupShare['id'])); + $this->verifyDeclinedGroupShare($shareData); + + // this will return sub-entries + $openShares = $this->manager->getOpenShares(); + + // accept through sub-share + $this->assertTrue($this->manager->acceptShare($openShares[0]['id'])); + $this->verifyAcceptedGroupShare($shareData); + + // accept a second time + $this->assertTrue($this->manager->acceptShare($openShares[0]['id'])); + $this->verifyAcceptedGroupShare($shareData); + } + /** * @param array $expected * @param array $actual -- cgit v1.2.3 From 33b01187cd7efd7be5255c1d80f8b0483427f113 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Tue, 20 Jul 2021 20:36:26 +0200 Subject: Replace ILogger with LoggerInterface in remote share manager Signed-off-by: Vincent Petry Co-authored-by: Carl Schwan --- .../lib/OCM/CloudFederationProviderFiles.php | 3 ++- apps/files_sharing/lib/AppInfo/Application.php | 3 ++- apps/files_sharing/lib/External/Manager.php | 20 ++++++++++---------- apps/files_sharing/lib/Hooks.php | 3 ++- apps/files_sharing/tests/External/ManagerTest.php | 6 +++--- 5 files changed, 19 insertions(+), 16 deletions(-) (limited to 'apps/files_sharing') diff --git a/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php b/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php index d9ae9f6d733..fdde3d98e33 100644 --- a/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php +++ b/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php @@ -60,6 +60,7 @@ use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\IManager; use OCP\Share\IShare; use OCP\Util; +use Psr\Log\LoggerInterface; class CloudFederationProviderFiles implements ICloudFederationProvider { @@ -252,7 +253,7 @@ class CloudFederationProviderFiles implements ICloudFederationProvider { \OC::$server->getUserManager(), $shareWith, \OC::$server->query(IEventDispatcher::class), - \OC::$server->getLogger() + \OC::$server->get(LoggerInterface::class) ); try { diff --git a/apps/files_sharing/lib/AppInfo/Application.php b/apps/files_sharing/lib/AppInfo/Application.php index 3975a8a3bde..2dfbe4d86a5 100644 --- a/apps/files_sharing/lib/AppInfo/Application.php +++ b/apps/files_sharing/lib/AppInfo/Application.php @@ -60,6 +60,7 @@ use OCP\Share\Events\ShareCreatedEvent; use OCP\Share\IManager; use OCP\Util; use Psr\Container\ContainerInterface; +use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\GenericEvent; @@ -99,7 +100,7 @@ class Application extends App { $server->getUserManager(), $uid, $server->query(IEventDispatcher::class), - $server->getLogger() + $server->get(LoggerInterface::class) ); }); diff --git a/apps/files_sharing/lib/External/Manager.php b/apps/files_sharing/lib/External/Manager.php index b74198c8793..a8b01a74464 100644 --- a/apps/files_sharing/lib/External/Manager.php +++ b/apps/files_sharing/lib/External/Manager.php @@ -46,12 +46,12 @@ use OCP\Files\Storage\IStorageFactory; use OCP\Http\Client\IClientService; use OCP\IDBConnection; use OCP\IGroupManager; -use OCP\ILogger; use OCP\IUserManager; use OCP\Notification\IManager; use OCP\OCS\IDiscoveryService; use OCP\Share; use OCP\Share\IShare; +use Psr\Log\LoggerInterface; class Manager { public const STORAGE = '\OCA\Files_Sharing\External\Storage'; @@ -92,7 +92,7 @@ class Manager { /** @var IEventDispatcher */ private $eventDispatcher; - /** @var ILogger */ + /** @var LoggerInterface */ private $logger; public function __construct( @@ -108,7 +108,7 @@ class Manager { IUserManager $userManager, ?string $uid, IEventDispatcher $eventDispatcher, - ILogger $logger + LoggerInterface $logger ) { $this->connection = $connection; $this->mountManager = $mountManager; @@ -343,7 +343,7 @@ class Manager { $acceptShare->execute([1, $mountPoint, $hash, $subshare['id'], $this->uid]); $result = true; } catch (Exception $e) { - $this->logger->logException($e); + $this->logger->emergency('Could not update share', ['exception' => $e]); $result = false; } } else { @@ -361,7 +361,7 @@ class Manager { $share['share_type']); $result = true; } catch (Exception $e) { - $this->logger->logException($e); + $this->logger->emergency('Could not create share', ['exception' => $e]); $result = false; } } @@ -412,7 +412,7 @@ class Manager { $this->updateAccepted((int)$subshare['id'], false); $result = true; } catch (Exception $e) { - $this->logger->logException($e); + $this->logger->emergency('Could not update share', ['exception' => $e]); $result = false; } } else { @@ -432,7 +432,7 @@ class Manager { $share['share_type']); $result = true; } catch (Exception $e) { - $this->logger->logException($e); + $this->logger->emergency('Could not create share', ['exception' => $e]); $result = false; } } @@ -634,7 +634,7 @@ class Manager { $this->removeReShares($id); } catch (\Doctrine\DBAL\Exception $ex) { - $this->logger->logException($ex); + $this->logger->emergency('Could not update share', ['exception' => $ex]); return false; } @@ -706,7 +706,7 @@ class Manager { $deleteResult->closeCursor(); } } catch (\Doctrine\DBAL\Exception $ex) { - $this->logger->logException($ex); + $this->logger->emergency('Could not get shares', ['exception' => $ex]); return false; } @@ -784,7 +784,7 @@ class Manager { } return array_values($shares); } catch (\Doctrine\DBAL\Exception $e) { - $this->logger->logException($e); + $this->logger->emergency('Error when retrieving shares', ['exception' => $e]); return []; } } diff --git a/apps/files_sharing/lib/Hooks.php b/apps/files_sharing/lib/Hooks.php index 26e799297ff..f28f6910abd 100644 --- a/apps/files_sharing/lib/Hooks.php +++ b/apps/files_sharing/lib/Hooks.php @@ -28,6 +28,7 @@ namespace OCA\Files_Sharing; use OC\Files\Filesystem; use OCP\EventDispatcher\IEventDispatcher; +use Psr\Log\LoggerInterface; class Hooks { public static function deleteUser($params) { @@ -44,7 +45,7 @@ class Hooks { \OC::$server->getUserManager(), $params['uid'], \OC::$server->query(IEventDispatcher::class), - \OC::$server->getLogger() + \OC::$server->get(LoggerInterface::class) ); $manager->removeUserShares($params['uid']); diff --git a/apps/files_sharing/tests/External/ManagerTest.php b/apps/files_sharing/tests/External/ManagerTest.php index f54d089827c..1b7abef859e 100644 --- a/apps/files_sharing/tests/External/ManagerTest.php +++ b/apps/files_sharing/tests/External/ManagerTest.php @@ -43,9 +43,9 @@ use OCP\Http\Client\IClientService; use OCP\Http\Client\IResponse; use OCP\IGroup; use OCP\IGroupManager; -use OCP\ILogger; use OCP\IUserManager; use OCP\Share\IShare; +use Psr\Log\LoggerInterface; use Test\Traits\UserTrait; /** @@ -113,8 +113,8 @@ class ManagerTest extends TestCase { ->method('search') ->willReturn([]); - $logger = $this->createMock(ILogger::class); - $logger->expects($this->never())->method('logException'); + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->never())->method('emergency'); $this->manager = $this->getMockBuilder(Manager::class) ->setConstructorArgs( -- cgit v1.2.3 From a22ea02bea2390841c48be7710303031f494cc03 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Tue, 27 Jul 2021 13:30:44 +0200 Subject: Fix route path for pending remote shares Signed-off-by: Vincent Petry --- apps/files_sharing/js/dist/additionalScripts.js | 2 +- apps/files_sharing/js/dist/additionalScripts.js.map | 2 +- apps/files_sharing/js/dist/files_sharing.js | 2 +- apps/files_sharing/js/dist/files_sharing.js.map | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'apps/files_sharing') diff --git a/apps/files_sharing/js/dist/additionalScripts.js b/apps/files_sharing/js/dist/additionalScripts.js index f65bd025fb1..cdbc3f5b3b1 100644 --- a/apps/files_sharing/js/dist/additionalScripts.js +++ b/apps/files_sharing/js/dist/additionalScripts.js @@ -52,7 +52,7 @@ n.p=OC.linkTo("files_sharing","js/dist/"),n.nc=btoa(OC.requestToken),window.OCP. * Copyright(c) 2015 Andreas Lubbe * Copyright(c) 2015 Tiancheng "Timothy" Gu * MIT Licensed - */var n=/["'&<>]/;e.exports=function(e){var t,r=""+e,a=n.exec(r);if(!a)return r;var i="",s=0,o=0;for(s=a.index;s=0&&(t.shareOwner=r[OC.Files.Client.PROPERTY_OWNER_DISPLAY_NAME],t.shareOwnerId=r[OC.Files.Client.PROPERTY_OWNER_ID]);var a=r[OC.Files.Client.PROPERTY_SHARE_TYPES];return a&&(t.shareTypes=_.chain(a).filter((function(e){return e.namespaceURI===OC.Files.Client.NS_OWNCLOUD&&"share-type"===e.nodeName.split(":")[1]})).map((function(e){return parseInt(e.textContent||e.text,10)})).value()),t})),e.$el.on("fileActionsReady",(function(e){var t=e.$files;_.each(t,(function(e){var t=$(e),r=t.attr("data-share-types")||"",n=t.attr("data-share-owner");if(r||n){var a=!1,i=!1;_.each(r.split(",")||[],(function(e){(e=parseInt(e,10))===OC.Share.SHARE_TYPE_LINK||e===OC.Share.SHARE_TYPE_EMAIL?a=!0:(e===OC.Share.SHARE_TYPE_USER||e===OC.Share.SHARE_TYPE_GROUP||e===OC.Share.SHARE_TYPE_REMOTE||e===OC.Share.SHARE_TYPE_CIRCLE||e===OC.Share.SHARE_TYPE_ROOM||e===OC.Share.SHARE_TYPE_DECK)&&(i=!0)})),OCA.Sharing.Util._updateFileActionIcon(t,i,a)}}))})),e.$el.on("changeDirectory",(function(){OCA.Sharing.sharesLoaded=!1})),r.registerAction({name:"Share",displayName:function(e){if(e&&e.$file){var r=parseInt(e.$file.data("share-types"),10),n=e.$file.data("share-owner-id");if(r>=0||n)return t("files_sharing","Shared")}return t("files_sharing","Share")},altText:t("files_sharing","Share"),mime:"all",order:-150,permissions:OC.PERMISSION_ALL,iconClass:function(e,t){var r=parseInt(t.$file.data("share-types"),10);return r===OC.Share.SHARE_TYPE_EMAIL||r===OC.Share.SHARE_TYPE_LINK?"icon-public":"icon-shared"},icon:function(e,t){var r=t.$file.data("share-owner-id");if(r)return OC.generateUrl("/avatar/".concat(r,"/32"))},type:OCA.Files.FileActions.TYPE_INLINE,actionHandler:function(t,r){var n=parseInt(r.$file.data("share-permissions"),10);(isNaN(n)||n>0)&&e.showDetailsView(t,"sharing")},render:function(e,t,n){return 0!=(parseInt(n.$file.data("permissions"),10)&OC.PERMISSION_SHARE)||n.$file.attr("data-share-owner")?r._defaultRenderAction.call(r,e,t,n):null}});var s=new OCA.Sharing.ShareBreadCrumbView;e.registerBreadCrumbDetailView(s)}},_updateFileListDataAttributes:function(e,t,r){if("files"!==e.id)if(_.pluck(r.get("shares"),"share_with_displayname").length){var n=_.mapObject(r.get("shares"),(function(e){return{shareWith:e.share_with,shareWithDisplayName:e.share_with_displayname}}));t.attr("data-share-recipient-data",JSON.stringify(n))}else t.removeAttr("data-share-recipient-data")},_updateFileActionIcon:function(e,t,r){return!!(t||r||e.attr("data-share-recipient-data")||e.attr("data-share-owner"))&&(OCA.Sharing.Util._markFileAsShared(e,!0,r),!0)},_markFileAsShared:function(e,r,n){var a,i,s,o,l=e.find('.fileactions .action[data-action="Share"]'),c=e.data("type"),d=l.find(".icon"),u=e.attr("data-share-owner-id"),h=e.attr("data-share-owner"),f=e.attr("data-mounttype"),p="icon-shared";l.removeClass("shared-style"),"dir"===c&&(r||n||u)?(o=void 0!==f&&"shared-root"!==f&&"shared"!==f?OC.MimeType.getIconUrl("dir-"+f):n?OC.MimeType.getIconUrl("dir-public"):OC.MimeType.getIconUrl("dir-shared"),e.find(".filename .thumbnail").css("background-image","url("+o+")"),e.attr("data-icon",o)):"dir"===c&&("true"===e.attr("data-e2eencrypted")?(o=OC.MimeType.getIconUrl("dir-encrypted"),e.attr("data-icon",o)):f&&0===f.indexOf("external")?(o=OC.MimeType.getIconUrl("dir-external"),e.attr("data-icon",o)):(o=OC.MimeType.getIconUrl("dir"),e.removeAttr("data-icon")),e.find(".filename .thumbnail").css("background-image","url("+o+")")),r||u?(i=e.data("share-recipient-data"),l.addClass("shared-style"),s=""+t("files_sharing","Shared")+"",u?(a=t("files_sharing","Shared by"),s=OCA.Sharing.Util._formatRemoteShare(u,h,a)):i&&(s=OCA.Sharing.Util._formatShareList(i)),l.html(s).prepend(d),(u||i)&&(l.find(".avatar").each((function(){$(this).avatar($(this).data("username"),32)})),l.find("span[title]").tooltip({placement:"top"}))):l.html(''+t("files_sharing","Shared")+"").prepend(d),n&&(p="icon-public"),d.removeClass("icon-shared icon-public").addClass(p)},_formatRemoteShare:function(e,t,r){var n=OCA.Sharing.Util._REMOTE_OWNER_REGEXP.exec(e);if(!n||!n[7])return''+r+" "+i()(t)+" ";var a=n[2],s=n[4],o=n[5],l=n[6],c=n[8]?n[7]:"",d=r+" "+a;s&&(d+="@"+s),o&&(d+="@"+o.replace(l,"")+c);var u='';return u+=''+i()(a)+"",s&&(u+='@'+i()(s)+""),u+=" "},_formatShareList:function(e){var r=this;return(e=_.toArray(e)).sort((function(e,t){return e.shareWithDisplayName.localeCompare(t.shareWithDisplayName)})),$.map(e,(function(e){return r._formatRemoteShare(e.shareWith,e.shareWithDisplayName,t("files_sharing","Shared with"))}))},markFileAsShared:function(e,r,n){var a,i,s,o,l=e.find('.fileactions .action[data-action="Share"]'),c=e.data("type"),d=l.find(".icon"),u=e.attr("data-share-owner-id"),h=e.attr("data-share-owner"),f=e.attr("data-mounttype"),p="icon-shared";l.removeClass("shared-style"),"dir"===c&&(r||n||u)?(o=void 0!==f&&"shared-root"!==f&&"shared"!==f?OC.MimeType.getIconUrl("dir-"+f):n?OC.MimeType.getIconUrl("dir-public"):OC.MimeType.getIconUrl("dir-shared"),e.find(".filename .thumbnail").css("background-image","url("+o+")"),e.attr("data-icon",o)):"dir"===c&&("true"===e.attr("data-e2eencrypted")?(o=OC.MimeType.getIconUrl("dir-encrypted"),e.attr("data-icon",o)):f&&0===f.indexOf("external")?(o=OC.MimeType.getIconUrl("dir-external"),e.attr("data-icon",o)):(o=OC.MimeType.getIconUrl("dir"),e.removeAttr("data-icon")),e.find(".filename .thumbnail").css("background-image","url("+o+")")),r||u?(i=e.data("share-recipient-data"),l.addClass("shared-style"),s=""+t("files_sharing","Shared")+"",u?(a=t("files_sharing","Shared by"),s=this._formatRemoteShare(u,h,a)):i&&(s=this._formatShareList(i)),l.html(s).prepend(d),(u||i)&&(l.find(".avatar").each((function(){$(this).avatar($(this).data("username"),32)})),l.find("span[title]").tooltip({placement:"top"}))):l.html(''+t("files_sharing","Shared")+"").prepend(d),n&&(p="icon-public"),d.removeClass("icon-shared icon-public").addClass(p)},getSharePermissions:function(e){return e.sharePermissions}},OC.Plugins.register("OCA.Files.FileList",OCA.Sharing.Util);n(315);var s=n(19),o=n.n(s),l=n(229),c={insert:"head",singleton:!1};o()(l.a,c),l.a.locals,n(159); + */var n=/["'&<>]/;e.exports=function(e){var t,r=""+e,a=n.exec(r);if(!a)return r;var i="",s=0,o=0;for(s=a.index;s=0&&(t.shareOwner=r[OC.Files.Client.PROPERTY_OWNER_DISPLAY_NAME],t.shareOwnerId=r[OC.Files.Client.PROPERTY_OWNER_ID]);var a=r[OC.Files.Client.PROPERTY_SHARE_TYPES];return a&&(t.shareTypes=_.chain(a).filter((function(e){return e.namespaceURI===OC.Files.Client.NS_OWNCLOUD&&"share-type"===e.nodeName.split(":")[1]})).map((function(e){return parseInt(e.textContent||e.text,10)})).value()),t})),e.$el.on("fileActionsReady",(function(e){var t=e.$files;_.each(t,(function(e){var t=$(e),r=t.attr("data-share-types")||"",n=t.attr("data-share-owner");if(r||n){var a=!1,i=!1;_.each(r.split(",")||[],(function(e){(e=parseInt(e,10))===OC.Share.SHARE_TYPE_LINK||e===OC.Share.SHARE_TYPE_EMAIL?a=!0:(e===OC.Share.SHARE_TYPE_USER||e===OC.Share.SHARE_TYPE_GROUP||e===OC.Share.SHARE_TYPE_REMOTE||e===OC.Share.SHARE_TYPE_REMOTE_GROUP||e===OC.Share.SHARE_TYPE_CIRCLE||e===OC.Share.SHARE_TYPE_ROOM||e===OC.Share.SHARE_TYPE_DECK)&&(i=!0)})),OCA.Sharing.Util._updateFileActionIcon(t,i,a)}}))})),e.$el.on("changeDirectory",(function(){OCA.Sharing.sharesLoaded=!1})),r.registerAction({name:"Share",displayName:function(e){if(e&&e.$file){var r=parseInt(e.$file.data("share-types"),10),n=e.$file.data("share-owner-id");if(r>=0||n)return t("files_sharing","Shared")}return t("files_sharing","Share")},altText:t("files_sharing","Share"),mime:"all",order:-150,permissions:OC.PERMISSION_ALL,iconClass:function(e,t){var r=parseInt(t.$file.data("share-types"),10);return r===OC.Share.SHARE_TYPE_EMAIL||r===OC.Share.SHARE_TYPE_LINK?"icon-public":"icon-shared"},icon:function(e,t){var r=t.$file.data("share-owner-id");if(r)return OC.generateUrl("/avatar/".concat(r,"/32"))},type:OCA.Files.FileActions.TYPE_INLINE,actionHandler:function(t,r){if(e._detailsView){var n=parseInt(r.$file.data("share-permissions"),10);(isNaN(n)||n>0)&&e.showDetailsView(t,"sharing")}},render:function(e,t,n){return 0!=(parseInt(n.$file.data("permissions"),10)&OC.PERMISSION_SHARE)||n.$file.attr("data-share-owner")?r._defaultRenderAction.call(r,e,t,n):null}});var s=new OCA.Sharing.ShareBreadCrumbView;e.registerBreadCrumbDetailView(s)}},_updateFileListDataAttributes:function(e,t,r){if("files"!==e.id)if(_.pluck(r.get("shares"),"share_with_displayname").length){var n=_.mapObject(r.get("shares"),(function(e){return{shareWith:e.share_with,shareWithDisplayName:e.share_with_displayname}}));t.attr("data-share-recipient-data",JSON.stringify(n))}else t.removeAttr("data-share-recipient-data")},_updateFileActionIcon:function(e,t,r){return!!(t||r||e.attr("data-share-recipient-data")||e.attr("data-share-owner"))&&(OCA.Sharing.Util._markFileAsShared(e,!0,r),!0)},_markFileAsShared:function(e,r,n){var a,i,s,o,l=e.find('.fileactions .action[data-action="Share"]'),c=e.data("type"),d=l.find(".icon"),u=e.attr("data-share-owner-id"),h=e.attr("data-share-owner"),f=e.attr("data-mounttype"),p="icon-shared";l.removeClass("shared-style"),"dir"===c&&(r||n||u)?(o=void 0!==f&&"shared-root"!==f&&"shared"!==f?OC.MimeType.getIconUrl("dir-"+f):n?OC.MimeType.getIconUrl("dir-public"):OC.MimeType.getIconUrl("dir-shared"),e.find(".filename .thumbnail").css("background-image","url("+o+")"),e.attr("data-icon",o)):"dir"===c&&("true"===e.attr("data-e2eencrypted")?(o=OC.MimeType.getIconUrl("dir-encrypted"),e.attr("data-icon",o)):f&&0===f.indexOf("external")?(o=OC.MimeType.getIconUrl("dir-external"),e.attr("data-icon",o)):(o=OC.MimeType.getIconUrl("dir"),e.removeAttr("data-icon")),e.find(".filename .thumbnail").css("background-image","url("+o+")")),r||u?(i=e.data("share-recipient-data"),l.addClass("shared-style"),s=""+t("files_sharing","Shared")+"",u?(a=t("files_sharing","Shared by"),s=OCA.Sharing.Util._formatRemoteShare(u,h,a)):i&&(s=OCA.Sharing.Util._formatShareList(i)),l.html(s).prepend(d),(u||i)&&(l.find(".avatar").each((function(){$(this).avatar($(this).data("username"),32)})),l.find("span[title]").tooltip({placement:"top"}))):l.html(''+t("files_sharing","Shared")+"").prepend(d),n&&(p="icon-public"),d.removeClass("icon-shared icon-public").addClass(p)},_formatRemoteShare:function(e,t,r){var n=OCA.Sharing.Util._REMOTE_OWNER_REGEXP.exec(e);if(!n||!n[7])return''+r+" "+i()(t)+" ";var a=n[2],s=n[4],o=n[5],l=n[6],c=n[8]?n[7]:"",d=r+" "+a;s&&(d+="@"+s),o&&(d+="@"+o.replace(l,"")+c);var u='';return u+=''+i()(a)+"",s&&(u+='@'+i()(s)+""),u+=" "},_formatShareList:function(e){var r=this;return(e=_.toArray(e)).sort((function(e,t){return e.shareWithDisplayName.localeCompare(t.shareWithDisplayName)})),$.map(e,(function(e){return r._formatRemoteShare(e.shareWith,e.shareWithDisplayName,t("files_sharing","Shared with"))}))},markFileAsShared:function(e,r,n){var a,i,s,o,l=e.find('.fileactions .action[data-action="Share"]'),c=e.data("type"),d=l.find(".icon"),u=e.attr("data-share-owner-id"),h=e.attr("data-share-owner"),f=e.attr("data-mounttype"),p="icon-shared";l.removeClass("shared-style"),"dir"===c&&(r||n||u)?(o=void 0!==f&&"shared-root"!==f&&"shared"!==f?OC.MimeType.getIconUrl("dir-"+f):n?OC.MimeType.getIconUrl("dir-public"):OC.MimeType.getIconUrl("dir-shared"),e.find(".filename .thumbnail").css("background-image","url("+o+")"),e.attr("data-icon",o)):"dir"===c&&("true"===e.attr("data-e2eencrypted")?(o=OC.MimeType.getIconUrl("dir-encrypted"),e.attr("data-icon",o)):f&&0===f.indexOf("external")?(o=OC.MimeType.getIconUrl("dir-external"),e.attr("data-icon",o)):(o=OC.MimeType.getIconUrl("dir"),e.removeAttr("data-icon")),e.find(".filename .thumbnail").css("background-image","url("+o+")")),r||u?(i=e.data("share-recipient-data"),l.addClass("shared-style"),s=""+t("files_sharing","Shared")+"",u?(a=t("files_sharing","Shared by"),s=this._formatRemoteShare(u,h,a)):i&&(s=this._formatShareList(i)),l.html(s).prepend(d),(u||i)&&(l.find(".avatar").each((function(){$(this).avatar($(this).data("username"),32)})),l.find("span[title]").tooltip({placement:"top"}))):l.html(''+t("files_sharing","Shared")+"").prepend(d),n&&(p="icon-public"),d.removeClass("icon-shared icon-public").addClass(p)},getSharePermissions:function(e){return e.sharePermissions}},OC.Plugins.register("OCA.Files.FileList",OCA.Sharing.Util);n(315);var s=n(19),o=n.n(s),l=n(229),c={insert:"head",singleton:!1};o()(l.a,c),l.a.locals,n(159); /** * @copyright Copyright (c) 2016 Roeland Jago Douma * diff --git a/apps/files_sharing/js/dist/additionalScripts.js.map b/apps/files_sharing/js/dist/additionalScripts.js.map index 217e2a7921d..094ddfe45c9 100644 --- a/apps/files_sharing/js/dist/additionalScripts.js.map +++ b/apps/files_sharing/js/dist/additionalScripts.js.map @@ -1 +1 @@ -{"version":3,"sources":["webpack:///webpack/bootstrap","webpack:///./apps/files_sharing/src/collaborationresourceshandler.js","webpack:///./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js","webpack:///./node_modules/css-loader/dist/runtime/cssWithMappingToString.js","webpack:///./node_modules/css-loader/dist/runtime/api.js","webpack:///./apps/files_sharing/src/style/sharebreadcrumb.scss","webpack:///./apps/files_sharing/src/sharebreadcrumbview.js","webpack:///./node_modules/escape-html/index.js","webpack:///./apps/files_sharing/src/share.js","webpack:///./apps/files_sharing/src/style/sharebreadcrumb.scss?a9a3","webpack:///./apps/files_sharing/src/additionalScripts.js"],"names":["installedModules","__webpack_require__","moduleId","exports","module","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","prototype","hasOwnProperty","p","s","__webpack_public_path__","OC","linkTo","__webpack_nonce__","btoa","requestToken","window","OCP","Collaboration","registerType","action","Promise","resolve","reject","dialogs","filepicker","f","Files","getClient","getFileInfo","then","status","fileInfo","id","fail","Error","FILEPICKER_TYPE_CHOOSE","allowDirectoryChooser","typeString","typeIconClass","memo","isOldIE","Boolean","document","all","atob","getTarget","target","styleTarget","querySelector","HTMLIFrameElement","contentDocument","head","e","stylesInDom","getIndexByIdentifier","identifier","result","length","modulesToDom","list","options","idCountMap","identifiers","item","base","count","concat","index","obj","css","media","sourceMap","references","updater","push","addStyle","insertStyleElement","style","createElement","attributes","nonce","keys","forEach","setAttribute","insert","appendChild","textStore","replaceText","replacement","filter","join","applyToSingletonTag","remove","styleSheet","cssText","cssNode","createTextNode","childNodes","removeChild","insertBefore","applyToTag","removeAttribute","unescape","encodeURIComponent","JSON","stringify","firstChild","singleton","singletonCounter","update","styleIndex","parentNode","removeStyleElement","newObj","lastIdentifiers","newList","toString","newLastIdentifiers","_i","_index","splice","_slicedToArray","arr","Array","isArray","_arrayWithHoles","iterator","_s","_e","_arr","_n","_d","next","done","err","_iterableToArrayLimit","minLen","_arrayLikeToArray","slice","constructor","from","test","_unsupportedIterableToArray","TypeError","_nonIterableRest","len","arr2","_item","content","cssMapping","base64","data","sourceMapping","sourceURLs","sources","map","source","sourceRoot","cssWithMappingToString","this","mediaQuery","dedupe","alreadyImportedModules","___CSS_LOADER_EXPORT___","BreadCrumbView","Backbone","View","extend","tagName","events","click","_dirInfo","undefined","render","dirInfo","path","$el","removeClass","hide","isShared","shareTypes","addClass","indexOf","Share","SHARE_TYPE_LINK","show","delegateEvents","_onClick","preventDefault","stopPropagation","fileInfoModel","OCA","FileInfoModel","self","on","Sidebar","open","setActiveTab","Sharing","ShareBreadCrumbView","matchHtmlRegExp","string","escape","str","match","exec","html","lastIndex","charCodeAt","substring","_","Client","PROPERTY_SHARE_TYPES","NS_OWNCLOUD","PROPERTY_OWNER_ID","PROPERTY_OWNER_DISPLAY_NAME","Util","_REMOTE_OWNER_REGEXP","RegExp","attach","fileList","fileActions","oldCreateRow","_createRow","fileData","tr","apply","arguments","sharePermissions","getSharePermissions","permissions","actions","Comment","Details","Goto","attr","shareOwner","shareOwnerId","mountType","PERMISSION_UPDATE","recipientData","isEmpty","oldElementToFile","elementToFile","split","expirationTimestamp","parseInt","shares","expiration","oldGetWebdavProperties","_getWebdavProperties","props","filesClient","addFileInfoParser","response","propStat","properties","permissionsProp","PROPERTY_PERMISSIONS","shareTypesProp","chain","xmlvalue","namespaceURI","nodeName","textContent","text","ev","$files","each","file","$tr","$","hasLink","hasShares","shareType","SHARE_TYPE_EMAIL","SHARE_TYPE_USER","SHARE_TYPE_GROUP","SHARE_TYPE_REMOTE","SHARE_TYPE_CIRCLE","SHARE_TYPE_ROOM","SHARE_TYPE_DECK","_updateFileActionIcon","sharesLoaded","registerAction","displayName","context","$file","altText","mime","order","PERMISSION_ALL","iconClass","fileName","icon","generateUrl","type","FileActions","TYPE_INLINE","actionHandler","isNaN","showDetailsView","actionSpec","isDefault","PERMISSION_SHARE","_defaultRenderAction","breadCrumbSharingDetailView","registerBreadCrumbDetailView","_updateFileListDataAttributes","shareModel","pluck","mapObject","share","shareWith","share_with","shareWithDisplayName","share_with_displayname","removeAttr","hasUserShares","hasLinkShares","_markFileAsShared","message","recipients","avatars","shareFolderIcon","find","ownerId","owner","MimeType","getIconUrl","_formatRemoteShare","_formatShareList","prepend","avatar","tooltip","placement","parts","escapeHTML","userName","userDomain","server","protocol","serverPath","replace","_parent","toArray","sort","a","b","localeCompare","recipient","markFileAsShared","Plugins","register","locals"],"mappings":"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","file":"additionalScripts.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"/js/\";\n\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 473);\n","/**\n * @copyright Copyright (c) 2016 John Molakvoæ \n *\n * @author John Molakvoæ \n * @author Julius Härtl \n *\n * @license GNU AGPL version 3 or any later version\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n */\n\n// eslint-disable-next-line camelcase\n__webpack_public_path__ = OC.linkTo('files_sharing', 'js/dist/')\n// eslint-disable-next-line camelcase\n__webpack_nonce__ = btoa(OC.requestToken)\n\nwindow.OCP.Collaboration.registerType('file', {\n\taction: () => {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tOC.dialogs.filepicker(t('files_sharing', 'Link to a file'), function(f) {\n\t\t\t\tconst client = OC.Files.getClient()\n\t\t\t\tclient.getFileInfo(f).then((status, fileInfo) => {\n\t\t\t\t\tresolve(fileInfo.id)\n\t\t\t\t}).fail(() => {\n\t\t\t\t\treject(new Error('Cannot get fileinfo'))\n\t\t\t\t})\n\t\t\t}, false, null, false, OC.dialogs.FILEPICKER_TYPE_CHOOSE, '', { allowDirectoryChooser: true })\n\t\t})\n\t},\n\ttypeString: t('files_sharing', 'Link to a file'),\n\ttypeIconClass: 'icon-files-dark',\n})\n","\"use strict\";\n\nvar isOldIE = function isOldIE() {\n var memo;\n return function memorize() {\n if (typeof memo === 'undefined') {\n // Test for IE <= 9 as proposed by Browserhacks\n // @see http://browserhacks.com/#hack-e71d8692f65334173fee715c222cb805\n // Tests for existence of standard globals is to allow style-loader\n // to operate correctly into non-standard environments\n // @see https://github.com/webpack-contrib/style-loader/issues/177\n memo = Boolean(window && document && document.all && !window.atob);\n }\n\n return memo;\n };\n}();\n\nvar getTarget = function getTarget() {\n var memo = {};\n return function memorize(target) {\n if (typeof memo[target] === 'undefined') {\n var styleTarget = document.querySelector(target); // Special case to return head of iframe instead of iframe itself\n\n if (window.HTMLIFrameElement && styleTarget instanceof window.HTMLIFrameElement) {\n try {\n // This will throw an exception if access to iframe is blocked\n // due to cross-origin restrictions\n styleTarget = styleTarget.contentDocument.head;\n } catch (e) {\n // istanbul ignore next\n styleTarget = null;\n }\n }\n\n memo[target] = styleTarget;\n }\n\n return memo[target];\n };\n}();\n\nvar stylesInDom = [];\n\nfunction getIndexByIdentifier(identifier) {\n var result = -1;\n\n for (var i = 0; i < stylesInDom.length; i++) {\n if (stylesInDom[i].identifier === identifier) {\n result = i;\n break;\n }\n }\n\n return result;\n}\n\nfunction modulesToDom(list, options) {\n var idCountMap = {};\n var identifiers = [];\n\n for (var i = 0; i < list.length; i++) {\n var item = list[i];\n var id = options.base ? item[0] + options.base : item[0];\n var count = idCountMap[id] || 0;\n var identifier = \"\".concat(id, \" \").concat(count);\n idCountMap[id] = count + 1;\n var index = getIndexByIdentifier(identifier);\n var obj = {\n css: item[1],\n media: item[2],\n sourceMap: item[3]\n };\n\n if (index !== -1) {\n stylesInDom[index].references++;\n stylesInDom[index].updater(obj);\n } else {\n stylesInDom.push({\n identifier: identifier,\n updater: addStyle(obj, options),\n references: 1\n });\n }\n\n identifiers.push(identifier);\n }\n\n return identifiers;\n}\n\nfunction insertStyleElement(options) {\n var style = document.createElement('style');\n var attributes = options.attributes || {};\n\n if (typeof attributes.nonce === 'undefined') {\n var nonce = typeof __webpack_nonce__ !== 'undefined' ? __webpack_nonce__ : null;\n\n if (nonce) {\n attributes.nonce = nonce;\n }\n }\n\n Object.keys(attributes).forEach(function (key) {\n style.setAttribute(key, attributes[key]);\n });\n\n if (typeof options.insert === 'function') {\n options.insert(style);\n } else {\n var target = getTarget(options.insert || 'head');\n\n if (!target) {\n throw new Error(\"Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.\");\n }\n\n target.appendChild(style);\n }\n\n return style;\n}\n\nfunction removeStyleElement(style) {\n // istanbul ignore if\n if (style.parentNode === null) {\n return false;\n }\n\n style.parentNode.removeChild(style);\n}\n/* istanbul ignore next */\n\n\nvar replaceText = function replaceText() {\n var textStore = [];\n return function replace(index, replacement) {\n textStore[index] = replacement;\n return textStore.filter(Boolean).join('\\n');\n };\n}();\n\nfunction applyToSingletonTag(style, index, remove, obj) {\n var css = remove ? '' : obj.media ? \"@media \".concat(obj.media, \" {\").concat(obj.css, \"}\") : obj.css; // For old IE\n\n /* istanbul ignore if */\n\n if (style.styleSheet) {\n style.styleSheet.cssText = replaceText(index, css);\n } else {\n var cssNode = document.createTextNode(css);\n var childNodes = style.childNodes;\n\n if (childNodes[index]) {\n style.removeChild(childNodes[index]);\n }\n\n if (childNodes.length) {\n style.insertBefore(cssNode, childNodes[index]);\n } else {\n style.appendChild(cssNode);\n }\n }\n}\n\nfunction applyToTag(style, options, obj) {\n var css = obj.css;\n var media = obj.media;\n var sourceMap = obj.sourceMap;\n\n if (media) {\n style.setAttribute('media', media);\n } else {\n style.removeAttribute('media');\n }\n\n if (sourceMap && typeof btoa !== 'undefined') {\n css += \"\\n/*# sourceMappingURL=data:application/json;base64,\".concat(btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))), \" */\");\n } // For old IE\n\n /* istanbul ignore if */\n\n\n if (style.styleSheet) {\n style.styleSheet.cssText = css;\n } else {\n while (style.firstChild) {\n style.removeChild(style.firstChild);\n }\n\n style.appendChild(document.createTextNode(css));\n }\n}\n\nvar singleton = null;\nvar singletonCounter = 0;\n\nfunction addStyle(obj, options) {\n var style;\n var update;\n var remove;\n\n if (options.singleton) {\n var styleIndex = singletonCounter++;\n style = singleton || (singleton = insertStyleElement(options));\n update = applyToSingletonTag.bind(null, style, styleIndex, false);\n remove = applyToSingletonTag.bind(null, style, styleIndex, true);\n } else {\n style = insertStyleElement(options);\n update = applyToTag.bind(null, style, options);\n\n remove = function remove() {\n removeStyleElement(style);\n };\n }\n\n update(obj);\n return function updateStyle(newObj) {\n if (newObj) {\n if (newObj.css === obj.css && newObj.media === obj.media && newObj.sourceMap === obj.sourceMap) {\n return;\n }\n\n update(obj = newObj);\n } else {\n remove();\n }\n };\n}\n\nmodule.exports = function (list, options) {\n options = options || {}; // Force single-tag solution on IE6-9, which has a hard limit on the # of