diff --git a/apps/files_sharing/appinfo/info.xml b/apps/files_sharing/appinfo/info.xml index 05ad282d3d11..a8859cdcbf22 100644 --- a/apps/files_sharing/appinfo/info.xml +++ b/apps/files_sharing/appinfo/info.xml @@ -24,6 +24,10 @@ Turning the feature off removes shared files and folders on the server for all s Files_Sharing + + OCA\Files_Sharing\Panels\Admin\SettingsPanel + + OCA\Files_Sharing\DeleteOrphanedSharesJob OCA\Files_Sharing\ExpireSharesJob diff --git a/apps/files_sharing/js/settings.js b/apps/files_sharing/js/settings.js new file mode 100644 index 000000000000..6b16576366bc --- /dev/null +++ b/apps/files_sharing/js/settings.js @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2018 + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + +$(document).ready(function() { + var $blacklistedGroups = $('#files_sharing input[name="blacklisted_receiver_groups"]'); + OC.Settings.setupGroupsSelect($blacklistedGroups); + $blacklistedGroups.change(function(ev) { + var groups = ev.val || []; + groups = JSON.stringify(groups); + OC.AppConfig.setValue('files_sharing', $(this).attr('name'), groups); + }); +}); \ No newline at end of file diff --git a/apps/files_sharing/lib/API/OCSShareWrapper.php b/apps/files_sharing/lib/API/OCSShareWrapper.php index bf3307b45c00..ecaca2183af9 100644 --- a/apps/files_sharing/lib/API/OCSShareWrapper.php +++ b/apps/files_sharing/lib/API/OCSShareWrapper.php @@ -22,6 +22,7 @@ use OCA\Files_Sharing\AppInfo\Application; use OCA\Files_Sharing\Service\NotificationPublisher; +use OCA\Files_Sharing\SharingBlacklist; class OCSShareWrapper { @@ -47,7 +48,8 @@ private function getShare20OCS() { \OC::$server->getL10N('files_sharing'), \OC::$server->getConfig(), $this->application->getContainer()->query(NotificationPublisher::class), - \OC::$server->getEventDispatcher() + \OC::$server->getEventDispatcher(), + $this->application->getContainer()->query(SharingBlacklist::class) ); } diff --git a/apps/files_sharing/lib/API/Share20OCS.php b/apps/files_sharing/lib/API/Share20OCS.php index 6b753fa35748..f3939766a4b2 100644 --- a/apps/files_sharing/lib/API/Share20OCS.php +++ b/apps/files_sharing/lib/API/Share20OCS.php @@ -39,6 +39,7 @@ use OCP\Share\IShare; use OCA\Files_Sharing\Service\NotificationPublisher; use OCA\Files_Sharing\Helper; +use OCA\Files_Sharing\SharingBlacklist; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\GenericEvent; @@ -71,6 +72,8 @@ class Share20OCS { private $notificationPublisher; /** @var EventDispatcher */ private $eventDispatcher; + /** @var SharingBlacklist */ + private $sharingBlacklist; /** * @var string @@ -102,7 +105,8 @@ public function __construct( IL10N $l10n, IConfig $config, NotificationPublisher $notificationPublisher, - EventDispatcher $eventDispatcher + EventDispatcher $eventDispatcher, + SharingBlacklist $sharingBlacklist ) { $this->shareManager = $shareManager; $this->userManager = $userManager; @@ -115,6 +119,7 @@ public function __construct( $this->config = $config; $this->notificationPublisher = $notificationPublisher; $this->eventDispatcher = $eventDispatcher; + $this->sharingBlacklist = $sharingBlacklist; $this->additionalInfoField = $this->config->getAppValue('core', 'user_additional_info_field', ''); } @@ -402,6 +407,9 @@ public function createShare() { $share->getNode()->unlock(ILockingProvider::LOCK_SHARED); return new \OC\OCS\Result(null, 404, $this->l->t('Please specify a valid group')); } + if ($this->sharingBlacklist->isGroupBlacklisted($this->groupManager->get($shareWith))) { + return new \OC\OCS\Result(null, 403, $this->l->t('The group is blacklisted for sharing')); + } $share->setSharedWith($shareWith); $share->setPermissions($permissions); if ($autoAccept) { diff --git a/apps/files_sharing/lib/Controller/ShareesController.php b/apps/files_sharing/lib/Controller/ShareesController.php index 580e4e5f97ff..459659b58427 100644 --- a/apps/files_sharing/lib/Controller/ShareesController.php +++ b/apps/files_sharing/lib/Controller/ShareesController.php @@ -40,6 +40,7 @@ use OCP\IUserManager; use OCP\IUserSession; use OCP\Share; +use OCA\Files_Sharing\SharingBlacklist; class ShareesController extends OCSController { @@ -107,6 +108,9 @@ class ShareesController extends OCSController { */ protected $additionalInfoField; + /** @var SharingBlacklist */ + protected $sharingBlacklist; + /** * @param IGroupManager $groupManager * @param IUserManager $userManager @@ -127,7 +131,8 @@ public function __construct($appName, IUserSession $userSession, IURLGenerator $urlGenerator, ILogger $logger, - \OCP\Share\IManager $shareManager) { + \OCP\Share\IManager $shareManager, + SharingBlacklist $sharingBlacklist) { parent::__construct($appName, $request); $this->groupManager = $groupManager; @@ -139,6 +144,7 @@ public function __construct($appName, $this->request = $request; $this->logger = $logger; $this->shareManager = $shareManager; + $this->sharingBlacklist = $sharingBlacklist; $this->additionalInfoField = $this->config->getAppValue('core', 'user_additional_info_field', ''); } @@ -284,7 +290,7 @@ protected function getGroups($search) { foreach ($groups as $group) { // FIXME: use a more efficient approach $gid = $group->getGID(); - if (!\in_array($gid, $groupIds)) { + if (!\in_array($gid, $groupIds) || $this->sharingBlacklist->isGroupBlacklisted($group)) { continue; } if (\strtolower($gid) === $lowerSearch || \strtolower($group->getDisplayName()) === $lowerSearch) { @@ -310,7 +316,8 @@ protected function getGroups($search) { // On page one we try if the search result has a direct hit on the // user id and if so, we add that to the exact match list $group = $this->groupManager->get($search); - if ($group instanceof IGroup && (!$this->shareWithMembershipGroupOnly || \in_array($group->getGID(), $userGroups))) { + if ($group instanceof IGroup && !$this->sharingBlacklist->isGroupBlacklisted($group) && + (!$this->shareWithMembershipGroupOnly || \in_array($group->getGID(), $userGroups))) { \array_push($this->result['exact']['groups'], [ 'label' => $group->getDisplayName(), 'value' => [ diff --git a/apps/files_sharing/lib/Panels/Admin/SettingsPanel.php b/apps/files_sharing/lib/Panels/Admin/SettingsPanel.php new file mode 100644 index 000000000000..1bb328d65dea --- /dev/null +++ b/apps/files_sharing/lib/Panels/Admin/SettingsPanel.php @@ -0,0 +1,47 @@ + + * + * @copyright Copyright (c) 2018, ownCloud GmbH + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +namespace OCA\Files_Sharing\Panels\Admin; +use OCP\Settings\ISettings; +use OCP\Template; +use OCA\Files_Sharing\SharingBlacklist; + +class SettingsPanel implements ISettings { + /** @var SharingBlacklist */ + private $sharingBlacklist; + + public function __construct(SharingBlacklist $sharingBlacklist) { + $this->sharingBlacklist = $sharingBlacklist; + } + + public function getPanel() { + $tmpl = new Template('files_sharing', 'settings'); + $tmpl->assign('blacklistedReceivers', \implode('|', $this->sharingBlacklist->getBlacklistedReceiverGroups())); + return $tmpl; + } + + public function getPriority() { + return 0; + } + + public function getSectionID() { + return 'sharing'; + } +} diff --git a/apps/files_sharing/lib/SharingBlacklist.php b/apps/files_sharing/lib/SharingBlacklist.php new file mode 100644 index 000000000000..07ad8b485beb --- /dev/null +++ b/apps/files_sharing/lib/SharingBlacklist.php @@ -0,0 +1,107 @@ + + * + * @copyright Copyright (c) 2018, ownCloud GmbH + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\Files_Sharing; + +use OCP\IConfig; +use OCP\IGroup; + +/** + * Class to handle a blacklist for sharing. The main functionality is to check if a particular group + * has been blacklisted for sharing, which means that noone should share with that group. + * + * Note that this class will only handle the configuration and perform the checks against the configuration + * This class won't prevent the sharing action by itself. + */ +class SharingBlacklist { + /** @var IConfig */ + private $config; + + private $blacklistCache = null; + + public function __construct(IConfig $config) { + $this->config = $config; + } + + /** + * Check if the target group is blacklisted + * @param IGroup $group the group to check + * @return bool true if the group is blacklisted, false otherwise + */ + public function isGroupBlacklisted(IGroup $group) { + $this->initCache(); + + $groupId = $group->getGID(); + + if (isset($this->blacklistCache['receivers']['ids'][$groupId])) { + return true; + } + return false; + } + + /** + * Clear the internal cache of this class. Use this function if any of the keys used by this class is changed + * outside of this class, such as a direct change of the 'blacklisted_group_displaynames' in the appconfig table + * Note that this is an object-based cache. It won't persist for multiple HTTP requests + */ + public function clearCache() { + $this->blacklistCache = null; + } + + /** + * Set the list of groups to be blacklisted by id. + * @param string[] $ids a list with the ids of the groups to be blacklisted + */ + public function setBlacklistedReceiverGroups(array $ids) { + $this->config->setAppValue('files_sharing', 'blacklisted_receiver_groups', \json_encode($ids)); + $this->blacklistCache = null; // clear the cache + } + + /** + * Get the list of blacklisted group ids + * Note that this might contain wrong information + * @return string[] the list of group ids + */ + public function getBlacklistedReceiverGroups() { + return \json_decode($this->config->getAppValue('files_sharing', 'blacklisted_receiver_groups', '[]'), true); + } + + private function initCache() { + if ($this->blacklistCache === null) { + $this->blacklistCache = [ + 'receivers' => [ + 'ids' => $this->fetchBlacklistedReceiverGroupIds(), + ], + ]; + } + } + + private function fetchBlacklistedReceiverGroupIds() { + $configuredBlacklist = $this->config->getAppValue('files_sharing', 'blacklisted_receiver_groups', '[]'); + $decodedGroups = \json_decode($configuredBlacklist, true); + // expected a plain array here + $groupSet = []; + foreach ($decodedGroups as $group) { + $groupSet[$group] = true; + } + return $groupSet; + } +} diff --git a/apps/files_sharing/templates/settings.php b/apps/files_sharing/templates/settings.php new file mode 100644 index 000000000000..20aeb328d79f --- /dev/null +++ b/apps/files_sharing/templates/settings.php @@ -0,0 +1,32 @@ + + * + * @copyright Copyright (c) 2018, ownCloud GmbH + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +script('files_sharing', 'settings'); +?> + +
+

t('Files Sharing')); ?>

+
+

t('Exclude groups from receiving shares.')); ?>

