From 53aafff972d14e09732f61809079e39d08a9cb3b Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Thu, 1 Jul 2021 17:26:00 +0200 Subject: [PATCH 01/14] 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(-) diff --git a/apps/files_sharing/js/app.js b/apps/files_sharing/js/app.js index 442d5bbeafefa..6af8224baf441 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/{shareId}', { 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 + '/{shareId}', { shareBase, 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/{shareId}', { shareId }), + url: OC.linkToOCS('apps/files_sharing/api/v1/' + shareBase + '/{shareId}', { 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 972b849bdb1f3..1f467f549d7d8 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 385d42eaaeaa4..ab41db7e8cd8a 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 } From 7130a98e4730c9ba4e40593a958beb3683d02ef1 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Fri, 2 Jul 2021 09:58:54 +0200 Subject: [PATCH 02/14] 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 +++- .../tests/External/ManagerTest.php | 226 +++++++++++------- 2 files changed, 184 insertions(+), 92 deletions(-) diff --git a/apps/files_sharing/lib/External/Manager.php b/apps/files_sharing/lib/External/Manager.php index e51bd64cf3818..247be47ac90a4 100644 --- a/apps/files_sharing/lib/External/Manager.php +++ b/apps/files_sharing/lib/External/Manager.php @@ -571,15 +571,21 @@ protected function removeReShares($mountPointId) { */ 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 @@ public function removeUserShares($uid): bool { '); $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 @@ private function getShares($accepted) { $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 @@ private function getShares($accepted) { $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 facfcbd1ee65e..60380aa06195f 100644 --- a/apps/files_sharing/tests/External/ManagerTest.php +++ b/apps/files_sharing/tests/External/ManagerTest.php @@ -41,6 +41,7 @@ use OCP\Federation\ICloudFederationProviderManager; use OCP\Http\Client\IClientService; use OCP\Http\Client\IResponse; +use OCP\IGroup; use OCP\IGroupManager; use OCP\IURLGenerator; use OCP\IUserManager; @@ -133,6 +134,23 @@ protected function setUp(): void { $this->testMountProvider = new MountProvider(\OC::$server->getDatabaseConnection(), function () { return $this->manager; }, new CloudIdManager($this->contactsManager, $this->createMock(IURLGenerator::class), $this->userManager)); + + $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() { @@ -142,8 +160,8 @@ private function setupMounts() { } } - public function testAddShare() { - $shareData1 = [ + public function testAddUserShare() { + $this->doTestAddShare([ 'remote' => 'http://localhost', 'token' => 'token1', 'password' => '', @@ -153,23 +171,41 @@ public function testAddShare() { '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'); @@ -179,33 +215,35 @@ public function testAddShare() { $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']); @@ -214,11 +252,11 @@ public function testAddShare() { $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']); @@ -229,33 +267,39 @@ public function testAddShare() { $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']); @@ -269,46 +313,62 @@ public function testAddShare() { $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(); @@ -324,13 +384,13 @@ public function testAddShare() { * @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); } From 46b8cf4f64a8a2d9320ded762eeb0a4b9347181d Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Fri, 2 Jul 2021 17:42:55 +0200 Subject: [PATCH 03/14] 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(-) diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js index c7883e4d2a69d..5d9e8578e1961 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 6af8224baf441..8fb5cb02e56b6 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 1f467f549d7d8..2afd8a21b3185 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 ab41db7e8cd8a..385d42eaaeaa4 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 } From cff8ae7ded95ca035c7a6ec9fe5c4212273e2b6c Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Fri, 2 Jul 2021 17:45:03 +0200 Subject: [PATCH 04/14] 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 +- .../files_sharing/lib/AppInfo/Application.php | 3 +- apps/files_sharing/lib/External/Manager.php | 37 ++++++++++++------- apps/files_sharing/lib/Hooks.php | 3 +- .../tests/External/ManagerTest.php | 2 + 5 files changed, 32 insertions(+), 16 deletions(-) diff --git a/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php b/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php index cc6b260add6e1..94fbb7870d5c4 100644 --- a/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php +++ b/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php @@ -250,7 +250,8 @@ public function shareReceived(ICloudFederationShare $share) { \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 7f234e63660dc..3975a8a3bdee0 100644 --- a/apps/files_sharing/lib/AppInfo/Application.php +++ b/apps/files_sharing/lib/AppInfo/Application.php @@ -98,7 +98,8 @@ public function __construct(array $urlParams = []) { $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 247be47ac90a4..de566692d6b25 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\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 @@ public function __construct(IDBConnection $connection, $this->groupManager = $groupManager; $this->userManager = $userManager; $this->eventDispatcher = $eventDispatcher; + $this->logger = $logger; } /** @@ -535,6 +544,7 @@ public function removeShare($mountPoint): bool { $this->removeReShares($id); } catch (\Doctrine\DBAL\Exception $ex) { + $this->logger->logException($ex); return false; } @@ -606,6 +616,7 @@ public function removeUserShares($uid): bool { $deleteResult->closeCursor(); } } catch (\Doctrine\DBAL\Exception $ex) { + $this->logger->logException($ex); return false; } @@ -677,7 +688,7 @@ private function getShares($accepted) { } 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 ff4ca59339a94..26e799297ffdc 100644 --- a/apps/files_sharing/lib/Hooks.php +++ b/apps/files_sharing/lib/Hooks.php @@ -43,7 +43,8 @@ public static function deleteUser($params) { \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 60380aa06195f..1b8b3e1cf4b4c 100644 --- a/apps/files_sharing/tests/External/ManagerTest.php +++ b/apps/files_sharing/tests/External/ManagerTest.php @@ -44,6 +44,7 @@ use OCP\IGroup; use OCP\IGroupManager; use OCP\IURLGenerator; +use OCP\ILogger; use OCP\IUserManager; use OCP\Share\IShare; use Test\Traits\UserTrait; @@ -128,6 +129,7 @@ protected function setUp(): void { $this->userManager, $this->uid, $this->eventDispatcher, + $this->createMock(ILogger::class), ] )->setMethods(['tryOCMEndPoint'])->getMock(); From 4b5bb4d9cfda9a080861a192524500edfe634ca2 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Fri, 2 Jul 2021 17:46:16 +0200 Subject: [PATCH 05/14] 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(-) diff --git a/apps/files_sharing/lib/External/Manager.php b/apps/files_sharing/lib/External/Manager.php index de566692d6b25..b67ca675c31bf 100644 --- a/apps/files_sharing/lib/External/Manager.php +++ b/apps/files_sharing/lib/External/Manager.php @@ -227,7 +227,7 @@ private function writeShareToDb($remote, $token, $password, $name, $owner, $user * @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 @@ public function getShare($id) { $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 @@ public function getShare($id) { 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 @@ public function acceptShare($id) { 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 @@ public function declineShare($id) { $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 @@ public function removeShare($mountPoint): bool { } $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); From f3f2faa815207b12172cefadcbebe68ab8bffcb3 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Fri, 2 Jul 2021 18:16:34 +0200 Subject: [PATCH 06/14] 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(-) diff --git a/apps/files_sharing/js/app.js b/apps/files_sharing/js/app.js index 8fb5cb02e56b6..1c01c880a6dbe 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 2afd8a21b3185..bd3de5031ab1a 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 385d42eaaeaa4..bb160f8715daa 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) { From c1563215e6916b7e44afa464adbbe60ad3af2755 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Mon, 5 Jul 2021 12:13:53 +0200 Subject: [PATCH 07/14] 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(+) diff --git a/apps/files/js/gotoplugin.js b/apps/files/js/gotoplugin.js index 4793420ed2d12..d686c7850cfb2 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 1c01c880a6dbe..d16bf264cbd6d 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 bb160f8715daa..a7660a29f58d2 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) { From 15f41a6b727ba437536fc727778cc41ca710d0d5 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Mon, 5 Jul 2021 16:07:02 +0200 Subject: [PATCH 08/14] 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 ++++- .../tests/External/ManagerTest.php | 166 +++++++++++++++++- 2 files changed, 199 insertions(+), 11 deletions(-) diff --git a/apps/files_sharing/lib/External/Manager.php b/apps/files_sharing/lib/External/Manager.php index b67ca675c31bf..082e1adef8af9 100644 --- a/apps/files_sharing/lib/External/Manager.php +++ b/apps/files_sharing/lib/External/Manager.php @@ -238,6 +238,20 @@ private function fetchShare($id) { 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 @@ public function acceptShare($id) { } 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 @@ public function declineShare($id) { $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 @@ public function setMountPoint($source, $target) { 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 1b8b3e1cf4b4c..6dc9ced87a7a6 100644 --- a/apps/files_sharing/tests/External/ManagerTest.php +++ b/apps/files_sharing/tests/External/ManagerTest.php @@ -114,6 +114,9 @@ protected function setUp(): void { ->method('search') ->willReturn([]); + $logger = $this->createMock(ILogger::class); + $logger->expects($this->never())->method('logException'); + $this->manager = $this->getMockBuilder(Manager::class) ->setConstructorArgs( [ @@ -129,7 +132,7 @@ protected function setUp(): void { $this->userManager, $this->uid, $this->eventDispatcher, - $this->createMock(ILogger::class), + $logger, ] )->setMethods(['tryOCMEndPoint'])->getMock(); @@ -156,6 +159,7 @@ protected function tearDown(): void { } private function setupMounts() { + $this->mountManager->clear(); $mounts = $this->testMountProvider->getMountsForUser($this->user, new StorageFactory()); foreach ($mounts as $mount) { $this->mountManager->addMount($mount); @@ -248,7 +252,7 @@ public function doTestAddShare($shareData1, $isGroup = false) { } // 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]); @@ -304,7 +308,7 @@ public function doTestAddShare($shareData1, $isGroup = false) { } // Decline the third share - $this->manager->declineShare($openShares[1]['id']); + $this->assertTrue($this->manager->declineShare($openShares[1]['id'])); $this->setupMounts(); $this->assertMount($shareData1['name']); @@ -380,6 +384,162 @@ public function doTestAddShare($shareData1, $isGroup = false) { $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 From e8f4a524a2ae917c62710dd635df2740081bc83f Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Tue, 13 Jul 2021 17:51:52 +0200 Subject: [PATCH 09/14] 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 ++++++++++++------- .../tests/External/ManagerTest.php | 12 ++++++++-- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/apps/files_sharing/lib/External/Manager.php b/apps/files_sharing/lib/External/Manager.php index 082e1adef8af9..fdc02f104afeb 100644 --- a/apps/files_sharing/lib/External/Manager.php +++ b/apps/files_sharing/lib/External/Manager.php @@ -37,6 +37,7 @@ 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 @@ private function getShares($accepted) { $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 6dc9ced87a7a6..39e25dd75a653 100644 --- a/apps/files_sharing/tests/External/ManagerTest.php +++ b/apps/files_sharing/tests/External/ManagerTest.php @@ -144,9 +144,17 @@ protected function setUp(): void { $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 { From 3deffc31615fa118565084ff2901affaf7d0a403 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Mon, 19 Jul 2021 23:14:06 +0200 Subject: [PATCH 10/14] 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(-) diff --git a/apps/files_sharing/js/sharedfilelist.js b/apps/files_sharing/js/sharedfilelist.js index bd3de5031ab1a..41bfbd2603eaf 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' } From e67e90afce36edb18cebbc5f5799e427b891e114 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Tue, 20 Jul 2021 16:42:19 +0200 Subject: [PATCH 11/14] 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 ++++- .../tests/External/ManagerTest.php | 36 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/apps/files_sharing/lib/External/Manager.php b/apps/files_sharing/lib/External/Manager.php index fdc02f104afeb..b74198c87937e 100644 --- a/apps/files_sharing/lib/External/Manager.php +++ b/apps/files_sharing/lib/External/Manager.php @@ -334,7 +334,13 @@ public function acceptShare($id) { 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 39e25dd75a653..86b05f6d2ad55 100644 --- a/apps/files_sharing/tests/External/ManagerTest.php +++ b/apps/files_sharing/tests/External/ManagerTest.php @@ -548,6 +548,42 @@ public function testDeclineGroupShareAgainThroughMountPoint() { $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 From dac676c14ef7a3bd866a674c2ced666435d977c6 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Tue, 20 Jul 2021 20:36:26 +0200 Subject: [PATCH 12/14] Replace ILogger with LoggerInterface in remote share manager Signed-off-by: Vincent Petry Co-authored-by: Carl Schwan --- .../lib/OCM/CloudFederationProviderFiles.php | 3 ++- .../files_sharing/lib/AppInfo/Application.php | 3 ++- apps/files_sharing/lib/External/Manager.php | 20 +++++++++---------- apps/files_sharing/lib/Hooks.php | 3 ++- .../tests/External/ManagerTest.php | 6 +++--- 5 files changed, 19 insertions(+), 16 deletions(-) diff --git a/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php b/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php index 94fbb7870d5c4..916a8538c6e20 100644 --- a/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php +++ b/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php @@ -59,6 +59,7 @@ use OCP\Share\IManager; use OCP\Share\IShare; use OCP\Util; +use Psr\Log\LoggerInterface; class CloudFederationProviderFiles implements ICloudFederationProvider { @@ -251,7 +252,7 @@ public function shareReceived(ICloudFederationShare $share) { \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 3975a8a3bdee0..2dfbe4d86a591 100644 --- a/apps/files_sharing/lib/AppInfo/Application.php +++ b/apps/files_sharing/lib/AppInfo/Application.php @@ -60,6 +60,7 @@ 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 @@ public function __construct(array $urlParams = []) { $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 b74198c87937e..a8b01a7446451 100644 --- a/apps/files_sharing/lib/External/Manager.php +++ b/apps/files_sharing/lib/External/Manager.php @@ -46,12 +46,12 @@ 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 @@ public function __construct( IUserManager $userManager, ?string $uid, IEventDispatcher $eventDispatcher, - ILogger $logger + LoggerInterface $logger ) { $this->connection = $connection; $this->mountManager = $mountManager; @@ -343,7 +343,7 @@ public function acceptShare($id) { $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 @@ public function acceptShare($id) { $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 @@ public function declineShare($id) { $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 @@ public function declineShare($id) { $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 @@ public function removeShare($mountPoint): bool { $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 @@ public function removeUserShares($uid): bool { $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 @@ private function getShares($accepted) { } 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 26e799297ffdc..f28f6910abd6a 100644 --- a/apps/files_sharing/lib/Hooks.php +++ b/apps/files_sharing/lib/Hooks.php @@ -28,6 +28,7 @@ use OC\Files\Filesystem; use OCP\EventDispatcher\IEventDispatcher; +use Psr\Log\LoggerInterface; class Hooks { public static function deleteUser($params) { @@ -44,7 +45,7 @@ public static function deleteUser($params) { \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 86b05f6d2ad55..f0e6a0e843bc1 100644 --- a/apps/files_sharing/tests/External/ManagerTest.php +++ b/apps/files_sharing/tests/External/ManagerTest.php @@ -44,9 +44,9 @@ use OCP\IGroup; use OCP\IGroupManager; use OCP\IURLGenerator; -use OCP\ILogger; use OCP\IUserManager; use OCP\Share\IShare; +use Psr\Log\LoggerInterface; use Test\Traits\UserTrait; /** @@ -114,8 +114,8 @@ protected function setUp(): void { ->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( From 11258326230b54aaab5a4c6b259ba0b5e6155e95 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Tue, 27 Jul 2021 13:30:44 +0200 Subject: [PATCH 13/14] Fix route path for pending remote shares Signed-off-by: Vincent Petry --- apps/files_sharing/js/app.js | 2 +- 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 +- apps/files_sharing/js/sharedfilelist.js | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/files_sharing/js/app.js b/apps/files_sharing/js/app.js index d16bf264cbd6d..ef08315c48859 100644 --- a/apps/files_sharing/js/app.js +++ b/apps/files_sharing/js/app.js @@ -302,7 +302,7 @@ OCA.Sharing.App = { if (context.$file.attr('data-remote-id')) { shareBase = 'remote_shares/pending' } - $.post(OC.linkToOCS('apps/files_sharing/api/v1/' + shareBase + '/{shareId}', { shareBase, shareId })) + $.post(OC.linkToOCS('apps/files_sharing/api/v1/' + shareBase + '/{shareId}', { shareId })) .success(function(result) { context.fileList.remove(context.fileInfoModel.attributes.name) }).fail(function() { diff --git a/apps/files_sharing/js/dist/additionalScripts.js b/apps/files_sharing/js/dist/additionalScripts.js index cb86adc900229..8b7cf907410b9 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(18),o=n.n(s),l=n(228),c={insert:"head",singleton:!1};o()(l.a,c),l.a.locals,n(152); + */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(18),o=n.n(s),l=n(228),c={insert:"head",singleton:!1};o()(l.a,c),l.a.locals,n(152); /** * @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 2fb2395a9abd7..44a78aafa5587 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 = 470);\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