diff --git a/apps/settings/l10n/en_GB.js b/apps/settings/l10n/en_GB.js index 948ef8b24092c..4efb0333965ca 100644 --- a/apps/settings/l10n/en_GB.js +++ b/apps/settings/l10n/en_GB.js @@ -483,6 +483,7 @@ OC.L10N.register( "Allow resharing" : "Allow resharing", "Allow sharing with groups" : "Allow sharing with groups", "Restrict users to only share with users in their groups" : "Restrict users to only share with users in their groups", + "Exclude some groups from sharing with users in their group" : "Exclude some groups from sharing with users in their group", "Exclude groups from sharing" : "Exclude groups from sharing", "These groups will still be able to receive shares, but not to initiate them." : "These groups will still be able to receive shares, but not to initiate them.", "Allow username autocompletion in share dialog" : "Allow username autocompletion in share dialogue", diff --git a/apps/settings/l10n/en_GB.json b/apps/settings/l10n/en_GB.json index 99a2f84c404db..1d831e2fad024 100644 --- a/apps/settings/l10n/en_GB.json +++ b/apps/settings/l10n/en_GB.json @@ -481,6 +481,7 @@ "Allow resharing" : "Allow resharing", "Allow sharing with groups" : "Allow sharing with groups", "Restrict users to only share with users in their groups" : "Restrict users to only share with users in their groups", + "Exclude some groups from sharing with users in their group" : "Exclude some groups from sharing with users in their group", "Exclude groups from sharing" : "Exclude groups from sharing", "These groups will still be able to receive shares, but not to initiate them." : "These groups will still be able to receive shares, but not to initiate them.", "Allow username autocompletion in share dialog" : "Allow username autocompletion in share dialogue", diff --git a/apps/settings/l10n/fr.js b/apps/settings/l10n/fr.js index 9dbd28a810986..60aac54bc7b32 100644 --- a/apps/settings/l10n/fr.js +++ b/apps/settings/l10n/fr.js @@ -483,6 +483,7 @@ OC.L10N.register( "Allow resharing" : "Autoriser le repartage", "Allow sharing with groups" : "Autoriser le partage avec les groupes", "Restrict users to only share with users in their groups" : "N'autoriser les partages qu'entre membres de mêmes groupes", + "Exclude some groups from sharing with users in their group" : "Empêcher certains groupes de partager avec les utilisateurs de leur groupe", "Exclude groups from sharing" : "Empêcher certains groupes de partager", "These groups will still be able to receive shares, but not to initiate them." : "Ces groupes ne pourront plus initier de partage, mais ils pourront toujours rejoindre les partages faits par d'autres. ", "Allow username autocompletion in share dialog" : "Autoriser l'autocomplétion du nom d'utilisateur dans la boite de dialogue de partage", diff --git a/apps/settings/l10n/fr.json b/apps/settings/l10n/fr.json index f762a760de0d4..309c71840f140 100644 --- a/apps/settings/l10n/fr.json +++ b/apps/settings/l10n/fr.json @@ -481,6 +481,7 @@ "Allow resharing" : "Autoriser le repartage", "Allow sharing with groups" : "Autoriser le partage avec les groupes", "Restrict users to only share with users in their groups" : "N'autoriser les partages qu'entre membres de mêmes groupes", + "Exclude some groups from sharing with users in their group" : "Empêcher certains groupes de partager avec les utilisateurs de leur groupe", "Exclude groups from sharing" : "Empêcher certains groupes de partager", "These groups will still be able to receive shares, but not to initiate them." : "Ces groupes ne pourront plus initier de partage, mais ils pourront toujours rejoindre les partages faits par d'autres. ", "Allow username autocompletion in share dialog" : "Autoriser l'autocomplétion du nom d'utilisateur dans la boite de dialogue de partage", diff --git a/apps/settings/lib/Settings/Admin/Sharing.php b/apps/settings/lib/Settings/Admin/Sharing.php index 32b09f333a9c6..9f4139e8a2bfc 100644 --- a/apps/settings/lib/Settings/Admin/Sharing.php +++ b/apps/settings/lib/Settings/Admin/Sharing.php @@ -76,6 +76,9 @@ public function getForm() { $excludedPasswordGroupsList = !is_null(json_decode($excludedPasswordGroups)) ? implode('|', json_decode($excludedPasswordGroups, true)) : ''; + $shareWithGroupMembersExcludeGroups = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', ''); + $shareWithGroupMembersExcludeGroupsList = !is_null(json_decode($shareWithGroupMembersExcludeGroups)) + ? implode('|', json_decode($shareWithGroupMembersExcludeGroups, true)) : ''; $parameters = [ // Built-In Sharing @@ -96,6 +99,7 @@ public function getForm() { 'passwordExcludedGroups' => $excludedPasswordGroupsList, 'passwordExcludedGroupsFeatureEnabled' => $this->config->getSystemValueBool('sharing.allow_disabled_password_enforcement_groups', false), 'onlyShareWithGroupMembers' => $this->shareManager->shareWithGroupMembersOnly(), + 'shareWithGroupMembersExcludeGroupsList'=> $shareWithGroupMembersExcludeGroupsList, 'shareAPIEnabled' => $this->config->getAppValue('core', 'shareapi_enabled', 'yes'), 'shareDefaultExpireDateSet' => $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no'), 'shareExpireAfterNDays' => $this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7'), diff --git a/apps/settings/src/admin.js b/apps/settings/src/admin.js index e7220df3c9109..45bcfcc68dc4a 100644 --- a/apps/settings/src/admin.js +++ b/apps/settings/src/admin.js @@ -1,5 +1,5 @@ window.addEventListener('DOMContentLoaded', () => { - $('#excludedGroups,#linksExcludedGroups,#passwordsExcludedGroups').each(function(index, element) { + $('#excludedGroups,#shareGroupMembersExcludeGroups,#linksExcludedGroups,#passwordsExcludedGroups').each(function(index, element) { OC.Settings.setupGroupsSelect($(element)) $(element).change(function(ev) { let groups = ev.val || [] @@ -214,6 +214,10 @@ window.addEventListener('DOMContentLoaded', () => { $('#selectExcludedGroups').toggleClass('hidden', !this.checked) }) + $('#onlyShareWithGroupMembers').change(function() { + $('#selectShareWithGroupMembersExcludeGroups').toggleClass('hidden', !this.checked) + }) + const setupChecks = () => { // run setup checks then gather error messages $.when( diff --git a/apps/settings/templates/settings/admin/sharing.php b/apps/settings/templates/settings/admin/sharing.php index d2c542248c12b..417d2221029c8 100644 --- a/apps/settings/templates/settings/admin/sharing.php +++ b/apps/settings/templates/settings/admin/sharing.php @@ -185,6 +185,13 @@ } ?> />

+

+ t('Exclude some groups from sharing with users in their group')); ?> +
+ +

diff --git a/lib/private/Collaboration/Collaborators/GroupPlugin.php b/lib/private/Collaboration/Collaborators/GroupPlugin.php index 75e52c19e0b27..8b18bf248e2a0 100644 --- a/lib/private/Collaboration/Collaborators/GroupPlugin.php +++ b/lib/private/Collaboration/Collaborators/GroupPlugin.php @@ -45,6 +45,8 @@ class GroupPlugin implements ISearchPlugin { protected $shareeEnumerationInGroupOnly; /** @var bool */ protected $groupSharingDisabled; + /** @var array */ + protected $shareWithGroupOnlyExcludeGroupsList; /** @var IGroupManager */ private $groupManager; @@ -62,6 +64,14 @@ public function __construct(IConfig $config, IGroupManager $groupManager, IUserS $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) { + $shareWithGroupOnlyExcludeGroups = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', ''); + $decodedExcludeGroups = json_decode($shareWithGroupOnlyExcludeGroups, true); + $this->shareWithGroupOnlyExcludeGroupsList = $decodedExcludeGroups ?? []; + } else { + $this->shareWithGroupOnlyExcludeGroupsList = []; + } } public function search($search, $limit, $offset, ISearchResult $searchResult) { @@ -89,6 +99,9 @@ public function search($search, $limit, $offset, ISearchResult $searchResult) { return $group->getGID(); }, $userGroups); $groupIds = array_intersect($groupIds, $userGroups); + + // ShareWithGroupOnly filtering + $groupIds = array_diff( $groupIds, $this->shareWithGroupOnlyExcludeGroupsList); } $lowerSearch = strtolower($search); diff --git a/lib/private/Collaboration/Collaborators/MailPlugin.php b/lib/private/Collaboration/Collaborators/MailPlugin.php index aa317ec17204f..f20b5cebba0ed 100644 --- a/lib/private/Collaboration/Collaborators/MailPlugin.php +++ b/lib/private/Collaboration/Collaborators/MailPlugin.php @@ -53,6 +53,8 @@ class MailPlugin implements ISearchPlugin { protected $shareeEnumerationFullMatch; /* @var bool */ protected $shareeEnumerationFullMatchEmail; + /** @var array */ + protected $shareWithGroupOnlyExcludeGroupsList; /** @var IManager */ private $contactsManager; @@ -91,6 +93,14 @@ public function __construct(IManager $contactsManager, $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) { + $shareWithGroupOnlyExcludeGroups = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', ''); + $decodedExcludeGroups = json_decode($shareWithGroupOnlyExcludeGroups, true); + $this->shareWithGroupOnlyExcludeGroupsList = $decodedExcludeGroups ?? []; + } else { + $this->shareWithGroupOnlyExcludeGroupsList = []; + } } /** @@ -150,6 +160,10 @@ public function search($search, $limit, $offset, ISearchResult $searchResult) { * 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)) { diff --git a/lib/private/Collaboration/Collaborators/UserPlugin.php b/lib/private/Collaboration/Collaborators/UserPlugin.php index 9beecdaa6cbbf..726a7099e8a3f 100644 --- a/lib/private/Collaboration/Collaborators/UserPlugin.php +++ b/lib/private/Collaboration/Collaborators/UserPlugin.php @@ -60,6 +60,8 @@ class UserPlugin implements ISearchPlugin { protected $shareeEnumerationFullMatchEmail; /* @var bool */ protected $shareeEnumerationFullMatchIgnoreSecondDisplayName; + /** @var array */ + protected $shareWithGroupOnlyExcludeGroupsList; /** @var IConfig */ private $config; @@ -96,6 +98,14 @@ public function __construct(IConfig $config, $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) { + $shareWithGroupOnlyExcludeGroups = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', ''); + $decodedExcludeGroups = json_decode($shareWithGroupOnlyExcludeGroups, true); + $this->shareWithGroupOnlyExcludeGroupsList = $decodedExcludeGroups ?? []; + } else { + $this->shareWithGroupOnlyExcludeGroupsList = []; + } } public function search($search, $limit, $offset, ISearchResult $searchResult) { @@ -105,6 +115,10 @@ public function search($search, $limit, $offset, ISearchResult $searchResult) { $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) { diff --git a/lib/private/Contacts/ContactsMenu/ContactsStore.php b/lib/private/Contacts/ContactsMenu/ContactsStore.php index 3a75e924d24f0..367c9cf2a04cc 100644 --- a/lib/private/Contacts/ContactsMenu/ContactsStore.php +++ b/lib/private/Contacts/ContactsMenu/ContactsStore.php @@ -125,6 +125,9 @@ public function getContacts(IUser $user, ?string $filter, ?int $limit = null, ?i * 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 IUser $self * @param Entry[] $entries @@ -160,6 +163,14 @@ private function filterContacts( } } + // ownGroupsOnly : some groups may be excluded + if ($ownGroupsOnly) { + $excludeGroupsFromOwnGroups = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', ''); + $decodedExcludeGroupsFromOwnGroups = json_decode($excludeGroupsFromOwnGroups, true); + $excludeGroupsFromOwnGroupsList = $decodedExcludeGroupsFromOwnGroups ?? []; + $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) { diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index 732bd5bb97d39..8a61c2467fb17 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -550,6 +550,11 @@ protected function userCreateChecks(IShare $share) { $this->groupManager->getUserGroupIds($sharedBy), $this->groupManager->getUserGroupIds($sharedWith) ); + + // optional excluded groups + $excludedGroups = $this->shareWithGroupMembersOnlyExcludedGroupsList(); + $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); @@ -610,7 +615,10 @@ protected function groupCreateChecks(IShare $share) { 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->shareWithGroupMembersOnlyExcludedGroupsList(); + if (is_null($sharedWith) || in_array($share->getSharedWith(), $excludedGroups) || !$sharedWith->inGroup($sharedBy)) { throw new \Exception('Sharing is only allowed within your own groups'); } } @@ -1940,6 +1948,22 @@ public function shareWithGroupMembersOnly() { 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 shareWithGroupMembersOnlyExcludedGroupsList() { + if (!$this->shareWithGroupMembersOnly()) { + return []; + } + $excludeGroups = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', ''); + $decodedExcludeGroups = json_decode($excludeGroups, true); + return $decodedExcludeGroups ?? []; + } + /** * Check if users can share with groups * diff --git a/lib/public/Share/IManager.php b/lib/public/Share/IManager.php index 9ac224ed7ef30..f951cf8106ae3 100644 --- a/lib/public/Share/IManager.php +++ b/lib/public/Share/IManager.php @@ -415,6 +415,15 @@ public function shareApiLinkAllowPublicUpload(); */ 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 shareWithGroupMembersOnlyExcludedGroupsList(); + /** * Check if users can share with groups * @return bool