+ +
+ t('These groups will not receive shares. Members of the group can still send and receive shares outside of the group.')); ?> +
+
\ No newline at end of file diff --git a/apps/files_sharing/tests/API/Share20OCSTest.php b/apps/files_sharing/tests/API/Share20OCSTest.php index 355020818425..6bb73ebdb409 100644 --- a/apps/files_sharing/tests/API/Share20OCSTest.php +++ b/apps/files_sharing/tests/API/Share20OCSTest.php @@ -27,6 +27,7 @@ use OC\OCS\Result; use OCA\Files_Sharing\API\Share20OCS; use OCA\Files_Sharing\Service\NotificationPublisher; +use OCA\Files_Sharing\SharingBlacklist; use OCP\Files\IRootFolder; use OCP\Files\NotFoundException; use OCP\IConfig; @@ -35,6 +36,7 @@ use OCP\IRequest; use OCP\IURLGenerator; use OCP\IUser; +use OCP\IGroup; use OCP\IUserManager; use OCP\Lock\LockedException; use OCP\Share; @@ -84,6 +86,8 @@ class Share20OCSTest extends TestCase { private $notificationPublisher; private $eventDispatcher; + /** @var SharingBlacklist */ + private $sharingBlacklist; protected function setUp() { $this->shareManager = $this->getMockBuilder('OCP\Share\IManager') @@ -118,6 +122,7 @@ protected function setUp() { $this->notificationPublisher = $this->createMock(NotificationPublisher::class); $this->eventDispatcher = $this->createMock(EventDispatcher::class); + $this->sharingBlacklist = $this->createMock(SharingBlacklist::class); $this->ocs = new Share20OCS( $this->shareManager, @@ -130,7 +135,8 @@ protected function setUp() { $this->l, $this->config, $this->notificationPublisher, - $this->eventDispatcher + $this->eventDispatcher, + $this->sharingBlacklist ); } @@ -151,7 +157,8 @@ private function mockFormatShare() { $this->l, $this->config, $this->notificationPublisher, - $this->eventDispatcher + $this->eventDispatcher, + $this->sharingBlacklist, ])->setMethods(['formatShare']) ->getMock(); } @@ -160,6 +167,20 @@ private function newShare() { return \OC::$server->getShareManager()->newShare(); } + private function getGroupMock(array $attrs) { + $groupMock = $this->getMockBuilder(IGroup::class) + ->disableOriginalConstructor() + ->getMock(); + + if (isset($attrs['guid'])) { + $groupMock->method('getGID')->willReturn($attrs['guid']); + } + if (isset($attrs['displayname'])) { + $groupMock->method('getDisplayName')->willReturn($attrs['displayname']); + } + return $groupMock; + } + public function testDeleteShareShareNotFound() { $this->shareManager ->expects($this->exactly(2)) @@ -461,7 +482,8 @@ public function testGetShare(\OCP\Share\IShare $share, array $result) { $this->l, $this->config, $this->notificationPublisher, - $this->eventDispatcher + $this->eventDispatcher, + $this->sharingBlacklist, ])->setMethods(['canAccessShare']) ->getMock(); @@ -895,6 +917,54 @@ public function testCreateShareGroupNoValidShareWith() { $this->assertEquals($expected->getData(), $result->getData()); } + public function testCreateShareGroupBlacklisted() { + $share = $this->newShare(); + $this->shareManager->method('newShare')->willReturn($share); + + $this->request->method('getParam') + ->will($this->returnValueMap([ + ['path', null, 'valid-path'], + ['permissions', null, \OCP\Constants::PERMISSION_ALL], + ['shareType', '-1', Share::SHARE_TYPE_GROUP], + ['shareWith', null, 'validGroup'], + ])); + + $userFolder = $this->createMock('\OCP\Files\Folder'); + $this->rootFolder->expects($this->once()) + ->method('getUserFolder') + ->with('currentUser') + ->willReturn($userFolder); + + $path = $this->createMock('\OCP\Files\Folder'); + $storage = $this->createMock('OCP\Files\Storage'); + $storage->method('instanceOfStorage') + ->with('OCA\Files_Sharing\External\Storage') + ->willReturn(false); + $path->method('getStorage')->willReturn($storage); + $userFolder->expects($this->once()) + ->method('get') + ->with('valid-path') + ->willReturn($path); + + $this->groupManager->method('groupExists')->with('validGroup')->willReturn(true); + $this->groupManager->method('get') + ->with('validGroup') + ->willReturn($this->getGroupMock(['guid' => 'gegege1', 'displayname' => 'validGroup'])); + + $this->shareManager->expects($this->once()) + ->method('allowGroupSharing') + ->willReturn(true); + + $this->sharingBlacklist->method('isGroupBlacklisted')->willReturn(true); + + $expected = new \OC\OCS\Result(null, 403, 'The group is blacklisted for sharing'); + + $result = $this->ocs->createShare(); + + $this->assertEquals($expected->getMeta(), $result->getMeta()); + $this->assertEquals($expected->getData(), $result->getData()); + } + public function testCreateShareGroup() { $share = $this->newShare(); $this->shareManager->method('newShare')->willReturn($share); @@ -928,6 +998,9 @@ public function testCreateShareGroup() { ->willReturn($path); $this->groupManager->method('groupExists')->with('validGroup')->willReturn(true); + $this->groupManager->method('get') + ->with('validGroup') + ->willReturn($this->getGroupMock(['guid' => 'gegege1', 'displayname' => 'validGroup'])); $this->shareManager->expects($this->once()) ->method('allowGroupSharing') @@ -950,6 +1023,8 @@ public function testCreateShareGroup() { })) ->will($this->returnArgument(0)); + $this->sharingBlacklist->method('isGroupBlacklisted')->willReturn(false); + $expected = new \OC\OCS\Result(); $result = $ocs->createShare(); @@ -2704,7 +2779,8 @@ public function getOcsDisabledAPI() { $this->l, $this->config, $this->notificationPublisher, - $this->eventDispatcher + $this->eventDispatcher, + $this->sharingBlacklist ); } @@ -2796,7 +2872,8 @@ public function testGetShareAdditionalInfo($configValue, $expectedInfo) { $this->l, $config, $this->notificationPublisher, - $this->eventDispatcher + $this->eventDispatcher, + $this->sharingBlacklist ); list($file, ) = $this->getMockFileFolder(); diff --git a/apps/files_sharing/tests/API/ShareesTest.php b/apps/files_sharing/tests/API/ShareesTest.php index 043e2c68fabd..a08b0daf72db 100644 --- a/apps/files_sharing/tests/API/ShareesTest.php +++ b/apps/files_sharing/tests/API/ShareesTest.php @@ -27,6 +27,7 @@ namespace OCA\Files_Sharing\Tests\API; use OCA\Files_Sharing\Controller\ShareesController; +use OCA\Files_Sharing\SharingBlacklist; use OCA\Files_Sharing\Tests\TestCase; use OCP\AppFramework\Http; use OCP\Contacts\IManager; @@ -73,6 +74,9 @@ class ShareesTest extends TestCase { /** @var \OCP\Share\IManager|\PHPUnit_Framework_MockObject_MockObject */ protected $shareManager; + /** @var SharingBlacklist|\PHPUnit_Framework_MockObject_MockObject */ + protected $sharingBlacklist; + protected function setUp() { parent::setUp(); @@ -104,6 +108,10 @@ protected function setUp() { ->disableOriginalConstructor() ->getMock(); + $this->sharingBlacklist = $this->getMockBuilder(SharingBlacklist::class) + ->disableOriginalConstructor() + ->getMock(); + $this->sharees = new ShareesController( 'files_sharing', $this->request, @@ -114,7 +122,8 @@ protected function setUp() { $this->session, $this->getMockBuilder(IURLGenerator::class)->disableOriginalConstructor()->getMock(), $this->getMockBuilder(ILogger::class)->disableOriginalConstructor()->getMock(), - $this->shareManager + $this->shareManager, + $this->sharingBlacklist ); } @@ -534,7 +543,8 @@ public function testGetUsers( $this->session, $this->getMockBuilder(IURLGenerator::class)->disableOriginalConstructor()->getMock(), $this->getMockBuilder(ILogger::class)->disableOriginalConstructor()->getMock(), - $this->shareManager + $this->shareManager, + $this->sharingBlacklist ); $this->invokePrivate($this->sharees, 'limit', [2]); $this->invokePrivate($this->sharees, 'offset', [0]); @@ -594,8 +604,8 @@ public function testGetUsers( public function dataGetGroups() { return [ - ['test', false, true, [], [], [], [], true, false], - ['test', false, false, [], [], [], [], true, false], + ['test', false, true, [], [], [], [], true], + ['test', false, false, [], [], [], [], true], // group without display name [ 'test', false, true, @@ -604,7 +614,6 @@ public function dataGetGroups() { [], [['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']]], true, - false, ], // group with display name, search by id [ @@ -614,7 +623,6 @@ public function dataGetGroups() { [], [['label' => 'Test One', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']]], true, - false, ], // group with display name, search by display name [ @@ -624,7 +632,6 @@ public function dataGetGroups() { [], [['label' => 'Test One', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']]], true, - false, ], // group with display name, search by display name, exact expected [ @@ -634,7 +641,6 @@ public function dataGetGroups() { [['label' => 'Test One', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']]], [], true, - false, ], [ 'test', false, false, @@ -643,7 +649,6 @@ public function dataGetGroups() { [], [], true, - false, ], [ 'test', false, true, @@ -655,7 +660,6 @@ public function dataGetGroups() { [['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']]], [['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']]], false, - false, ], [ 'test', false, false, @@ -667,7 +671,6 @@ public function dataGetGroups() { [['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']]], [], true, - false, ], [ 'test', false, true, @@ -682,7 +685,6 @@ public function dataGetGroups() { ['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']], ], false, - null, ], [ 'test', false, false, @@ -694,7 +696,6 @@ public function dataGetGroups() { [], [], true, - null, ], [ 'test', false, true, @@ -703,15 +704,12 @@ public function dataGetGroups() { $this->getGroupMock('test1'), ], [], - [ - ['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']], - ], + [], [ ['label' => 'test0', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test0']], ['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']], ], false, - $this->getGroupMock('test'), ], [ 'test', false, false, @@ -720,15 +718,12 @@ public function dataGetGroups() { $this->getGroupMock('test1'), ], [], - [ - ['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']], - ], + [], [], true, - $this->getGroupMock('test'), ], - ['test', true, true, [], [], [], [], true, false], - ['test', true, false, [], [], [], [], true, false], + ['test', true, true, [], [], [], [], true], + ['test', true, false, [], [], [], [], true], [ 'test', true, true, [ @@ -739,7 +734,6 @@ public function dataGetGroups() { [], [['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']]], false, - false, ], [ 'test', true, false, @@ -751,7 +745,6 @@ public function dataGetGroups() { [], [], true, - false, ], [ 'test', true, true, @@ -763,7 +756,6 @@ public function dataGetGroups() { [['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']]], [], false, - false, ], [ 'test', true, false, @@ -775,7 +767,6 @@ public function dataGetGroups() { [['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']]], [], true, - false, ], [ 'test', true, true, @@ -787,7 +778,6 @@ public function dataGetGroups() { [], [['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']]], false, - false, ], [ 'test', true, false, @@ -799,7 +789,6 @@ public function dataGetGroups() { [], [], true, - false, ], [ 'test', true, true, @@ -811,7 +800,6 @@ public function dataGetGroups() { [['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']]], [['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']]], false, - false, ], [ 'test', true, false, @@ -823,7 +811,6 @@ public function dataGetGroups() { [['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']]], [], true, - false, ], [ 'test', true, true, @@ -838,7 +825,6 @@ public function dataGetGroups() { ['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']], ], false, - null, ], [ 'test', true, false, @@ -850,7 +836,6 @@ public function dataGetGroups() { [], [], true, - null, ], [ 'test', true, true, @@ -859,15 +844,12 @@ public function dataGetGroups() { $this->getGroupMock('test1'), ], [$this->getGroupMock('test'), $this->getGroupMock('test0'), $this->getGroupMock('test1')], - [ - ['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']], - ], + [], [ ['label' => 'test0', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test0']], ['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']], ], false, - $this->getGroupMock('test'), ], [ 'test', true, false, @@ -876,12 +858,9 @@ public function dataGetGroups() { $this->getGroupMock('test1'), ], [$this->getGroupMock('test'), $this->getGroupMock('test0'), $this->getGroupMock('test1')], - [ - ['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']], - ], + [], [], true, - $this->getGroupMock('test'), ], // group enumeration restricted to group memberships [ @@ -900,14 +879,13 @@ public function dataGetGroups() { ['label' => 'test0', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test0']], ], true, - false, true ], [ // exact match 'test0', false, true, // group results - [], + [$this->getGroupMock('test0')], // user group memberships [$this->getGroupMock('test')], // exact expected @@ -917,10 +895,62 @@ public function dataGetGroups() { // non-exact expected [], true, - // exact match to test for - $this->getGroupMock('test0'), true ], + [ + // check with group blacklist (not exact match) + 'test', false, true, + [ + $this->getGroupMock('test'), + $this->getGroupMock('test0'), + $this->getGroupMock('test1'), + ], + [$this->getGroupMock('test'), $this->getGroupMock('test0'), $this->getGroupMock('test1')], + [ + ['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']], + ], + [ + ['label' => 'test0', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test0']], + ], + false, + false, + ['test1'] + ], + [ + // check with group blacklist (exact match) + 'test', false, true, + [ + $this->getGroupMock('test'), + $this->getGroupMock('test0'), + $this->getGroupMock('test1'), + ], + [$this->getGroupMock('test'), $this->getGroupMock('test0'), $this->getGroupMock('test1')], + [], + [ + ['label' => 'test0', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test0']], + ['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']], + ], + false, + false, + ['test'] + ], + [ + // check with group blacklist (both exact and not) + 'test', false, true, + [ + $this->getGroupMock('test'), + $this->getGroupMock('test0'), + $this->getGroupMock('test1'), + ], + [$this->getGroupMock('test'), $this->getGroupMock('test0'), $this->getGroupMock('test1')], + [], + [ + ['label' => 'test0', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test0']], + ], + false, + false, + ['test', 'test1'] + ], ]; } @@ -935,7 +965,8 @@ public function dataGetGroups() { * @param array $exactExpected * @param array $expected * @param bool $reachedEnd - * @param mixed $singleGroup false when testing a search or group mock when testing direct match + * @param bool $shareeEnumerationGroupMembers + * @param array $blacklistedGroupNames list with the names of the blacklisted groups */ public function testGetGroups( $searchTerm, @@ -946,8 +977,8 @@ public function testGetGroups( $exactExpected, $expected, $reachedEnd, - $singleGroup, - $shareeEnumerationGroupMembers = false + $shareeEnumerationGroupMembers = false, + $blacklistedGroupNames = [] ) { $this->invokePrivate($this->sharees, 'limit', [2]); $this->invokePrivate($this->sharees, 'offset', [0]); @@ -960,12 +991,12 @@ public function testGetGroups( ->with($searchTerm, $this->invokePrivate($this->sharees, 'limit'), $this->invokePrivate($this->sharees, 'offset')) ->willReturn($groupResponse); - if ($singleGroup !== false) { - $this->groupManager->expects($this->once()) - ->method('get') - ->with($searchTerm) - ->willReturn($singleGroup); - } + $getGroupValueMap = \array_map(function ($group) { + return [$group->getGID(), $group]; + }, $groupResponse); + + $this->groupManager->method('get') + ->will($this->returnValueMap($getGroupValueMap)); if ($shareWithMembershipGroupOnly || $shareeEnumerationGroupMembers) { $user = $this->getUserMock('admin', 'Administrator'); @@ -980,6 +1011,13 @@ public function testGetGroups( ->willReturn($userGroupsResponse); } + // don't care about the particular implementation of the method + // just mark the group as blacklisted based on the displayname + $this->sharingBlacklist->method('isGroupBlacklisted') + ->will($this->returnCallback(function (IGroup $group) use ($blacklistedGroupNames) { + return \in_array($group->getDisplayName(), $blacklistedGroupNames, true); + })); + $this->invokePrivate($this->sharees, 'getGroups', [$searchTerm]); $result = $this->invokePrivate($this->sharees, 'result'); @@ -1484,7 +1522,8 @@ public function testSearchInvalid($message, $search = '', $itemType = null, $pag $this->session, $this->getMockBuilder('OCP\IURLGenerator')->disableOriginalConstructor()->getMock(), $this->getMockBuilder('OCP\ILogger')->disableOriginalConstructor()->getMock(), - $this->shareManager + $this->shareManager, + $this->sharingBlacklist ]) ->setMethods(['searchSharees', 'isRemoteSharingAllowed']) ->getMock(); @@ -1633,7 +1672,8 @@ public function testSearchSharees($searchTerm, $itemType, array $shareTypes, $pa $this->session, $this->getMockBuilder(IURLGenerator::class)->disableOriginalConstructor()->getMock(), $this->getMockBuilder(ILogger::class)->disableOriginalConstructor()->getMock(), - $this->shareManager + $this->shareManager, + $this->sharingBlacklist ]) ->setMethods(['getShareesForShareIds', 'getUsers', 'getGroups', 'getRemote']) ->getMock(); diff --git a/apps/files_sharing/tests/ApiTest.php b/apps/files_sharing/tests/ApiTest.php index cd0d986debcf..527b80690d01 100644 --- a/apps/files_sharing/tests/ApiTest.php +++ b/apps/files_sharing/tests/ApiTest.php @@ -33,6 +33,7 @@ use OCP\IRequest; use OCP\Share; use OCA\Files_Sharing\Service\NotificationPublisher; +use OCA\Files_Sharing\SharingBlacklist; /** * Class ApiTest @@ -125,7 +126,8 @@ private function createOCS($request, $userId) { $l, \OC::$server->getConfig(), \OC::$server->getAppContainer('files_sharing')->query(NotificationPublisher::class), - \OC::$server->getEventDispatcher() + \OC::$server->getEventDispatcher(), + \OC::$server->getAppContainer('files_sharing')->query(SharingBlacklist::class) ); } diff --git a/apps/files_sharing/tests/Panels/Admin/SettingsPanelTest.php b/apps/files_sharing/tests/Panels/Admin/SettingsPanelTest.php new file mode 100644 index 000000000000..0f0838457277 --- /dev/null +++ b/apps/files_sharing/tests/Panels/Admin/SettingsPanelTest.php @@ -0,0 +1,89 @@ + + * + * @copyright Copyright (c) 2018, ownCloud GmbH + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +namespace OCA\Files_Sharing\Tests\Panels\Admin; + +use OCP\GroupInterface; +use OCA\Files_Sharing\SharingBlacklist; +use OCA\Files_Sharing\Panels\Admin\SettingsPanel; + +class SettingsPanelTest extends \Test\TestCase { + /** @var SharingBlacklist | \PHPUnit_Framework_MockObject_MockObject */ + private $sharingBlacklist; + + /** @var SettingsPanel | \PHPUnit_Framework_MockObject_MockObject */ + private $settingsPanel; + + protected function setUp() { + $this->sharingBlacklist = $this->getMockBuilder(SharingBlacklist::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->settingsPanel = new SettingsPanel($this->sharingBlacklist); + } + + public function testGetSectionID() { + $this->assertEquals('sharing', $this->settingsPanel->getSectionID()); + } + + public function testGetPriority() { + $this->assertEquals(0, $this->settingsPanel->getPriority()); + } + + public function testGetPanel() { + $this->sharingBlacklist->method('getBlacklistedReceiverGroups')->willReturn([]); + + $page = $this->settingsPanel->getPanel()->fetchPage(); + $doc = new \DOMDocument(); + $doc->loadHTML($page); + $xpath = new \DOMXPath($doc); + + $inputNodes = $xpath->query('//input[@name="blacklisted_receiver_groups"]'); + $this->assertEquals(1, $inputNodes->length); // only 1 element should be found + $inputNode = $inputNodes->item(0); + $this->assertSame('', $inputNode->attributes->getNamedItem('value')->value); + } + + public function getPanelWithBlacklistProvider() { + return [ + [['group1']], + [['group1', 'group2']], + [['group1', 'group2', 'group3']], + [['1234mkdds_lklk', '12345678-1234-1234-123456789abcdf']], + ]; + } + + /** + * @dataProvider getPanelWithBlacklistProvider + */ + public function testGetPanelWithBlacklist($ids) { + $this->sharingBlacklist->method('getBlacklistedReceiverGroups')->willReturn($ids); + + $page = $this->settingsPanel->getPanel()->fetchPage(); + $doc = new \DOMDocument(); + $doc->loadHTML($page); + $xpath = new \DOMXPath($doc); + + $inputNodes = $xpath->query('//input[@name="blacklisted_receiver_groups"]'); + $this->assertEquals(1, $inputNodes->length); // only 1 element should be found + $inputNode = $inputNodes->item(0); + $this->assertSame(\implode("|", $ids), $inputNode->attributes->getNamedItem('value')->value); + } +} diff --git a/apps/files_sharing/tests/SharingBlacklistTest.php b/apps/files_sharing/tests/SharingBlacklistTest.php new file mode 100644 index 000000000000..55a68d34f86b --- /dev/null +++ b/apps/files_sharing/tests/SharingBlacklistTest.php @@ -0,0 +1,120 @@ + + * + * @copyright Copyright (c) 2018, ownCloud GmbH + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +namespace OCA\Files_Sharing\Tests; + +use OCP\IConfig; +use OCP\IGroup; +use OCP\GroupInterface; +use OCA\Files_Sharing\SharingBlacklist; + +class SharingBlacklistTest extends \Test\TestCase { + /** @var IConfig | \PHPUnit_Framework_MockObject_MockObject */ + private $config; + + /** @var SharingBlacklist | \PHPUnit_Framework_MockObject_MockObject */ + private $sharingBlacklist; + + public function setUp() { + $this->config = $this->getMockBuilder(IConfig::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->sharingBlacklist = new SharingBlacklist($this->config); + } + + public function setGetBlacklistedReceiverGroupsProvider() { + return [ + [[]], + [["group1"]], + [["group1", "group2", "$group3"]], + ]; + } + + /** + * @dataProvider setGetBlacklistedReceiverGroupsProvider + */ + public function testSetGetBlacklistedReceiverGroups($ids) { + $keyValues = []; + $this->config->method('setAppValue') + ->will($this->returnCallback(function ($app, $key, $value) use (&$keyValues) { + $keyValues[$key] = $value; + })); + + $this->config->method('getAppValue') + ->will($this->returnCallback(function ($app, $key, $default) use (&$keyValues) { + return (isset($keyValues[$key])) ? $keyValues[$key] : $default; + })); + + $this->sharingBlacklist->setBlacklistedReceiverGroups($ids); + $this->assertEquals($ids, $this->sharingBlacklist->getBlacklistedReceiverGroups()); + } + + private function getGroupMock($id, $displayname) { + $groupMock = $this->getMockBuilder(IGroup::class) + ->disableOriginalConstructor() + ->getMock(); + + $groupBackendMock = $this->getMockBuilder(GroupInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $groupMock->method('getBackend')->willReturn($groupBackendMock); + $groupMock->method('getGID')->willReturn($id); + $groupMock->method('getDisplayName')->willReturn($displayname); + return $groupMock; + } + + public function isGroupBlacklistedProvider() { + $groupMock1 = $this->getGroupMock('Mygroup', 'my group'); + return [ + [$groupMock1, '["Mygroup"]'], + [$groupMock1, '["Mygroup", "my_other_group"]'], + [$groupMock1, '["one group", "Mygroup"]'], + ]; + } + + /** + * @dataProvider isGroupBlacklistedProvider + */ + public function testIsGroupBlacklisted($group, $configValue) { + $this->config->method('getAppValue')->willReturn($configValue); + + $this->assertTrue($this->sharingBlacklist->isGroupBlacklisted($group)); + } + + public function isGroupBlacklistedNotBlacklistedProvider() { + $groupMock1 = $this->getGroupMock('Mygroup', 'my group'); + return [ + [$groupMock1, '[]'], + [$groupMock1, '["my group"]'], + [$groupMock1, '["Mygroup2", "my group"]'], + ]; + } + + /** + * @dataProvider isGroupBlacklistedNotBlacklistedProvider + */ + public function testIsGroupBlacklistedNotBlacklisted($group, $configValue) { + $this->config->method('getAppValue')->willReturn($configValue); + + $this->assertFalse($this->sharingBlacklist->isGroupBlacklisted($group)); + } +}