From 503b25e6fff09e72de859253baeb8beb5855d5f2 Mon Sep 17 00:00:00 2001 From: JanAckermann Date: Mon, 12 Jul 2021 14:05:35 +0200 Subject: [PATCH] Server part --- apps/files_sharing/appinfo/app.php | 3 + apps/files_sharing/js/settings.js | 18 ++ apps/files_sharing/js/sharedialogview.js | 102 ++++++++++ .../files_sharing/lib/AppInfo/Application.php | 5 +- apps/files_sharing/lib/Capabilities.php | 36 +++- .../lib/Controller/Share20OcsController.php | 16 +- apps/files_sharing/lib/Helper.php | 2 + apps/files_sharing/lib/Hooks.php | 31 +++ .../lib/Panels/Admin/SettingsPanel.php | 9 +- apps/files_sharing/lib/SharingAllowlist.php | 124 ++++++++++++ apps/files_sharing/templates/settings.php | 20 ++ apps/files_sharing/tests/ApiTest.php | 4 +- apps/files_sharing/tests/CapabilitiesTest.php | 61 +++++- .../Controller/Share20OcsControllerTest.php | 47 ++++- apps/files_sharing/tests/HooksTest.php | 29 +++ .../tests/Panels/Admin/SettingsPanelTest.php | 32 +++- .../tests/SharingAllowlistTest.php | 177 ++++++++++++++++++ .../tests/js/sharedialogviewSpec.js | 100 ++++++++++ changelog/unreleased/38980 | 7 + core/js/config.php | 5 +- core/js/js.js | 4 +- .../allowGroupToCreatePublicLinks.feature | 113 +++++++++++ .../bootstrap/WebUISharingContext.php | 32 ++++ .../lib/FilesPageElement/SharingDialog.php | 31 +++ .../SharingDialogElement/PublicLinkTab.php | 17 ++ .../allowGroupToCreatePublicLinks.feature | 79 ++++++++ tests/karma.config.js | 3 +- 27 files changed, 1090 insertions(+), 17 deletions(-) create mode 100644 apps/files_sharing/js/sharedialogview.js create mode 100644 apps/files_sharing/lib/SharingAllowlist.php create mode 100644 apps/files_sharing/tests/SharingAllowlistTest.php create mode 100644 apps/files_sharing/tests/js/sharedialogviewSpec.js create mode 100644 changelog/unreleased/38980 create mode 100644 tests/acceptance/features/apiSharePublicLink2/allowGroupToCreatePublicLinks.feature create mode 100644 tests/acceptance/features/webUISharingPublic2/allowGroupToCreatePublicLinks.feature diff --git a/apps/files_sharing/appinfo/app.php b/apps/files_sharing/appinfo/app.php index 793a2e1f69ac..aff5d78b112a 100644 --- a/apps/files_sharing/appinfo/app.php +++ b/apps/files_sharing/appinfo/app.php @@ -27,6 +27,8 @@ * */ +use OCP\Util; + \OCA\Files_Sharing\Helper::registerHooks(); \OCP\Share::registerBackend('file', 'OCA\Files_Sharing\ShareBackend\File'); @@ -44,6 +46,7 @@ function () { \OCP\Util::addScript('files_sharing', 'share'); \OCP\Util::addScript('files_sharing', 'sharetabview'); + \OCP\Util::addScript('files_sharing', 'sharedialogview'); if (\OC::$server->getConfig()->getAppValue('files_sharing', 'incoming_server2server_share_enabled', 'yes') === 'yes') { \OCP\Util::addScript('files_sharing', 'external'); } diff --git a/apps/files_sharing/js/settings.js b/apps/files_sharing/js/settings.js index 6b16576366bc..234918d38533 100644 --- a/apps/files_sharing/js/settings.js +++ b/apps/files_sharing/js/settings.js @@ -16,4 +16,22 @@ $(document).ready(function() { groups = JSON.stringify(groups); OC.AppConfig.setValue('files_sharing', $(this).attr('name'), groups); }); + + var $publicShareSharersGroupsAllowlist = $('input[name="public_share_sharers_groups_allowlist"]'); + var $publicShareSharersGroupsAllowlistEnabled = $('#publicShareSharersGroupsAllowlistEnabled'); + OC.Settings.setupGroupsSelect($publicShareSharersGroupsAllowlist); + + $publicShareSharersGroupsAllowlist.change(function(ev) { + var groups = ev.val || []; + groups = JSON.stringify(groups); + OC.AppConfig.setValue('files_sharing', 'public_share_sharers_groups_allowlist', groups); + + }); + $publicShareSharersGroupsAllowlistEnabled.change(function() { + $("#setAllowlistPublicShareSharersGroups").toggleClass('hidden', !this.checked); + OC.AppConfig.setValue('files_sharing', 'public_share_sharers_groups_allowlist_enabled', this.checked ? 'yes' : 'no'); + + }); + // Move setting to sharing section + $publicShareSharersGroupsAllowlist.closest('p').detach().insertAfter($('#selectExcludedGroups').closest('p')); }); \ No newline at end of file diff --git a/apps/files_sharing/js/sharedialogview.js b/apps/files_sharing/js/sharedialogview.js new file mode 100644 index 000000000000..aa6169ea584b --- /dev/null +++ b/apps/files_sharing/js/sharedialogview.js @@ -0,0 +1,102 @@ +/** + * @author Jan Ackermann + * + * @copyright Copyright (c) 2021, ownCloud GmbH + * @license AGPL-3.0 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +(function() { + if (!OCA.Sharing) { + OCA.Sharing = {}; + } + + /** + * @namespace + */ + OCA.Sharing.ShareDialogView = { + attach: function (view) { + var that = this; + var baseRenderCall = view.render; + + var shareCollectionModel = view.model.getLinkSharesCollection(); + view.linkShareView = new OC.Share.ShareDialogLinkListView({ + collection: shareCollectionModel, + itemModel: view.model + }); + + var baseLinkShareViewRender = view.linkShareView.render; + + view.render = function () { + baseRenderCall.call(view); + if (that.isPublicSharingBlockedByAllowlist() && !shareCollectionModel.length) { + view.$el.find('.subtab-publicshare').remove(); + } + }; + + view.linkShareView.render = function () { + baseLinkShareViewRender.call(view.linkShareView); + + if (that.isPublicSharingBlockedByAllowlist()) { + view.linkShareView.$el.find('.addLink').remove(); + view.$el.find('.empty-message').remove(); + } + }; + + var baseShareeListViewRender = view.shareeListView.render; + + var linkShareViewInitialized = false; + view.shareeListView.render = function () { + baseShareeListViewRender.call(view.shareeListView); + + view.$el.find(".subTabHeader.subtab-publicshare").on("click", function () { + if (linkShareViewInitialized) { + return; + } + + view.linkShareView.render(); + view.$('.linkListView').html(view.linkShareView.$el); + linkShareViewInitialized = true; + }); + + }; + }, + + isPublicSharingBlockedByAllowlist: function () { + var allowlistEnabled = oc_appconfig.files_sharing.publicShareSharersGroupsAllowlistEnabled; + var allowlistGroups = oc_appconfig.files_sharing.publicShareSharersGroupsAllowlist; + + + if (allowlistEnabled === true && + allowlistGroups.length + ) { + var userGroups = OC.getCurrentUser().groups; + + for (var i = 0; i < userGroups.length; i++) { + if (allowlistGroups.indexOf(userGroups[i]) >= 0) { + return false; + } + } + + return true; + } + + return false; + } + }; +})(); + +OC.Plugins.register('OC.Share.ShareDialogView', OCA.Sharing.ShareDialogView); diff --git a/apps/files_sharing/lib/AppInfo/Application.php b/apps/files_sharing/lib/AppInfo/Application.php index 82b9c60ee747..6a2bef30541b 100644 --- a/apps/files_sharing/lib/AppInfo/Application.php +++ b/apps/files_sharing/lib/AppInfo/Application.php @@ -38,6 +38,7 @@ use OCA\Files_Sharing\MountProvider; use OCA\Files_Sharing\Notifier; use OCA\Files_Sharing\SharingBlacklist; +use OCA\Files_Sharing\SharingAllowlist; use OCP\AppFramework\App; use OCP\IContainer; use OCA\Files_Sharing\Hooks; @@ -97,7 +98,8 @@ public function __construct(array $urlParams = []) { $server->getConfig(), $c->query(NotificationPublisher::class), $server->getEventDispatcher(), - $c->query(SharingBlacklist::class) + $c->query(SharingBlacklist::class), + $c->query(SharingAllowlist::class) ); }); @@ -182,6 +184,7 @@ function () use ($c) { $c->getServer()->getShareManager(), $c->query(NotificationPublisher::class), $c->getServer()->getActivityManager(), + $c->query(SharingAllowlist::class), $c->getServer()->getUserSession() ); }); diff --git a/apps/files_sharing/lib/Capabilities.php b/apps/files_sharing/lib/Capabilities.php index 6d74ef6c824e..b76dfcb971fe 100644 --- a/apps/files_sharing/lib/Capabilities.php +++ b/apps/files_sharing/lib/Capabilities.php @@ -23,6 +23,7 @@ use OCP\Capabilities\ICapability; use OCP\IConfig; use OCP\IL10N; +use OCP\IUserSession; use OCP\Util\UserSearch; use OCP\Share\IManager; @@ -43,20 +44,44 @@ class Capabilities implements ICapability { */ private $userSearch; - /** @var IL10N */ + /** + * @var IL10N + */ private $l10n; + /** + * @var SharingAllowlist + */ + private $sharingAllowlist; + + /** + * @var IUserSession + */ + private $userSession; + /** * Capabilities constructor. * * @param IConfig $config * @param UserSearch $userSearch + * @param IL10N $l10n + * @param SharingAllowlist $sharingAllowlist + * @param IUserSession $userSession */ - public function __construct(IManager $shareManager, IConfig $config, UserSearch $userSearch, IL10N $l10n) { + public function __construct( + IManager $shareManager, + IConfig $config, + UserSearch $userSearch, + IL10N $l10n, + SharingAllowlist $sharingAllowlist, + IUserSession $userSession + ) { $this->shareManager = $shareManager; $this->config = $config; $this->userSearch = $userSearch; $this->l10n = $l10n; + $this->sharingAllowlist = $sharingAllowlist; + $this->userSession = $userSession; } /** @@ -93,6 +118,7 @@ public function getCapabilities() { $public['password']['enforced'] = $roPasswordEnforced || $rwPasswordEnforced || $woPasswordEnforced || $rwdPasswordEnforced; $public['roles_api'] = true; + $public['can_create_public_link'] = true; $public['expire_date'] = []; $public['expire_date']['enabled'] = $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no') === 'yes'; @@ -107,6 +133,12 @@ public function getCapabilities() { $public['multiple'] = true; $public['supports_upload_only'] = true; $public['defaultPublicLinkShareName'] = $this->l10n->t('Public link'); + + if ($this->sharingAllowlist->isPublicShareSharersGroupsAllowlistEnabled() && + ! $this->sharingAllowlist->isUserInPublicShareSharersGroupsAllowlist($this->userSession->getUser()) + ) { + $public['can_create_public_link'] = false; + } } $res["public"] = $public; diff --git a/apps/files_sharing/lib/Controller/Share20OcsController.php b/apps/files_sharing/lib/Controller/Share20OcsController.php index 17feb301adaa..88ee7888700e 100644 --- a/apps/files_sharing/lib/Controller/Share20OcsController.php +++ b/apps/files_sharing/lib/Controller/Share20OcsController.php @@ -23,6 +23,7 @@ use Exception; use OC\Files\Filesystem; +use OCA\Files_Sharing\SharingAllowlist; use OCP\Constants; use OC\OCS\Result; use OCP\AppFramework\OCSController; @@ -79,9 +80,10 @@ class Share20OcsController extends OCSController { private $notificationPublisher; /** @var EventDispatcher */ private $eventDispatcher; - /** @var SharingBlacklist */ private $sharingBlacklist; + /** @var SharingAllowlist */ + private $sharingAllowlist; /** * @var string */ @@ -103,7 +105,8 @@ public function __construct( IConfig $config, NotificationPublisher $notificationPublisher, EventDispatcher $eventDispatcher, - SharingBlacklist $sharingBlacklist + SharingBlacklist $sharingBlacklist, + SharingAllowlist $sharingAllowlist ) { parent::__construct($appName, $request); $this->request = $request; @@ -117,6 +120,7 @@ public function __construct( $this->notificationPublisher = $notificationPublisher; $this->eventDispatcher = $eventDispatcher; $this->sharingBlacklist = $sharingBlacklist; + $this->sharingAllowlist = $sharingAllowlist; $this->additionalInfoField = $this->config->getAppValue('core', 'user_additional_info_field', ''); $this->userSession = $userSession; } @@ -418,6 +422,7 @@ public function createShare() { $shareWith = $this->request->getParam('shareWith', null); $globalAutoAccept = $this->config->getAppValue('core', 'shareapi_auto_accept_share', 'yes') === 'yes'; + if ($shareType === Share::SHARE_TYPE_USER) { $userAutoAccept = false; if ($globalAutoAccept) { @@ -457,12 +462,17 @@ public function createShare() { $share->setState(Share::STATE_PENDING); } } elseif ($shareType === Share::SHARE_TYPE_LINK) { - //Can we even share links? if (!$this->shareManager->shareApiAllowLinks()) { $share->getNode()->unlock(ILockingProvider::LOCK_SHARED); return new Result(null, 404, $this->l->t('Public link sharing is disabled by the administrator')); } + if ($this->sharingAllowlist->isPublicShareSharersGroupsAllowlistEnabled() && + !$this->sharingAllowlist->isUserInPublicShareSharersGroupsAllowlist($this->userSession->getUser()) + ) { + return new Result(null, 403, $this->l->t('Public link creation is only possible for certain groups')); + } + // legacy way, expecting that this won't be used together with "create-only" shares $publicUpload = $this->request->getParam('publicUpload', null); // a few permission checks diff --git a/apps/files_sharing/lib/Helper.php b/apps/files_sharing/lib/Helper.php index 387f7d2efc9a..34c3f5bb469f 100644 --- a/apps/files_sharing/lib/Helper.php +++ b/apps/files_sharing/lib/Helper.php @@ -41,6 +41,8 @@ public static function registerHooks() { \OCP\Util::connectHook('OC_Filesystem', 'post_delete', '\OCA\Files_Sharing\Hooks', 'unshareChildren'); \OCP\Util::connectHook('OC_User', 'post_deleteUser', '\OCA\Files_Sharing\Hooks', 'deleteUser'); + + \OCP\Util::connectHook('\OCP\Config', 'js', '\OCA\Files_Sharing\Hooks', 'extendJsConfig'); } /** diff --git a/apps/files_sharing/lib/Hooks.php b/apps/files_sharing/lib/Hooks.php index f2124a23b37d..02e2b9d11d29 100644 --- a/apps/files_sharing/lib/Hooks.php +++ b/apps/files_sharing/lib/Hooks.php @@ -66,6 +66,11 @@ class Hooks { */ private $notificationPublisher; + /** + * @var SharingAllowlist + */ + private $sharingAllowlist; + /** * @var ActivityManager */ @@ -80,6 +85,7 @@ class Hooks { * @param \OCP\Share\IManager $shareManager * @param NotificationPublisher $notificationPublisher * @param ActivityManager $activityManager + * @param SharingAllowlist $sharingAllowlist * @param IUserSession|null $userSession */ public function __construct( @@ -89,6 +95,7 @@ public function __construct( \OCP\Share\IManager $shareManager, NotificationPublisher $notificationPublisher, ActivityManager $activityManager, + SharingAllowlist $sharingAllowlist, $userSession ) { $this->userSession = $userSession; @@ -98,6 +105,7 @@ public function __construct( $this->shareManager = $shareManager; $this->notificationPublisher = $notificationPublisher; $this->activityManager = $activityManager; + $this->sharingAllowlist= $sharingAllowlist; } public static function deleteUser($params) { @@ -223,6 +231,15 @@ function (GenericEvent $event) { $this->activityManager->publish($activityEvent); } ); + + $this->eventDispatcher->addListener('group.postDelete', function ($event) { + $groupId = $event->getSubject()->getGID(); + $groupsAllowlist = $this->sharingAllowlist->getPublicShareSharersGroupsAllowlist(); + + if (\in_array($groupId, $groupsAllowlist)) { + $this->sharingAllowlist->setPublicShareSharersGroupsAllowlist(array_diff($groupsAllowlist, [$groupId])); + } + }); } private function getCurrentUserUid() { @@ -264,4 +281,18 @@ private function resolvePrivateLink($uid, $fileId) { return null; } + + public static function extendJsConfig($array) { + $sharingAllowlist = new SharingAllowlist( + \OC::$server->getConfig(), + \OC::$server->getGroupManager() + ); + + $array['array']['oc_appconfig']['files_sharing'] = [ + 'publicShareSharersGroupsAllowlist' => $sharingAllowlist->getPublicShareSharersGroupsAllowlist(), + 'publicShareSharersGroupsAllowlistEnabled' => $sharingAllowlist->isPublicShareSharersGroupsAllowlistEnabled(), + ]; + + return $array; + } } diff --git a/apps/files_sharing/lib/Panels/Admin/SettingsPanel.php b/apps/files_sharing/lib/Panels/Admin/SettingsPanel.php index 682c43887840..94e1d3702808 100644 --- a/apps/files_sharing/lib/Panels/Admin/SettingsPanel.php +++ b/apps/files_sharing/lib/Panels/Admin/SettingsPanel.php @@ -22,18 +22,25 @@ use OCP\Settings\ISettings; use OCP\Template; use OCA\Files_Sharing\SharingBlacklist; +use OCA\Files_Sharing\SharingAllowlist; class SettingsPanel implements ISettings { /** @var SharingBlacklist */ private $sharingBlacklist; - public function __construct(SharingBlacklist $sharingBlacklist) { + /** @var SharingAllowlist */ + private $sharingAllowlist; + + public function __construct(SharingBlacklist $sharingBlacklist, SharingAllowlist $sharingAllowlist) { $this->sharingBlacklist = $sharingBlacklist; + $this->sharingAllowlist= $sharingAllowlist; } public function getPanel() { $tmpl = new Template('files_sharing', 'settings'); $tmpl->assign('blacklistedReceivers', \implode('|', $this->sharingBlacklist->getBlacklistedReceiverGroups())); + $tmpl->assign('publicShareSharersGroupsAllowlist', \implode('|', $this->sharingAllowlist->getPublicShareSharersGroupsAllowlist())); + $tmpl->assign('publicShareSharersGroupsAllowlistEnabled', $this->sharingAllowlist->isPublicShareSharersGroupsAllowlistEnabled() ? 'yes' : 'no'); return $tmpl; } diff --git a/apps/files_sharing/lib/SharingAllowlist.php b/apps/files_sharing/lib/SharingAllowlist.php new file mode 100644 index 000000000000..9bcc96726e24 --- /dev/null +++ b/apps/files_sharing/lib/SharingAllowlist.php @@ -0,0 +1,124 @@ + + * + * @copyright Copyright (c) 2021, 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; +use OCP\IUser; +use OCP\IGroupManager; +use phpDocumentor\Reflection\Types\Boolean; + +/** + * Class to handle an allowlist for sharing. The main functionality is to check if a particular group + * is in the allowlist for public sharing, which means that only users in a group in the allowlist should be able to create public shares. + * + * 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 SharingAllowlist { + /** @var IConfig */ + private $config; + + /** @var IGroupManager */ + private $groupManager; + + public function __construct(IConfig $config, IGroupManager $groupManager) { + $this->config = $config; + $this->groupManager = $groupManager; + } + + /** + * Check if the allowlist is enabled + * @return bool true if allowlist is enabled, false otherwise + */ + public function isPublicShareSharersGroupsAllowlistEnabled() { + $configRecord = $this->config->getAppValue('files_sharing', 'public_share_sharers_groups_allowlist_enabled', 'no'); + return $configRecord === 'yes'; + } + + /** + * Check if the target group is in allowlist + * @param IGroup $group the group to check + * @return bool true if the group is in allowlist, false otherwise + */ + public function isGroupInPublicShareSharersAllowlist(IGroup $group) { + return \in_array($group->getGID(), $this->fetchPublicShareSharersGroupsAllowlist()); + } + + /** + * Set the list of groups to be in allowlist by id. + * @param string[] $ids a list with the ids of the groups in allowlist + */ + public function setPublicShareSharersGroupsAllowlist(array $ids) { + $this->config->setAppValue('files_sharing', 'public_share_sharers_groups_allowlist', \json_encode($ids)); + } + + /** + * Set allowlist enabled. + * @param bool $enabled the value if enabled or not + */ + public function setPublicShareSharersGroupsAllowlistEnabled(bool $enabled) { + $this->config->setAppValue('files_sharing', 'public_share_sharers_groups_allowlist_enabled', $enabled ? 'yes' : 'no'); + } + + /** + * Get the list of group ids in allowlist + * Note that this might contain group ids that have since been deleted + * @return string[] the list of group ids + */ + public function getPublicShareSharersGroupsAllowlist() { + return $this->fetchPublicShareSharersGroupsAllowlist(); + } + + /** + * Check if a given user is assigned to any group in allowlist + * @param IUser $user the user to check + * @return bool true if the user is assigned to any group in allowlist, false otherwise + */ + public function isUserInPublicShareSharersGroupsAllowlist(IUser $user) { + // Evaluate to true, if the admin enables the settings but don't set any groups. + if (empty($this->fetchPublicShareSharersGroupsAllowlist())) { + return true; + } + + $userGroups = $this->groupManager->getUserGroups($user); + + foreach ($userGroups as $userGroup) { + if ($this->isGroupInPublicShareSharersAllowlist($userGroup)) { + return true; + } + } + + return false; + } + + /** + * @return bool[] an array with group ids + * If the allowlist groups cannot be parsed as valid JSON, + * then an empty list is returned. + */ + private function fetchPublicShareSharersGroupsAllowlist() { + $configuredAllowlist = $this->config->getAppValue('files_sharing', 'public_share_sharers_groups_allowlist', '[]'); + $parsedValues = json_decode($configuredAllowlist, true); + return $parsedValues === null ? [] : $parsedValues; + } +} diff --git a/apps/files_sharing/templates/settings.php b/apps/files_sharing/templates/settings.php index ba56f8886cb1..948cb46a2bf6 100644 --- a/apps/files_sharing/templates/settings.php +++ b/apps/files_sharing/templates/settings.php @@ -29,4 +29,24 @@
t('These groups will not be available to share with. Members of the group are not restricted in initiating shares and can receive shares with other groups they are a member of as usual.')); ?> + +

