diff --git a/apps/files_sharing/tests/CapabilitiesTest.php b/apps/files_sharing/tests/CapabilitiesTest.php index eb10c9271406a..f52ebf9909c4b 100644 --- a/apps/files_sharing/tests/CapabilitiesTest.php +++ b/apps/files_sharing/tests/CapabilitiesTest.php @@ -137,6 +137,7 @@ public function testOnlyLinkSharing() { $map = [ ['core', 'shareapi_enabled', 'yes', 'yes'], ['core', 'shareapi_allow_links', 'yes', 'yes'], + ['core', 'shareapi_enforce_links_password_excluded_groups', '', ''], ]; $result = $this->getResults($map); $this->assertIsArray($result['public']); @@ -147,6 +148,7 @@ public function testLinkPassword() { $map = [ ['core', 'shareapi_enabled', 'yes', 'yes'], ['core', 'shareapi_allow_links', 'yes', 'yes'], + ['core', 'shareapi_enforce_links_password_excluded_groups', '', ''], ['core', 'shareapi_enforce_links_password', 'no', 'yes'], ]; $result = $this->getResults($map); @@ -159,6 +161,7 @@ public function testLinkNoPassword() { $map = [ ['core', 'shareapi_enabled', 'yes', 'yes'], ['core', 'shareapi_allow_links', 'yes', 'yes'], + ['core', 'shareapi_enforce_links_password_excluded_groups', '', ''], ['core', 'shareapi_enforce_links_password', 'no', 'no'], ]; $result = $this->getResults($map); @@ -172,6 +175,7 @@ public function testLinkNoExpireDate() { ['core', 'shareapi_enabled', 'yes', 'yes'], ['core', 'shareapi_allow_links', 'yes', 'yes'], ['core', 'shareapi_default_expire_date', 'no', 'no'], + ['core', 'shareapi_enforce_links_password_excluded_groups', '', ''], ]; $result = $this->getResults($map); $this->assertArrayHasKey('expire_date', $result['public']); @@ -186,6 +190,7 @@ public function testLinkExpireDate() { ['core', 'shareapi_default_expire_date', 'no', 'yes'], ['core', 'shareapi_expire_after_n_days', '7', '7'], ['core', 'shareapi_enforce_expire_date', 'no', 'no'], + ['core', 'shareapi_enforce_links_password_excluded_groups', '', ''], ]; $result = $this->getResults($map); $this->assertArrayHasKey('expire_date', $result['public']); @@ -201,6 +206,7 @@ public function testLinkExpireDateEnforced() { ['core', 'shareapi_allow_links', 'yes', 'yes'], ['core', 'shareapi_default_expire_date', 'no', 'yes'], ['core', 'shareapi_enforce_expire_date', 'no', 'yes'], + ['core', 'shareapi_enforce_links_password_excluded_groups', '', ''], ]; $result = $this->getResults($map); $this->assertArrayHasKey('expire_date', $result['public']); @@ -213,6 +219,7 @@ public function testLinkSendMail() { ['core', 'shareapi_enabled', 'yes', 'yes'], ['core', 'shareapi_allow_links', 'yes', 'yes'], ['core', 'shareapi_allow_public_notification', 'no', 'yes'], + ['core', 'shareapi_enforce_links_password_excluded_groups', '', ''], ]; $result = $this->getResults($map); $this->assertTrue($result['public']['send_mail']); @@ -223,6 +230,7 @@ public function testLinkNoSendMail() { ['core', 'shareapi_enabled', 'yes', 'yes'], ['core', 'shareapi_allow_links', 'yes', 'yes'], ['core', 'shareapi_allow_public_notification', 'no', 'no'], + ['core', 'shareapi_enforce_links_password_excluded_groups', '', ''], ]; $result = $this->getResults($map); $this->assertFalse($result['public']['send_mail']); @@ -232,6 +240,7 @@ public function testResharing() { $map = [ ['core', 'shareapi_enabled', 'yes', 'yes'], ['core', 'shareapi_allow_resharing', 'yes', 'yes'], + ['core', 'shareapi_enforce_links_password_excluded_groups', '', ''], ]; $result = $this->getResults($map); $this->assertTrue($result['resharing']); @@ -241,6 +250,7 @@ public function testNoResharing() { $map = [ ['core', 'shareapi_enabled', 'yes', 'yes'], ['core', 'shareapi_allow_resharing', 'yes', 'no'], + ['core', 'shareapi_enforce_links_password_excluded_groups', '', ''], ]; $result = $this->getResults($map); $this->assertFalse($result['resharing']); @@ -251,6 +261,7 @@ public function testLinkPublicUpload() { ['core', 'shareapi_enabled', 'yes', 'yes'], ['core', 'shareapi_allow_links', 'yes', 'yes'], ['core', 'shareapi_allow_public_upload', 'yes', 'yes'], + ['core', 'shareapi_enforce_links_password_excluded_groups', '', ''], ]; $result = $this->getResults($map); $this->assertTrue($result['public']['upload']); @@ -262,6 +273,7 @@ public function testLinkNoPublicUpload() { ['core', 'shareapi_enabled', 'yes', 'yes'], ['core', 'shareapi_allow_links', 'yes', 'yes'], ['core', 'shareapi_allow_public_upload', 'yes', 'no'], + ['core', 'shareapi_enforce_links_password_excluded_groups', '', ''], ]; $result = $this->getResults($map); $this->assertFalse($result['public']['upload']); diff --git a/apps/settings/js/admin.js b/apps/settings/js/admin.js index 20d9843fe1450..5e3580c076050 100644 --- a/apps/settings/js/admin.js +++ b/apps/settings/js/admin.js @@ -1,5 +1,5 @@ window.addEventListener('DOMContentLoaded', function(){ - $('#excludedGroups,#linksExcludedGroups').each(function (index, element) { + $('#excludedGroups,#linksExcludedGroups,#passwordsExcludedGroups').each(function(index, element) { OC.Settings.setupGroupsSelect($(element)); $(element).change(function(ev) { var groups = ev.val || []; @@ -94,6 +94,10 @@ window.addEventListener('DOMContentLoaded', function(){ $("#setDefaultRemoteExpireDate").toggleClass('hidden', !this.checked); }); + $('#enforceLinkPassword').change(function() { + $('#selectPasswordsExcludedGroups').toggleClass('hidden', !this.checked) + }) + $('#publicShareDisclaimer').change(function() { $("#publicShareDisclaimerText").toggleClass('hidden', !this.checked); if(!this.checked) { diff --git a/apps/settings/lib/Settings/Admin/Sharing.php b/apps/settings/lib/Settings/Admin/Sharing.php index 85d3e4d8d40a6..f4c44d49ef80c 100644 --- a/apps/settings/lib/Settings/Admin/Sharing.php +++ b/apps/settings/lib/Settings/Admin/Sharing.php @@ -70,6 +70,11 @@ public function getForm() { $linksExcludeGroupsList = !is_null(json_decode($linksExcludedGroups)) ? implode('|', json_decode($linksExcludedGroups, true)) : ''; + $excludedPasswordGroups = $this->config->getAppValue('core', 'shareapi_enforce_links_password_excluded_groups', ''); + $excludedPasswordGroupsList = !is_null(json_decode($excludedPasswordGroups)) + ? implode('|', json_decode($excludedPasswordGroups, true)) : ''; + + $parameters = [ // Built-In Sharing 'allowGroupSharing' => $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes'), @@ -81,7 +86,9 @@ public function getForm() { 'restrictUserEnumerationToGroup' => $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no'), 'restrictUserEnumerationToPhone' => $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no'), 'restrictUserEnumerationFullMatch' => $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes'), - 'enforceLinkPassword' => Util::isPublicLinkPasswordRequired(), + 'enforceLinkPassword' => Util::isPublicLinkPasswordRequired(false), + 'passwordExcludedGroups' => $excludedPasswordGroupsList, + 'passwordExcludedGroupsFeatureEnabled' => $this->config->getSystemValueBool('sharing.allow_disabled_password_enforcement_groups', false), 'onlyShareWithGroupMembers' => $this->shareManager->shareWithGroupMembersOnly(), 'shareAPIEnabled' => $this->config->getAppValue('core', 'shareapi_enabled', 'yes'), 'shareDefaultExpireDateSet' => $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no'), diff --git a/apps/settings/templates/settings/admin/sharing.php b/apps/settings/templates/settings/admin/sharing.php index e85047b7037af..917c20ea2d833 100644 --- a/apps/settings/templates/settings/admin/sharing.php +++ b/apps/settings/templates/settings/admin/sharing.php @@ -115,6 +115,16 @@ } ?> />
+ +
+
+
+
+ + '7', 'shareRemoteEnforceExpireDate' => 'no', 'allowLinksExcludeGroups' => '', + 'passwordExcludedGroups' => '', + 'passwordExcludedGroupsFeatureEnabled' => false, ], '' ); @@ -194,6 +196,8 @@ public function testGetFormWithExcludedGroups() { 'shareRemoteExpireAfterNDays' => '7', 'shareRemoteEnforceExpireDate' => 'no', 'allowLinksExcludeGroups' => '', + 'passwordExcludedGroups' => '', + 'passwordExcludedGroupsFeatureEnabled' => false, ], '' ); diff --git a/config/config.sample.php b/config/config.sample.php index 4bb976fbcad3a..cffb3fa8f892b 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -1553,6 +1553,11 @@ */ 'sharing.enable_share_mail' => true, +/** + * Set to true to enable the feature to add exceptions for share password enforcement + */ +'sharing.allow_disabled_password_enforcement_groups' => false, + /** * Set to true to always transfer incoming shares by default * when running "occ files:transfer-ownership". diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index 84d23e1b08773..e17bb7ee79d0f 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -1763,9 +1763,21 @@ public function shareApiAllowLinks() { /** * Is password on public link requires * + * @param bool Check group membership exclusion * @return bool */ - public function shareApiLinkEnforcePassword() { + public function shareApiLinkEnforcePassword(bool $checkGroupMembership = true) { + $excludedGroups = $this->config->getAppValue('core', 'shareapi_enforce_links_password_excluded_groups', ''); + if ($excludedGroups !== '' && $checkGroupMembership) { + $excludedGroups = json_decode($excludedGroups); + $user = $this->userSession->getUser(); + if ($user) { + $userGroups = $this->groupManager->getUserGroupIds($user); + if ((bool)array_intersect($excludedGroups, $userGroups)) { + return false; + } + } + } return $this->config->getAppValue('core', 'shareapi_enforce_links_password', 'no') === 'yes'; } diff --git a/lib/private/legacy/OC_Util.php b/lib/private/legacy/OC_Util.php index 9892faf6032e8..7e0fd45e0540c 100644 --- a/lib/private/legacy/OC_Util.php +++ b/lib/private/legacy/OC_Util.php @@ -347,15 +347,16 @@ public static function setupFS(?string $user = '') { } /** - * check if a password is required for each public link + * Check if a password is required for each public link * + * @param bool $checkGroupMembership Check group membership exclusion * @return boolean * @suppress PhanDeprecatedFunction */ - public static function isPublicLinkPasswordRequired() { + public static function isPublicLinkPasswordRequired(bool $checkGroupMembership = true) { /** @var IManager $shareManager */ $shareManager = \OC::$server->get(IManager::class); - return $shareManager->shareApiLinkEnforcePassword(); + return $shareManager->shareApiLinkEnforcePassword($checkGroupMembership); } /** diff --git a/lib/public/Share/IManager.php b/lib/public/Share/IManager.php index 77a9980a8946f..bad3af6837fbc 100644 --- a/lib/public/Share/IManager.php +++ b/lib/public/Share/IManager.php @@ -316,10 +316,12 @@ public function shareApiAllowLinks(); /** * Is password on public link requires * + * @param bool $checkGroupMembership Check group membership exclusion * @return bool * @since 9.0.0 + * @since 24.0.0 Added optional $checkGroupMembership parameter */ - public function shareApiLinkEnforcePassword(); + public function shareApiLinkEnforcePassword(bool $checkGroupMembership = true); /** * Is default expire date enabled diff --git a/lib/public/Util.php b/lib/public/Util.php index 5165846707a66..b0937a5bb52c2 100644 --- a/lib/public/Util.php +++ b/lib/public/Util.php @@ -482,12 +482,14 @@ public static function naturalSortCompare($a, $b) { } /** - * check if a password is required for each public link + * Check if a password is required for each public link + * + * @param bool $checkGroupMembership Check group membership exclusion * @return boolean * @since 7.0.0 */ - public static function isPublicLinkPasswordRequired() { - return \OC_Util::isPublicLinkPasswordRequired(); + public static function isPublicLinkPasswordRequired(bool $checkGroupMembership = true) { + return \OC_Util::isPublicLinkPasswordRequired($checkGroupMembership); } /** diff --git a/tests/lib/Share20/ManagerTest.php b/tests/lib/Share20/ManagerTest.php index a6320fe305b34..1aaf1144c03e2 100644 --- a/tests/lib/Share20/ManagerTest.php +++ b/tests/lib/Share20/ManagerTest.php @@ -497,14 +497,46 @@ public function testVerifyPasswordNullButEnforced() { $this->expectExceptionMessage('Passwords are enforced for link and mail shares'); $this->config->method('getAppValue')->willReturnMap([ + ['core', 'shareapi_enforce_links_password_excluded_groups', '', ''], ['core', 'shareapi_enforce_links_password', 'no', 'yes'], ]); self::invokePrivate($this->manager, 'verifyPassword', [null]); } + public function testVerifyPasswordNotEnforcedGroup() { + $this->config->method('getAppValue')->willReturnMap([ + ['core', 'shareapi_enforce_links_password_excluded_groups', '', '["admin"]'], + ['core', 'shareapi_enforce_links_password', 'no', 'yes'], + ]); + + // Create admin user + $user = $this->createMock(IUser::class); + $this->userSession->method('getUser')->willReturn($user); + $this->groupManager->method('getUserGroupIds')->with($user)->willReturn(['admin']); + + $result = self::invokePrivate($this->manager, 'verifyPassword', [null]); + $this->assertNull($result); + } + + public function testVerifyPasswordNotEnforcedMultipleGroups() { + $this->config->method('getAppValue')->willReturnMap([ + ['core', 'shareapi_enforce_links_password_excluded_groups', '', '["admin", "special"]'], + ['core', 'shareapi_enforce_links_password', 'no', 'yes'], + ]); + + // Create admin user + $user = $this->createMock(IUser::class); + $this->userSession->method('getUser')->willReturn($user); + $this->groupManager->method('getUserGroupIds')->with($user)->willReturn(['special']); + + $result = self::invokePrivate($this->manager, 'verifyPassword', [null]); + $this->assertNull($result); + } + public function testVerifyPasswordNull() { $this->config->method('getAppValue')->willReturnMap([ + ['core', 'shareapi_enforce_links_password_excluded_groups', '', ''], ['core', 'shareapi_enforce_links_password', 'no', 'no'], ]); @@ -514,6 +546,7 @@ public function testVerifyPasswordNull() { public function testVerifyPasswordHook() { $this->config->method('getAppValue')->willReturnMap([ + ['core', 'shareapi_enforce_links_password_excluded_groups', '', ''], ['core', 'shareapi_enforce_links_password', 'no', 'no'], ]); @@ -535,6 +568,7 @@ public function testVerifyPasswordHookFails() { $this->expectExceptionMessage('password not accepted'); $this->config->method('getAppValue')->willReturnMap([ + ['core', 'shareapi_enforce_links_password_excluded_groups', '', ''], ['core', 'shareapi_enforce_links_password', 'no', 'no'], ]);