/* * This software is in the public domain, furnished "as is", without technical * support, and with no warranty, express or implied, as to its usefulness for * any purpose. * */ #include #include "testutils/syncenginetestutils.h" #include #include "common/ownsql.h" using namespace OCC; static void applyPermissionsFromName(FileInfo &info) { static QRegularExpression rx(QStringLiteral("_PERM_([^_]*)_[^/]*$")); auto m = rx.match(info.name); if (m.hasMatch()) { info.permissions = RemotePermissions::fromServerString(m.captured(1)); } for (FileInfo &sub : info.children) applyPermissionsFromName(sub); } // Check if the expected rows in the DB are non-empty. Note that in some cases they might be, then we cannot use this function // https://github.com/owncloud/client/issues/2038 static void assertCsyncJournalOk(SyncJournalDb &journal) { // The DB is openend in locked mode: close to allow us to access. journal.close(); SqlDatabase db; QVERIFY(db.openReadOnly(journal.databaseFilePath())); SqlQuery q("SELECT count(*) from metadata where length(fileId) == 0", db); QVERIFY(q.exec()); QVERIFY(q.next().hasData); QCOMPARE(q.intValue(0), 0); #if defined(Q_OS_WIN) // Make sure the file does not appear in the FileInfo FileSystem::setFileHidden(journal.databaseFilePath() + QStringLiteral("-shm"), true); #endif journal.allowReopen(); } SyncFileItemPtr findDiscoveryItem(const SyncFileItemSet &spy, const QString &path) { for (const auto &item : spy) { if (item->destination() == path) return item; } return SyncFileItemPtr(new SyncFileItem); } bool itemInstruction(const ItemCompletedSpy &spy, const QString &path, const SyncInstructions instr) { auto item = spy.findItem(path); Q_ASSERT(!item.isNull()); return item->instruction() == instr; } bool discoveryInstruction(const SyncFileItemSet &spy, const QString &path, const SyncInstructions instr) { auto item = findDiscoveryItem(spy, path); return item->instruction() == instr; } class TestPermissions : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase_data() { QTest::addColumn("vfsMode"); QTest::addColumn("filesAreDehydrated"); QTest::newRow("Vfs::Mode::Off") << Vfs::Mode::Off << false; if (VfsPluginManager::instance().isVfsPluginAvailable(Vfs::Mode::WindowsCfApi)) { QTest::newRow("Vfs::Mode::WindowsCfApi dehydrated") << Vfs::Mode::WindowsCfApi << true; // TODO: the hydrated version will fail due to an issue in the winvfs plugin, so leave it disabled for now. // QTest::newRow("Vfs::Mode::WindowsCfApi hydrated") << Vfs::Mode::WindowsCfApi << false; } else if (Utility::isWindows()) { qWarning("Skipping Vfs::Mode::WindowsCfApi"); } } void t7pl() { QFETCH_GLOBAL(Vfs::Mode, vfsMode); QFETCH_GLOBAL(bool, filesAreDehydrated); if (vfsMode == Vfs::Mode::WindowsCfApi) { QSKIP("Known issue: the winvfs plug-in fails to set file permissions"); } FakeFolder fakeFolder(FileInfo(), vfsMode, filesAreDehydrated); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); // 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 = [] { return 1; }; fakeFolder.syncEngine().setSyncOptions(syncOpts); const auto cannotBeModifiedSize = 133_B; const auto canBeModifiedSize = 144_B; //create some files auto insertIn = [&](const QString &dir) { fakeFolder.remoteModifier().insert(dir + QStringLiteral("normalFile_PERM_WVND_.data"), 100_B); fakeFolder.remoteModifier().insert(dir + QStringLiteral("cannotBeRemoved_PERM_WVN_.data"), 101_B); fakeFolder.remoteModifier().insert(dir + QStringLiteral("canBeRemoved_PERM_D_.data"), 102_B); fakeFolder.remoteModifier().insert(dir + QStringLiteral("cannotBeModified_PERM_DVN_.data"), cannotBeModifiedSize, 'A'); fakeFolder.remoteModifier().insert(dir + QStringLiteral("canBeModified_PERM_W_.data"), canBeModifiedSize); }; //put them in some directories fakeFolder.remoteModifier().mkdir(QStringLiteral("normalDirectory_PERM_CKDNV_")); insertIn(QStringLiteral("normalDirectory_PERM_CKDNV_/")); fakeFolder.remoteModifier().mkdir(QStringLiteral("readonlyDirectory_PERM_M_")); insertIn(QStringLiteral("readonlyDirectory_PERM_M_/")); fakeFolder.remoteModifier().mkdir(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_")); fakeFolder.remoteModifier().mkdir(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_")); fakeFolder.remoteModifier().insert(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data"), 100_B); applyPermissionsFromName(fakeFolder.remoteModifier()); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); assertCsyncJournalOk(fakeFolder.syncJournal()); qInfo("Do some changes and see how they propagate"); //1. remove the file than cannot be removed // (they should be recovered) fakeFolder.localModifier().remove(QStringLiteral("normalDirectory_PERM_CKDNV_/cannotBeRemoved_PERM_WVN_.data")); fakeFolder.localModifier().remove(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeRemoved_PERM_WVN_.data")); //2. remove the file that can be removed // (they should properly be gone) auto removeReadOnly = [&] (const QString &file) { QVERIFY(!QFileInfo(fakeFolder.localPath() + file).permission(QFile::WriteOwner)); QFile(fakeFolder.localPath() + file).setPermissions(QFile::WriteOwner | QFile::ReadOwner); fakeFolder.localModifier().remove(file); }; removeReadOnly(QStringLiteral("normalDirectory_PERM_CKDNV_/canBeRemoved_PERM_D_.data")); removeReadOnly(QStringLiteral("readonlyDirectory_PERM_M_/canBeRemoved_PERM_D_.data")); //3. Edit the files that cannot be modified // (they should be recovered, and a conflict shall be created) auto editReadOnly = [&] (const QString &file) { QVERIFY(!QFileInfo(fakeFolder.localPath() + file).permission(QFile::WriteOwner)); QFile(fakeFolder.localPath() + file).setPermissions(QFile::WriteOwner | QFile::ReadOwner); fakeFolder.localModifier().appendByte(file); }; editReadOnly(QStringLiteral("normalDirectory_PERM_CKDNV_/cannotBeModified_PERM_DVN_.data")); editReadOnly(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data")); //4. Edit other files // (they should be uploaded) fakeFolder.localModifier().appendByte(QStringLiteral("normalDirectory_PERM_CKDNV_/canBeModified_PERM_W_.data")); fakeFolder.localModifier().appendByte(QStringLiteral("readonlyDirectory_PERM_M_/canBeModified_PERM_W_.data")); //5. Create a new file in a read write folder // (should be uploaded) fakeFolder.localModifier().insert(QStringLiteral("normalDirectory_PERM_CKDNV_/newFile_PERM_WDNV_.data"), 106_B); applyPermissionsFromName(fakeFolder.remoteModifier()); //do the sync QVERIFY(fakeFolder.applyLocalModificationsAndSync()); assertCsyncJournalOk(fakeFolder.syncJournal()); auto currentLocalState = fakeFolder.currentLocalState(); //1. // File should be recovered QVERIFY(currentLocalState.find(QStringLiteral("normalDirectory_PERM_CKDNV_/cannotBeRemoved_PERM_WVN_.data"))); QVERIFY(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeRemoved_PERM_WVN_.data"))); //2. // File should be deleted QVERIFY(!currentLocalState.find(QStringLiteral("normalDirectory_PERM_CKDNV_/canBeRemoved_PERM_D_.data"))); QVERIFY(!currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/canBeRemoved_PERM_D_.data"))); //3. // File should be recovered QCOMPARE(currentLocalState.find(QStringLiteral("normalDirectory_PERM_CKDNV_/cannotBeModified_PERM_DVN_.data"))->contentSize, cannotBeModifiedSize); QCOMPARE(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data"))->contentSize, cannotBeModifiedSize); // and conflict created auto c1 = findConflict(currentLocalState, QStringLiteral("normalDirectory_PERM_CKDNV_/cannotBeModified_PERM_DVN_.data")); QVERIFY(c1); QCOMPARE(c1->contentSize, cannotBeModifiedSize + 1); auto c2 = findConflict(currentLocalState, QStringLiteral("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data")); QVERIFY(c2); QCOMPARE(c2->contentSize, cannotBeModifiedSize + 1); // remove the conflicts for the next state comparison fakeFolder.localModifier().remove(c1->path()); fakeFolder.localModifier().remove(c2->path()); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); //4. File should be updated, that's tested by assertLocalAndRemoteDir QCOMPARE(currentLocalState.find(QStringLiteral("normalDirectory_PERM_CKDNV_/canBeModified_PERM_W_.data"))->contentSize, canBeModifiedSize + 1); QCOMPARE(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/canBeModified_PERM_W_.data"))->contentSize, canBeModifiedSize + 1); //5. // the file should be in the server and local QVERIFY(currentLocalState.find(QStringLiteral("normalDirectory_PERM_CKDNV_/newFile_PERM_WDNV_.data"))); // Both side should still be the same QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); // Next test //6. Create a new file in a read only folder // (they should not be uploaded) fakeFolder.localModifier().insert(QStringLiteral("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data"), 105_B); applyPermissionsFromName(fakeFolder.remoteModifier()); // error: can't upload to readonly QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); assertCsyncJournalOk(fakeFolder.syncJournal()); currentLocalState = fakeFolder.currentLocalState(); //6. // The file should not exist on the remote, but still be there QVERIFY(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data"))); QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data"))); // remove it so next test succeed. fakeFolder.localModifier().remove(QStringLiteral("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data")); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); // Both side should still be the same QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); //###################################################################### qInfo( "remove the read only directory" ); // -> It must be recovered fakeFolder.localModifier().remove(QStringLiteral("readonlyDirectory_PERM_M_")); applyPermissionsFromName(fakeFolder.remoteModifier()); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); assertCsyncJournalOk(fakeFolder.syncJournal()); currentLocalState = fakeFolder.currentLocalState(); QVERIFY(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeRemoved_PERM_WVN_.data"))); QVERIFY(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_"))); // the subdirectory had delete permissions, so the contents were deleted QVERIFY(!currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_"))); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); // restore fakeFolder.remoteModifier().mkdir(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_")); fakeFolder.remoteModifier().insert(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data")); applyPermissionsFromName(fakeFolder.remoteModifier()); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); //###################################################################### qInfo( "move a directory in a outside read only folder" ); //Missing directory should be restored //new directory should be uploaded fakeFolder.localModifier().rename(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_"), QStringLiteral("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_")); applyPermissionsFromName(fakeFolder.remoteModifier()); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); currentLocalState = fakeFolder.currentLocalState(); // old name restored QVERIFY(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_"))); // contents moved (had move permissions) QVERIFY(!currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_"))); QVERIFY(!currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data"))); // new still exist (and is uploaded) QVERIFY(currentLocalState.find(QStringLiteral("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data"))); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); // restore for further tests fakeFolder.remoteModifier().mkdir(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_")); fakeFolder.remoteModifier().insert(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data")); applyPermissionsFromName(fakeFolder.remoteModifier()); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); //###################################################################### qInfo( "rename a directory in a read only folder and move a directory to a read-only" ); // do a sync to update the database applyPermissionsFromName(fakeFolder.remoteModifier()); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); assertCsyncJournalOk(fakeFolder.syncJournal()); QVERIFY( fakeFolder.currentLocalState().find(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data"))); //1. rename a directory in a read only folder //Missing directory should be restored //new directory should stay but not be uploaded fakeFolder.localModifier().rename(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_"), QStringLiteral("readonlyDirectory_PERM_M_/newname_PERM_CK_")); //2. move a directory from read to read only (move the directory from previous step) fakeFolder.localModifier().rename(QStringLiteral("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_"), QStringLiteral("readonlyDirectory_PERM_M_/moved_PERM_CK_")); // error: can't upload to readonly! QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); currentLocalState = fakeFolder.currentLocalState(); //1. // old name restored QVERIFY(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_"))); // including contents QVERIFY(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data"))); // new still exist QVERIFY(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/newname_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data"))); // but is not on server: so remove it localy for the future comarison fakeFolder.localModifier().remove(QStringLiteral("readonlyDirectory_PERM_M_/newname_PERM_CK_")); //2. // old removed QVERIFY(!currentLocalState.find(QStringLiteral("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_"))); // but still on the server: the rename causing an error meant the deletes didn't execute QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_"))); // new still there QVERIFY(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/moved_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data"))); //but not on server fakeFolder.localModifier().remove(QStringLiteral("readonlyDirectory_PERM_M_/moved_PERM_CK_")); fakeFolder.remoteModifier().remove(QStringLiteral("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_")); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); //###################################################################### qInfo( "multiple restores of a file create different conflict files" ); fakeFolder.remoteModifier().insert(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data")); applyPermissionsFromName(fakeFolder.remoteModifier()); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); editReadOnly(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data")); fakeFolder.localModifier().setContents(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data"), FileModifier::DefaultFileSize, 's'); //do the sync applyPermissionsFromName(fakeFolder.remoteModifier()); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); assertCsyncJournalOk(fakeFolder.syncJournal()); QThread::sleep(1); // make sure changes have different mtime editReadOnly(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data")); fakeFolder.localModifier().setContents(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data"), FileModifier::DefaultFileSize, 'd'); //do the sync applyPermissionsFromName(fakeFolder.remoteModifier()); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); assertCsyncJournalOk(fakeFolder.syncJournal()); // there should be two conflict files currentLocalState = fakeFolder.currentLocalState(); int count = 0; while (auto i = findConflict(currentLocalState, QStringLiteral("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data"))) { QVERIFY((i->contentChar == 's') || (i->contentChar == 'd')); fakeFolder.localModifier().remove(i->path()); QVERIFY(fakeFolder.applyLocalModificationsWithoutSync()); currentLocalState = fakeFolder.currentLocalState(); count++; } QCOMPARE(count, 2); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } static void setAllPerm(FileInfo *fi, RemotePermissions perm) { fi->permissions = perm; for (auto &subFi : fi->children) setAllPerm(&subFi, perm); } // What happens if the source can't be moved or the target can't be created? void testForbiddenMoves() { QFETCH_GLOBAL(Vfs::Mode, vfsMode); QFETCH_GLOBAL(bool, filesAreDehydrated); if (vfsMode == Vfs::Mode::WindowsCfApi) { QSKIP("Known issue: the winvfs plug-in fails to set file permissions"); } FakeFolder fakeFolder(FileInfo(), vfsMode, filesAreDehydrated); // 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 = [] { return 1; }; fakeFolder.syncEngine().setSyncOptions(syncOpts); auto &lm = fakeFolder.localModifier(); auto &rm = fakeFolder.remoteModifier(); rm.mkdir(QStringLiteral("allowed")); rm.mkdir(QStringLiteral("norename")); rm.mkdir(QStringLiteral("nomove")); rm.mkdir(QStringLiteral("nocreatefile")); rm.mkdir(QStringLiteral("nocreatedir")); rm.mkdir(QStringLiteral("zallowed")); // order of discovery matters rm.mkdir(QStringLiteral("allowed/sub")); rm.mkdir(QStringLiteral("allowed/sub2")); rm.insert(QStringLiteral("allowed/file")); rm.insert(QStringLiteral("allowed/sub/file")); rm.insert(QStringLiteral("allowed/sub2/file")); rm.mkdir(QStringLiteral("norename/sub")); rm.insert(QStringLiteral("norename/file")); rm.insert(QStringLiteral("norename/sub/file")); rm.mkdir(QStringLiteral("nomove/sub")); rm.insert(QStringLiteral("nomove/file")); rm.insert(QStringLiteral("nomove/sub/file")); rm.mkdir(QStringLiteral("zallowed/sub")); rm.mkdir(QStringLiteral("zallowed/sub2")); rm.insert(QStringLiteral("zallowed/file")); rm.insert(QStringLiteral("zallowed/sub/file")); rm.insert(QStringLiteral("zallowed/sub2/file")); setAllPerm(rm.find(QStringLiteral("norename")), RemotePermissions::fromServerString(QStringLiteral("WDVCK"))); setAllPerm(rm.find(QStringLiteral("nomove")), RemotePermissions::fromServerString(QStringLiteral("WDNCK"))); setAllPerm(rm.find(QStringLiteral("nocreatefile")), RemotePermissions::fromServerString(QStringLiteral("WDNVK"))); setAllPerm(rm.find(QStringLiteral("nocreatedir")), RemotePermissions::fromServerString(QStringLiteral("WDNVC"))); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); // Renaming errors lm.rename(QStringLiteral("norename/file"), QStringLiteral("norename/file_renamed")); lm.rename(QStringLiteral("norename/sub"), QStringLiteral("norename/sub_renamed")); // Moving errors lm.rename(QStringLiteral("nomove/file"), QStringLiteral("allowed/file_moved")); lm.rename(QStringLiteral("nomove/sub"), QStringLiteral("allowed/sub_moved")); // Createfile errors lm.rename(QStringLiteral("allowed/file"), QStringLiteral("nocreatefile/file")); lm.rename(QStringLiteral("zallowed/file"), QStringLiteral("nocreatefile/zfile")); lm.rename(QStringLiteral("allowed/sub"), QStringLiteral("nocreatefile/sub")); // TODO: probably forbidden because it contains file children? // Createdir errors lm.rename(QStringLiteral("allowed/sub2"), QStringLiteral("nocreatedir/sub2")); lm.rename(QStringLiteral("zallowed/sub2"), QStringLiteral("nocreatedir/zsub2")); // also hook into discovery!! SyncFileItemSet discovery; connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToPropagate, this, [&discovery](auto v) { discovery = v; }); ItemCompletedSpy completeSpy(fakeFolder); QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); // if renaming doesn't work, just delete+create QVERIFY(itemInstruction(completeSpy, QStringLiteral("norename/file"), CSYNC_INSTRUCTION_REMOVE)); QVERIFY(itemInstruction(completeSpy, QStringLiteral("norename/sub"), CSYNC_INSTRUCTION_REMOVE)); QVERIFY(discoveryInstruction(discovery, QStringLiteral("norename/sub"), CSYNC_INSTRUCTION_REMOVE)); QVERIFY(itemInstruction(completeSpy, QStringLiteral("norename/file_renamed"), CSYNC_INSTRUCTION_NEW)); QVERIFY(itemInstruction(completeSpy, QStringLiteral("norename/sub_renamed"), CSYNC_INSTRUCTION_NEW)); // the contents can _move_ QVERIFY(itemInstruction(completeSpy, QStringLiteral("norename/sub_renamed/file"), CSYNC_INSTRUCTION_RENAME)); // simiilarly forbidding moves becomes delete+create QVERIFY(itemInstruction(completeSpy, QStringLiteral("nomove/file"), CSYNC_INSTRUCTION_REMOVE)); QVERIFY(itemInstruction(completeSpy, QStringLiteral("nomove/sub"), CSYNC_INSTRUCTION_REMOVE)); QVERIFY(discoveryInstruction(discovery, QStringLiteral("nomove/sub"), CSYNC_INSTRUCTION_REMOVE)); // nomove/sub/file is removed as part of the dir QVERIFY(itemInstruction(completeSpy, QStringLiteral("allowed/file_moved"), CSYNC_INSTRUCTION_NEW)); QVERIFY(itemInstruction(completeSpy, QStringLiteral("allowed/sub_moved"), CSYNC_INSTRUCTION_NEW)); QVERIFY(itemInstruction(completeSpy, QStringLiteral("allowed/sub_moved/file"), CSYNC_INSTRUCTION_NEW)); // when moving to an invalid target, the targets should be an error QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatefile/file"), CSYNC_INSTRUCTION_ERROR)); QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatefile/zfile"), CSYNC_INSTRUCTION_ERROR)); QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatefile/sub"), CSYNC_INSTRUCTION_RENAME)); // TODO: What does a real server say? QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatedir/sub2"), CSYNC_INSTRUCTION_ERROR)); QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatedir/zsub2"), CSYNC_INSTRUCTION_ERROR)); // and the sources of the invalid moves should be restored, not deleted // (depending on the order of discovery a follow-up sync is needed) QVERIFY(completeSpy.findItem(QStringLiteral("allowed/file")).isNull()); QVERIFY(completeSpy.findItem(QStringLiteral("allowed/sub2")).isNull()); QVERIFY(itemInstruction(completeSpy, QStringLiteral("zallowed/file"), CSYNC_INSTRUCTION_NEW)); QVERIFY(itemInstruction(completeSpy, QStringLiteral("zallowed/sub2"), CSYNC_INSTRUCTION_NEW)); QVERIFY(itemInstruction(completeSpy, QStringLiteral("zallowed/sub2/file"), CSYNC_INSTRUCTION_NEW)); QCOMPARE(fakeFolder.syncEngine().isAnotherSyncNeeded(), true); // A follow-up sync will restore allowed/file and allowed/sub2 and maintain the nocreatedir/file errors completeSpy.clear(); QCOMPARE(fakeFolder.syncJournal().wipeErrorBlacklist(), 4); QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatefile/file"), CSYNC_INSTRUCTION_ERROR)); QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatefile/zfile"), CSYNC_INSTRUCTION_ERROR)); QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatedir/sub2"), CSYNC_INSTRUCTION_ERROR)); QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatedir/zsub2"), CSYNC_INSTRUCTION_ERROR)); QVERIFY(itemInstruction(completeSpy, QStringLiteral("allowed/file"), CSYNC_INSTRUCTION_NEW)); QVERIFY(itemInstruction(completeSpy, QStringLiteral("allowed/sub2"), CSYNC_INSTRUCTION_NEW)); QVERIFY(itemInstruction(completeSpy, QStringLiteral("allowed/sub2/file"), CSYNC_INSTRUCTION_NEW)); auto cls = fakeFolder.currentLocalState(); QVERIFY(cls.find(QStringLiteral("allowed/file"))); QVERIFY(cls.find(QStringLiteral("allowed/sub2"))); QVERIFY(cls.find(QStringLiteral("zallowed/file"))); QVERIFY(cls.find(QStringLiteral("zallowed/sub2"))); QVERIFY(cls.find(QStringLiteral("zallowed/sub2/file"))); } // Test for issue #7293 void testAllowedMoveForbiddenDelete() { QFETCH_GLOBAL(Vfs::Mode, vfsMode); QFETCH_GLOBAL(bool, filesAreDehydrated); if (vfsMode == Vfs::Mode::WindowsCfApi) { QSKIP("Known issue: the winvfs plug-in fails to set file permissions"); } FakeFolder fakeFolder(FileInfo(), vfsMode, filesAreDehydrated); // 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 = [] { return 1; }; fakeFolder.syncEngine().setSyncOptions(syncOpts); auto &lm = fakeFolder.localModifier(); auto &rm = fakeFolder.remoteModifier(); rm.mkdir(QStringLiteral("changeonly")); rm.mkdir(QStringLiteral("changeonly/sub1")); rm.insert(QStringLiteral("changeonly/sub1/file1")); rm.insert(QStringLiteral("changeonly/sub1/filetorname1a")); rm.insert(QStringLiteral("changeonly/sub1/filetorname1z")); rm.mkdir(QStringLiteral("changeonly/sub2")); rm.insert(QStringLiteral("changeonly/sub2/file2")); rm.insert(QStringLiteral("changeonly/sub2/filetorname2a")); rm.insert(QStringLiteral("changeonly/sub2/filetorname2z")); setAllPerm(rm.find(QStringLiteral("changeonly")), RemotePermissions::fromServerString(QStringLiteral("NSV"))); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); lm.rename(QStringLiteral("changeonly/sub1/filetorname1a"), QStringLiteral("changeonly/sub1/aaa1_renamed")); lm.rename(QStringLiteral("changeonly/sub1/filetorname1z"), QStringLiteral("changeonly/sub1/zzz1_renamed")); lm.rename(QStringLiteral("changeonly/sub2/filetorname2a"), QStringLiteral("changeonly/sub2/aaa2_renamed")); lm.rename(QStringLiteral("changeonly/sub2/filetorname2z"), QStringLiteral("changeonly/sub2/zzz2_renamed")); lm.rename(QStringLiteral("changeonly/sub1"), QStringLiteral("changeonly/aaa")); lm.rename(QStringLiteral("changeonly/sub2"), QStringLiteral("changeonly/zzz")); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } }; QTEST_GUILESS_MAIN(TestPermissions) #include "testpermissions.moc"