Skip to content

Commit 618f702

Browse files
redblomsusnux
authored andcommitted
feat(core): exclude selected groups from sharing
Refs: #49198 Signed-off-by: Antoon Prins <antoon.prins@surf.nl>
1 parent 366eb9c commit 618f702

File tree

8 files changed

+164
-3
lines changed

8 files changed

+164
-3
lines changed

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,
@@ -41,6 +43,7 @@ public function getForm() {
4143
// Built-In Sharing
4244
'enabled' => $this->getHumanBooleanConfig('core', 'shareapi_enabled', true),
4345
'allowGroupSharing' => $this->getHumanBooleanConfig('core', 'shareapi_allow_group_sharing', true),
46+
'groupsBlockList' => $this->appConfig->getValueArray('core', 'shareapi_groups_block_list', []),
4447
'allowLinks' => $this->getHumanBooleanConfig('core', 'shareapi_allow_links', true),
4548
'allowLinksExcludeGroups' => json_decode($linksExcludedGroups, true) ?? [],
4649
'allowPublicUpload' => $this->getHumanBooleanConfig('core', 'shareapi_allow_public_upload', true),

apps/settings/src/components/AdminSettingsSharingForm.vue

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@
1717
<NcCheckboxRadioSwitch :checked.sync="settings.allowGroupSharing">
1818
{{ t('settings', 'Allow sharing with groups') }}
1919
</NcCheckboxRadioSwitch>
20+
<div v-show="settings.allowGroupSharing" id="settings-sharing-api-groups-block-list" class="sharing__labeled-entry sharing__input">
21+
<label for="sharing-groups-block-list">{{ t('settings', 'Groups that are excluded from sharing') }}</label>
22+
<NcSettingsSelectGroup id="sharing-groups-block-list"
23+
v-model="settings.groupsBlockList"
24+
:label="t('settings', 'Select groups')"
25+
style="width: 100%" />
26+
</div>
2027
<NcCheckboxRadioSwitch :checked.sync="settings.onlyShareWithGroupMembers">
2128
{{ t('settings', 'Restrict users to only share with users in their groups') }}
2229
</NcCheckboxRadioSwitch>
@@ -258,6 +265,7 @@ interface IShareSettings {
258265
remoteExpireAfterNDays: string
259266
enforceRemoteExpireDate: boolean
260267
allowCustomTokens: boolean
268+
groupsBlockList: string[]
261269
}
262270
263271
export default defineComponent({

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

Lines changed: 9 additions & 0 deletions
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;
@@ -22,6 +23,8 @@ class SharingTest extends TestCase {
2223
private $admin;
2324
/** @var IConfig&MockObject */
2425
private $config;
26+
/** @var IAppConfig */
27+
private $appConfig;
2528
/** @var IL10N&MockObject */
2629
private $l10n;
2730
/** @var IManager|MockObject */
@@ -36,6 +39,7 @@ class SharingTest extends TestCase {
3639
protected function setUp(): void {
3740
parent::setUp();
3841
$this->config = $this->getMockBuilder(IConfig::class)->getMock();
42+
$this->appConfig = $this->getMockBuilder(IAppConfig::class)->getMock();
3943
$this->l10n = $this->getMockBuilder(IL10N::class)->getMock();
4044

4145
/** @var IManager|MockObject */
@@ -49,6 +53,7 @@ protected function setUp(): void {
4953

5054
$this->admin = new Sharing(
5155
$this->config,
56+
$this->appConfig,
5257
$this->l10n,
5358
$this->shareManager,
5459
$this->appManager,
@@ -65,6 +70,7 @@ public function testGetFormWithoutExcludedGroups(): void {
6570
['core', 'shareapi_exclude_groups_list', '', ''],
6671
['core', 'shareapi_allow_links_exclude_groups', '', ''],
6772
['core', 'shareapi_allow_group_sharing', 'yes', 'yes'],
73+
['core', 'shareapi_groups_block_list', '[]', '[]'],
6874
['core', 'shareapi_allow_links', 'yes', 'yes'],
6975
['core', 'shareapi_allow_public_upload', 'yes', 'yes'],
7076
['core', 'shareapi_allow_resharing', 'yes', 'yes'],
@@ -110,6 +116,7 @@ public function testGetFormWithoutExcludedGroups(): void {
110116
'sharingDocumentation' => '',
111117
'sharingSettings' => [
112118
'allowGroupSharing' => true,
119+
'groupsBlockList' => [],
113120
'allowLinks' => true,
114121
'allowPublicUpload' => true,
115122
'allowResharing' => true,
@@ -162,6 +169,7 @@ public function testGetFormWithExcludedGroups(): void {
162169
['core', 'shareapi_exclude_groups_list', '', '["NoSharers","OtherNoSharers"]'],
163170
['core', 'shareapi_allow_links_exclude_groups', '', ''],
164171
['core', 'shareapi_allow_group_sharing', 'yes', 'yes'],
172+
['core', 'shareapi_groups_block_list', '[]', '[]'],
165173
['core', 'shareapi_allow_links', 'yes', 'yes'],
166174
['core', 'shareapi_allow_public_upload', 'yes', 'yes'],
167175
['core', 'shareapi_allow_resharing', 'yes', 'yes'],
@@ -207,6 +215,7 @@ public function testGetFormWithExcludedGroups(): void {
207215
'sharingDocumentation' => '',
208216
'sharingSettings' => [
209217
'allowGroupSharing' => true,
218+
'groupsBlockList' => [],
210219
'allowLinks' => true,
211220
'allowPublicUpload' => true,
212221
'allowResharing' => true,

lib/private/Group/Group.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use OCP\Group\Events\UserAddedEvent;
2626
use OCP\Group\Events\UserRemovedEvent;
2727
use OCP\GroupInterface;
28+
use OCP\IAppConfig;
2829
use OCP\IGroup;
2930
use OCP\IUser;
3031
use OCP\IUserManager;
@@ -51,7 +52,15 @@ class Group implements IGroup {
5152
/** @var PublicEmitter */
5253
private $emitter;
5354

54-
public function __construct(string $gid, array $backends, IEventDispatcher $dispatcher, IUserManager $userManager, ?PublicEmitter $emitter = null, ?string $displayName = null) {
55+
public function __construct(
56+
string $gid,
57+
array $backends,
58+
IEventDispatcher $dispatcher,
59+
IUserManager $userManager,
60+
private IAppConfig $appConfig,
61+
?PublicEmitter $emitter = null,
62+
?string $displayName = null,
63+
) {
5564
$this->gid = $gid;
5665
$this->backends = $backends;
5766
$this->dispatcher = $dispatcher;
@@ -376,6 +385,10 @@ public function canAddUser(): bool {
376385
* @since 16.0.0
377386
*/
378387
public function hideFromCollaboration(): bool {
388+
if (in_array($this->gid, $this->appConfig->getValueArray('core', 'shareapi_groups_block_list', []))) {
389+
return true;
390+
}
391+
379392
return array_reduce($this->backends, function (bool $hide, GroupInterface $backend) {
380393
return $hide | ($backend instanceof IHideFromCollaborationBackend && $backend->hideGroup($this->gid));
381394
}, false);

lib/private/Group/Manager.php

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use OCP\Group\Events\BeforeGroupCreatedEvent;
1717
use OCP\Group\Events\GroupCreatedEvent;
1818
use OCP\GroupInterface;
19+
use OCP\IAppConfig;
1920
use OCP\ICacheFactory;
2021
use OCP\IGroup;
2122
use OCP\IGroupManager;
@@ -52,6 +53,9 @@ class Manager extends PublicEmitter implements IGroupManager {
5253
/** @var \OC\SubAdmin */
5354
private $subAdmin = null;
5455

56+
/** @var IAppConfig $appConfig */
57+
private $appConfig;
58+
5559
private DisplayNameCache $displayNameCache;
5660

5761
private const MAX_GROUP_LENGTH = 255;
@@ -159,7 +163,7 @@ protected function getGroupObject($gid, $displayName = null) {
159163
return null;
160164
}
161165
/** @var GroupInterface[] $backends */
162-
$this->cachedGroups[$gid] = new Group($gid, $backends, $this->dispatcher, $this->userManager, $this, $displayName);
166+
$this->cachedGroups[$gid] = new Group($gid, $backends, $this->dispatcher, $this->userManager, $this->getAppConfig(), $this, $displayName);
163167
return $this->cachedGroups[$gid];
164168
}
165169

@@ -214,7 +218,7 @@ protected function getGroupsObjects(array $gids, array $displayNames = []): arra
214218
if (count($backends[$gid]) === 0) {
215219
continue;
216220
}
217-
$this->cachedGroups[$gid] = new Group($gid, $backends[$gid], $this->dispatcher, $this->userManager, $this, $displayNames[$gid]);
221+
$this->cachedGroups[$gid] = new Group($gid, $backends[$gid], $this->dispatcher, $this->userManager, $this->getAppConfig(), $this, $displayNames[$gid]);
218222
$groups[$gid] = $this->cachedGroups[$gid];
219223
}
220224
return $groups;
@@ -472,4 +476,17 @@ public function getSubAdmin() {
472476

473477
return $this->subAdmin;
474478
}
479+
480+
/**
481+
* Returns the appConfig object
482+
*
483+
* @return IAppConfig
484+
*/
485+
private function getAppConfig():IAppConfig {
486+
if (isset($this->appConfig)) {
487+
return $this->appConfig;
488+
} else {
489+
return \OC::$server->get(IAppConfig::class);
490+
}
491+
}
475492
}

lib/private/Share20/Manager.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,12 @@ protected function groupCreateChecks(IShare $share) {
513513
throw new \Exception($this->l->t('Group sharing is now allowed'));
514514
}
515515

516+
// Check if sharing with this group is blocked
517+
$groupsBlockList = $this->appConfig->getValueArray('core', 'shareapi_groups_block_list', []);
518+
if(in_array($share->getSharedWith(), $groupsBlockList)) {
519+
throw new \InvalidArgumentException('Sharing with group ' . $share->getSharedWith() . ' is not allowed.');
520+
}
521+
516522
// Verify if the user can share with this group
517523
if ($this->shareWithGroupMembersOnly()) {
518524
$sharedBy = $this->userManager->get($share->getSharedBy());

tests/lib/Collaboration/Collaborators/GroupPluginTest.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<?php
2+
23
/**
34
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
45
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -9,6 +10,7 @@
910
use OC\Collaboration\Collaborators\GroupPlugin;
1011
use OC\Collaboration\Collaborators\SearchResult;
1112
use OCP\Collaboration\Collaborators\ISearchResult;
13+
use OCP\IAppConfig;
1214
use OCP\IConfig;
1315
use OCP\IGroup;
1416
use OCP\IGroupManager;
@@ -21,6 +23,9 @@ class GroupPluginTest extends TestCase {
2123
/** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */
2224
protected $config;
2325

26+
/** @var IAppConfig|\PHPUnit\Framework\MockObject\MockObject */
27+
protected $appConfig;
28+
2429
/** @var IGroupManager|\PHPUnit\Framework\MockObject\MockObject */
2530
protected $groupManager;
2631

@@ -47,6 +52,8 @@ protected function setUp(): void {
4752

4853
$this->config = $this->createMock(IConfig::class);
4954

55+
$this->appConfig = $this->createMock(IAppConfig::class);
56+
5057
$this->groupManager = $this->createMock(IGroupManager::class);
5158

5259
$this->session = $this->createMock(IUserSession::class);
@@ -413,6 +420,24 @@ public function dataGetGroups(): array {
413420
true,
414421
false,
415422
],
423+
[
424+
'test', true, true, false,
425+
[
426+
$this->getGroupMock('test0', 'test0', true),
427+
$this->getGroupMock('test1'),
428+
],
429+
[
430+
$this->getGroupMock('test0'),
431+
$this->getGroupMock('test1')
432+
],
433+
[],
434+
[
435+
['label' => 'test1', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test1']],
436+
],
437+
false,
438+
false,
439+
['test0'],
440+
],
416441
];
417442
}
418443

@@ -429,6 +454,7 @@ public function dataGetGroups(): array {
429454
* @param array $expected
430455
* @param bool $reachedEnd
431456
* @param bool|IGroup $singleGroup
457+
* @param array $groupsBlockList
432458
*/
433459
public function testSearch(
434460
string $searchTerm,
@@ -441,6 +467,7 @@ public function testSearch(
441467
array $expected,
442468
bool $reachedEnd,
443469
$singleGroup,
470+
array $groupsBlockList = [],
444471
): void {
445472
$this->config->expects($this->any())
446473
->method('getAppValue')
@@ -462,6 +489,15 @@ function ($appName, $key, $default) use ($shareWithGroupOnly, $shareeEnumeration
462489
}
463490
);
464491

492+
if ($groupsBlockList != []) {
493+
/** setup blocked groups list */
494+
$appConfig = $this->createMock(IAppConfig::class);
495+
$appConfig->method('getValueArray')
496+
->with('core', 'shareapi_groups_block_list')
497+
->willReturn($groupsBlockList);
498+
$this->appConfig = $appConfig;
499+
}
500+
465501
$this->instantiatePlugin();
466502

467503
if (!$groupSharingDisabled) {

tests/lib/Share20/ManagerTest.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<?php
2+
23
/**
34
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
45
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
@@ -2480,6 +2481,74 @@ public function testCreateShareUser(): void {
24802481
$manager->createShare($share);
24812482
}
24822483

2484+
public function testCreateShareBlockedGroups() {
2485+
/** setup blocked groups list */
2486+
$appConfig = $this->createMock(IAppConfig::class);
2487+
$appConfig->method('getValueArray')
2488+
->with('core', 'shareapi_groups_block_list')
2489+
->willReturn(['blocked-group-1', 'blocked-group-2']);
2490+
$this->appConfig = $appConfig;
2491+
2492+
$shareProvider = $this->createMock(IShareProvider::class);
2493+
$shareProvider->method('getSharesByPath')->willReturn([]);
2494+
$this->factory->setProvider($shareProvider);
2495+
2496+
$manager = $this->createManagerMock()
2497+
->setMethods(['allowGroupSharing', 'canShare', 'generalCreateChecks', 'pathCreateChecks'])
2498+
->getMock();
2499+
2500+
$shareOwner = $this->createMock(IUser::class);
2501+
$shareOwner->method('getUID')->willReturn('shareOwner');
2502+
2503+
$storage = $this->createMock(IStorage::class);
2504+
$path = $this->createMock(File::class);
2505+
$path->method('getOwner')->willReturn($shareOwner);
2506+
$path->method('getName')->willReturn('target');
2507+
$path->method('getStorage')->willReturn($storage);
2508+
2509+
/** test create share with 'blocked-group-2': should throw exception */
2510+
$this->expectException(\InvalidArgumentException::class);
2511+
$share = $this->createShare(
2512+
null,
2513+
IShare::TYPE_GROUP,
2514+
$path,
2515+
'blocked-group-1',
2516+
'sharedBy',
2517+
null,
2518+
\OCP\Constants::PERMISSION_ALL,
2519+
);
2520+
2521+
$manager->expects($this->any())
2522+
->method('allowGroupSharing')
2523+
->willReturn(true);
2524+
$manager->expects($this->once())
2525+
->method('canShare')
2526+
->with($share)
2527+
->willReturn(true);
2528+
$manager->expects($this->once())
2529+
->method('generalCreateChecks')
2530+
->with($share);
2531+
$manager->expects($this->once())
2532+
->method('pathCreateChecks')
2533+
->with($path);
2534+
2535+
$this->defaultProvider
2536+
->expects($this->any())
2537+
->method('create')
2538+
->with($share)
2539+
->willReturnArgument(0);
2540+
2541+
$share->expects($this->any())
2542+
->method('setShareOwner')
2543+
->with('shareOwner');
2544+
$share->expects($this->any())
2545+
->method('setTarget')
2546+
->with('/target');
2547+
2548+
$manager->createShare($share);
2549+
2550+
}
2551+
24832552
public function testCreateShareGroup(): void {
24842553
$manager = $this->createManagerMock()
24852554
->setMethods(['canShare', 'generalCreateChecks', 'groupCreateChecks', 'pathCreateChecks'])

0 commit comments

Comments
 (0)