When moving is allowed but deleting is not, do not restore moved items
authorOlivier Goffart <ogoffart@woboq.com>
Thu, 10 Oct 2019 11:24:24 +0000 (13:24 +0200)
committerKevin Ottens <kevin.ottens@nextcloud.com>
Tue, 15 Dec 2020 09:59:01 +0000 (10:59 +0100)
Issue #7293

src/libsync/discovery.cpp
src/libsync/discoveryphase.cpp
test/testpermissions.cpp

index d2d562d5a1d29f1f46c994d11d54b72c8705943f..b018c1a8ce036f7ef6299e2b7a1e18e7835d6624 100644 (file)
@@ -1108,6 +1108,7 @@ void ProcessDirectoryJob::processFileFinalize(
 
     if (item->isDirectory() && item->_instruction == CSYNC_INSTRUCTION_SYNC)
         item->_instruction = CSYNC_INSTRUCTION_UPDATE_METADATA;
+    bool removed = item->_instruction == CSYNC_INSTRUCTION_REMOVE;
     if (checkPermissions(item)) {
         if (item->_isRestoration && item->isDirectory())
             recurse = true;
@@ -1116,7 +1117,7 @@ void ProcessDirectoryJob::processFileFinalize(
     }
     if (recurse) {
         auto job = new ProcessDirectoryJob(path, item, recurseQueryLocal, recurseQueryServer, this);
-        if (item->_instruction == CSYNC_INSTRUCTION_REMOVE) {
+        if (removed) {
             job->setParent(_discoveryData);
             _discoveryData->_queuedDeletedDirectories[path._original] = job;
         } else {
@@ -1124,7 +1125,7 @@ void ProcessDirectoryJob::processFileFinalize(
             _queuedJobs.push_back(job);
         }
     } else {
-        if (item->_instruction == CSYNC_INSTRUCTION_REMOVE
+        if (removed
             // For the purpose of rename deletion, restored deleted placeholder is as if it was deleted
             || (item->_type == ItemTypeVirtualFile && item->_instruction == CSYNC_INSTRUCTION_NEW)) {
             _discoveryData->_deletedItem[path._original] = item;
index ca0322c5dc4261ceea766c075d6853c67d2f2d2b..3cd1f3e3410908078c567f577cc5b1c15bb2c9ec 100644 (file)
@@ -161,7 +161,9 @@ QPair<bool, QByteArray> DiscoveryPhase::findAndCancelDeletedJob(const QString &o
     if (it != _deletedItem.end()) {
         ENFORCE((*it)->_instruction == CSYNC_INSTRUCTION_REMOVE
             // re-creation of virtual files count as a delete
-             || ((*it)->_type == ItemTypeVirtualFile && (*it)->_instruction == CSYNC_INSTRUCTION_NEW));
+             || ((*it)->_type == ItemTypeVirtualFile && (*it)->_instruction == CSYNC_INSTRUCTION_NEW)
+             || ((*it)->_isRestoration && (*it)->_instruction == CSYNC_INSTRUCTION_NEW)
+               );
         (*it)->_instruction = CSYNC_INSTRUCTION_NONE;
         result = true;
         oldEtag = (*it)->_etag;
index 10494d3b2a5f67dc476d2339f89cb504ad397906..8fdcc62de6dbcda94e5b5e016fe3610a34c72a88 100644 (file)
@@ -465,6 +465,50 @@ private slots:
         QVERIFY(cls.find("zallowed/sub2"));
         QVERIFY(cls.find("zallowed/sub2/file"));
     }
+
+    // Test for issue #7293
+    void testAllowedMoveForbiddenDelete() {
+         FakeFolder fakeFolder{FileInfo{}};
+
+        // Some of this test depends on the order of discovery. With threading
+        // that order becomes effectively random, but we want to make sure to test
+        // all cases and thus disable threading.
+        auto syncOpts = fakeFolder.syncEngine().syncOptions();
+        syncOpts._parallelNetworkJobs = 1;
+        fakeFolder.syncEngine().setSyncOptions(syncOpts);
+
+        auto &lm = fakeFolder.localModifier();
+        auto &rm = fakeFolder.remoteModifier();
+        rm.mkdir("changeonly");
+        rm.mkdir("changeonly/sub1");
+        rm.insert("changeonly/sub1/file1");
+        rm.insert("changeonly/sub1/filetorname1a");
+        rm.insert("changeonly/sub1/filetorname1z");
+        rm.mkdir("changeonly/sub2");
+        rm.insert("changeonly/sub2/file2");
+        rm.insert("changeonly/sub2/filetorname2a");
+        rm.insert("changeonly/sub2/filetorname2z");
+
+        setAllPerm(rm.find("changeonly"), RemotePermissions::fromServerString("NSV"));
+
+        QVERIFY(fakeFolder.syncOnce());
+
+        lm.rename("changeonly/sub1/filetorname1a", "changeonly/sub1/aaa1_renamed");
+        lm.rename("changeonly/sub1/filetorname1z", "changeonly/sub1/zzz1_renamed");
+
+        lm.rename("changeonly/sub2/filetorname2a", "changeonly/sub2/aaa2_renamed");
+        lm.rename("changeonly/sub2/filetorname2z", "changeonly/sub2/zzz2_renamed");
+
+        lm.rename("changeonly/sub1", "changeonly/aaa");
+        lm.rename("changeonly/sub2", "changeonly/zzz");
+
+
+        auto expectedState = fakeFolder.currentLocalState();
+
+        QVERIFY(fakeFolder.syncOnce());
+        QCOMPARE(fakeFolder.currentLocalState(), expectedState);
+        QCOMPARE(fakeFolder.currentRemoteState(), expectedState);
+    }
 };
 
 QTEST_GUILESS_MAIN(TestPermissions)