mirror of
https://github.com/nextcloud/server.git
synced 2026-05-28 04:32:30 -04:00
feat(federatedfilesharing): auto-accept shares from trusted servers
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
This commit is contained in:
parent
771584f5c1
commit
5c359e424f
12 changed files with 101 additions and 40 deletions
|
|
@ -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();
|
||||
|
|
|
|||
2
apps/federation/openapi-administration.json.license
Normal file
2
apps/federation/openapi-administration.json.license
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
2
apps/federation/openapi-federation.json.license
Normal file
2
apps/federation/openapi-federation.json.license
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
2
apps/federation/openapi-full.json.license
Normal file
2
apps/federation/openapi-full.json.license
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
89
apps/files_sharing/lib/External/Manager.php
vendored
89
apps/files_sharing/lib/External/Manager.php
vendored
|
|
@ -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…
Reference in a new issue