Skip to content

Commit 4201a71

Browse files
committed
feat(files_sharing): add config option for extending link-share permissions
This allows the admin to control the behavior whether link shares with READ permissions should be extended to also gain SHARE permissions, allowing users (public share receivers) to add the share to their cloud. Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
1 parent 4cd026a commit 4201a71

File tree

10 files changed

+109
-12
lines changed

10 files changed

+109
-12
lines changed

apps/files_sharing/lib/Controller/ShareAPIController.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
namespace OCA\Files_Sharing\Controller;
1111

1212
use Exception;
13+
use OC\Core\AppInfo\ConfigLexicon;
1314
use OC\Files\Storage\Wrapper\Wrapper;
1415
use OCA\Circles\Api\v1\Circles;
1516
use OCA\Files\Helper;
@@ -39,6 +40,7 @@
3940
use OCP\Files\Node;
4041
use OCP\Files\NotFoundException;
4142
use OCP\HintException;
43+
use OCP\IAppConfig;
4244
use OCP\IConfig;
4345
use OCP\IDateTimeZone;
4446
use OCP\IGroupManager;
@@ -86,6 +88,7 @@ public function __construct(
8688
private IURLGenerator $urlGenerator,
8789
private IL10N $l,
8890
private IConfig $config,
91+
private IAppConfig $appConfig,
8992
private IAppManager $appManager,
9093
private ContainerInterface $serverContainer,
9194
private IUserStatusManager $userStatusManager,
@@ -967,9 +970,9 @@ private function getLinkSharePermissions(?int $permissions, ?bool $legacyPublicU
967970
: Constants::PERMISSION_READ;
968971
}
969972

970-
// TODO: It might make sense to have a dedicated setting to allow/deny converting link shares into federated ones
971973
if ($this->hasPermission($permissions, Constants::PERMISSION_READ)
972-
&& $this->shareManager->outgoingServer2ServerSharesAllowed()) {
974+
&& $this->shareManager->outgoingServer2ServerSharesAllowed()
975+
&& $this->appConfig->getValueBool('core', ConfigLexicon::SHAREAPI_ALLOW_FEDERATION_ON_PUBLIC_SHARES, true)) {
973976
$permissions |= Constants::PERMISSION_SHARE;
974977
}
975978

apps/files_sharing/tests/ApiTest.php

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use OCP\Constants;
2121
use OCP\Files\Folder;
2222
use OCP\Files\IRootFolder;
23+
use OCP\IAppConfig;
2324
use OCP\IConfig;
2425
use OCP\IDateTimeZone;
2526
use OCP\IGroupManager;
@@ -34,6 +35,7 @@
3435
use OCP\Share\IProviderFactory;
3536
use OCP\Share\IShare;
3637
use OCP\UserStatus\IManager as IUserStatusManager;
38+
use PHPUnit\Framework\MockObject\MockObject;
3739
use Psr\Container\ContainerInterface;
3840
use Psr\Log\LoggerInterface;
3941

@@ -49,11 +51,9 @@ class ApiTest extends TestCase {
4951

5052
private static $tempStorage;
5153

52-
/** @var Folder */
53-
private $userFolder;
54-
55-
/** @var string */
56-
private $subsubfolder;
54+
private Folder $userFolder;
55+
private string $subsubfolder;
56+
protected IAppConfig&MockObject $appConfig;
5757

5858
protected function setUp(): void {
5959
parent::setUp();
@@ -80,6 +80,8 @@ protected function setUp(): void {
8080
$mount->getStorage()->getScanner()->scan('', Scanner::SCAN_RECURSIVE);
8181

8282
$this->userFolder = \OC::$server->getUserFolder(self::TEST_FILES_SHARING_API_USER1);
83+
84+
$this->appConfig = $this->createMock(IAppConfig::class);
8385
}
8486

8587
protected function tearDown(): void {
@@ -125,6 +127,7 @@ private function createOCS($userId) {
125127
Server::get(IURLGenerator::class),
126128
$l,
127129
$config,
130+
$this->appConfig,
128131
$appManager,
129132
$serverContainer,
130133
$userStatusManager,
@@ -232,8 +235,12 @@ public function testCreateShareLink(): void {
232235

233236
/**
234237
* @group RoutingWeirdness
238+
* @dataProvider dataAllowFederationOnPublicShares
235239
*/
236-
public function testCreateShareLinkPublicUpload(): void {
240+
public function testCreateShareLinkPublicUpload(array $appConfig, int $permissions): void {
241+
$this->appConfig->method('getValueBool')
242+
->willReturnMap([$appConfig]);
243+
237244
$ocs = $this->createOCS(self::TEST_FILES_SHARING_API_USER1);
238245
$result = $ocs->createShare($this->folder, Constants::PERMISSION_ALL, IShare::TYPE_LINK, null, 'true');
239246
$ocs->cleanup();
@@ -244,7 +251,7 @@ public function testCreateShareLinkPublicUpload(): void {
244251
Constants::PERMISSION_CREATE |
245252
Constants::PERMISSION_UPDATE |
246253
Constants::PERMISSION_DELETE |
247-
Constants::PERMISSION_SHARE,
254+
$permissions,
248255
$data['permissions']
249256
);
250257
$this->assertEmpty($data['expiration']);
@@ -1004,8 +1011,13 @@ public function testUpdateShare(): void {
10041011

10051012
/**
10061013
* @medium
1014+
* @dataProvider dataAllowFederationOnPublicShares
10071015
*/
1008-
public function testUpdateShareUpload(): void {
1016+
public function testUpdateShareUpload(array $appConfig, int $permissions): void {
1017+
$this->appConfig->method('getValueBool')->willReturnMap([
1018+
$appConfig,
1019+
]);
1020+
10091021
$node1 = $this->userFolder->get($this->folder);
10101022
$share1 = $this->shareManager->newShare();
10111023
$share1->setNode($node1)
@@ -1025,14 +1037,21 @@ public function testUpdateShareUpload(): void {
10251037
Constants::PERMISSION_CREATE |
10261038
Constants::PERMISSION_UPDATE |
10271039
Constants::PERMISSION_DELETE |
1028-
Constants::PERMISSION_SHARE,
1040+
$permissions,
10291041
$share1->getPermissions()
10301042
);
10311043

10321044
// cleanup
10331045
$this->shareManager->deleteShare($share1);
10341046
}
10351047

1048+
public static function dataAllowFederationOnPublicShares(): array {
1049+
return [
1050+
[['core', 'shareapi_allow_federation_on_public_shares', true, false], 0],
1051+
[['core', 'shareapi_allow_federation_on_public_shares', true, true], Constants::PERMISSION_SHARE],
1052+
];
1053+
}
1054+
10361055
/**
10371056
* @medium
10381057
*/

apps/files_sharing/tests/Controller/ShareAPIControllerTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use OCP\Files\Mount\IShareOwnerlessMount;
2222
use OCP\Files\NotFoundException;
2323
use OCP\Files\Storage\IStorage;
24+
use OCP\IAppConfig;
2425
use OCP\IConfig;
2526
use OCP\IDateTimeZone;
2627
use OCP\IGroup;
@@ -70,6 +71,7 @@ class ShareAPIControllerTest extends TestCase {
7071
private IURLGenerator&MockObject $urlGenerator;
7172
private IL10N&MockObject $l;
7273
private IConfig&MockObject $config;
74+
private IAppConfig&MockObject $appConfig;
7375
private IAppManager&MockObject $appManager;
7476
private ContainerInterface&MockObject $serverContainer;
7577
private IUserStatusManager&MockObject $userStatusManager;
@@ -102,6 +104,7 @@ protected function setUp(): void {
102104
return vsprintf($text, $parameters);
103105
});
104106
$this->config = $this->createMock(IConfig::class);
107+
$this->appConfig = $this->createMock(IAppConfig::class);
105108
$this->appManager = $this->createMock(IAppManager::class);
106109
$this->serverContainer = $this->createMock(ContainerInterface::class);
107110
$this->userStatusManager = $this->createMock(IUserStatusManager::class);
@@ -126,6 +129,7 @@ protected function setUp(): void {
126129
$this->urlGenerator,
127130
$this->l,
128131
$this->config,
132+
$this->appConfig,
129133
$this->appManager,
130134
$this->serverContainer,
131135
$this->userStatusManager,
@@ -154,6 +158,7 @@ private function mockFormatShare() {
154158
$this->urlGenerator,
155159
$this->l,
156160
$this->config,
161+
$this->appConfig,
157162
$this->appManager,
158163
$this->serverContainer,
159164
$this->userStatusManager,
@@ -839,6 +844,7 @@ public function testGetShare(IShare $share, array $result): void {
839844
$this->urlGenerator,
840845
$this->l,
841846
$this->config,
847+
$this->appConfig,
842848
$this->appManager,
843849
$this->serverContainer,
844850
$this->userStatusManager,
@@ -1472,6 +1478,7 @@ public function testGetShares(array $getSharesParameters, array $shares, array $
14721478
$this->urlGenerator,
14731479
$this->l,
14741480
$this->config,
1481+
$this->appConfig,
14751482
$this->appManager,
14761483
$this->serverContainer,
14771484
$this->userStatusManager,
@@ -1863,6 +1870,7 @@ public function testCreateShareUser(): void {
18631870
$this->urlGenerator,
18641871
$this->l,
18651872
$this->config,
1873+
$this->appConfig,
18661874
$this->appManager,
18671875
$this->serverContainer,
18681876
$this->userStatusManager,
@@ -1961,6 +1969,7 @@ public function testCreateShareGroup(): void {
19611969
$this->urlGenerator,
19621970
$this->l,
19631971
$this->config,
1972+
$this->appConfig,
19641973
$this->appManager,
19651974
$this->serverContainer,
19661975
$this->userStatusManager,
@@ -2387,6 +2396,7 @@ public function testCreateShareRemote(): void {
23872396
$this->urlGenerator,
23882397
$this->l,
23892398
$this->config,
2399+
$this->appConfig,
23902400
$this->appManager,
23912401
$this->serverContainer,
23922402
$this->userStatusManager,
@@ -2458,6 +2468,7 @@ public function testCreateShareRemoteGroup(): void {
24582468
$this->urlGenerator,
24592469
$this->l,
24602470
$this->config,
2471+
$this->appConfig,
24612472
$this->appManager,
24622473
$this->serverContainer,
24632474
$this->userStatusManager,
@@ -2696,6 +2707,7 @@ public function testCreateReshareOfFederatedMountNoDeletePermissions(): void {
26962707
$this->urlGenerator,
26972708
$this->l,
26982709
$this->config,
2710+
$this->appConfig,
26992711
$this->appManager,
27002712
$this->serverContainer,
27012713
$this->userStatusManager,

apps/settings/lib/Settings/Admin/Sharing.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use OCP\AppFramework\Http\TemplateResponse;
1010
use OCP\AppFramework\Services\IInitialState;
1111
use OCP\Constants;
12+
use OCP\IAppConfig;
1213
use OCP\IConfig;
1314
use OCP\IL10N;
1415
use OCP\IURLGenerator;
@@ -19,6 +20,7 @@
1920
class Sharing implements IDelegatedSettings {
2021
public function __construct(
2122
private IConfig $config,
23+
private IAppConfig $appConfig,
2224
private IL10N $l,
2325
private IManager $shareManager,
2426
private IAppManager $appManager,
@@ -46,6 +48,7 @@ public function getForm() {
4648
'allowPublicUpload' => $this->getHumanBooleanConfig('core', 'shareapi_allow_public_upload', true),
4749
'allowResharing' => $this->getHumanBooleanConfig('core', 'shareapi_allow_resharing', true),
4850
'allowShareDialogUserEnumeration' => $this->getHumanBooleanConfig('core', 'shareapi_allow_share_dialog_user_enumeration', true),
51+
'allowFederationOnPublicShares' => $this->appConfig->getValueBool('core', 'shareapi_allow_federation_on_public_shares', true),
4952
'restrictUserEnumerationToGroup' => $this->getHumanBooleanConfig('core', 'shareapi_restrict_user_enumeration_to_group'),
5053
'restrictUserEnumerationToPhone' => $this->getHumanBooleanConfig('core', 'shareapi_restrict_user_enumeration_to_phone'),
5154
'restrictUserEnumerationFullMatch' => $this->getHumanBooleanConfig('core', 'shareapi_restrict_user_enumeration_full_match', true),

apps/settings/src/components/AdminSettingsSharingForm.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@
3939
<NcCheckboxRadioSwitch :checked.sync="settings.allowPublicUpload">
4040
{{ t('settings', 'Allow public uploads') }}
4141
</NcCheckboxRadioSwitch>
42+
<NcCheckboxRadioSwitch v-model="settings.allowFederationOnPublicShares">
43+
{{ t('settings', 'Allow public shares to be added to other clouds by federation.') }}
44+
{{ t('settings', 'This will add share permissions to all newly created link shares.') }}
45+
</NcCheckboxRadioSwitch>
4246
<NcCheckboxRadioSwitch :checked.sync="settings.enableLinkPasswordByDefault">
4347
{{ t('settings', 'Always ask for a password') }}
4448
</NcCheckboxRadioSwitch>
@@ -232,6 +236,7 @@ interface IShareSettings {
232236
allowPublicUpload: boolean
233237
allowResharing: boolean
234238
allowShareDialogUserEnumeration: boolean
239+
allowFederationOnPublicShares: boolean
235240
restrictUserEnumerationToGroup: boolean
236241
restrictUserEnumerationToPhone: boolean
237242
restrictUserEnumerationFullMatch: boolean

apps/settings/tests/Settings/Admin/SharingTest.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use OCP\AppFramework\Http\TemplateResponse;
1111
use OCP\AppFramework\Services\IInitialState;
1212
use OCP\Constants;
13+
use OCP\IAppConfig;
1314
use OCP\IConfig;
1415
use OCP\IL10N;
1516
use OCP\IURLGenerator;
@@ -18,25 +19,30 @@
1819
use Test\TestCase;
1920

2021
class SharingTest extends TestCase {
22+
private Sharing $admin;
23+
2124
private IConfig&MockObject $config;
25+
private IAppConfig&MockObject $appConfig;
2226
private IL10N&MockObject $l10n;
2327
private IManager&MockObject $shareManager;
2428
private IAppManager&MockObject $appManager;
2529
private IURLGenerator&MockObject $urlGenerator;
2630
private IInitialState&MockObject $initialState;
27-
private Sharing $admin;
2831

2932
protected function setUp(): void {
3033
parent::setUp();
3134
$this->config = $this->createMock(IConfig::class);
35+
$this->appConfig = $this->createMock(IAppConfig::class);
3236
$this->l10n = $this->createMock(IL10N::class);
37+
3338
$this->shareManager = $this->createMock(IManager::class);
3439
$this->appManager = $this->createMock(IAppManager::class);
3540
$this->urlGenerator = $this->createMock(IURLGenerator::class);
3641
$this->initialState = $this->createMock(IInitialState::class);
3742

3843
$this->admin = new Sharing(
3944
$this->config,
45+
$this->appConfig,
4046
$this->l10n,
4147
$this->shareManager,
4248
$this->appManager,
@@ -47,6 +53,12 @@ protected function setUp(): void {
4753
}
4854

4955
public function testGetFormWithoutExcludedGroups(): void {
56+
$this->appConfig
57+
->method('getValueBool')
58+
->willReturnMap([
59+
['core', 'shareapi_allow_federation_on_public_shares', true, false, true],
60+
]);
61+
5062
$this->config
5163
->method('getAppValue')
5264
->willReturnMap([
@@ -102,6 +114,7 @@ public function testGetFormWithoutExcludedGroups(): void {
102114
'allowPublicUpload' => true,
103115
'allowResharing' => true,
104116
'allowShareDialogUserEnumeration' => true,
117+
'allowFederationOnPublicShares' => true,
105118
'restrictUserEnumerationToGroup' => false,
106119
'restrictUserEnumerationToPhone' => false,
107120
'restrictUserEnumerationFullMatch' => true,

core/AppInfo/Application.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ public function register(IRegistrationContext $context): void {
7878

7979
// Tags
8080
$context->registerEventListener(UserDeletedEvent::class, TagManager::class);
81+
82+
// config lexicon
83+
$context->registerConfigLexicon(ConfigLexicon::class);
8184
}
8285

8386
public function boot(IBootContext $context): void {

core/AppInfo/ConfigLexicon.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
6+
* SPDX-License-Identifier: AGPL-3.0-or-later
7+
*/
8+
9+
namespace OC\Core\AppInfo;
10+
11+
use NCU\Config\Lexicon\ConfigLexiconEntry;
12+
use NCU\Config\Lexicon\ConfigLexiconStrictness;
13+
use NCU\Config\Lexicon\IConfigLexicon;
14+
use NCU\Config\ValueType;
15+
16+
/**
17+
* Config Lexicon for core.
18+
*
19+
* Please Add & Manage your Config Keys in that file and keep the Lexicon up to date!
20+
*/
21+
class ConfigLexicon implements IConfigLexicon {
22+
public const SHAREAPI_ALLOW_FEDERATION_ON_PUBLIC_SHARES = 'shareapi_allow_federation_on_public_shares';
23+
24+
public function getStrictness(): ConfigLexiconStrictness {
25+
return ConfigLexiconStrictness::IGNORE;
26+
}
27+
28+
public function getAppConfigs(): array {
29+
return [
30+
new ConfigLexiconEntry(self::SHAREAPI_ALLOW_FEDERATION_ON_PUBLIC_SHARES, ValueType::BOOL, true, 'adds share permission to public shares to allow adding them to your Nextcloud (federation)'),
31+
];
32+
}
33+
34+
public function getUserConfigs(): array {
35+
return [];
36+
}
37+
}

0 commit comments

Comments
 (0)