diff --git a/apps/federatedfilesharing/lib/Controller/RequestHandlerController.php b/apps/federatedfilesharing/lib/Controller/RequestHandlerController.php index 63de8ff605e..90e7f53da80 100644 --- a/apps/federatedfilesharing/lib/Controller/RequestHandlerController.php +++ b/apps/federatedfilesharing/lib/Controller/RequestHandlerController.php @@ -39,9 +39,6 @@ use Psr\Log\LoggerInterface; #[OpenAPI(scope: OpenAPI::SCOPE_FEDERATION)] class RequestHandlerController extends OCSController { - /** @var string */ - private $shareTable = 'share'; - public function __construct( string $appName, IRequest $request, diff --git a/apps/federatedfilesharing/lib/FederatedShareProvider.php b/apps/federatedfilesharing/lib/FederatedShareProvider.php index 139c873b0d6..4d2157c1b44 100644 --- a/apps/federatedfilesharing/lib/FederatedShareProvider.php +++ b/apps/federatedfilesharing/lib/FederatedShareProvider.php @@ -999,6 +999,11 @@ class FederatedShareProvider implements IShareProvider { return ($result === 'yes'); } + public function isFederatedTrustedShareAutoAccept() { + $result = $this->config->getAppValue('files_sharing', 'federatedTrustedShareAutoAccept', 'yes'); + return ($result === 'yes'); + } + /** * @inheritdoc */ diff --git a/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php b/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php index 5c633c0fbbf..d5082eafc56 100644 --- a/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php +++ b/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php @@ -10,6 +10,7 @@ use OC\AppFramework\Http; use OC\Files\Filesystem; use OCA\FederatedFileSharing\AddressHandler; use OCA\FederatedFileSharing\FederatedShareProvider; +use OCA\Federation\TrustedServers; use OCA\Files_Sharing\Activity\Providers\RemoteShares; use OCA\Files_Sharing\External\Manager; use OCA\GlobalSiteSelector\Service\SlaveService; @@ -66,6 +67,7 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider { private LoggerInterface $logger, private IFilenameValidator $filenameValidator, private readonly IProviderFactory $shareProviderFactory, + private TrustedServers $trustedServers, ) { } @@ -163,6 +165,11 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider { ->setObject('remote_share', $shareId, $name); \OC::$server->getActivityManager()->publish($event); $this->notifyAboutNewShare($shareWith, $shareId, $ownerFederatedId, $sharedByFederatedId, $name, $ownerDisplayName); + + // If auto-accept is enabled, accept the share + if ($this->federatedShareProvider->isFederatedTrustedShareAutoAccept() && $this->trustedServers->isTrustedServer($remote)) { + $this->externalShareManager->acceptShare($shareId, $shareWith); + } } else { $groupMembers = $this->groupManager->get($shareWith)->getUsers(); foreach ($groupMembers as $user) { @@ -174,8 +181,14 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider { ->setObject('remote_share', $shareId, $name); \OC::$server->getActivityManager()->publish($event); $this->notifyAboutNewShare($user->getUID(), $shareId, $ownerFederatedId, $sharedByFederatedId, $name, $ownerDisplayName); + + // If auto-accept is enabled, accept the share + if ($this->federatedShareProvider->isFederatedTrustedShareAutoAccept() && $this->trustedServers->isTrustedServer($remote)) { + $this->externalShareManager->acceptShare($shareId, $user->getUID()); + } } } + return $shareId; } catch (\Exception $e) { $this->logger->error('Server can not add remote share.', [ diff --git a/apps/federatedfilesharing/lib/Settings/Admin.php b/apps/federatedfilesharing/lib/Settings/Admin.php index 1343513e65a..e21c34638ad 100644 --- a/apps/federatedfilesharing/lib/Settings/Admin.php +++ b/apps/federatedfilesharing/lib/Settings/Admin.php @@ -40,6 +40,7 @@ class Admin implements IDelegatedSettings { $this->initialState->provideInitialState('incomingServer2serverGroupShareEnabled', $this->fedShareProvider->isIncomingServer2serverGroupShareEnabled()); $this->initialState->provideInitialState('lookupServerEnabled', $this->fedShareProvider->isLookupServerQueriesEnabled()); $this->initialState->provideInitialState('lookupServerUploadEnabled', $this->fedShareProvider->isLookupServerUploadEnabled()); + $this->initialState->provideInitialState('federatedTrustedShareAutoAccept', $this->fedShareProvider->isFederatedTrustedShareAutoAccept()); return new TemplateResponse('federatedfilesharing', 'settings-admin', [], ''); } @@ -76,6 +77,7 @@ class Admin implements IDelegatedSettings { 'incomingServer2serverGroupShareEnabled', 'lookupServerEnabled', 'lookupServerUploadEnabled', + 'federatedTrustedShareAutoAccept', ], ]; } diff --git a/apps/federatedfilesharing/src/components/AdminSettings.vue b/apps/federatedfilesharing/src/components/AdminSettings.vue index dfafe64c062..4f2049942d5 100644 --- a/apps/federatedfilesharing/src/components/AdminSettings.vue +++ b/apps/federatedfilesharing/src/components/AdminSettings.vue @@ -43,6 +43,12 @@ @update:checked="update('lookupServerUploadEnabled', lookupServerUploadEnabled)"> {{ t('federatedfilesharing', 'Allow people to publish their data to a global and public address book') }} + + + {{ t('federatedfilesharing', 'Automatically accept shares from federated accounts and groups by default') }} + @@ -74,6 +80,7 @@ export default { federatedGroupSharingSupported: loadState('federatedfilesharing', 'federatedGroupSharingSupported'), lookupServerEnabled: loadState('federatedfilesharing', 'lookupServerEnabled'), lookupServerUploadEnabled: loadState('federatedfilesharing', 'lookupServerUploadEnabled'), + federatedTrustedShareAutoAccept: loadState('federatedfilesharing', 'federatedTrustedShareAutoAccept'), internalOnly: loadState('federatedfilesharing', 'internalOnly'), sharingFederatedDocUrl: loadState('federatedfilesharing', 'sharingFederatedDocUrl'), } diff --git a/apps/federation/lib/BackgroundJob/GetSharedSecret.php b/apps/federation/lib/BackgroundJob/GetSharedSecret.php index 01dbf7b80b6..fe3b360dd18 100644 --- a/apps/federation/lib/BackgroundJob/GetSharedSecret.php +++ b/apps/federation/lib/BackgroundJob/GetSharedSecret.php @@ -44,7 +44,7 @@ class GetSharedSecret extends Job { private LoggerInterface $logger, private IDiscoveryService $ocsDiscoveryService, ITimeFactory $timeFactory, - private IConfig $config + private IConfig $config, ) { parent::__construct($timeFactory); $this->httpClient = $httpClientService->newClient(); diff --git a/apps/federation/lib/BackgroundJob/RequestSharedSecret.php b/apps/federation/lib/BackgroundJob/RequestSharedSecret.php index 6691e39e682..cc50ee75ca0 100644 --- a/apps/federation/lib/BackgroundJob/RequestSharedSecret.php +++ b/apps/federation/lib/BackgroundJob/RequestSharedSecret.php @@ -48,7 +48,7 @@ class RequestSharedSecret extends Job { private IDiscoveryService $ocsDiscoveryService, private LoggerInterface $logger, ITimeFactory $timeFactory, - private IConfig $config + private IConfig $config, ) { parent::__construct($timeFactory); $this->httpClient = $httpClientService->newClient(); diff --git a/apps/federation/openapi-administration.json.license b/apps/federation/openapi-administration.json.license new file mode 100644 index 00000000000..5dcb9c9e84b --- /dev/null +++ b/apps/federation/openapi-administration.json.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors +SPDX-License-Identifier: AGPL-3.0-or-later \ No newline at end of file diff --git a/apps/federation/openapi-federation.json.license b/apps/federation/openapi-federation.json.license new file mode 100644 index 00000000000..5dcb9c9e84b --- /dev/null +++ b/apps/federation/openapi-federation.json.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors +SPDX-License-Identifier: AGPL-3.0-or-later \ No newline at end of file diff --git a/apps/federation/openapi-full.json.license b/apps/federation/openapi-full.json.license new file mode 100644 index 00000000000..5dcb9c9e84b --- /dev/null +++ b/apps/federation/openapi-full.json.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors +SPDX-License-Identifier: AGPL-3.0-or-later \ No newline at end of file diff --git a/apps/files_sharing/lib/External/Manager.php b/apps/files_sharing/lib/External/Manager.php index a69755edf2c..1ea7775641e 100644 --- a/apps/files_sharing/lib/External/Manager.php +++ b/apps/files_sharing/lib/External/Manager.php @@ -129,7 +129,7 @@ class Manager { 'mountpoint' => $mountPoint, 'owner' => $owner ]; - return $this->mountShare($options); + return $this->mountShare($options, $user); } /** @@ -214,11 +214,12 @@ class Manager { * @param int $id share id * @return mixed share of false */ - public function getShare($id) { + public function getShare(int $id, ?string $user = null): array|false { + $user = $user ?? $this->uid; $share = $this->fetchShare($id); // check if the user is allowed to access it - if ($this->canAccessShare($share)) { + if ($this->canAccessShare($share, $user)) { return $share; } @@ -243,7 +244,7 @@ class Manager { return $share; } - private function canAccessShare(array $share): bool { + private function canAccessShare(array $share, string $user): bool { $validShare = isset($share['share_type']) && isset($share['user']); if (!$validShare) { @@ -252,7 +253,7 @@ class Manager { // If the share is a user share, check if the user is the recipient if ((int)$share['share_type'] === IShare::TYPE_USER - && $share['user'] === $this->uid) { + && $share['user'] === $user) { return true; } @@ -266,7 +267,7 @@ class Manager { $groupShare = $share; } - $user = $this->userManager->get($this->uid); + $user = $this->userManager->get($user); if ($this->groupManager->get($groupShare['user'])->inGroup($user)) { return true; } @@ -295,13 +296,22 @@ class Manager { * @param int $id * @return bool True if the share could be accepted, false otherwise */ - public function acceptShare($id) { - $share = $this->getShare($id); + public function acceptShare(int $id, ?string $user = null) { + // If we're auto-accepting a share, we need to know the user id + // as there is no session available while processing the share + // from the remote server request. + $user = $user ?? $this->uid; + if ($user === null) { + $this->logger->error('No user specified for accepting share'); + return false; + } + + $share = $this->getShare($id, $user); $result = false; if ($share) { - \OC_Util::setupFS($this->uid); - $shareFolder = Helper::getShareFolder(null, $this->uid); + \OC_Util::setupFS($user); + $shareFolder = Helper::getShareFolder(null, $user); $mountPoint = Files::buildNotExistingFileName($shareFolder, $share['name']); $mountPoint = Filesystem::normalizePath($mountPoint); $hash = md5($mountPoint); @@ -314,14 +324,14 @@ class Manager { `mountpoint` = ?, `mountpoint_hash` = ? WHERE `id` = ? AND `user` = ?'); - $userShareAccepted = $acceptShare->execute([1, $mountPoint, $hash, $id, $this->uid]); + $userShareAccepted = $acceptShare->execute([1, $mountPoint, $hash, $id, $user]); } else { $parentId = (int)$share['parent']; if ($parentId !== -1) { // this is the sub-share $subshare = $share; } else { - $subshare = $this->fetchUserShare($id, $this->uid); + $subshare = $this->fetchUserShare($id, $user); } if ($subshare !== null) { @@ -332,7 +342,7 @@ class Manager { `mountpoint` = ?, `mountpoint_hash` = ? WHERE `id` = ? AND `user` = ?'); - $acceptShare->execute([1, $mountPoint, $hash, $subshare['id'], $this->uid]); + $acceptShare->execute([1, $mountPoint, $hash, $subshare['id'], $user]); $result = true; } catch (Exception $e) { $this->logger->emergency('Could not update share', ['exception' => $e]); @@ -346,7 +356,7 @@ class Manager { $share['password'], $share['name'], $share['owner'], - $this->uid, + $user, $mountPoint, $hash, 1, $share['remote_id'], $id, @@ -358,17 +368,18 @@ class Manager { } } } + if ($userShareAccepted !== false) { $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'accept'); $event = new FederatedShareAddedEvent($share['remote']); $this->eventDispatcher->dispatchTyped($event); - $this->eventDispatcher->dispatchTyped(new InvalidateMountCacheEvent($this->userManager->get($this->uid))); + $this->eventDispatcher->dispatchTyped(new InvalidateMountCacheEvent($this->userManager->get($user))); $result = true; } } // Make sure the user has no notification for something that does not exist anymore. - $this->processNotification($id); + $this->processNotification($id, $user); return $result; } @@ -379,17 +390,23 @@ class Manager { * @param int $id * @return bool True if the share could be declined, false otherwise */ - public function declineShare($id) { - $share = $this->getShare($id); + public function declineShare(int $id, ?string $user = null) { + $user = $user ?? $this->uid; + if ($user === null) { + $this->logger->error('No user specified for declining share'); + return false; + } + + $share = $this->getShare($id, $user); $result = false; if ($share && (int)$share['share_type'] === IShare::TYPE_USER) { $removeShare = $this->connection->prepare(' DELETE FROM `*PREFIX*share_external` WHERE `id` = ? AND `user` = ?'); - $removeShare->execute([$id, $this->uid]); + $removeShare->execute([$id, $user]); $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline'); - $this->processNotification($id); + $this->processNotification($id, $user); $result = true; } elseif ($share && (int)$share['share_type'] === IShare::TYPE_GROUP) { $parentId = (int)$share['parent']; @@ -397,7 +414,7 @@ class Manager { // this is the sub-share $subshare = $share; } else { - $subshare = $this->fetchUserShare($id, $this->uid); + $subshare = $this->fetchUserShare($id, $user); } if ($subshare !== null) { @@ -416,7 +433,7 @@ class Manager { $share['password'], $share['name'], $share['owner'], - $this->uid, + $user, $share['mountpoint'], $share['mountpoint_hash'], 0, @@ -429,16 +446,27 @@ class Manager { $result = false; } } - $this->processNotification($id); + $this->processNotification($id, $user); } return $result; } - public function processNotification(int $remoteShare): void { + public function processNotification(int $remoteShare, ?string $user = null): void { + $user = $user ?? $this->uid; + if ($user === null) { + $this->logger->error('No user specified for processing notification'); + return; + } + + $share = $this->fetchShare($remoteShare); + if ($share === false) { + return; + } + $filter = $this->notificationManager->createNotification(); $filter->setApp('files_sharing') - ->setUser($this->uid) + ->setUser($user) ->setObject('remote_share', (string)$remoteShare); $this->notificationManager->markProcessed($filter); } @@ -538,9 +566,10 @@ class Manager { return rtrim(substr($path, strlen($prefix)), '/'); } - public function getMount($data) { + public function getMount($data, ?string $user = null) { + $user = $user ?? $this->uid; $data['manager'] = $this; - $mountPoint = '/' . $this->uid . '/files' . $data['mountpoint']; + $mountPoint = '/' . $user . '/files' . $data['mountpoint']; $data['mountpoint'] = $mountPoint; $data['certificateManager'] = \OC::$server->getCertificateManager(); return new Mount(self::STORAGE, $mountPoint, $data, $this, $this->storageLoader); @@ -550,8 +579,8 @@ class Manager { * @param array $data * @return Mount */ - protected function mountShare($data) { - $mount = $this->getMount($data); + protected function mountShare($data, ?string $user = null) { + $mount = $this->getMount($data, $user); $this->mountManager->addMount($mount); return $mount; } @@ -768,6 +797,8 @@ class Manager { * @return list list of open server-to-server shares */ private function getShares($accepted) { + // Not allowing providing a user here, + // as we only want to retrieve shares for the current user. $user = $this->userManager->get($this->uid); $groups = $this->groupManager->getUserGroups($user); $userGroups = []; diff --git a/apps/files_sharing/tests/External/ManagerTest.php b/apps/files_sharing/tests/External/ManagerTest.php index 2bd0a1e14bf..29a873a72eb 100644 --- a/apps/files_sharing/tests/External/ManagerTest.php +++ b/apps/files_sharing/tests/External/ManagerTest.php @@ -645,10 +645,10 @@ class ManagerTest extends TestCase { 'user' => 'user2', 'remoteId' => '2342' ]; - $this->assertSame(null, call_user_func_array([$manager2, 'addShare'], $shareData2)); - $user2Shares = $manager2->getOpenShares(); - $this->assertCount(2, $user2Shares); + $this->assertCount(1, $manager2->getOpenShares()); + $this->assertSame(null, call_user_func_array([$manager2, 'addShare'], $shareData2)); + $this->assertCount(2, $manager2->getOpenShares()); $this->manager->expects($this->once())->method('tryOCMEndPoint')->with('http://localhost', 'token1', '2342', 'decline')->willReturn([]); $this->manager->removeUserShares($this->uid); @@ -690,10 +690,10 @@ class ManagerTest extends TestCase { 'user' => 'user2', 'remoteId' => '2342' ]; - $this->assertSame(null, call_user_func_array([$manager2, 'addShare'], $shareData2)); - $user2Shares = $manager2->getOpenShares(); - $this->assertCount(2, $user2Shares); + $this->assertCount(1, $manager2->getOpenShares()); + $this->assertSame(null, call_user_func_array([$manager2, 'addShare'], $shareData2)); + $this->assertCount(2, $manager2->getOpenShares()); $this->manager->expects($this->never())->method('tryOCMEndPoint'); $this->manager->removeGroupShares('group1');