From 02dd7eff4337bc3db2fee89b176d81a591f2627a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Wed, 16 Dec 2020 15:56:24 +0100 Subject: [PATCH 1/3] feat: Add app config to allow groups of users to be lock breakers --- apps/dav/lib/Connector/Sabre/LockPlugin.php | 38 ++++++++++++++++++- .../dav/lib/Connector/Sabre/ServerFactory.php | 2 +- apps/dav/lib/Server.php | 2 +- apps/dav/tests/unit/DAV/LockPluginTest.php | 38 +++++++++++++++++-- changelog/unreleased/38222 | 6 +++ settings/Panels/Admin/PersistentLocking.php | 5 ++- settings/js/panels/persistentlocking.js | 10 +++++ .../panels/admin/persistentlocking.php | 16 ++++++++ .../Panels/Admin/PersistentLockingTest.php | 26 ++++++++----- 9 files changed, 126 insertions(+), 17 deletions(-) create mode 100644 changelog/unreleased/38222 diff --git a/apps/dav/lib/Connector/Sabre/LockPlugin.php b/apps/dav/lib/Connector/Sabre/LockPlugin.php index 245f177cded5..9ef45e1b159c 100644 --- a/apps/dav/lib/Connector/Sabre/LockPlugin.php +++ b/apps/dav/lib/Connector/Sabre/LockPlugin.php @@ -26,6 +26,9 @@ use OC\Lock\Persistent\LockMapper; use OCA\DAV\Connector\Sabre\Exception\FileLocked; +use OCP\IConfig; +use OCP\IGroupManager; +use OCP\IUser; use OCP\Lock\ILockingProvider; use OCP\Lock\LockedException; use Sabre\DAV\Exception\Forbidden; @@ -42,6 +45,19 @@ class LockPlugin extends ServerPlugin { * @var \Sabre\DAV\Server */ private $server; + /** + * @var IConfig + */ + private $config; + /** + * @var IGroupManager + */ + private $groupManager; + + public function __construct(IConfig $config, IGroupManager $groupManager) { + $this->config = $config; + $this->groupManager = $groupManager; + } private $missedLocks = []; @@ -125,8 +141,28 @@ public function beforeUnlock($uri, LockInfo $lock) { return; } $currentUser = \OC::$server->getUserSession()->getUser(); - if ($currentUser === null || $lock->getOwnerAccountId() !== $currentUser->getAccountId()) { + if ($currentUser === null) { throw new Forbidden(); } + + if ($lock->getOwnerAccountId() === $currentUser->getAccountId()) { + return; + } + + if (!$this->userIsALockBreaker($currentUser)) { + throw new Forbidden(); + } + } + + private function userIsALockBreaker(IUser $currentUser): bool { + $lockBreakerGroups = $this->config->getAppValue('core', 'lock-breaker-groups', '[]'); + $lockBreakerGroups = \json_decode($lockBreakerGroups) ?? []; + + foreach ($lockBreakerGroups as $lockBreakerGroup) { + if ($this->groupManager->isInGroup($currentUser->getUID(), $lockBreakerGroup)) { + return true; + } + } + return false; } } diff --git a/apps/dav/lib/Connector/Sabre/ServerFactory.php b/apps/dav/lib/Connector/Sabre/ServerFactory.php index 8ace928664d4..ad4af4d589e9 100644 --- a/apps/dav/lib/Connector/Sabre/ServerFactory.php +++ b/apps/dav/lib/Connector/Sabre/ServerFactory.php @@ -122,7 +122,7 @@ public function createServer( // FIXME: The following line is a workaround for legacy components relying on being able to send a GET to / $server->addPlugin(new \OCA\DAV\Connector\Sabre\DummyGetResponsePlugin()); $server->addPlugin(new \OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin('webdav', $this->logger)); - $server->addPlugin(new \OCA\DAV\Connector\Sabre\LockPlugin()); + $server->addPlugin(new \OCA\DAV\Connector\Sabre\LockPlugin($this->config, \OC::$server->getGroupManager())); $fileLocksBackend = new FileLocksBackend($server->tree, true, $this->timeFactory, $isPublicAccess); $server->addPlugin(new \OCA\DAV\Connector\Sabre\PublicDavLocksPlugin($fileLocksBackend, function ($uri) use ($isPublicAccess) { diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php index ed2318e370be..99cbac5410f1 100644 --- a/apps/dav/lib/Server.php +++ b/apps/dav/lib/Server.php @@ -159,7 +159,7 @@ public function __construct(IRequest $request, $baseUri) { $this->server->addPlugin(new ExceptionLoggerPlugin('webdav', $logger)); $this->server->addPlugin(new \Sabre\DAV\Sync\Plugin()); - $this->server->addPlugin(new LockPlugin()); + $this->server->addPlugin(new LockPlugin(\OC::$server->getConfig(), \OC::$server->getGroupManager())); $fileLocksBackend = new FileLocksBackend($this->server->tree, false, OC::$server->getTimeFactory(), $isPublicAccess); $this->server->addPlugin(new \OCA\DAV\Connector\Sabre\PublicDavLocksPlugin($fileLocksBackend, function ($uri) { diff --git a/apps/dav/tests/unit/DAV/LockPluginTest.php b/apps/dav/tests/unit/DAV/LockPluginTest.php index 91ab31ebd17b..56a4facc0da5 100644 --- a/apps/dav/tests/unit/DAV/LockPluginTest.php +++ b/apps/dav/tests/unit/DAV/LockPluginTest.php @@ -24,8 +24,14 @@ use OC\Lock\Persistent\Lock; use OC\Lock\Persistent\LockMapper; use OCA\DAV\Connector\Sabre\LockPlugin; +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Db\MultipleObjectsReturnedException; +use OCP\AppFramework\QueryException; +use OCP\IConfig; +use OCP\IGroupManager; use OCP\IUser; use OCP\IUserSession; +use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Locks\LockInfo; use Sabre\DAV\Server; use Sabre\DAV\Tree; @@ -50,10 +56,20 @@ class LockPluginTest extends TestCase { private $lockMapper; /** @var IUserSession | \PHPUnit\Framework\MockObject\MockObject */ private $userSession; + /** + * @var IConfig|\PHPUnit\Framework\MockObject\MockObject + */ + private $config; + /** + * @var IGroupManager|\PHPUnit\Framework\MockObject\MockObject + */ + private $groupManager; public function setUp(): void { parent::setUp(); - $this->plugin = new LockPlugin(); + $this->config = $this->createMock(IConfig::class); + $this->groupManager = $this->createMock(IGroupManager::class); + $this->plugin = new LockPlugin($this->config, $this->groupManager); $this->server = $this->createMock(Server::class); $this->tree = $this->createMock(Tree::class); @@ -74,9 +90,15 @@ protected function tearDown(): void { } /** + * @dataProvider providesConfigValues + * @param string $lockBreakerGroups + * @throws DoesNotExistException + * @throws Forbidden + * @throws MultipleObjectsReturnedException + * @throws QueryException */ - public function testBeforeUnlock() { - $this->expectException(\Sabre\DAV\Exception\Forbidden::class); + public function testBeforeUnlock(string $lockBreakerGroups): void { + $this->expectException(Forbidden::class); $lock = new LockInfo(); $lock->token = '123-456-789'; @@ -88,6 +110,16 @@ public function testBeforeUnlock() { $user->method('getAccountId')->willReturn(777); $this->userSession->method('getUser')->willReturn($user); + $this->config->method('getAppValue')->willReturn($lockBreakerGroups); + $this->plugin->beforeUnlock('foo', $lock); } + + public function providesConfigValues(): array { + return [ + 'empty array' => ['[]'], + 'not a valid json string' => ['[}'], + 'not in any group' => [\json_encode(['group1', 'group2'])], + ]; + } } diff --git a/changelog/unreleased/38222 b/changelog/unreleased/38222 new file mode 100644 index 000000000000..266e9bee003b --- /dev/null +++ b/changelog/unreleased/38222 @@ -0,0 +1,6 @@ +Enhancement: Special user groups can break persistent locks + +Not only the owner of a lock can unlock a resource but the lock breaker groups +are allowed to break locks as well. + +https://github.com/owncloud/core/pull/38222 diff --git a/settings/Panels/Admin/PersistentLocking.php b/settings/Panels/Admin/PersistentLocking.php index f67c6710a9b5..94cfeeee90b7 100644 --- a/settings/Panels/Admin/PersistentLocking.php +++ b/settings/Panels/Admin/PersistentLocking.php @@ -39,15 +39,18 @@ public function getPriority() { } public function getSectionID() { - return 'additional'; + return 'general'; } public function getPanel() { + $lockBreakerGroups = \json_decode($this->config->getAppValue('core', 'lock-breaker-groups', '[]'), true); + // we must use the same container $tmpl = new Template('settings', 'panels/admin/persistentlocking'); $tmpl->assign('defaultTimeout', $this->config->getAppValue('core', 'lock_timeout_default', LockManager::LOCK_TIMEOUT_DEFAULT)); $tmpl->assign('maximumTimeout', $this->config->getAppValue('core', 'lock_timeout_max', LockManager::LOCK_TIMEOUT_MAX)); $tmpl->assign('manualFileLockOnClientsEnabled', $this->config->getAppValue('files', 'enable_lock_file_action', 'no')); + $tmpl->assign('lock-breaker-groups', \implode('|', $lockBreakerGroups)); return $tmpl; } diff --git a/settings/js/panels/persistentlocking.js b/settings/js/panels/persistentlocking.js index f0d9335f13c5..23d1e59b1418 100644 --- a/settings/js/panels/persistentlocking.js +++ b/settings/js/panels/persistentlocking.js @@ -1,5 +1,11 @@ $(document).ready(function() { + var lockBreakersGroupsList = $('#lock_breakers_groups_list'); + OC.Settings.setupGroupsSelect(lockBreakersGroupsList); + lockBreakersGroupsList.change(function(ev) { + OC.AppConfig.setValue('core', 'lock-breaker-groups', JSON.stringify(ev.val || [])); + }); + $('#persistentlocking input').change(function () { var currentInput = $(this); var name = currentInput.attr('name'); @@ -7,6 +13,10 @@ $(document).ready(function() { var app = ''; var value = ''; + // handled above + if (name === 'lock_breakers_groups_list') { + return; + } if (name === 'enable_lock_file_action') { app = 'files'; if (this.checked) { diff --git a/settings/templates/panels/admin/persistentlocking.php b/settings/templates/panels/admin/persistentlocking.php index 12819e001d44..13de91919168 100644 --- a/settings/templates/panels/admin/persistentlocking.php +++ b/settings/templates/panels/admin/persistentlocking.php @@ -1,5 +1,10 @@

t('Manual File Locking')); ?>