+ /> +
+ + + +

\ No newline at end of file diff --git a/apps/files_sharing/tests/ApiTest.php b/apps/files_sharing/tests/ApiTest.php index fb5af5b3703d..9d9fde8a0db8 100644 --- a/apps/files_sharing/tests/ApiTest.php +++ b/apps/files_sharing/tests/ApiTest.php @@ -35,6 +35,7 @@ use OCP\Share; use OCA\Files_Sharing\Service\NotificationPublisher; use OCA\Files_Sharing\SharingBlacklist; +use OCA\Files_Sharing\SharingAllowlist; /** * Class ApiTest @@ -131,7 +132,8 @@ private function createOCS($request, $userId) { \OC::$server->getConfig(), \OC::$server->getAppContainer('files_sharing')->query(NotificationPublisher::class), \OC::$server->getEventDispatcher(), - \OC::$server->getAppContainer('files_sharing')->query(SharingBlacklist::class) + \OC::$server->getAppContainer('files_sharing')->query(SharingBlacklist::class), + \OC::$server->getAppContainer('files_sharing')->query(SharingAllowlist::class) ); } diff --git a/apps/files_sharing/tests/CapabilitiesTest.php b/apps/files_sharing/tests/CapabilitiesTest.php index ce493f29fe9e..7084556d42ea 100644 --- a/apps/files_sharing/tests/CapabilitiesTest.php +++ b/apps/files_sharing/tests/CapabilitiesTest.php @@ -23,7 +23,10 @@ namespace OCA\Files_Sharing\Tests; use OCA\Files_Sharing\Capabilities; +use OCA\Files_Sharing\SharingAllowlist; use OCP\IL10N; +use OCP\IUser; +use OCP\IUserSession; use OCP\Share\IManager; /** @@ -42,6 +45,10 @@ class CapabilitiesTest extends \Test\TestCase { private $l10n; /** @var IManager | \PHPUnit\Framework\MockObject\MockObject */ private $shareManager; + /** @var IUserSession | \PHPUnit\Framework\MockObject\MockObject */ + private $userSession; + /** @var SharingAllowlist | \PHPUnit\Framework\MockObject\MockObject */ + private $sharingAllowlist; /** * @@ -61,6 +68,10 @@ protected function setUp(): void { ->willReturn('Public link'); $this->shareManager = $this->createMock(IManager::class); + + $this->userSession = $this->createMock(IUserSession::class); + + $this->sharingAllowlist = $this->createMock(SharingAllowlist::class); } /** @@ -86,7 +97,7 @@ private function getFilesSharingPart(array $data) { private function getResults(array $map) { $stub = $this->getMockBuilder('\OCP\IConfig')->disableOriginalConstructor()->getMock(); $stub->method('getAppValue')->will($this->returnValueMap($map)); - $cap = new Capabilities($this->shareManager, $stub, $this->userSearch, $this->l10n); + $cap = new Capabilities($this->shareManager, $stub, $this->userSearch, $this->l10n, $this->sharingAllowlist, $this->userSession); $result = $this->getFilesSharingPart($cap->getCapabilities()); return $result; } @@ -126,6 +137,54 @@ public function testNoLinkSharing() { $this->assertFalse($result['public']['enabled']); } + public function testCanCreatePublicLink() { + $this->userSession + ->expects($this->once()) + ->method('getUser') + ->willReturn($this->createMock(IUser::class)); + + $this->sharingAllowlist + ->expects($this->once()) + ->method('isPublicShareSharersGroupsAllowlistEnabled')->willReturn(true); + + $this->sharingAllowlist + ->expects($this->once()) + ->method('isUserInPublicShareSharersGroupsAllowlist') + ->willReturn(true); + + $map = [ + ['core', 'shareapi_enabled', 'yes', 'yes'], + ['core', 'shareapi_allow_links', 'yes', 'yes'], + ]; + + $result = $this->getResults($map); + $this->assertTrue($result['public']['can_create_public_link']); + } + + public function testCannotCreatePublicLink() { + $this->userSession + ->expects($this->once()) + ->method('getUser') + ->willReturn($this->createMock(IUser::class)); + + $this->sharingAllowlist + ->expects($this->once()) + ->method('isPublicShareSharersGroupsAllowlistEnabled')->willReturn(true); + + $this->sharingAllowlist + ->expects($this->once()) + ->method('isUserInPublicShareSharersGroupsAllowlist') + ->willReturn(false); + + $map = [ + ['core', 'shareapi_enabled', 'yes', 'yes'], + ['core', 'shareapi_allow_links', 'yes', 'yes'], + ]; + + $result = $this->getResults($map); + $this->assertFalse($result['public']['can_create_public_link']); + } + public function providesRolesCapability() { return [ [['core', 'shareapi_enabled', 'yes', 'no'],['core', 'shareapi_allow_links', 'yes', 'no']], diff --git a/apps/files_sharing/tests/Controller/Share20OcsControllerTest.php b/apps/files_sharing/tests/Controller/Share20OcsControllerTest.php index 763d2ba3744e..00b69db93468 100644 --- a/apps/files_sharing/tests/Controller/Share20OcsControllerTest.php +++ b/apps/files_sharing/tests/Controller/Share20OcsControllerTest.php @@ -29,6 +29,7 @@ use OCA\Files_Sharing\Controller\Share20OcsController; use OCA\Files_Sharing\Service\NotificationPublisher; use OCA\Files_Sharing\SharingBlacklist; +use OCA\Files_Sharing\SharingAllowlist; use OCP\Files\IRootFolder; use OCP\Files\NotFoundException; use OCP\IConfig; @@ -98,6 +99,8 @@ class Share20OcsControllerTest extends TestCase { private $eventDispatcher; /** @var SharingBlacklist */ private $sharingBlacklist; + /** @var SharingAllowlist */ + private $sharingAllowlist; /** @var IConfig */ private $config; @@ -141,6 +144,7 @@ protected function setUp(): void { $this->notificationPublisher = $this->createMock(NotificationPublisher::class); $this->eventDispatcher = $this->createMock(EventDispatcher::class); $this->sharingBlacklist = $this->createMock(SharingBlacklist::class); + $this->sharingAllowlist= $this->createMock(SharingAllowlist::class); $this->ocs = new Share20OcsController( 'files_sharing', @@ -155,7 +159,8 @@ protected function setUp(): void { $this->config, $this->notificationPublisher, $this->eventDispatcher, - $this->sharingBlacklist + $this->sharingBlacklist, + $this->sharingAllowlist ); } @@ -178,6 +183,7 @@ private function mockFormatShare() { $this->notificationPublisher, $this->eventDispatcher, $this->sharingBlacklist, + $this->sharingAllowlist ])->setMethods(['formatShare']) ->getMock(); } @@ -560,6 +566,7 @@ public function testGetShare(\OCP\Share\IShare $share, array $result) { $this->notificationPublisher, $this->eventDispatcher, $this->sharingBlacklist, + $this->sharingAllowlist, ])->setMethods(['canAccessShare']) ->getMock(); @@ -1154,6 +1161,38 @@ public function testCreateShareLinkNoPublicUpload() { $this->assertEquals($expected->getData(), $result->getData()); } + public function testCreateShareLinkNotInAllowlist() { + $this->request + ->method('getParam') + ->will($this->returnValueMap([ + ['path', null, 'valid-path'], + ['shareType', '-1', Share::SHARE_TYPE_LINK], + ['publicUpload', null, 'true'], + ])); + + $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); + $this->rootFolder->method('getUserFolder')->with($this->currentUser->getUID())->will($this->returnSelf()); + $this->rootFolder->method('get')->with('valid-path')->willReturn($path); + + $this->shareManager->method('newShare')->willReturn(\OC::$server->getShareManager()->newShare()); + $this->shareManager->method('shareApiAllowLinks')->willReturn(true); + $this->shareManager->method('shareApiLinkAllowPublicUpload')->willReturn(true); + + $this->sharingAllowlist->method('isPublicShareSharersGroupsAllowlistEnabled')->willReturn(true); + $this->sharingAllowlist->method('isUserInPublicShareSharersGroupsAllowlist')->willReturn(false); + + $expected = new Result(null, 403, 'Public link creation is only possible for certain groups'); + $result = $this->ocs->createShare(); + + $this->assertEquals($expected->getMeta(), $result->getMeta()); + $this->assertEquals($expected->getData(), $result->getData()); + } + public function testCreateShareLinkPublicUploadFile() { $this->request ->method('getParam') @@ -2726,7 +2765,8 @@ public function getOcsDisabledAPI() { $this->config, $this->notificationPublisher, $this->eventDispatcher, - $this->sharingBlacklist + $this->sharingBlacklist, + $this->sharingAllowlist ); } @@ -2826,7 +2866,8 @@ public function testGetShareAdditionalInfo($configValue, $expectedInfo, $expecte $config, $this->notificationPublisher, $this->eventDispatcher, - $this->sharingBlacklist + $this->sharingBlacklist, + $this->sharingAllowlist ); list($file, ) = $this->getMockFileFolder(); diff --git a/apps/files_sharing/tests/HooksTest.php b/apps/files_sharing/tests/HooksTest.php index 61599e1c69b9..9a1f20390b80 100644 --- a/apps/files_sharing/tests/HooksTest.php +++ b/apps/files_sharing/tests/HooksTest.php @@ -23,11 +23,13 @@ use OCA\Files_Sharing\Hooks; use OCA\Files_Sharing\SharedStorage; +use OCA\Files_Sharing\SharingAllowlist; use OCP\Activity\IEvent; use OCP\Activity\IManager as ActivityManager; use OCP\Files\File; use OCP\Files\Folder; use OCP\Files\Storage\IStorage; +use OCP\IGroup; use OCP\IUser; use OCP\IUserSession; use OCP\Share\IAttributes; @@ -79,6 +81,11 @@ class HooksTest extends \Test\TestCase { */ private $activityManager; + /** + * @var SharingAllowlist | \PHPUnit\Framework\MockObject\MockObject + */ + private $sharingAllowlist; + /** * @var Hooks */ @@ -92,6 +99,7 @@ public function setUp(): void { $this->notificationPublisher = $this->createMock(NotificationPublisher::class); $this->activityManager = $this->createMock(ActivityManager::class); $this->userSession = $this->createMock(IUserSession::class); + $this->sharingAllowlist= $this->createMock(SharingAllowlist::class); $this->hooks = new Hooks( $this->rootFolder, @@ -100,6 +108,7 @@ public function setUp(): void { $this->shareManager, $this->notificationPublisher, $this->activityManager, + $this->sharingAllowlist, $this->userSession ); $this->hooks->registerListeners(); @@ -166,6 +175,17 @@ public function testPrivateLinkNoMatch() { $this->assertNull($event->getArgument('resolvedDavLink')); } + public function testDeleteGroup() { + $group = $this->createMock(IGroup::class); + + $group->expects($this->once())->method('getGID')->willReturn('admin'); + $this->sharingAllowlist->expects($this->once())->method('getPublicShareSharersGroupsAllowlist')->willReturn(['admin']); + $this->sharingAllowlist->expects($this->once())->method('setPublicShareSharersGroupsAllowlist')->with([]); + + $event = new GenericEvent($group); + $this->eventDispatcher->dispatch('group.postDelete', $event); + } + public function testPublishShareNotification() { $share = $this->createMock(IShare::class); @@ -377,4 +397,13 @@ public function testPublishShareSelfUnshareEvent() { ]); $this->eventDispatcher->dispatch('fromself.unshare', $event); } + + public function testExtendJsConfig() { + $expected['array']['oc_appconfig']['files_sharing'] = [ + 'publicShareSharersGroupsAllowlist' => [], + 'publicShareSharersGroupsAllowlistEnabled' => false, + ]; + + $this->assertEquals($expected, $this->hooks->extendJsConfig([])); + } } diff --git a/apps/files_sharing/tests/Panels/Admin/SettingsPanelTest.php b/apps/files_sharing/tests/Panels/Admin/SettingsPanelTest.php index 81c93ebd21cb..f5b9c9b3a1f4 100644 --- a/apps/files_sharing/tests/Panels/Admin/SettingsPanelTest.php +++ b/apps/files_sharing/tests/Panels/Admin/SettingsPanelTest.php @@ -20,14 +20,17 @@ */ namespace OCA\Files_Sharing\Tests\Panels\Admin; -use OCP\GroupInterface; use OCA\Files_Sharing\SharingBlacklist; +use OCA\Files_Sharing\SharingAllowlist; use OCA\Files_Sharing\Panels\Admin\SettingsPanel; class SettingsPanelTest extends \Test\TestCase { /** @var SharingBlacklist | \PHPUnit\Framework\MockObject\MockObject */ private $sharingBlacklist; + /** @var SharingAllowlist | \PHPUnit\Framework\MockObject\MockObject */ + private $sharingAllowlist; + /** @var SettingsPanel | \PHPUnit\Framework\MockObject\MockObject */ private $settingsPanel; @@ -36,7 +39,11 @@ protected function setUp(): void { ->disableOriginalConstructor() ->getMock(); - $this->settingsPanel = new SettingsPanel($this->sharingBlacklist); + $this->sharingAllowlist= $this->getMockBuilder(SharingAllowlist::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->settingsPanel = new SettingsPanel($this->sharingBlacklist, $this->sharingAllowlist); } public function testGetSectionID() { @@ -50,6 +57,16 @@ public function testGetPriority() { public function testGetPanel() { $this->sharingBlacklist->method('getBlacklistedReceiverGroups')->willReturn([]); + $this->sharingAllowlist + ->expects($this->any()) + ->method('getPublicShareSharersGroupsAllowlist') + ->willReturn([]); + + $this->sharingAllowlist + ->expects($this->any()) + ->method('isPublicShareSharersGroupsAllowlistEnabled') + ->willReturn(false); + $page = $this->settingsPanel->getPanel()->fetchPage(); $doc = new \DOMDocument(); $doc->loadHTML($page); @@ -76,6 +93,16 @@ public function getPanelWithBlacklistProvider() { public function testGetPanelWithBlacklist($ids) { $this->sharingBlacklist->method('getBlacklistedReceiverGroups')->willReturn($ids); + $this->sharingAllowlist + ->expects($this->any()) + ->method('getPublicShareSharersGroupsAllowlist') + ->willReturn([]); + + $this->sharingAllowlist + ->expects($this->any()) + ->method('isPublicShareSharersGroupsAllowlistEnabled') + ->willReturn(false); + $page = $this->settingsPanel->getPanel()->fetchPage(); $doc = new \DOMDocument(); $doc->loadHTML($page); @@ -84,6 +111,7 @@ public function testGetPanelWithBlacklist($ids) { $inputNodes = $xpath->query('//input[@name="blacklisted_receiver_groups"]'); $this->assertEquals(1, $inputNodes->length); // only 1 element should be found $inputNode = $inputNodes->item(0); + $b = $inputNode->attributes->getNamedItem('value')->value; $this->assertSame(\implode("|", $ids), $inputNode->attributes->getNamedItem('value')->value); } } diff --git a/apps/files_sharing/tests/SharingAllowlistTest.php b/apps/files_sharing/tests/SharingAllowlistTest.php new file mode 100644 index 000000000000..15632e18fa13 --- /dev/null +++ b/apps/files_sharing/tests/SharingAllowlistTest.php @@ -0,0 +1,177 @@ + + * + * @copyright Copyright (c) 2021, 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 OCA\Files_Sharing\SharingBlacklist; +use OCA\Files_Sharing\SharingAllowlist; +use OCP\IGroupManager; +use OCP\IUser; + +class SharingAllowlistTest extends \Test\TestCase { + /** @var IConfig | \PHPUnit\Framework\MockObject\MockObject */ + private $config; + + /** @var IGroupManager | \PHPUnit\Framework\MockObject\MockObject */ + private $groupManager; + + /** @var SharingAllowlist | \PHPUnit\Framework\MockObject\MockObject */ + private $sharingAllowlist; + + public function setUp(): void { + $this->config = $this->getMockBuilder(IConfig::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->groupManager = $this->getMockBuilder(IGroupManager::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->sharingAllowlist= new SharingAllowlist($this->config, $this->groupManager); + } + + /** + * @dataProvider isPublicShareSharersGroupsAllowlistEnabledDataProvider + */ + public function testIsPublicShareSharersGroupsAllowlistEnabled($config, $result) { + $this->config->method('getAppValue') + ->will($this->returnValueMap($config)); + + $this->assertEquals($this->sharingAllowlist->isPublicShareSharersGroupsAllowlistEnabled(), $result); + } + + public function isPublicShareSharersGroupsAllowlistEnabledDataProvider() { + return [ + [[['files_sharing', 'public_share_sharers_groups_allowlist_enabled', 'no', 'yes']], true ], + [[['files_sharing', 'public_share_sharers_groups_allowlist_enabled', 'no', 'no']], false ], + ]; + } + + /** + * @dataProvider isGroupInPublicShareSharersAllowlistDataProvider + */ + public function testIsGroupInPublicShareSharersAllowlist($config, $groupId, $result) { + $this->config->method('getAppValue') + ->will($this->returnValueMap($config)); + + $group = $this->createMock(IGroup::class); + $group->method('getGID') + ->willReturn($groupId); + + $this->assertEquals($this->sharingAllowlist->isGroupInPublicShareSharersAllowlist($group), $result); + } + + public function isGroupInPublicShareSharersAllowlistDataProvider() { + return [ + [[['files_sharing', 'public_share_sharers_groups_allowlist', '[]', '[]']], 'admin', false ], + [[['files_sharing', 'public_share_sharers_groups_allowlist', '[]', '["group1","group2"]']], 'admin', false ], + [[['files_sharing', 'public_share_sharers_groups_allowlist', '[]', '["group1","group2","admin"]']], 'admin', true ], + [[['files_sharing', 'public_share_sharers_groups_allowlist', '[]', '["admin"]']], 'admin', true ], + ]; + } + + /** + * @dataProvider isUserInPublicShareSharersGroupsAllowlistDataProvider + */ + public function testIsUserInPublicShareSharersGroupsAllowlist($config, $userGroupIds, $result) { + $this->config->method('getAppValue') + ->will($this->returnValueMap($config)); + + $user = $this->createMock(IUser::class); + $groups = []; + + foreach ($userGroupIds as $userGroupId) { + $group = $this->createMock(IGroup::class); + $group->method('getGID') + ->willReturn($userGroupId); + $groups[] = $group; + } + + $this->groupManager->expects($this->any())->method('getUserGroups') + ->willReturn($groups); + + $this->assertEquals($this->sharingAllowlist->isUserInPublicShareSharersGroupsAllowlist($user), $result); + } + + public function isUserInPublicShareSharersGroupsAllowlistDataProvider() { + return [ + [[['files_sharing', 'public_share_sharers_groups_allowlist', '[]', '[]']], [], true ], + [[['files_sharing', 'public_share_sharers_groups_allowlist', '[]', '[]']], ['admin'], true ], + [[['files_sharing', 'public_share_sharers_groups_allowlist', '[]', '["group1","group2"]']], [], false ], + [[['files_sharing', 'public_share_sharers_groups_allowlist', '[]', '["group1","group2"]']], ['admin'], false ], + [[['files_sharing', 'public_share_sharers_groups_allowlist', '[]', '["group1","group2", "admin"]']], ['admin'], true ], + [[['files_sharing', 'public_share_sharers_groups_allowlist', '[]', '["group1","group2", "admin"]']], ['group1','group2', 'admin'], true ], + [[['files_sharing', 'public_share_sharers_groups_allowlist', '[]', '["group1","group2", "admin"]']], ['group1','group2', 'group3', 'admin'], true ], + ]; + } + + /** + * @dataProvider getPublicShareSharersGroupsAllowlistDataProvider + */ + public function testGetPublicShareSharersGroupsAllowlist($config, $result) { + $this->config->method('getAppValue') + ->will($this->returnValueMap($config)); + + $this->assertEquals($this->sharingAllowlist->getPublicShareSharersGroupsAllowlist(), $result); + } + + public function getPublicShareSharersGroupsAllowlistDataProvider() { + return [ + [[['files_sharing', 'public_share_sharers_groups_allowlist', '[]', '']], []], + [[['files_sharing', 'public_share_sharers_groups_allowlist', '[]', '["invalid JSON missing right square bracket"']], []], + [[['files_sharing', 'public_share_sharers_groups_allowlist', '[]', '[]']], [] ], + [[['files_sharing', 'public_share_sharers_groups_allowlist', '[]', '["group1", "group2"]']], ['group1', 'group2']], + ]; + } + + /** + * @dataProvider setPublicShareSharersGroupsAllowlistEnabledDataProvider + */ + public function testSetPublicShareSharersGroupsAllowlistEnabled($config, $result) { + $this->config->method('setAppValue') + ->will($this->returnValueMap($config)); + + $this->config->method('getAppValue') + ->will($this->returnValueMap($config)); + + $this->sharingAllowlist->setPublicShareSharersGroupsAllowlistEnabled($result); + $this->assertEquals($this->sharingAllowlist->isPublicShareSharersGroupsAllowlistEnabled(), $result); + } + + public function setPublicShareSharersGroupsAllowlistEnabledDataProvider() { + return [ + [[['files_sharing', 'public_share_sharers_groups_allowlist_enabled', 'no', 'yes']], true ], + [[['files_sharing', 'public_share_sharers_groups_allowlist_enabled', 'no', 'no']], false ], + ]; + } + + public function testSetPublicShareSharersGroupsAllowlist() { + $this->config->method('setAppValue') + ->will($this->returnValueMap([['files_sharing', 'public_share_sharers_groups_allowlist', '["group1", "group2"]']])); + + $this->config->method('getAppValue') + ->will($this->returnValueMap([['files_sharing', 'public_share_sharers_groups_allowlist', '[]', '["group1", "group2"]']])); + + $this->sharingAllowlist->setPublicShareSharersGroupsAllowlist(["group1", "group2"]); + $this->assertEquals(["group1", "group2"], $this->sharingAllowlist->getPublicShareSharersGroupsAllowlist()); + } +} diff --git a/apps/files_sharing/tests/js/sharedialogviewSpec.js b/apps/files_sharing/tests/js/sharedialogviewSpec.js new file mode 100644 index 000000000000..10b13499326b --- /dev/null +++ b/apps/files_sharing/tests/js/sharedialogviewSpec.js @@ -0,0 +1,100 @@ +/** +* ownCloud +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library 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 along with this library. If not, see . +* +*/ + +/* global oc_appconfig */ +describe('OCA.Sharing.ShareDialogView tests', function() { + var $container; + var oldAppConfig; + var oldCurrentUser; + + var configModel; + var shareModel; + var fileInfoModel; + var dialog; + + beforeEach(function() { + // horrible parameters + $('#testArea').append(''); + $('#testArea').append(''); + $container = $('#shareContainer'); + /* jshint camelcase:false */ + oldAppConfig = _.extend({}, oc_appconfig.core); + oc_appconfig.files_sharing = oc_appconfig.files_sharing || {}; + + fileInfoModel = new OCA.Files.FileInfoModel({ + id: 123, + name: 'shared_file_name.txt', + path: '/subdir', + size: 100, + mimetype: 'text/plain', + permissions: 31, + sharePermissions: 31 + }); + + var attributes = { + itemType: fileInfoModel.isDirectory() ? 'folder' : 'file', + itemSource: fileInfoModel.get('id'), + possiblePermissions: 31, + permissions: 31 + }; + configModel = new OC.Share.ShareConfigModel(); + shareModel = new OC.Share.ShareItemModel(attributes, { + configModel: configModel, + fileInfoModel: fileInfoModel + }); + dialog = new OC.Share.ShareDialogView({ + configModel: configModel, + model: shareModel + }); + + OCA.Sharing.ShareDialogView.attach(dialog); + + $('#testArea').append(dialog.$el); + + // triggers rendering + shareModel.set({ + shares: [] + }); + + oldCurrentUser = OC.currentUser; + OC.currentUser = 'user0'; + OC.currentUser.groups = ['admin']; + }); + afterEach(function() { + OC.currentUser = oldCurrentUser; + /* jshint camelcase:false */ + oc_appconfig.core = oldAppConfig; + dialog.remove(); + }); + + describe('tabs', function() { + it('renders tabs', function() { + oc_appconfig.files_sharing.publicShareSharersGroupsAllowlistEnabled = false; + dialog.render(); + expect(dialog.$('.subTabHeaders>.subTabHeader').length).toEqual(2); + expect(dialog.$('.tabsContainer>.tab').length).toEqual(2); + }); + it('does not render public sharing tab when not allowed', function() { + oc_appconfig.files_sharing.publicShareSharersGroupsAllowlistEnabled = true; + oc_appconfig.files_sharing.publicShareSharersGroupsAllowlist = ['allowedGroup']; + dialog.render(); + expect(dialog.$('.subTabHeaders>.subtab-publicshare').length).toEqual(0); + }); + }); + +}); diff --git a/changelog/unreleased/38980 b/changelog/unreleased/38980 new file mode 100644 index 000000000000..1d870eb618ca --- /dev/null +++ b/changelog/unreleased/38980 @@ -0,0 +1,7 @@ +Enhancement: Add setting to limit public link share creation to certain groups + +With this PR a new setting in settings->admin-sharing has been introduced which +allows the admin to limit public link share creation to certain groups. + +https://github.com/owncloud/enterprise/issues/3632 +https://github.com/owncloud/core/pull/38980 diff --git a/core/js/config.php b/core/js/config.php index 30b7a9676b82..320783f14db1 100644 --- a/core/js/config.php +++ b/core/js/config.php @@ -253,10 +253,13 @@ $user = \OC::$server->getUserSession()->getUser(); if ($user !== null) { + $groups = \OC::$server->getGroupManager()->getUserGroupIds($user); + $array['oc_user'] = \json_encode([ 'uid' => $user->getUID(), 'displayName' => $user->getDisplayName(), - 'email' => $user->getEMailAddress() + 'email' => $user->getEMailAddress(), + 'groups' => $groups, ]); } } diff --git a/core/js/js.js b/core/js/js.js index 7f255b3e56bf..bd223bf9b819 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -327,7 +327,8 @@ var OC = { return { uid: null, displayName: null, - email: null + email: null, + groups: [], }; }, @@ -856,6 +857,7 @@ var OC = { * * @property {String} uid user id * @property {String} displayName display name + * @property {Array} groups users group ids */ /** diff --git a/tests/acceptance/features/apiSharePublicLink2/allowGroupToCreatePublicLinks.feature b/tests/acceptance/features/apiSharePublicLink2/allowGroupToCreatePublicLinks.feature new file mode 100644 index 000000000000..8e7c8fb4d7a7 --- /dev/null +++ b/tests/acceptance/features/apiSharePublicLink2/allowGroupToCreatePublicLinks.feature @@ -0,0 +1,113 @@ +@api @files_sharing-app-required +Feature: public share sharers groups setting + As an admin + I should be able to allow only certain groups to create public links + So that random links are not generated on my file system + + Background: + Given group "grp1" has been created + And user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file with content "file to share with user" to "/fileToShare.txt" + + + Scenario: users present in public share shares groups can create new public link shares + Given parameter "public_share_sharers_groups_allowlist_enabled" of app "files_sharing" has been set to "yes" + And parameter "public_share_sharers_groups_allowlist" of app "files_sharing" has been set to '["grp1"]' + And user "Alice" has been added to group "grp1" + When user "Alice" creates a public link share using the sharing API with settings + | path | fileToShare.txt | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | item_type | file | + | mimetype | text/plain | + | file_target | /fileToShare.txt | + | path | /fileToShare.txt | + | permissions | read | + | share_type | public_link | + | displayname_file_owner | %displayname% | + | displayname_owner | %displayname% | + | uid_file_owner | %username% | + | uid_owner | %username% | + | name | | + + + Scenario: users not present in public share shares groups cannot create a new public link share + Given parameter "public_share_sharers_groups_allowlist_enabled" of app "files_sharing" has been set to "yes" + And parameter "public_share_sharers_groups_allowlist" of app "files_sharing" has been set to '["grp1"]' + When user "Alice" creates a public link share using the sharing API with settings + | path | fileToShare.txt | + Then the OCS status code should be "403" + And the HTTP status code should be "200" + And the OCS status message should be "Public link creation is only possible for certain groups" + + + Scenario: existing links can still be updated by sharers even if they are not present in public share sharers groups + Given user "Alice" has created a public link share with settings + | path | /fileToShare.txt | + | permissions | read | + And parameter "public_share_sharers_groups_allowlist_enabled" of app "files_sharing" has been set to "yes" + And parameter "public_share_sharers_groups_allowlist" of app "files_sharing" has been set to '["grp1"]' + When user "Alice" updates the last share using the sharing API with + | expireDate | +3 days | + Then the HTTP status code should be "200" + And the OCS status code should be "100" + And the fields of the last response to user "Alice" should include + | expiration | +3 days | + + + Scenario: existing links can still be deleted by sharers even if they are not present in public share sharers groups + Given user "Alice" has created a public link share with settings + | path | /fileToShare.txt | + | permissions | read | + | name | shared-link | + And parameter "public_share_sharers_groups_allowlist_enabled" of app "files_sharing" has been set to "yes" + And parameter "public_share_sharers_groups_allowlist" of app "files_sharing" has been set to '["grp1"]' + When user "Alice" deletes public link share named "shared-link" in file "fileToShare.txt" using the sharing API + Then the HTTP status code should be "200" + And the OCS status code should be "100" + And as user "Alice" the file "fileToShare.txt" should not have any shares + + + Scenario: creating a new link share is not restricted if no groups are inside the allowed public share sharers groups even if allowlist is enabled + Given parameter "public_share_sharers_groups_allowlist_enabled" of app "files_sharing" has been set to "yes" + When user "Alice" creates a public link share using the sharing API with settings + | path | fileToShare.txt | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And the fields of the last response to user "Alice" should include + | item_type | file | + | mimetype | text/plain | + | file_target | /fileToShare.txt | + | path | /fileToShare.txt | + | permissions | read | + | share_type | public_link | + | displayname_file_owner | %displayname% | + | displayname_owner | %displayname% | + | uid_file_owner | %username% | + | uid_owner | %username% | + | name | | + + Scenario: multiple groups can be added to public share sharers groups allow list + Given parameter "public_share_sharers_groups_allowlist_enabled" of app "files_sharing" has been set to "yes" + And user "Brian" has been created with default attributes and without skeleton files + And user "Carol" has been created with default attributes and without skeleton files + And user "Brian" has uploaded file with content "file to share with user" to "/fileToShare.txt" + And user "Carol" has uploaded file with content "file to share with user" to "/fileToShare.txt" + And group "grp2" has been created + And user "Alice" has been added to group "grp1" + And user "Brian" has been added to group "grp2" + And parameter "public_share_sharers_groups_allowlist" of app "files_sharing" has been set to '["grp1", "grp2"]' + When user "Alice" creates a public link share using the sharing API with settings + | path | fileToShare.txt | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + When user "Brian" creates a public link share using the sharing API with settings + | path | fileToShare.txt | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + When user "Carol" creates a public link share using the sharing API with settings + | path | fileToShare.txt | + Then the OCS status code should be "403" + And the HTTP status code should be "200" + And the OCS status message should be "Public link creation is only possible for certain groups" diff --git a/tests/acceptance/features/bootstrap/WebUISharingContext.php b/tests/acceptance/features/bootstrap/WebUISharingContext.php index bc114cbc1df7..b9abbbd7688f 100644 --- a/tests/acceptance/features/bootstrap/WebUISharingContext.php +++ b/tests/acceptance/features/bootstrap/WebUISharingContext.php @@ -1754,6 +1754,18 @@ public function thePublicLinkWithNameShouldNotBeInPublicLinksList($entryName) { $this->sharingDialog->checkThatNameIsNotInPublicLinkList($this->getSession(), $entryName); } + /** + * @Then the public link with name :entryName should be in the public links list on the webUI + * + * @param string $entryName + * + * @return void + */ + public function thePublicLinkWithNameShouldBeInPublicLinksList(string $entryName) { + $isPresent = $this->sharingDialog->checkThatNameIsInPublicLinkList($this->getSession(), $entryName); + Assert::assertTrue($isPresent, "Expected " . $entryName . " in public link lists, but not found."); + } + /** * @Then the number of public links should be :count * @@ -2225,4 +2237,24 @@ public function theUserClosesFederationShareDialog() { public function theUserAcceptsThePendingRemoteSharesUsingTheWebui() { $this->sharedWithYouPage->acceptPendingShare(); } + + /** + * @Then the user should not see the public link share tab on the webUI + * + * @return void + */ + public function userShouldNotBeAbleToCreateNewPublicLink() { + $is_visible = $this->sharingDialog->isPublicLinkTabVisible(); + Assert::assertFalse($is_visible, "Public link tab is expected to be not present but found visible."); + } + + /** + * @Then the user should not see the create public link button on the webUI + * + * @return void + */ + public function theUserShouldSeeCreatePublicLinkButtonOnTheWebui() { + $is_visible = $this->publicShareTab->isCreateLinkShareButtonPresent(); + Assert::assertFalse($is_visible, "Create public link button is expected to be not present but found visible."); + } } diff --git a/tests/acceptance/features/lib/FilesPageElement/SharingDialog.php b/tests/acceptance/features/lib/FilesPageElement/SharingDialog.php index 0f8b515842c1..fbee5dabd2d9 100644 --- a/tests/acceptance/features/lib/FilesPageElement/SharingDialog.php +++ b/tests/acceptance/features/lib/FilesPageElement/SharingDialog.php @@ -730,6 +730,20 @@ public function openPublicShareTab(Session $session) { return $publicLinkTab; } + /** + * returns visibility of public link share tab + * + * @return bool + */ + public function isPublicLinkTabVisible() { + $publicLinksShareTab = $this->find("xpath", $this->publicLinksShareTabXpath); + if ($publicLinksShareTab) { + return true; + } else { + return false; + } + } + /** * @param Session $session * @param integer $number @@ -823,6 +837,23 @@ public function checkThatNameIsNotInPublicLinkList(Session $session, $entryName) } } + /** + * @param Session $session + * @param $entryName + * + * @return bool + */ + public function checkThatNameIsInPublicLinkList(Session $session, $entryName) { + $publicLinkTitles = $this->findAll("xpath", $this->publicLinkTitleXpath); + $found = false; + foreach ($publicLinkTitles as $publicLinkTitle) { + if ($entryName === $publicLinkTitle->getText()) { + $found = true; + } + } + return $found; + } + /** * get the list of shared with users * diff --git a/tests/acceptance/features/lib/FilesPageElement/SharingDialogElement/PublicLinkTab.php b/tests/acceptance/features/lib/FilesPageElement/SharingDialogElement/PublicLinkTab.php index 649374f3a70c..70b78e60fedd 100644 --- a/tests/acceptance/features/lib/FilesPageElement/SharingDialogElement/PublicLinkTab.php +++ b/tests/acceptance/features/lib/FilesPageElement/SharingDialogElement/PublicLinkTab.php @@ -177,6 +177,23 @@ public function openSharingPopup(Session $session) { return $this->editPublicLinkPopupPageObject; } + /** + * return if create link share button is present or not + * + * @return bool + */ + public function isCreateLinkShareButtonPresent() { + $createLinkBtn = $this->publicLinkTabElement->find( + "xpath", + $this->createLinkBtnXpath + ); + if ($createLinkBtn) { + return true; + } else { + return false; + } + } + /** * Updates sharing popup as popup may change * diff --git a/tests/acceptance/features/webUISharingPublic2/allowGroupToCreatePublicLinks.feature b/tests/acceptance/features/webUISharingPublic2/allowGroupToCreatePublicLinks.feature new file mode 100644 index 000000000000..c40e7fd032c1 --- /dev/null +++ b/tests/acceptance/features/webUISharingPublic2/allowGroupToCreatePublicLinks.feature @@ -0,0 +1,79 @@ +@webUI @files_sharing-app-required +Feature: public share sharers groups setting + As an admin + I should be able to allow only certain groups to create public links + So that random links are not generated on my file system + + Background: + Given group "grp1" has been created + And user "Alice" has been created with default attributes and without skeleton files + And user "Alice" has created folder "FolderToShare" + And user "Alice" has uploaded file with content "file to share with user" to "FolderToShare/fileToShare.txt" + + + Scenario: users present in public share shares groups can create new public link shares using webUI + Given parameter "public_share_sharers_groups_allowlist_enabled" of app "files_sharing" has been set to "yes" + And parameter "public_share_sharers_groups_allowlist" of app "files_sharing" has been set to '["grp1"]' + And user "Alice" has been added to group "grp1" + And user "Alice" has logged in using the webUI + When the user creates a new public link for folder "FolderToShare" using the webUI + And the public accesses the last created public link using the webUI + Then file "fileToShare.txt" should be listed on the webUI + + + Scenario: users not present in public share shares groups cannot create a new public link share using webUI + Given parameter "public_share_sharers_groups_allowlist_enabled" of app "files_sharing" has been set to "yes" + And parameter "public_share_sharers_groups_allowlist" of app "files_sharing" has been set to '["grp1"]' + And user "Alice" has logged in using the webUI + When the user opens the share dialog for folder "FolderToShare" + Then the user should not see the public link share tab on the webUI + + + Scenario: sharer not present in public share shares groups can still access existing share but cannot create new link share + Given user "Alice" has created a public link share with settings + | path | /FolderToShare | + | permissions | read | + | name | shared-link | + And parameter "public_share_sharers_groups_allowlist_enabled" of app "files_sharing" has been set to "yes" + And parameter "public_share_sharers_groups_allowlist" of app "files_sharing" has been set to '["grp1"]' + And user "Alice" has logged in using the webUI + When the user opens the share dialog for folder "FolderToShare" + And the user has opened the public link share tab + Then the public link with name "shared-link" should be in the public links list on the webUI + But the user should not see the create public link button on the webUI + + + Scenario: existing links are still accessible for sharers even if not present in public share shares groups using webUI + Given user "Alice" has created a public link share with settings + | path | /FolderToShare | + | permissions | read,delete | + | name | Public link | + And parameter "public_share_sharers_groups_allowlist_enabled" of app "files_sharing" has been set to "yes" + And parameter "public_share_sharers_groups_allowlist" of app "files_sharing" has been set to '["grp1"]' + And user "Alice" has logged in using the webUI + And the user has opened the share dialog for folder "FolderToShare" + And the user has opened the public link share tab + When the user changes the permission of the public link named "Public link" to "read" + And the public accesses the last created public link using the webUI + Then file "fileToShare.txt" should be listed on the webUI + And it should not be possible to delete file "fileToShare.txt" using the webUI + + + Scenario: existing links can still be deleted by sharers even if not present in public share shares groups using webUI + Given user "Alice" has created a public link share with settings + | path | /FolderToShare | + | permissions | read,delete | + | name | Public link | + And parameter "public_share_sharers_groups_allowlist_enabled" of app "files_sharing" has been set to "yes" + And parameter "public_share_sharers_groups_allowlist" of app "files_sharing" has been set to '["grp1"]' + And user "Alice" has logged in using the webUI + When the user removes the public link of folder "FolderToShare" using the webUI + Then the public should see an error message "File not found" while accessing last created public link using the webUI + + + Scenario: creating new link shares is not restricted if no groups are inside the allowed public share sharers groups even if allowlist is enabled + Given parameter "public_share_sharers_groups_allowlist_enabled" of app "files_sharing" has been set to "yes" + And user "Alice" has logged in using the webUI + When the user creates a new public link for folder "FolderToShare" using the webUI + And the public accesses the last created public link using the webUI + Then file "fileToShare.txt" should be listed on the webUI diff --git a/tests/karma.config.js b/tests/karma.config.js index 8c897deff051..e031bf11930d 100644 --- a/tests/karma.config.js +++ b/tests/karma.config.js @@ -57,7 +57,8 @@ module.exports = function(config) { 'apps/files_sharing/js/external.js', 'apps/files_sharing/js/public.js', 'apps/files_sharing/js/sharetabview.js', - 'apps/files_sharing/js/PublicUploadView.js' + 'apps/files_sharing/js/PublicUploadView.js', + 'apps/files_sharing/js/sharedialogview.js' ], testFiles: ['apps/files_sharing/tests/js/*.js'] },