mirror of
https://github.com/nextcloud/server.git
synced 2026-06-11 01:30:50 -04:00
Merge pull request #38173 from arawa/feature/37677/exclude-some-groups-from-sharing-with-users
New core setting : shareapi_only_share_with_group_members_exclude_gro…
This commit is contained in:
commit
7dc5a91f71
13 changed files with 96 additions and 4 deletions
|
|
@ -63,6 +63,7 @@ class Sharing implements IDelegatedSettings {
|
|||
$excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
|
||||
$linksExcludedGroups = $this->config->getAppValue('core', 'shareapi_allow_links_exclude_groups', '');
|
||||
$excludedPasswordGroups = $this->config->getAppValue('core', 'shareapi_enforce_links_password_excluded_groups', '');
|
||||
$onlyShareWithGroupMembersExcludeGroupList = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', '');
|
||||
|
||||
$parameters = [
|
||||
// Built-In Sharing
|
||||
|
|
@ -83,6 +84,7 @@ class Sharing implements IDelegatedSettings {
|
|||
'passwordExcludedGroups' => json_decode($excludedPasswordGroups) ?? [],
|
||||
'passwordExcludedGroupsFeatureEnabled' => $this->config->getSystemValueBool('sharing.allow_disabled_password_enforcement_groups', false),
|
||||
'onlyShareWithGroupMembers' => $this->shareManager->shareWithGroupMembersOnly(),
|
||||
'onlyShareWithGroupMembersExcludeGroupList' => json_decode($onlyShareWithGroupMembersExcludeGroupList) ?? [],
|
||||
'defaultExpireDate' => $this->getHumanBooleanConfig('core', 'shareapi_default_expire_date'),
|
||||
'expireAfterNDays' => $this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7'),
|
||||
'enforceExpireDate' => $this->getHumanBooleanConfig('core', 'shareapi_enforce_expire_date'),
|
||||
|
|
|
|||
|
|
@ -37,6 +37,13 @@
|
|||
<NcCheckboxRadioSwitch :checked.sync="settings.onlyShareWithGroupMembers">
|
||||
{{ t('settings', 'Restrict users to only share with users in their groups') }}
|
||||
</NcCheckboxRadioSwitch>
|
||||
<div v-show="settings.onlyShareWithGroupMembers" id="settings-sharing-api-excluded-groups" class="sharing__labeled-entry sharing__input">
|
||||
<label for="settings-sharing-only-group-members-excluded-groups">{{ t('settings', 'Ignore the following groups when checking group membership') }}</label>
|
||||
<NcSettingsSelectGroup id="settings-sharing-only-group-members-excluded-groups"
|
||||
v-model="settings.onlyShareWithGroupMembersExcludeGroupList"
|
||||
:label="t('settings', 'Ignore the following groups when checking group membership')"
|
||||
style="width: 100%" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-show="settings.enabled" id="settings-sharing-api" class="sharing__section">
|
||||
|
|
@ -216,6 +223,7 @@ interface IShareSettings {
|
|||
passwordExcludedGroups: string[]
|
||||
passwordExcludedGroupsFeatureEnabled: boolean
|
||||
onlyShareWithGroupMembers: boolean
|
||||
onlyShareWithGroupMembersExcludeGroupList: string[]
|
||||
defaultExpireDate: boolean
|
||||
expireAfterNDays: string
|
||||
enforceExpireDate: boolean
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@ class SharingTest extends TestCase {
|
|||
['core', 'shareapi_remote_expire_after_n_days', '7', '7'],
|
||||
['core', 'shareapi_enforce_remote_expire_date', 'no', 'no'],
|
||||
['core', 'shareapi_enforce_links_password_excluded_groups', '', ''],
|
||||
['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
|
||||
]);
|
||||
$this->shareManager->method('shareWithGroupMembersOnly')
|
||||
->willReturn(false);
|
||||
|
|
@ -163,6 +164,7 @@ class SharingTest extends TestCase {
|
|||
'allowLinksExcludeGroups' => [],
|
||||
'passwordExcludedGroups' => [],
|
||||
'passwordExcludedGroupsFeatureEnabled' => false,
|
||||
'onlyShareWithGroupMembersExcludeGroupList' => [],
|
||||
]
|
||||
],
|
||||
);
|
||||
|
|
@ -209,6 +211,7 @@ class SharingTest extends TestCase {
|
|||
['core', 'shareapi_remote_expire_after_n_days', '7', '7'],
|
||||
['core', 'shareapi_enforce_remote_expire_date', 'no', 'no'],
|
||||
['core', 'shareapi_enforce_links_password_excluded_groups', '', ''],
|
||||
['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
|
||||
]);
|
||||
$this->shareManager->method('shareWithGroupMembersOnly')
|
||||
->willReturn(false);
|
||||
|
|
@ -254,6 +257,7 @@ class SharingTest extends TestCase {
|
|||
'allowLinksExcludeGroups' => [],
|
||||
'passwordExcludedGroups' => [],
|
||||
'passwordExcludedGroupsFeatureEnabled' => false,
|
||||
'onlyShareWithGroupMembersExcludeGroupList' => [],
|
||||
]
|
||||
],
|
||||
);
|
||||
|
|
|
|||
4
dist/settings-vue-settings-admin-sharing.js
vendored
4
dist/settings-vue-settings-admin-sharing.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -49,11 +49,16 @@ class GroupPlugin implements ISearchPlugin {
|
|||
private IConfig $config,
|
||||
private IGroupManager $groupManager,
|
||||
private IUserSession $userSession,
|
||||
private mixed $shareWithGroupOnlyExcludeGroupsList = [],
|
||||
) {
|
||||
$this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
|
||||
$this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
|
||||
$this->shareeEnumerationInGroupOnly = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
|
||||
$this->groupSharingDisabled = $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes') === 'no';
|
||||
|
||||
if ($this->shareWithGroupOnly) {
|
||||
$this->shareWithGroupOnlyExcludeGroupsList = json_decode($this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', ''), true) ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
public function search($search, $limit, $offset, ISearchResult $searchResult): bool {
|
||||
|
|
@ -81,6 +86,9 @@ class GroupPlugin implements ISearchPlugin {
|
|||
return $group->getGID();
|
||||
}, $userGroups);
|
||||
$groupIds = array_intersect($groupIds, $userGroups);
|
||||
|
||||
// ShareWithGroupOnly filtering
|
||||
$groupIds = array_diff($groupIds, $this->shareWithGroupOnlyExcludeGroupsList);
|
||||
}
|
||||
|
||||
$lowerSearch = strtolower($search);
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ class MailPlugin implements ISearchPlugin {
|
|||
private KnownUserService $knownUserService,
|
||||
private IUserSession $userSession,
|
||||
private IMailer $mailer,
|
||||
private mixed $shareWithGroupOnlyExcludeGroupsList = [],
|
||||
) {
|
||||
$this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
|
||||
$this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
|
||||
|
|
@ -68,6 +69,10 @@ class MailPlugin implements ISearchPlugin {
|
|||
$this->shareeEnumerationPhone = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
|
||||
$this->shareeEnumerationFullMatch = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
|
||||
$this->shareeEnumerationFullMatchEmail = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_email', 'yes') === 'yes';
|
||||
|
||||
if ($this->shareWithGroupOnly) {
|
||||
$this->shareWithGroupOnlyExcludeGroupsList = json_decode($this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', ''), true) ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -127,6 +132,10 @@ class MailPlugin implements ISearchPlugin {
|
|||
* Check if the user may share with the user associated with the e-mail of the just found contact
|
||||
*/
|
||||
$userGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser());
|
||||
|
||||
// ShareWithGroupOnly filtering
|
||||
$userGroups = array_diff($userGroups, $this->shareWithGroupOnlyExcludeGroupsList);
|
||||
|
||||
$found = false;
|
||||
foreach ($userGroups as $userGroup) {
|
||||
if ($this->groupManager->isInGroup($contact['UID'], $userGroup)) {
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ class UserPlugin implements ISearchPlugin {
|
|||
private IUserSession $userSession,
|
||||
private KnownUserService $knownUserService,
|
||||
private IUserStatusManager $userStatusManager,
|
||||
private mixed $shareWithGroupOnlyExcludeGroupsList = [],
|
||||
) {
|
||||
$this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
|
||||
$this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
|
||||
|
|
@ -76,6 +77,10 @@ class UserPlugin implements ISearchPlugin {
|
|||
$this->shareeEnumerationFullMatchUserId = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_userid', 'yes') === 'yes';
|
||||
$this->shareeEnumerationFullMatchEmail = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_email', 'yes') === 'yes';
|
||||
$this->shareeEnumerationFullMatchIgnoreSecondDisplayName = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_ignore_second_dn', 'no') === 'yes';
|
||||
|
||||
if ($this->shareWithGroupOnly) {
|
||||
$this->shareWithGroupOnlyExcludeGroupsList = json_decode($this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', ''), true) ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
public function search($search, $limit, $offset, ISearchResult $searchResult): bool {
|
||||
|
|
@ -85,6 +90,10 @@ class UserPlugin implements ISearchPlugin {
|
|||
|
||||
$currentUserId = $this->userSession->getUser()->getUID();
|
||||
$currentUserGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser());
|
||||
|
||||
// ShareWithGroupOnly filtering
|
||||
$currentUserGroups = array_diff($currentUserGroups, $this->shareWithGroupOnlyExcludeGroupsList);
|
||||
|
||||
if ($this->shareWithGroupOnly || $this->shareeEnumerationInGroupOnly) {
|
||||
// Search in all the groups this user is part of
|
||||
foreach ($currentUserGroups as $userGroupId) {
|
||||
|
|
|
|||
|
|
@ -177,6 +177,9 @@ class ContactsStore implements IContactsStore {
|
|||
* 3. if the `shareapi_only_share_with_group_members` config option is
|
||||
* enabled it will filter all users which doesn't have a common group
|
||||
* with the current user.
|
||||
* If enabled, the 'shareapi_only_share_with_group_members_exclude_group_list'
|
||||
* config option may specify some groups excluded from the principle of
|
||||
* belonging to the same group.
|
||||
*
|
||||
* @param Entry[] $entries
|
||||
* @return Entry[] the filtered contacts
|
||||
|
|
@ -210,6 +213,13 @@ class ContactsStore implements IContactsStore {
|
|||
}
|
||||
}
|
||||
|
||||
// ownGroupsOnly : some groups may be excluded
|
||||
if ($ownGroupsOnly) {
|
||||
$excludeGroupsFromOwnGroups = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', '');
|
||||
$excludeGroupsFromOwnGroupsList = json_decode($excludeGroupsFromOwnGroups, true) ?? [];
|
||||
$selfGroups = array_diff($selfGroups, $excludeGroupsFromOwnGroupsList);
|
||||
}
|
||||
|
||||
$selfUID = $self->getUID();
|
||||
|
||||
return array_values(array_filter($entries, function (IEntry $entry) use ($skipLocal, $ownGroupsOnly, $selfGroups, $selfUID, $disallowEnumeration, $restrictEnumerationGroup, $restrictEnumerationPhone, $allowEnumerationFullMatch, $filter) {
|
||||
|
|
|
|||
|
|
@ -548,6 +548,11 @@ class Manager implements IManager {
|
|||
$this->groupManager->getUserGroupIds($sharedBy),
|
||||
$this->groupManager->getUserGroupIds($sharedWith)
|
||||
);
|
||||
|
||||
// optional excluded groups
|
||||
$excludedGroups = $this->shareWithGroupMembersOnlyExcludeGroupsList();
|
||||
$groups = array_diff($groups, $excludedGroups);
|
||||
|
||||
if (empty($groups)) {
|
||||
$message_t = $this->l->t('Sharing is only allowed with group members');
|
||||
throw new \Exception($message_t);
|
||||
|
|
@ -608,7 +613,10 @@ class Manager implements IManager {
|
|||
if ($this->shareWithGroupMembersOnly()) {
|
||||
$sharedBy = $this->userManager->get($share->getSharedBy());
|
||||
$sharedWith = $this->groupManager->get($share->getSharedWith());
|
||||
if (is_null($sharedWith) || !$sharedWith->inGroup($sharedBy)) {
|
||||
|
||||
// optional excluded groups
|
||||
$excludedGroups = $this->shareWithGroupMembersOnlyExcludeGroupsList();
|
||||
if (is_null($sharedWith) || in_array($share->getSharedWith(), $excludedGroups) || !$sharedWith->inGroup($sharedBy)) {
|
||||
throw new \Exception('Sharing is only allowed within your own groups');
|
||||
}
|
||||
}
|
||||
|
|
@ -1938,6 +1946,21 @@ class Manager implements IManager {
|
|||
return $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
|
||||
}
|
||||
|
||||
/**
|
||||
* If shareWithGroupMembersOnly is enabled, return an optional
|
||||
* list of groups that must be excluded from the principle of
|
||||
* belonging to the same group.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function shareWithGroupMembersOnlyExcludeGroupsList() {
|
||||
if (!$this->shareWithGroupMembersOnly()) {
|
||||
return [];
|
||||
}
|
||||
$excludeGroups = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', '');
|
||||
return json_decode($excludeGroups, true) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if users can share with groups
|
||||
*
|
||||
|
|
|
|||
|
|
@ -415,6 +415,15 @@ interface IManager {
|
|||
*/
|
||||
public function shareWithGroupMembersOnly();
|
||||
|
||||
/**
|
||||
* If shareWithGroupMembersOnly is enabled, return an optional
|
||||
* list of groups that must be excluded from the principle of
|
||||
* belonging to the same group.
|
||||
* @return array
|
||||
* @since 27.0.0
|
||||
*/
|
||||
public function shareWithGroupMembersOnlyExcludeGroupsList();
|
||||
|
||||
/**
|
||||
* Check if users can share with groups
|
||||
* @return bool
|
||||
|
|
|
|||
|
|
@ -217,6 +217,7 @@ class ContactsStoreTest extends TestCase {
|
|||
['core', 'shareapi_exclude_groups', 'no', 'yes'],
|
||||
['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
|
||||
['core', 'shareapi_exclude_groups_list', '', '["group1", "group5", "group6"]'],
|
||||
['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
|
||||
]);
|
||||
|
||||
/** @var IUser|MockObject $currentUser */
|
||||
|
|
@ -260,6 +261,7 @@ class ContactsStoreTest extends TestCase {
|
|||
['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
|
||||
['core', 'shareapi_exclude_groups', 'no', 'no'],
|
||||
['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
|
||||
['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
|
||||
]);
|
||||
|
||||
/** @var IUser|MockObject $currentUser */
|
||||
|
|
@ -334,6 +336,7 @@ class ContactsStoreTest extends TestCase {
|
|||
['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
|
||||
['core', 'shareapi_exclude_groups', 'no', 'no'],
|
||||
['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
|
||||
['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
|
||||
]);
|
||||
|
||||
/** @var IUser|MockObject $currentUser */
|
||||
|
|
@ -466,6 +469,7 @@ class ContactsStoreTest extends TestCase {
|
|||
['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'yes'],
|
||||
['core', 'shareapi_exclude_groups', 'no', 'no'],
|
||||
['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
|
||||
['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
|
||||
]);
|
||||
|
||||
/** @var IUser|MockObject $currentUser */
|
||||
|
|
@ -619,6 +623,7 @@ class ContactsStoreTest extends TestCase {
|
|||
['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'yes'],
|
||||
['core', 'shareapi_exclude_groups', 'no', 'no'],
|
||||
['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
|
||||
['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
|
||||
]);
|
||||
|
||||
/** @var IUser|MockObject $currentUser */
|
||||
|
|
|
|||
|
|
@ -1569,6 +1569,7 @@ class ManagerTest extends \Test\TestCase {
|
|||
->method('getAppValue')
|
||||
->willReturnMap([
|
||||
['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
|
||||
['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
|
||||
]);
|
||||
|
||||
self::invokePrivate($this->manager, 'userCreateChecks', [$share]);
|
||||
|
|
@ -1602,6 +1603,7 @@ class ManagerTest extends \Test\TestCase {
|
|||
->method('getAppValue')
|
||||
->willReturnMap([
|
||||
['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
|
||||
['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
|
||||
]);
|
||||
|
||||
$this->defaultProvider
|
||||
|
|
@ -1794,6 +1796,7 @@ class ManagerTest extends \Test\TestCase {
|
|||
->willReturnMap([
|
||||
['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
|
||||
['core', 'shareapi_allow_group_sharing', 'yes', 'yes'],
|
||||
['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
|
||||
]);
|
||||
|
||||
self::invokePrivate($this->manager, 'groupCreateChecks', [$share]);
|
||||
|
|
@ -1817,6 +1820,7 @@ class ManagerTest extends \Test\TestCase {
|
|||
->willReturnMap([
|
||||
['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
|
||||
['core', 'shareapi_allow_group_sharing', 'yes', 'yes'],
|
||||
['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
|
||||
]);
|
||||
|
||||
$this->assertNull($this->invokePrivate($this->manager, 'groupCreateChecks', [$share]));
|
||||
|
|
@ -1846,6 +1850,7 @@ class ManagerTest extends \Test\TestCase {
|
|||
->willReturnMap([
|
||||
['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
|
||||
['core', 'shareapi_allow_group_sharing', 'yes', 'yes'],
|
||||
['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
|
||||
]);
|
||||
|
||||
self::invokePrivate($this->manager, 'groupCreateChecks', [$share]);
|
||||
|
|
|
|||
Loading…
Reference in a new issue