@@ -23,4 +28,15 @@ } ?> />
+

+
+ t('Allow users in the following groups to unlock files they have access to:')); ?> +
+ + +
+ t('Users in these groups can unlock files even if they are not the owner of the lock.')); ?> +
+

+
diff --git a/tests/Settings/Panels/Admin/PersistentLockingTest.php b/tests/Settings/Panels/Admin/PersistentLockingTest.php index e157f92abe9d..3282113d57ab 100644 --- a/tests/Settings/Panels/Admin/PersistentLockingTest.php +++ b/tests/Settings/Panels/Admin/PersistentLockingTest.php @@ -23,13 +23,18 @@ use OC\Settings\Panels\Admin\PersistentLocking; use OC\Lock\Persistent\LockManager; use OCP\IConfig; +use Test\TestCase; /** * @package Tests\Settings\Panels\Admin */ -class PersistentLockingTest extends \Test\TestCase { +class PersistentLockingTest extends TestCase { /** @var IConfig */ private $config; + /** + * @var PersistentLocking + */ + private $panel; public function setUp(): void { parent::setUp(); @@ -37,24 +42,25 @@ public function setUp(): void { $this->panel = new PersistentLocking($this->config); } - public function testGetPriority() { - $this->assertSame(0, $this->panel->getPriority()); + public function testGetPriority(): void { + self::assertSame(0, $this->panel->getPriority()); } - public function testGetSection() { - $this->assertEquals('additional', $this->panel->getSectionID()); + public function testGetSection(): void { + self::assertEquals('general', $this->panel->getSectionID()); } - public function testGetPanel() { + public function testGetPanel(): void { $this->config->method('getAppValue') - ->will($this->returnValueMap([ + ->willReturnMap([ + ['core', 'lock-breaker-groups', '[]', '[]'], ['core', 'lock_timeout_default', LockManager::LOCK_TIMEOUT_DEFAULT, 44], ['core', 'lock_timeout_max', LockManager::LOCK_TIMEOUT_MAX, 9999], - ])); + ]); $templateHtml = $this->panel->getPanel()->fetchPage(); // applied modifiers "m" for multiline and "s" to include newlines in the dot char - $this->assertRegExp('/input[[:space:]].*name="lock_timeout_default"[[:space:]].*value="44"/ms', $templateHtml); - $this->assertRegExp('/input[[:space:]].*name="lock_timeout_max"[[:space:]].*value="9999"/ms', $templateHtml); + self::assertRegExp('/input[[:space:]].*name="lock_timeout_default"[[:space:]].*value="44"/ms', $templateHtml); + self::assertRegExp('/input[[:space:]].*name="lock_timeout_max"[[:space:]].*value="9999"/ms', $templateHtml); } } From 4350f0215c6ebe32c339fe5141e89539bb941751 Mon Sep 17 00:00:00 2001 From: Swikriti Tripathi Date: Mon, 12 Jul 2021 11:49:45 +0545 Subject: [PATCH 2/3] Add API and UI acceptance tests for lock breaker groups --- tests/acceptance/config/behat.yml | 1 + .../lockBreakersGroups.feature | 314 ++++++++++++++++++ .../bootstrap/AppConfigurationContext.php | 7 +- .../bootstrap/WebDavLockingContext.php | 52 +++ .../WebUIAdminGeneralSettingsContext.php | 43 +++ .../features/lib/AdminGeneralSettingsPage.php | 40 +++ .../features/lib/AdminSharingSettingsPage.php | 1 + .../webUIWebdavLocks/lockBreakerGroup.feature | 121 +++++++ 8 files changed, 577 insertions(+), 2 deletions(-) create mode 100644 tests/acceptance/features/apiWebdavLocksUnlock/lockBreakersGroups.feature create mode 100644 tests/acceptance/features/webUIWebdavLocks/lockBreakerGroup.feature diff --git a/tests/acceptance/config/behat.yml b/tests/acceptance/config/behat.yml index 702a6cc751d9..b2275bd9c174 100644 --- a/tests/acceptance/config/behat.yml +++ b/tests/acceptance/config/behat.yml @@ -1177,6 +1177,7 @@ default: contexts: - FeatureContext: *common_feature_context_params - WebDavLockingContext: + - WebUIAdminGeneralSettingsContext: - WebUIFilesContext: - WebUIGeneralContext: - WebUILoginContext: diff --git a/tests/acceptance/features/apiWebdavLocksUnlock/lockBreakersGroups.feature b/tests/acceptance/features/apiWebdavLocksUnlock/lockBreakersGroups.feature new file mode 100644 index 000000000000..55287a0d2b38 --- /dev/null +++ b/tests/acceptance/features/apiWebdavLocksUnlock/lockBreakersGroups.feature @@ -0,0 +1,314 @@ +@api +Feature: UNLOCK locked items + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + + + Scenario Outline: a group can be added as a lock breaker group + Given using DAV path + And group "grp1" has been created + When the administrator sets parameter "lock-breaker-groups" of app "core" to '["grp1"]' + Then the HTTP status code should be "200" + And group "grp1" should exist as a lock breaker group + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: more than one group can be added as a lock breaker group + Given using DAV path + And group "grp1" has been created + And group "grp2" has been created + When the administrator sets parameter "lock-breaker-groups" of app "core" to '["grp1","grp2"]' + Then the HTTP status code should be "200" + And following groups should exist as lock breaker groups + | groups | + | grp1 | + | grp2 | + Examples: + | dav-path | + | old | + | new | + + + Scenario Outline: member of the lock breakers group can unlock a locked folder shared with them + Given using DAV path + And group "grp1" has been created + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "FOLDER" + And parameter "lock-breaker-groups" of app "core" has been set to '["grp1"]' + And user "Brian" has been added to group "grp1" + And user "Alice" has locked folder "FOLDER" setting the following properties + | lockscope | | + And user "Alice" has shared folder "FOLDER" with user "Brian" + When user "Brian" unlocks folder "FOLDER" with the last created lock of folder "FOLDER" of user "Alice" using the WebDAV API + Then the HTTP status code should be "204" + And 0 locks should be reported for folder "FOLDER" of user "Brian" by the WebDAV API + And 0 locks should be reported for folder "FOLDER" of user "Alice" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: members of the lock breakers group can unlock a locked folder shared with them and lock it back again + Given using DAV path + And group "grp1" has been created + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "FOLDER" + And parameter "lock-breaker-groups" of app "core" has been set to '["grp1"]' + And user "Brian" has been added to group "grp1" + And user "Alice" has locked folder "FOLDER" setting the following properties + | lockscope | | + And user "Alice" has shared folder "FOLDER" with user "Brian" + When user "Brian" unlocks folder "FOLDER" with the last created lock of folder "FOLDER" of user "Alice" using the WebDAV API + Then the HTTP status code should be "204" + And 0 locks should be reported for folder "FOLDER" of user "Brian" by the WebDAV API + When user "Brian" locks folder "FOLDER" using the WebDAV API setting the following properties + | lockscope | | + Then the HTTP status code should be "200" + And 1 locks should be reported for folder "FOLDER" of user "Brian" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: members of the lock breakers group can unlock a locked file shared with them + Given using DAV path + And group "grp1" has been created + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + And parameter "lock-breaker-groups" of app "core" has been set to '["grp1"]' + And user "Brian" has been added to group "grp1" + And user "Alice" has locked file "textfile0.txt" setting the following properties + | lockscope | | + And user "Alice" has shared file "textfile0.txt" with user "Brian" + When user "Brian" unlocks file "textfile0.txt" with the last created lock of file "textfile0.txt" of user "Alice" using the WebDAV API + Then the HTTP status code should be "204" + And 0 locks should be reported for file "textfile0.txt" of user "Brian" by the WebDAV API + And 0 locks should be reported for file "textfile0.txt" of user "Alice" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: members of the lock breakers group can unlock a locked file shared with them and lock it back again + Given using DAV path + And group "grp1" has been created + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + And parameter "lock-breaker-groups" of app "core" has been set to '["grp1"]' + And user "Brian" has been added to group "grp1" + And user "Alice" has locked file "textfile0.txt" setting the following properties + | lockscope | | + And user "Alice" has shared file "textfile0.txt" with user "Brian" + When user "Brian" unlocks file "textfile0.txt" with the last created lock of file "textfile0.txt" of user "Alice" using the WebDAV API + Then the HTTP status code should be "204" + And 0 locks should be reported for file "textfile0.txt" of user "Brian" by the WebDAV API + When user "Brian" locks file "textfile0.txt" using the WebDAV API setting the following properties + | lockscope | | + Then the HTTP status code should be "200" + And 1 locks should be reported for file "textfile0.txt" of user "Brian" by the WebDAV API + And 1 locks should be reported for file "textfile0.txt" of user "Alice" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: as a member of lock breaker group unlocking a file in a share locked by the file owner is possible + Given using DAV path + And group "grp1" has been created + And user "Brian" has been created with default attributes and without skeleton files + And parameter "lock-breaker-groups" of app "core" has been set to '["grp1"]' + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "PARENT" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "PARENT/parent.txt" + And user "Alice" has locked file "PARENT/parent.txt" setting the following properties + | lockscope | | + And user "Alice" has shared folder "PARENT" with user "Brian" + When user "Brian" unlocks file "PARENT/parent.txt" with the last created lock of file "PARENT/parent.txt" of user "Alice" using the WebDAV API + Then the HTTP status code should be "204" + And 0 locks should be reported for file "PARENT/parent.txt" of user "Alice" by the WebDAV API + And 0 locks should be reported for file "PARENT/parent.txt" of user "Brian" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: as a member of lock breaker group unlocking a folder in a share locked by the folder owner is possible + Given using DAV path + And group "grp1" has been created + And user "Brian" has been created with default attributes and without skeleton files + And parameter "lock-breaker-groups" of app "core" has been set to '["grp1"]' + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "PARENT" + And user "Alice" has created folder "CHILD" + And user "Alice" has locked folder "PARENT/CHILD" setting the following properties + | lockscope | | + And user "Alice" has shared folder "PARENT" with user "Brian" + When user "Brian" unlocks folder "PARENT/CHILD" with the last created lock of folder "PARENT/CHILD" of user "Alice" using the WebDAV API + Then the HTTP status code should be "204" + And 0 locks should be reported for folder "PARENT/CHILD" of user "Alice" by the WebDAV API + And 0 locks should be reported for folder "PARENT/CHILD" of user "Brian" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: members of different lock breaker groups can lock and unlock same folder shared to them + Given using DAV path + And group "grp1" has been created + And group "grp2" has been created + 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 parameter "lock-breaker-groups" of app "core" has been set to '["grp1","grp2"]' + And user "Carol" has been added to group "grp2" + And user "Brian" has been added to group "grp1" + And user "Alice" has created folder "PARENT" + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + And user "Alice" has shared folder "PARENT" with user "Brian" + And user "Alice" has shared folder "PARENT" with user "Carol" + When user "Brian" unlocks folder "PARENT" with the last created lock of file "PARENT" of user "Alice" using the WebDAV API + Then the HTTP status code should be "204" + And 0 locks should be reported for folder "PARENT" of user "Alice" by the WebDAV API + And 0 locks should be reported for folder "PARENT" of user "Brian" by the WebDAV API + And 0 locks should be reported for folder "PARENT" of user "Carol" by the WebDAV API + When user "Brian" locks folder "PARENT" using the WebDAV API setting the following properties + | lockscope | | + And 1 locks should be reported for folder "PARENT" of user "Alice" by the WebDAV API + And 1 locks should be reported for folder "PARENT" of user "Brian" by the WebDAV API + And 1 locks should be reported for folder "PARENT" of user "Carol" by the WebDAV API + Then the HTTP status code should be "200" + When user "Carol" unlocks folder "PARENT" with the last created lock of folder "PARENT" of user "Brian" using the WebDAV API + Then the HTTP status code should be "204" + And 0 locks should be reported for folder "PARENT" of user "Alice" by the WebDAV API + And 0 locks should be reported for folder "PARENT" of user "Brian" by the WebDAV API + And 0 locks should be reported for folder "PARENT" of user "Carol" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: members of different lock breaker groups can lock and unlock same file shared to them + Given using DAV path + And group "grp1" has been created + And group "grp2" has been created + 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 parameter "lock-breaker-groups" of app "core" has been set to '["grp1","grp2"]' + And user "Carol" has been added to group "grp2" + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + And user "Alice" has locked file "textfile.txt" setting the following properties + | lockscope | | + And user "Alice" has shared file "textfile.txt" with user "Brian" + And user "Alice" has shared file "textfile.txt" with user "Carol" + When user "Brian" unlocks file "textfile.txt" with the last created lock of file "textfile.txt" of user "Alice" using the WebDAV API + Then the HTTP status code should be "204" + And 0 locks should be reported for file "textfile.txt" of user "Alice" by the WebDAV API + And 0 locks should be reported for file "textfile.txt" of user "Brian" by the WebDAV API + And 0 locks should be reported for file "textfile.txt" of user "Carol" by the WebDAV API + When user "Brian" locks file "textfile.txt" using the WebDAV API setting the following properties + | lockscope | | + And 1 locks should be reported for file "textfile.txt" of user "Alice" by the WebDAV API + And 1 locks should be reported for file "textfile.txt" of user "Brian" by the WebDAV API + And 1 locks should be reported for file "textfile.txt" of user "Carol" by the WebDAV API + Then the HTTP status code should be "200" + When user "Carol" unlocks file "textfile.txt" with the last created lock of file "textfile.txt" of user "Brian" using the WebDAV API + Then the HTTP status code should be "204" + And 0 locks should be reported for file "textfile.txt" of user "Alice" by the WebDAV API + And 0 locks should be reported for file "textfile.txt" of user "Brian" by the WebDAV API + And 0 locks should be reported for file "textfile.txt" of user "Carol" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: members of lock breaker group can unlock a folder in group sharing + Given using DAV path + And group "grp1" has been created + And group "grp2" has been created + 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 parameter "lock-breaker-groups" of app "core" has been set to '["grp1"]' + And user "Carol" has been added to group "grp2" + And user "Brian" has been added to group "grp1" + And user "Brian" has been added to group "grp2" + And user "Alice" has created folder "PARENT" + And user "Alice" has locked folder "PARENT" setting the following properties + | lockscope | | + And user "Alice" has shared folder "PARENT" with group "grp2" + When user "Carol" unlocks folder "PARENT" with the last created lock of folder "PARENT" of user "Alice" using the WebDAV API + Then the HTTP status code should be "403" + And 1 locks should be reported for folder "PARENT" of user "Alice" by the WebDAV API + And 1 locks should be reported for folder "PARENT" of user "Brian" by the WebDAV API + And 1 locks should be reported for folder "PARENT" of user "Carol" by the WebDAV API + When user "Brian" unlocks folder "PARENT" with the last created lock of file "PARENT" of user "Alice" using the WebDAV API + Then the HTTP status code should be "204" + And 0 locks should be reported for folder "PARENT" of user "Alice" by the WebDAV API + And 0 locks should be reported for folder "PARENT" of user "Brian" by the WebDAV API + And 0 locks should be reported for folder "PARENT" of user "Carol" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | + + + Scenario Outline: members of lock breaker group can unlock a file in group sharing + Given using DAV path + And group "grp1" has been created + And group "grp2" has been created + 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 parameter "lock-breaker-groups" of app "core" has been set to '["grp1"]' + And user "Carol" has been added to group "grp2" + And user "Brian" has been added to group "grp1" + And user "Brian" has been added to group "grp2" + And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "textfile0.txt" + And user "Alice" has locked file "textfile.txt0" setting the following properties + | lockscope | | + And user "Alice" has shared file "textfile.txt0" with group "grp2" + When user "Carol" unlocks file "textfile.txt0" with the last created lock of file "textfile.txt0" of user "Alice" using the WebDAV API + Then the HTTP status code should be "403" + And 1 locks should be reported for file "textfile.txt0" of user "Alice" by the WebDAV API + And 1 locks should be reported for file "textfile.txt0" of user "Brian" by the WebDAV API + And 1 locks should be reported for file "textfile.txt0" of user "Carol" by the WebDAV API + When user "Brian" unlocks file "textfile.txt0" with the last created lock of file "textfile.txt0" of user "Alice" using the WebDAV API + Then the HTTP status code should be "204" + And 0 locks should be reported for file "textfile.txt0" of user "Alice" by the WebDAV API + And 0 locks should be reported for file "textfile.txt0" of user "Brian" by the WebDAV API + And 0 locks should be reported for file "textfile.txt0" of user "Carol" by the WebDAV API + Examples: + | dav-path | lock-scope | + | old | shared | + | old | exclusive | + | new | shared | + | new | exclusive | diff --git a/tests/acceptance/features/bootstrap/AppConfigurationContext.php b/tests/acceptance/features/bootstrap/AppConfigurationContext.php index 096ce5f21dfc..1cc4ade961c5 100644 --- a/tests/acceptance/features/bootstrap/AppConfigurationContext.php +++ b/tests/acceptance/features/bootstrap/AppConfigurationContext.php @@ -40,7 +40,7 @@ class AppConfigurationContext implements Context { private $featureContext; /** - * @When /^the administrator sets parameter "([^"]*)" of app "([^"]*)" to "([^"]*)"$/ + * @When /^the administrator sets parameter "([^"]*)" of app "([^"]*)" to ((?:'[^']*')|(?:"[^"]*"))$/ * * @param string $parameter * @param string $app @@ -53,6 +53,9 @@ public function adminSetsServerParameterToUsingAPI( $app, $value ) { + // The capturing group of the regex always includes the quotes at each + // end of the captured string, so trim them. + $value = \trim($value, $value[0]); $this->modifyAppConfig($app, $parameter, $value); } @@ -72,7 +75,7 @@ public function serverParameterHasBeenSetTo($parameter, $app, $value) { return; } $value = \trim($value, $value[0]); - $this->adminSetsServerParameterToUsingAPI($parameter, $app, $value); + $this->modifyAppConfig($app, $parameter, $value); } /** diff --git a/tests/acceptance/features/bootstrap/WebDavLockingContext.php b/tests/acceptance/features/bootstrap/WebDavLockingContext.php index 5ddd248351bc..635d022117e7 100644 --- a/tests/acceptance/features/bootstrap/WebDavLockingContext.php +++ b/tests/acceptance/features/bootstrap/WebDavLockingContext.php @@ -25,6 +25,8 @@ use Behat\Gherkin\Node\TableNode; use GuzzleHttp\Exception\ConnectException; use PHPUnit\Framework\Assert; +use TestHelpers\HttpRequestHelper; +use TestHelpers\OcsApiHelper; use TestHelpers\WebDavHelper; require_once 'bootstrap.php'; @@ -572,6 +574,56 @@ public function numberOfLockShouldBeReported($count, $file, $user) { ); } + /** + * @Then group :expectedGroup should exist as a lock breaker group + * + * @param string $expectedGroup + * + * @return void + * + * @throws Exception + */ + public function groupShouldExistAsLockBreakerGroups($expectedGroup) { + $baseUrl = $this->featureContext->getBaseUrl(); + $admin = $this->featureContext->getAdminUsername(); + $password = $this->featureContext->getAdminPassword(); + $ocsApiVersion = $this->featureContext->getOcsApiVersion(); + + $response = OcsApiHelper::sendRequest( + $baseUrl, + $admin, + $password, + 'GET', + "/apps/testing/api/v1/app/core/lock-breaker-groups", + $ocsApiVersion + ); + + $responseXml = HttpRequestHelper::getResponseXml($response, __METHOD__)->data->element; + $lockbreakergroup = trim(\json_decode(\json_encode($responseXml), true)['value'], '\'[]"'); + $actualgroup = explode("\",\"", $lockbreakergroup); + if (!\in_array($expectedGroup, $actualgroup)) { + Assert::fail("could not find group '$expectedGroup' in lock breakers group"); + } + } + + /** + * @Then following groups should exist as lock breaker groups + * + * @param TableNode $table + * + * @return void + * + * @throws Exception + */ + public function followingGroupShouldExistAsLockBreakerGroups(TableNode $table) { + $this->featureContext->verifyTableNodeColumns($table, ["groups"]); + $paths = $table->getHash(); + + foreach ($paths as $group) { + $this->groupShouldExistAsLockBreakerGroups($group["groups"]); + } + } + /** * This will run before EVERY scenario. * It will set the properties for this object. diff --git a/tests/acceptance/features/bootstrap/WebUIAdminGeneralSettingsContext.php b/tests/acceptance/features/bootstrap/WebUIAdminGeneralSettingsContext.php index 29edb56e3369..6e700f24b080 100644 --- a/tests/acceptance/features/bootstrap/WebUIAdminGeneralSettingsContext.php +++ b/tests/acceptance/features/bootstrap/WebUIAdminGeneralSettingsContext.php @@ -156,6 +156,49 @@ public function theAdministratorSetsTheLogLevelUsingTheWebui($logLevel) { $this->adminGeneralSettingsPage->setLogLevel($logLevel); } + /** + * @When the administrator adds group :lockBreakerGroup to the lock breakers groups using the webUI + * + * @param string $lockBreakerGroup + * + * @return void + */ + public function theAdministratorAddsGroupToLockBreakersGroupUsingTheWebui($lockBreakerGroup) { + $this->adminGeneralSettingsPage-> addGroupLockBreakersGroup( + $this->getSession(), + $lockBreakerGroup + ); + } + + /** + * @Then group :expectedGroup should be listed in the lock breakers groups on the webUI + * + * @param $expectedGroup + * + * @return void + */ + public function groupShouldBeListedAsLockBreakersGroupInTheWebui($expectedGroup) { + $actualGroup = $this->adminGeneralSettingsPage-> getLockBreakersGroups(); + if (!\in_array($expectedGroup, $actualGroup)) { + Assert::assertFalse( + "$expectedGroup should be present in lock breakers groups, but it isn't" + ); + } + } + + /** + * @Then the following groups should be listed in the lock breakers groups on the webUI + * + * @param TableNode $table + * + * @return void + */ + public function followingGroupsShouldBeListedInTheLockBreakersGroupsInTheWebui(TableNode $table) { + foreach ($table as $row) { + $this->groupShouldBeListedAsLockBreakersGroupInTheWebui($row["groups"]); + } + } + /** * This will run before EVERY scenario. * It will set the properties for this object. diff --git a/tests/acceptance/features/lib/AdminGeneralSettingsPage.php b/tests/acceptance/features/lib/AdminGeneralSettingsPage.php index 9425d1d20c03..dfc28798b13f 100644 --- a/tests/acceptance/features/lib/AdminGeneralSettingsPage.php +++ b/tests/acceptance/features/lib/AdminGeneralSettingsPage.php @@ -60,6 +60,9 @@ class AdminGeneralSettingsPage extends OwncloudPage { protected $logLevelId = 'loglevel'; + protected $lockBreakerXpath = '//div[@id="persistentlocking"]/p[@id="lock-breakers"]/div//li[contains(@class, "select2-search-field")]'; + protected $lockBreakerGroups = '//div[@id="persistentlocking"]/p[@id="lock-breakers"]/div//li[contains(@class, "select2-search-choice")]'; + protected $ownCloudVersionXpath = '//td[text() = "version"]/following-sibling::td'; protected $ownCloudVersionStringXpath = '//td[text() = "versionstring"]/following-sibling::td'; @@ -300,4 +303,41 @@ public function waitTillPageIsLoaded( $timeout_msec ); } + + /** + * add group to lock breakers group + * + * @param Session $session + * @param string $groupName + * + * @return void + */ + public function addGroupLockBreakersGroup( + Session $session, + $groupName + ) { + $this->getPage('AdminSharingSettingsPage')->addGroupToInputField($groupName, $this->lockBreakerXpath); + $this->waitForAjaxCallsToStartAndFinish($session); + } + + /** + * get lock breakers group + * + * @return array + */ + public function getLockBreakersGroups() { + $this->waitTillElementIsNotNull($this->lockBreakerGroups); + $groupList = $this->findAll("xpath", $this->lockBreakerGroups); + $this->assertElementNotNull( + $groupList, + __METHOD__ . + " xpath $this->lockBreakerGroups " . + "could not find xpath for lock breaker groups" + ); + + foreach ($groupList as $group) { + $groups[] = $this->getTrimmedText($group); + } + return $groups; + } } diff --git a/tests/acceptance/features/lib/AdminSharingSettingsPage.php b/tests/acceptance/features/lib/AdminSharingSettingsPage.php index f195a09bfbf8..d9a2ce94fbd3 100644 --- a/tests/acceptance/features/lib/AdminSharingSettingsPage.php +++ b/tests/acceptance/features/lib/AdminSharingSettingsPage.php @@ -710,6 +710,7 @@ public function addGroupToInputField($groupName, $fieldXpath) { foreach ($groupList as $group) { if ($this->getTrimmedText($group) === $groupName) { $group->click(); + break; } } } diff --git a/tests/acceptance/features/webUIWebdavLocks/lockBreakerGroup.feature b/tests/acceptance/features/webUIWebdavLocks/lockBreakerGroup.feature new file mode 100644 index 000000000000..3bf35be5cf0e --- /dev/null +++ b/tests/acceptance/features/webUIWebdavLocks/lockBreakerGroup.feature @@ -0,0 +1,121 @@ +@webUI @insulated @disablePreviews +Feature: Unlock locked files and folders + As a member of a lock breaker group + I would like to be able to unlock files and folders + So that I can access them + + + Scenario: Add a group to lock breakers group + Given group "grp1" has been created + And the administrator has browsed to the admin general settings page + When the administrator adds group "grp1" to the lock breakers groups using the webUI + Then group "grp1" should be listed in the lock breakers groups on the webUI + And group "grp1" should exist as a lock breaker group + + + Scenario: Add multiple groups to lock breakers group + Given group "grp1" has been created + And group "grp2" has been created + And group "grp3" has been created + And the administrator has browsed to the admin general settings page + When the administrator adds group "grp1" to the lock breakers groups using the webUI + And the administrator adds group "grp2" to the lock breakers groups using the webUI + Then the following groups should be listed in the lock breakers groups on the webUI + | groups | + | grp1 | + | grp2 | + And following groups should exist as lock breaker groups + | groups | + | grp1 | + | grp2 | + + + Scenario Outline: members from lock breaker groups can unlock a locked folder/file shared to them + Given group "grp1" has been created + And user "Alice" has been created with default attributes and without skeleton files + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "simple-folder" + And user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/lorem.txt" + And parameter "lock-breaker-groups" of app "core" has been set to '["grp1"]' + And user "Brian" has been added to group "grp1" + And user "Alice" has locked folder "simple-folder" setting the following properties + | lockscope | | + And user "Alice" has locked file "lorem.txt" setting the following properties + | lockscope | | + And user "Alice" has shared folder "simple-folder" with user "Brian" + And user "Alice" has shared file "lorem.txt" with user "Brian" + And user "Brian" has logged in using the webUI + When the user unlocks the lock no 1 of file "lorem.txt" on the webUI + And the user unlocks the lock no 1 of folder "simple-folder" on the webUI + And the user reloads the current page of the webUI + Then file "lorem.txt" should not be marked as locked on the webUI + And folder "simple-folder" should not be marked as locked on the webUI + And 0 locks should be reported for file "lorem.txt" of user "Brian" by the WebDAV API + And 0 locks should be reported for file "lorem.txt" of user "Alice" by the WebDAV API + And 0 locks should be reported for folder "simple-folder" of user "Brain" by the WebDAV API + And 0 locks should be reported for folder "simple-folder" of user "Alice" by the WebDAV API + Examples: + | lockscope | + | exclusive | + | shared | + + + Scenario Outline: as a member of lock breaker group unlocking a folder/file in a share locked by the folder owner is possible + Given group "grp1" has been created + And user "Alice" has been created with default attributes and without skeleton files + And user "Brian" has been created with default attributes and without skeleton files + And user "Alice" has created folder "simple-folder" + And user "Alice" has created folder "simple-folder/sub-folder" + And user "Alice" has uploaded file "filesForUpload/lorem.txt" to "simple-folder/lorem.txt" + And parameter "lock-breaker-groups" of app "core" has been set to '["grp1"]' + And user "Brian" has been added to group "grp1" + And user "Alice" has locked folder "simple-folder/sub-folder" setting the following properties + | lockscope | | + And user "Alice" has locked file "simple-folder/lorem.txt" setting the following properties + | lockscope | | + And user "Alice" has shared folder "simple-folder" with user "Brian" + And user "Brian" has logged in using the webUI + When the user opens folder "simple-folder" using the webUI + And the user unlocks the lock no 1 of file "lorem.txt" on the webUI + And the user unlocks the lock no 1 of folder "sub-folder" on the webUI + And the user reloads the current page of the webUI + Then file "lorem.txt" should not be marked as locked on the webUI + And folder "sub-folder" should not be marked as locked on the webUI + And 0 locks should be reported for folder "simple-folder" of user "Brain" by the WebDAV API + And 0 locks should be reported for folder "simple-folder" of user "Alice" by the WebDAV API + Examples: + | lockscope | + | exclusive | + | shared | + + + Scenario Outline: user that isn't member of lock breakers group cannot unlock a file/folder shared to them + Given group "grp1" has been created + And parameter "lock-breaker-groups" of app "core" has been set to '["grp1"]' + And these users have been created without skeleton files: + | username | + | Alice | + | Brian | + And user "Alice" has been added to group "grp1" + And user "Alice" has created folder "FOLDER" + And user "Alice" has uploaded file "filesForUpload/lorem.txt" to "/lorem.txt" + And user "Alice" has locked folder "FOLDER" setting the following properties + | lockscope | | + And user "Alice" has locked file "lorem.txt" setting the following properties + | lockscope | | + And user "Alice" has shared folder "FOLDER" with user "Brian" + And user "Alice" has shared file "lorem.txt" with user "Brian" + And user "Brian" has logged in using the webUI + When the user unlocks the lock no 1 of folder "FOLDER" on the webUI + Then notifications should be displayed on the webUI with the text + | Could not unlock, please contact the lock owner Alice | + And the user unlocks the lock no 1 of file "lorem.txt" on the webUI + And notifications should be displayed on the webUI with the text + | Could not unlock, please contact the lock owner Alice | + And the user reloads the current page of the webUI + And file "lorem.txt" should be marked as locked on the webUI + And folder "FOLDER" should be marked as locked on the webUI + Examples: + | lockscope | + | exclusive | + | shared | From 70d498ab56152fd9275c9055e1fb5c17a0ea5480 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Mon, 16 Aug 2021 18:58:46 +0545 Subject: [PATCH 3/3] skip new lock-breaker-groups tests on old oC10 --- .../features/apiWebdavLocksUnlock/lockBreakersGroups.feature | 2 +- .../features/webUIWebdavLocks/lockBreakerGroup.feature | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/acceptance/features/apiWebdavLocksUnlock/lockBreakersGroups.feature b/tests/acceptance/features/apiWebdavLocksUnlock/lockBreakersGroups.feature index 55287a0d2b38..2131d1d7f8a2 100644 --- a/tests/acceptance/features/apiWebdavLocksUnlock/lockBreakersGroups.feature +++ b/tests/acceptance/features/apiWebdavLocksUnlock/lockBreakersGroups.feature @@ -1,4 +1,4 @@ -@api +@api @skipOnOcV10.7 @skipOnOcV10.8.0 Feature: UNLOCK locked items Background: diff --git a/tests/acceptance/features/webUIWebdavLocks/lockBreakerGroup.feature b/tests/acceptance/features/webUIWebdavLocks/lockBreakerGroup.feature index 3bf35be5cf0e..25482eba4700 100644 --- a/tests/acceptance/features/webUIWebdavLocks/lockBreakerGroup.feature +++ b/tests/acceptance/features/webUIWebdavLocks/lockBreakerGroup.feature @@ -1,4 +1,4 @@ -@webUI @insulated @disablePreviews +@webUI @insulated @disablePreviews @skipOnOcV10.7 @skipOnOcV10.8.0 Feature: Unlock locked files and folders As a member of a lock breaker group I would like to be able to unlock files and folders