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);
}
}
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..2131d1d7f8a2
--- /dev/null
+++ b/tests/acceptance/features/apiWebdavLocksUnlock/lockBreakersGroups.feature
@@ -0,0 +1,314 @@
+@api @skipOnOcV10.7 @skipOnOcV10.8.0
+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..25482eba4700
--- /dev/null
+++ b/tests/acceptance/features/webUIWebdavLocks/lockBreakerGroup.feature
@@ -0,0 +1,121 @@
+@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
+ 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 |