feat(federatedfilesharing): auto-accept shares from trusted servers

Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
pull/49973/head
skjnldsv 1 year ago
parent 771584f5c1
commit 5c359e424f
  1. 3
      apps/federatedfilesharing/lib/Controller/RequestHandlerController.php
  2. 5
      apps/federatedfilesharing/lib/FederatedShareProvider.php
  3. 13
      apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php
  4. 2
      apps/federatedfilesharing/lib/Settings/Admin.php
  5. 7
      apps/federatedfilesharing/src/components/AdminSettings.vue
  6. 2
      apps/federation/lib/BackgroundJob/GetSharedSecret.php
  7. 2
      apps/federation/lib/BackgroundJob/RequestSharedSecret.php
  8. 2
      apps/federation/openapi-administration.json.license
  9. 2
      apps/federation/openapi-federation.json.license
  10. 2
      apps/federation/openapi-full.json.license
  11. 89
      apps/files_sharing/lib/External/Manager.php
  12. 12
      apps/files_sharing/tests/External/ManagerTest.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,

@ -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
*/

@ -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.', [

@ -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',
],
];
}

@ -43,6 +43,12 @@
@update:checked="update('lookupServerUploadEnabled', lookupServerUploadEnabled)">
{{ t('federatedfilesharing', 'Allow people to publish their data to a global and public address book') }}
</NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch type="switch"
:checked.sync="federatedTrustedShareAutoAccept"
@update:checked="update('federatedTrustedShareAutoAccept', federatedTrustedShareAutoAccept)">
{{ t('federatedfilesharing', 'Automatically accept shares from federated accounts and groups by default') }}
</NcCheckboxRadioSwitch>
</NcSettingsSection>
</template>
@ -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'),
}

@ -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();

@ -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();

@ -0,0 +1,2 @@
SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
SPDX-License-Identifier: AGPL-3.0-or-later

@ -0,0 +1,2 @@
SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
SPDX-License-Identifier: AGPL-3.0-or-later

@ -0,0 +1,2 @@
SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
SPDX-License-Identifier: AGPL-3.0-or-later

@ -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<Files_SharingRemoteShare> 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 = [];

@ -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');

Loading…
Cancel
Save