Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/settings/lib/Settings/Admin/Sharing.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public function getForm() {
// Built-In Sharing
'enabled' => $this->getHumanBooleanConfig('core', 'shareapi_enabled', true),
'allowGroupSharing' => $this->getHumanBooleanConfig('core', 'shareapi_allow_group_sharing', true),
'groupsBlockList' => $this->appConfig->getValueArray('core', 'shareapi_groups_block_list', []),
'allowLinks' => $this->getHumanBooleanConfig('core', 'shareapi_allow_links', true),
'allowLinksExcludeGroups' => json_decode($linksExcludedGroups, true) ?? [],
'allowPublicUpload' => $this->getHumanBooleanConfig('core', 'shareapi_allow_public_upload', true),
Expand Down
8 changes: 8 additions & 0 deletions apps/settings/src/components/AdminSettingsSharingForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@
<NcCheckboxRadioSwitch v-model="settings.allowGroupSharing">
{{ t('settings', 'Allow sharing with groups') }}
</NcCheckboxRadioSwitch>
<div v-show="settings.allowGroupSharing" id="settings-sharing-api-groups-block-list" class="sharing__labeled-entry sharing__input">
<label for="sharing-groups-block-list">{{ t('settings', 'Groups that are excluded from sharing') }}</label>
<NcSettingsSelectGroup id="sharing-groups-block-list"
v-model="settings.groupsBlockList"
:label="t('settings', 'Select groups')"
style="width: 100%" />
</div>
<NcCheckboxRadioSwitch v-model="settings.onlyShareWithGroupMembers">
{{ t('settings', 'Restrict users to only share with users in their groups') }}
</NcCheckboxRadioSwitch>
Expand Down Expand Up @@ -318,6 +325,7 @@ interface IShareSettings {
enforceRemoteExpireDate: boolean
allowCustomTokens: boolean
allowViewWithoutDownload: boolean
groupsBlockList: string[]
}

export default defineComponent({
Expand Down
4 changes: 4 additions & 0 deletions apps/settings/tests/Settings/Admin/SharingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public function testGetFormWithoutExcludedGroups(): void {
['core', 'shareapi_exclude_groups_list', '', ''],
['core', 'shareapi_allow_links_exclude_groups', '', ''],
['core', 'shareapi_allow_group_sharing', 'yes', 'yes'],
['core', 'shareapi_groups_block_list', '[]', '[]'],
['core', 'shareapi_allow_links', 'yes', 'yes'],
['core', 'shareapi_allow_public_upload', 'yes', 'yes'],
['core', 'shareapi_allow_resharing', 'yes', 'yes'],
Expand Down Expand Up @@ -115,6 +116,7 @@ public function testGetFormWithoutExcludedGroups(): void {
'sharingDocumentation' => '',
'sharingSettings' => [
'allowGroupSharing' => true,
'groupsBlockList' => [],
'allowLinks' => true,
'allowPublicUpload' => true,
'allowResharing' => true,
Expand Down Expand Up @@ -169,6 +171,7 @@ public function testGetFormWithExcludedGroups(): void {
['core', 'shareapi_exclude_groups_list', '', '["NoSharers","OtherNoSharers"]'],
['core', 'shareapi_allow_links_exclude_groups', '', ''],
['core', 'shareapi_allow_group_sharing', 'yes', 'yes'],
['core', 'shareapi_groups_block_list', '[]', '[]'],
['core', 'shareapi_allow_links', 'yes', 'yes'],
['core', 'shareapi_allow_public_upload', 'yes', 'yes'],
['core', 'shareapi_allow_resharing', 'yes', 'yes'],
Expand Down Expand Up @@ -215,6 +218,7 @@ public function testGetFormWithExcludedGroups(): void {
'sharingDocumentation' => '',
'sharingSettings' => [
'allowGroupSharing' => true,
'groupsBlockList' => [],
'allowLinks' => true,
'allowPublicUpload' => true,
'allowResharing' => true,
Expand Down
6 changes: 6 additions & 0 deletions lib/private/Group/Group.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
use OCP\Group\Events\UserAddedEvent;
use OCP\Group\Events\UserRemovedEvent;
use OCP\GroupInterface;
use OCP\IAppConfig;
use OCP\IGroup;
use OCP\IUser;
use OCP\IUserManager;
Expand All @@ -45,6 +46,7 @@ public function __construct(
private string $gid,
/** @var list<GroupInterface> */
private array $backends,
private IAppConfig $appConfig,
private IEventDispatcher $dispatcher,
private IUserManager $userManager,
private ?PublicEmitter $emitter = null,
Expand Down Expand Up @@ -389,6 +391,10 @@ public function canAddUser(): bool {
*/
#[\Override]
public function hideFromCollaboration(): bool {
if (in_array($this->gid, $this->appConfig->getValueArray('core', 'shareapi_groups_block_list', []))) {
return true;
}

return array_reduce($this->backends, function (bool $hide, GroupInterface $backend) {
return $hide || ($backend instanceof IHideFromCollaborationBackend && $backend->hideGroup($this->gid));
}, false);
Expand Down
6 changes: 6 additions & 0 deletions lib/private/Share20/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,12 @@ protected function groupCreateChecks(IShare $share): void {
throw new \Exception($this->l->t('Group sharing is now allowed'));
}

// Check if sharing with this group is blocked
$groupsBlockList = $this->appConfig->getValueArray('core', 'shareapi_groups_block_list', []);
if (in_array($share->getSharedWith(), $groupsBlockList)) {
throw new \InvalidArgumentException('Sharing with group ' . $share->getSharedWith() . ' is not allowed.');
}

// Verify if the user can share with this group
if ($this->shareWithGroupMembersOnly()) {
$sharedBy = $this->userManager->get($share->getSharedBy());
Expand Down
34 changes: 34 additions & 0 deletions tests/lib/Collaboration/Collaborators/GroupPluginTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use OC\Collaboration\Collaborators\GroupPlugin;
use OC\Collaboration\Collaborators\SearchResult;
use OCP\Collaboration\Collaborators\ISearchResult;
use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IGroup;
use OCP\IGroupManager;
Expand All @@ -30,6 +31,9 @@ class GroupPluginTest extends TestCase {

protected ISearchResult $searchResult;

/** @var IAppConfig|\PHPUnit\Framework\MockObject\MockObject */
protected $appConfig;

/** @var GroupPlugin */
protected $plugin;

Expand All @@ -42,6 +46,8 @@ protected function setUp(): void {

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

$this->appConfig = $this->createMock(IAppConfig::class);

$this->groupManager = $this->createMock(IGroupManager::class);

$this->session = $this->createMock(IUserSession::class);
Expand Down Expand Up @@ -402,6 +408,24 @@ public static function dataGetGroups(): array {
true,
false,
],
[
'test', true, true, false,
[
['test0', 'test0', true],
['test1'],
],
[
['test0'],
['test1']
],
[],
[
['label' => 'test1', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test1']],
],
false,
false,
['test0'],
],
];
}

Expand All @@ -417,6 +441,7 @@ public function testSearch(
array $expected,
bool $reachedEnd,
array|false $singleGroup,
array $groupsBlockList = [],
): void {
$groupResponse = array_map(
fn ($args) => $this->getGroupMock(...$args),
Expand Down Expand Up @@ -449,6 +474,15 @@ function ($appName, $key, $default) use ($shareWithGroupOnly, $shareeEnumeration
}
);

if ($groupsBlockList != []) {
/** setup blocked groups list */
$appConfig = $this->createMock(IAppConfig::class);
$appConfig->method('getValueArray')
->with('core', 'shareapi_groups_block_list')
->willReturn($groupsBlockList);
$this->appConfig = $appConfig;
}

$this->instantiatePlugin();

if (!$groupSharingDisabled) {
Expand Down
68 changes: 68 additions & 0 deletions tests/lib/Share20/ManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2656,6 +2656,74 @@ public function testCreateShareUser(): void {
$manager->createShare($share);
}

public function testCreateShareBlockedGroups() {
/** setup blocked groups list */
$appConfig = $this->createMock(IAppConfig::class);
$appConfig->method('getValueArray')
->with('core', 'shareapi_groups_block_list')
->willReturn(['blocked-group-1', 'blocked-group-2']);
$this->appConfig = $appConfig;

$shareProvider = $this->createMock(IShareProvider::class);
$shareProvider->method('getSharesByPath')->willReturn([]);
$this->factory->setProvider($shareProvider);

$manager = $this->createManagerMock()
->setMethods(['allowGroupSharing', 'canShare', 'generalCreateChecks', 'pathCreateChecks'])
->getMock();

$shareOwner = $this->createMock(IUser::class);
$shareOwner->method('getUID')->willReturn('shareOwner');

$storage = $this->createMock(IStorage::class);
$path = $this->createMock(File::class);
$path->method('getOwner')->willReturn($shareOwner);
$path->method('getName')->willReturn('target');
$path->method('getStorage')->willReturn($storage);

/** test create share with 'blocked-group-2': should throw exception */
$this->expectException(\InvalidArgumentException::class);
$share = $this->createShare(
null,
IShare::TYPE_GROUP,
$path,
'blocked-group-1',
'sharedBy',
null,
\OCP\Constants::PERMISSION_ALL,
);

$manager->expects($this->any())
->method('allowGroupSharing')
->willReturn(true);
$manager->expects($this->once())
->method('canShare')
->with($share)
->willReturn(true);
$manager->expects($this->once())
->method('generalCreateChecks')
->with($share);
$manager->expects($this->once())
->method('pathCreateChecks')
->with($path);

$this->defaultProvider
->expects($this->any())
->method('create')
->with($share)
->willReturnArgument(0);

$share->expects($this->any())
->method('setShareOwner')
->with('shareOwner');
$share->expects($this->any())
->method('setTarget')
->with('/target');

$manager->createShare($share);

}

public function testCreateShareGroup(): void {
$manager = $this->createManagerMock()
->onlyMethods(['canShare', 'generalCreateChecks', 'groupCreateChecks', 'pathCreateChecks', 'validateExpirationDateInternal'])
Expand Down