testpermissions.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. /*
  2. * This software is in the public domain, furnished "as is", without technical
  3. * support, and with no warranty, express or implied, as to its usefulness for
  4. * any purpose.
  5. *
  6. */
  7. #include <QtTest>
  8. #include "syncenginetestutils.h"
  9. #include <syncengine.h>
  10. #include "common/ownsql.h"
  11. using namespace OCC;
  12. static void applyPermissionsFromName(FileInfo &info) {
  13. static QRegularExpression rx("_PERM_([^_]*)_[^/]*$");
  14. auto m = rx.match(info.name);
  15. if (m.hasMatch()) {
  16. info.permissions = RemotePermissions::fromServerString(m.captured(1));
  17. }
  18. for (FileInfo &sub : info.children)
  19. applyPermissionsFromName(sub);
  20. }
  21. // 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
  22. // https://github.com/owncloud/client/issues/2038
  23. static void assertCsyncJournalOk(SyncJournalDb &journal)
  24. {
  25. // The DB is opened in locked mode: close to allow us to access.
  26. journal.close();
  27. SqlDatabase db;
  28. QVERIFY(db.openReadOnly(journal.databaseFilePath()));
  29. SqlQuery q("SELECT count(*) from metadata where length(fileId) == 0", db);
  30. QVERIFY(q.exec());
  31. QVERIFY(q.next().hasData);
  32. QCOMPARE(q.intValue(0), 0);
  33. #if defined(Q_OS_WIN) // Make sure the file does not appear in the FileInfo
  34. FileSystem::setFileHidden(journal.databaseFilePath() + "-shm", true);
  35. #endif
  36. }
  37. SyncFileItemPtr findDiscoveryItem(const SyncFileItemVector &spy, const QString &path)
  38. {
  39. for (const auto &item : spy) {
  40. if (item->destination() == path)
  41. return item;
  42. }
  43. return SyncFileItemPtr(new SyncFileItem);
  44. }
  45. bool itemInstruction(const ItemCompletedSpy &spy, const QString &path, const SyncInstructions instr)
  46. {
  47. auto item = spy.findItem(path);
  48. return item->_instruction == instr;
  49. }
  50. bool discoveryInstruction(const SyncFileItemVector &spy, const QString &path, const SyncInstructions instr)
  51. {
  52. auto item = findDiscoveryItem(spy, path);
  53. return item->_instruction == instr;
  54. }
  55. class TestPermissions : public QObject
  56. {
  57. Q_OBJECT
  58. private slots:
  59. void initTestCase()
  60. {
  61. OCC::Logger::instance()->setLogDebug(true);
  62. }
  63. void t7pl()
  64. {
  65. FakeFolder fakeFolder{ FileInfo() };
  66. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  67. // Some of this test depends on the order of discovery. With threading
  68. // that order becomes effectively random, but we want to make sure to test
  69. // all cases and thus disable threading.
  70. auto syncOpts = fakeFolder.syncEngine().syncOptions();
  71. syncOpts._parallelNetworkJobs = 1;
  72. fakeFolder.syncEngine().setSyncOptions(syncOpts);
  73. const int cannotBeModifiedSize = 133;
  74. const int canBeModifiedSize = 144;
  75. //create some files
  76. auto insertIn = [&](const QString &dir) {
  77. fakeFolder.remoteModifier().insert(dir + "normalFile_PERM_WVND_.data", 100 );
  78. fakeFolder.remoteModifier().insert(dir + "cannotBeRemoved_PERM_WVN_.data", 101 );
  79. fakeFolder.remoteModifier().insert(dir + "canBeRemoved_PERM_D_.data", 102 );
  80. fakeFolder.remoteModifier().insert(dir + "cannotBeModified_PERM_DVN_.data", cannotBeModifiedSize , 'A');
  81. fakeFolder.remoteModifier().insert(dir + "canBeModified_PERM_W_.data", canBeModifiedSize );
  82. };
  83. //put them in some directories
  84. fakeFolder.remoteModifier().mkdir("normalDirectory_PERM_CKDNV_");
  85. insertIn("normalDirectory_PERM_CKDNV_/");
  86. fakeFolder.remoteModifier().mkdir("readonlyDirectory_PERM_M_" );
  87. insertIn("readonlyDirectory_PERM_M_/" );
  88. fakeFolder.remoteModifier().mkdir("readonlyDirectory_PERM_M_/subdir_PERM_CK_");
  89. fakeFolder.remoteModifier().mkdir("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_");
  90. fakeFolder.remoteModifier().insert("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data", 100);
  91. applyPermissionsFromName(fakeFolder.remoteModifier());
  92. QVERIFY(fakeFolder.syncOnce());
  93. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  94. assertCsyncJournalOk(fakeFolder.syncJournal());
  95. qInfo("Do some changes and see how they propagate");
  96. //1. remove the file than cannot be removed
  97. // (they should be recovered)
  98. fakeFolder.localModifier().remove("normalDirectory_PERM_CKDNV_/cannotBeRemoved_PERM_WVN_.data");
  99. fakeFolder.localModifier().remove("readonlyDirectory_PERM_M_/cannotBeRemoved_PERM_WVN_.data");
  100. //2. remove the file that can be removed
  101. // (they should properly be gone)
  102. auto removeReadOnly = [&] (const QString &file) {
  103. QVERIFY(!QFileInfo(fakeFolder.localPath() + file).permission(QFile::WriteOwner));
  104. QFile(fakeFolder.localPath() + file).setPermissions(QFile::WriteOwner | QFile::ReadOwner);
  105. fakeFolder.localModifier().remove(file);
  106. };
  107. removeReadOnly("normalDirectory_PERM_CKDNV_/canBeRemoved_PERM_D_.data");
  108. removeReadOnly("readonlyDirectory_PERM_M_/canBeRemoved_PERM_D_.data");
  109. //3. Edit the files that cannot be modified
  110. // (they should be recovered, and a conflict shall be created)
  111. auto editReadOnly = [&] (const QString &file) {
  112. QVERIFY(!QFileInfo(fakeFolder.localPath() + file).permission(QFile::WriteOwner));
  113. QFile(fakeFolder.localPath() + file).setPermissions(QFile::WriteOwner | QFile::ReadOwner);
  114. fakeFolder.localModifier().appendByte(file);
  115. };
  116. editReadOnly("normalDirectory_PERM_CKDNV_/cannotBeModified_PERM_DVN_.data");
  117. editReadOnly("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data");
  118. //4. Edit other files
  119. // (they should be uploaded)
  120. fakeFolder.localModifier().appendByte("normalDirectory_PERM_CKDNV_/canBeModified_PERM_W_.data");
  121. fakeFolder.localModifier().appendByte("readonlyDirectory_PERM_M_/canBeModified_PERM_W_.data");
  122. //5. Create a new file in a read write folder
  123. // (should be uploaded)
  124. fakeFolder.localModifier().insert("normalDirectory_PERM_CKDNV_/newFile_PERM_WDNV_.data", 106 );
  125. applyPermissionsFromName(fakeFolder.remoteModifier());
  126. //do the sync
  127. QVERIFY(fakeFolder.syncOnce());
  128. assertCsyncJournalOk(fakeFolder.syncJournal());
  129. auto currentLocalState = fakeFolder.currentLocalState();
  130. //1.
  131. // File should be recovered
  132. QVERIFY(currentLocalState.find("normalDirectory_PERM_CKDNV_/cannotBeRemoved_PERM_WVN_.data"));
  133. QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/cannotBeRemoved_PERM_WVN_.data"));
  134. //2.
  135. // File should be deleted
  136. QVERIFY(!currentLocalState.find("normalDirectory_PERM_CKDNV_/canBeRemoved_PERM_D_.data"));
  137. QVERIFY(!currentLocalState.find("readonlyDirectory_PERM_M_/canBeRemoved_PERM_D_.data"));
  138. //3.
  139. // File should be recovered
  140. QCOMPARE(currentLocalState.find("normalDirectory_PERM_CKDNV_/cannotBeModified_PERM_DVN_.data")->size, cannotBeModifiedSize);
  141. QCOMPARE(currentLocalState.find("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data")->size, cannotBeModifiedSize);
  142. // and conflict created
  143. auto c1 = findConflict(currentLocalState, "normalDirectory_PERM_CKDNV_/cannotBeModified_PERM_DVN_.data");
  144. QVERIFY(c1);
  145. QCOMPARE(c1->size, cannotBeModifiedSize + 1);
  146. auto c2 = findConflict(currentLocalState, "readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data");
  147. QVERIFY(c2);
  148. QCOMPARE(c2->size, cannotBeModifiedSize + 1);
  149. // remove the conflicts for the next state comparison
  150. fakeFolder.localModifier().remove(c1->path());
  151. fakeFolder.localModifier().remove(c2->path());
  152. //4. File should be updated, that's tested by assertLocalAndRemoteDir
  153. QCOMPARE(currentLocalState.find("normalDirectory_PERM_CKDNV_/canBeModified_PERM_W_.data")->size, canBeModifiedSize + 1);
  154. QCOMPARE(currentLocalState.find("readonlyDirectory_PERM_M_/canBeModified_PERM_W_.data")->size, canBeModifiedSize + 1);
  155. //5.
  156. // the file should be in the server and local
  157. QVERIFY(currentLocalState.find("normalDirectory_PERM_CKDNV_/newFile_PERM_WDNV_.data"));
  158. // Both side should still be the same
  159. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  160. // Next test
  161. //6. Create a new file in a read only folder
  162. // (they should not be uploaded)
  163. fakeFolder.localModifier().insert("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data", 105 );
  164. applyPermissionsFromName(fakeFolder.remoteModifier());
  165. // error: can't upload to readonly
  166. QVERIFY(!fakeFolder.syncOnce());
  167. assertCsyncJournalOk(fakeFolder.syncJournal());
  168. currentLocalState = fakeFolder.currentLocalState();
  169. //6.
  170. // The file should not exist on the remote, but still be there
  171. QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data"));
  172. QVERIFY(!fakeFolder.currentRemoteState().find("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data"));
  173. // remove it so next test succeed.
  174. fakeFolder.localModifier().remove("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data");
  175. // Both side should still be the same
  176. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  177. //######################################################################
  178. qInfo( "remove the read only directory" );
  179. // -> It must be recovered
  180. fakeFolder.localModifier().remove("readonlyDirectory_PERM_M_");
  181. applyPermissionsFromName(fakeFolder.remoteModifier());
  182. QVERIFY(fakeFolder.syncOnce());
  183. assertCsyncJournalOk(fakeFolder.syncJournal());
  184. currentLocalState = fakeFolder.currentLocalState();
  185. QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/cannotBeRemoved_PERM_WVN_.data"));
  186. QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/subdir_PERM_CK_"));
  187. // the subdirectory had delete permissions, but, it was within the recovered directory, so must also get recovered
  188. QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_"));
  189. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  190. applyPermissionsFromName(fakeFolder.remoteModifier());
  191. QVERIFY(fakeFolder.syncOnce());
  192. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  193. //######################################################################
  194. qInfo( "move a directory in a outside read only folder" );
  195. //Missing directory should be restored
  196. //new directory should be uploaded
  197. fakeFolder.localModifier().rename("readonlyDirectory_PERM_M_/subdir_PERM_CK_", "normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_");
  198. applyPermissionsFromName(fakeFolder.remoteModifier());
  199. QVERIFY(fakeFolder.syncOnce());
  200. currentLocalState = fakeFolder.currentLocalState();
  201. // old name restored
  202. QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/subdir_PERM_CK_"));
  203. // contents moved (had move permissions)
  204. QVERIFY(!currentLocalState.find("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_"));
  205. QVERIFY(!currentLocalState.find("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data"));
  206. // new still exist (and is uploaded)
  207. QVERIFY(currentLocalState.find("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data"));
  208. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  209. // restore for further tests
  210. fakeFolder.remoteModifier().mkdir("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_");
  211. fakeFolder.remoteModifier().insert("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data");
  212. applyPermissionsFromName(fakeFolder.remoteModifier());
  213. QVERIFY(fakeFolder.syncOnce());
  214. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  215. //######################################################################
  216. qInfo( "rename a directory in a read only folder and move a directory to a read-only" );
  217. // do a sync to update the database
  218. applyPermissionsFromName(fakeFolder.remoteModifier());
  219. QVERIFY(fakeFolder.syncOnce());
  220. assertCsyncJournalOk(fakeFolder.syncJournal());
  221. QVERIFY(fakeFolder.currentLocalState().find("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data" ));
  222. //1. rename a directory in a read only folder
  223. //Missing directory should be restored
  224. //new directory should stay but not be uploaded
  225. fakeFolder.localModifier().rename("readonlyDirectory_PERM_M_/subdir_PERM_CK_", "readonlyDirectory_PERM_M_/newname_PERM_CK_" );
  226. //2. move a directory from read to read only (move the directory from previous step)
  227. fakeFolder.localModifier().rename("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_", "readonlyDirectory_PERM_M_/moved_PERM_CK_" );
  228. // error: can't upload to readonly!
  229. QVERIFY(!fakeFolder.syncOnce());
  230. currentLocalState = fakeFolder.currentLocalState();
  231. //1.
  232. // old name restored
  233. QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/subdir_PERM_CK_" ));
  234. // including contents
  235. QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data" ));
  236. // new still exist
  237. QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/newname_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data" ));
  238. // but is not on server: so remove it locally for the future comarison
  239. fakeFolder.localModifier().remove("readonlyDirectory_PERM_M_/newname_PERM_CK_");
  240. //2.
  241. // old removed
  242. QVERIFY(!currentLocalState.find("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_"));
  243. // but still on the server: the rename causing an error meant the deletes didn't execute
  244. QVERIFY(fakeFolder.currentRemoteState().find("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_"));
  245. // new still there
  246. QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/moved_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data" ));
  247. //but not on server
  248. fakeFolder.localModifier().remove("readonlyDirectory_PERM_M_/moved_PERM_CK_");
  249. fakeFolder.remoteModifier().remove("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_");
  250. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  251. //######################################################################
  252. qInfo( "multiple restores of a file create different conflict files" );
  253. fakeFolder.remoteModifier().insert("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data");
  254. applyPermissionsFromName(fakeFolder.remoteModifier());
  255. QVERIFY(fakeFolder.syncOnce());
  256. editReadOnly("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data");
  257. fakeFolder.localModifier().setContents("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data", 's');
  258. //do the sync
  259. applyPermissionsFromName(fakeFolder.remoteModifier());
  260. QVERIFY(fakeFolder.syncOnce());
  261. assertCsyncJournalOk(fakeFolder.syncJournal());
  262. QThread::sleep(1); // make sure changes have different mtime
  263. editReadOnly("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data");
  264. fakeFolder.localModifier().setContents("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data", 'd');
  265. //do the sync
  266. applyPermissionsFromName(fakeFolder.remoteModifier());
  267. QVERIFY(fakeFolder.syncOnce());
  268. assertCsyncJournalOk(fakeFolder.syncJournal());
  269. // there should be two conflict files
  270. currentLocalState = fakeFolder.currentLocalState();
  271. int count = 0;
  272. while (auto i = findConflict(currentLocalState, "readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data")) {
  273. QVERIFY((i->contentChar == 's') || (i->contentChar == 'd'));
  274. fakeFolder.localModifier().remove(i->path());
  275. currentLocalState = fakeFolder.currentLocalState();
  276. count++;
  277. }
  278. QCOMPARE(count, 2);
  279. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  280. }
  281. static void setAllPerm(FileInfo *fi, OCC::RemotePermissions perm)
  282. {
  283. fi->permissions = perm;
  284. for (auto &subFi : fi->children)
  285. setAllPerm(&subFi, perm);
  286. }
  287. // What happens if the source can't be moved or the target can't be created?
  288. void testForbiddenMoves()
  289. {
  290. FakeFolder fakeFolder{FileInfo{}};
  291. // Some of this test depends on the order of discovery. With threading
  292. // that order becomes effectively random, but we want to make sure to test
  293. // all cases and thus disable threading.
  294. auto syncOpts = fakeFolder.syncEngine().syncOptions();
  295. syncOpts._parallelNetworkJobs = 1;
  296. fakeFolder.syncEngine().setSyncOptions(syncOpts);
  297. auto &lm = fakeFolder.localModifier();
  298. auto &rm = fakeFolder.remoteModifier();
  299. rm.mkdir("allowed");
  300. rm.mkdir("norename");
  301. rm.mkdir("nomove");
  302. rm.mkdir("nocreatefile");
  303. rm.mkdir("nocreatedir");
  304. rm.mkdir("zallowed"); // order of discovery matters
  305. rm.mkdir("allowed/sub");
  306. rm.mkdir("allowed/sub2");
  307. rm.insert("allowed/file");
  308. rm.insert("allowed/sub/file");
  309. rm.insert("allowed/sub2/file");
  310. rm.mkdir("norename/sub");
  311. rm.insert("norename/file");
  312. rm.insert("norename/sub/file");
  313. rm.mkdir("nomove/sub");
  314. rm.insert("nomove/file");
  315. rm.insert("nomove/sub/file");
  316. rm.mkdir("zallowed/sub");
  317. rm.mkdir("zallowed/sub2");
  318. rm.insert("zallowed/file");
  319. rm.insert("zallowed/sub/file");
  320. rm.insert("zallowed/sub2/file");
  321. setAllPerm(rm.find("norename"), RemotePermissions::fromServerString("WDVCK"));
  322. setAllPerm(rm.find("nomove"), RemotePermissions::fromServerString("WDNCK"));
  323. setAllPerm(rm.find("nocreatefile"), RemotePermissions::fromServerString("WDNVK"));
  324. setAllPerm(rm.find("nocreatedir"), RemotePermissions::fromServerString("WDNVC"));
  325. QVERIFY(fakeFolder.syncOnce());
  326. // Renaming errors
  327. lm.rename("norename/file", "norename/file_renamed");
  328. lm.rename("norename/sub", "norename/sub_renamed");
  329. // Moving errors
  330. lm.rename("nomove/file", "allowed/file_moved");
  331. lm.rename("nomove/sub", "allowed/sub_moved");
  332. // Createfile errors
  333. lm.rename("allowed/file", "nocreatefile/file");
  334. lm.rename("zallowed/file", "nocreatefile/zfile");
  335. lm.rename("allowed/sub", "nocreatefile/sub"); // TODO: probably forbidden because it contains file children?
  336. // Createdir errors
  337. lm.rename("allowed/sub2", "nocreatedir/sub2");
  338. lm.rename("zallowed/sub2", "nocreatedir/zsub2");
  339. // also hook into discovery!!
  340. SyncFileItemVector discovery;
  341. connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToPropagate, this, [&discovery](auto v) { discovery = v; });
  342. ItemCompletedSpy completeSpy(fakeFolder);
  343. QVERIFY(!fakeFolder.syncOnce());
  344. // if renaming doesn't work, just delete+create
  345. QVERIFY(itemInstruction(completeSpy, "norename/file", CSYNC_INSTRUCTION_REMOVE));
  346. QVERIFY(itemInstruction(completeSpy, "norename/sub", CSYNC_INSTRUCTION_NONE));
  347. QVERIFY(discoveryInstruction(discovery, "norename/sub", CSYNC_INSTRUCTION_REMOVE));
  348. QVERIFY(itemInstruction(completeSpy, "norename/file_renamed", CSYNC_INSTRUCTION_NEW));
  349. QVERIFY(itemInstruction(completeSpy, "norename/sub_renamed", CSYNC_INSTRUCTION_NEW));
  350. // the contents can _move_
  351. QVERIFY(itemInstruction(completeSpy, "norename/sub_renamed/file", CSYNC_INSTRUCTION_RENAME));
  352. // simiilarly forbidding moves becomes delete+create
  353. QVERIFY(itemInstruction(completeSpy, "nomove/file", CSYNC_INSTRUCTION_REMOVE));
  354. QVERIFY(itemInstruction(completeSpy, "nomove/sub", CSYNC_INSTRUCTION_NONE));
  355. QVERIFY(discoveryInstruction(discovery, "nomove/sub", CSYNC_INSTRUCTION_REMOVE));
  356. // nomove/sub/file is removed as part of the dir
  357. QVERIFY(itemInstruction(completeSpy, "allowed/file_moved", CSYNC_INSTRUCTION_NEW));
  358. QVERIFY(itemInstruction(completeSpy, "allowed/sub_moved", CSYNC_INSTRUCTION_NEW));
  359. QVERIFY(itemInstruction(completeSpy, "allowed/sub_moved/file", CSYNC_INSTRUCTION_NEW));
  360. // when moving to an invalid target, the targets should be an error
  361. QVERIFY(itemInstruction(completeSpy, "nocreatefile/file", CSYNC_INSTRUCTION_ERROR));
  362. QVERIFY(itemInstruction(completeSpy, "nocreatefile/zfile", CSYNC_INSTRUCTION_ERROR));
  363. QVERIFY(itemInstruction(completeSpy, "nocreatefile/sub", CSYNC_INSTRUCTION_RENAME)); // TODO: What does a real server say?
  364. QVERIFY(itemInstruction(completeSpy, "nocreatedir/sub2", CSYNC_INSTRUCTION_ERROR));
  365. QVERIFY(itemInstruction(completeSpy, "nocreatedir/zsub2", CSYNC_INSTRUCTION_ERROR));
  366. // and the sources of the invalid moves should be restored, not deleted
  367. // (depending on the order of discovery a follow-up sync is needed)
  368. QVERIFY(itemInstruction(completeSpy, "allowed/file", CSYNC_INSTRUCTION_NONE));
  369. QVERIFY(itemInstruction(completeSpy, "allowed/sub2", CSYNC_INSTRUCTION_NONE));
  370. QVERIFY(itemInstruction(completeSpy, "zallowed/file", CSYNC_INSTRUCTION_NEW));
  371. QVERIFY(itemInstruction(completeSpy, "zallowed/sub2", CSYNC_INSTRUCTION_NEW));
  372. QVERIFY(itemInstruction(completeSpy, "zallowed/sub2/file", CSYNC_INSTRUCTION_NEW));
  373. QCOMPARE(fakeFolder.syncEngine().isAnotherSyncNeeded(), ImmediateFollowUp);
  374. // A follow-up sync will restore allowed/file and allowed/sub2 and maintain the nocreatedir/file errors
  375. completeSpy.clear();
  376. QVERIFY(!fakeFolder.syncOnce());
  377. QVERIFY(itemInstruction(completeSpy, "nocreatefile/file", CSYNC_INSTRUCTION_ERROR));
  378. QVERIFY(itemInstruction(completeSpy, "nocreatefile/zfile", CSYNC_INSTRUCTION_ERROR));
  379. QVERIFY(itemInstruction(completeSpy, "nocreatedir/sub2", CSYNC_INSTRUCTION_ERROR));
  380. QVERIFY(itemInstruction(completeSpy, "nocreatedir/zsub2", CSYNC_INSTRUCTION_ERROR));
  381. QVERIFY(itemInstruction(completeSpy, "allowed/file", CSYNC_INSTRUCTION_NEW));
  382. QVERIFY(itemInstruction(completeSpy, "allowed/sub2", CSYNC_INSTRUCTION_NEW));
  383. QVERIFY(itemInstruction(completeSpy, "allowed/sub2/file", CSYNC_INSTRUCTION_NEW));
  384. auto cls = fakeFolder.currentLocalState();
  385. QVERIFY(cls.find("allowed/file"));
  386. QVERIFY(cls.find("allowed/sub2"));
  387. QVERIFY(cls.find("zallowed/file"));
  388. QVERIFY(cls.find("zallowed/sub2"));
  389. QVERIFY(cls.find("zallowed/sub2/file"));
  390. }
  391. // Test for issue #7293
  392. void testAllowedMoveForbiddenDelete() {
  393. FakeFolder fakeFolder{FileInfo{}};
  394. // Some of this test depends on the order of discovery. With threading
  395. // that order becomes effectively random, but we want to make sure to test
  396. // all cases and thus disable threading.
  397. auto syncOpts = fakeFolder.syncEngine().syncOptions();
  398. syncOpts._parallelNetworkJobs = 1;
  399. fakeFolder.syncEngine().setSyncOptions(syncOpts);
  400. auto &lm = fakeFolder.localModifier();
  401. auto &rm = fakeFolder.remoteModifier();
  402. rm.mkdir("changeonly");
  403. rm.mkdir("changeonly/sub1");
  404. rm.insert("changeonly/sub1/file1");
  405. rm.insert("changeonly/sub1/filetorname1a");
  406. rm.insert("changeonly/sub1/filetorname1z");
  407. rm.mkdir("changeonly/sub2");
  408. rm.insert("changeonly/sub2/file2");
  409. rm.insert("changeonly/sub2/filetorname2a");
  410. rm.insert("changeonly/sub2/filetorname2z");
  411. setAllPerm(rm.find("changeonly"), RemotePermissions::fromServerString("NSV"));
  412. QVERIFY(fakeFolder.syncOnce());
  413. lm.rename("changeonly/sub1/filetorname1a", "changeonly/sub1/aaa1_renamed");
  414. lm.rename("changeonly/sub1/filetorname1z", "changeonly/sub1/zzz1_renamed");
  415. lm.rename("changeonly/sub2/filetorname2a", "changeonly/sub2/aaa2_renamed");
  416. lm.rename("changeonly/sub2/filetorname2z", "changeonly/sub2/zzz2_renamed");
  417. auto expectedState = fakeFolder.currentLocalState();
  418. QVERIFY(fakeFolder.syncOnce());
  419. QCOMPARE(fakeFolder.currentLocalState(), expectedState);
  420. QCOMPARE(fakeFolder.currentRemoteState(), expectedState);
  421. lm.rename("changeonly/sub1", "changeonly/aaa");
  422. lm.rename("changeonly/sub2", "changeonly/zzz");
  423. expectedState = fakeFolder.currentLocalState();
  424. QVERIFY(fakeFolder.syncOnce());
  425. QCOMPARE(fakeFolder.currentLocalState(), expectedState);
  426. QCOMPARE(fakeFolder.currentRemoteState(), expectedState);
  427. }
  428. void testParentMoveNotAllowedChildrenRestored()
  429. {
  430. FakeFolder fakeFolder{FileInfo{}};
  431. auto &lm = fakeFolder.localModifier();
  432. auto &rm = fakeFolder.remoteModifier();
  433. rm.mkdir("forbidden-move");
  434. rm.mkdir("forbidden-move/sub1");
  435. rm.insert("forbidden-move/sub1/file1.txt", 100);
  436. rm.mkdir("forbidden-move/sub2");
  437. rm.insert("forbidden-move/sub2/file2.txt", 100);
  438. rm.find("forbidden-move")->permissions = RemotePermissions::fromServerString("WNCK");
  439. QVERIFY(fakeFolder.syncOnce());
  440. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  441. lm.rename("forbidden-move", "forbidden-move-new");
  442. QVERIFY(fakeFolder.syncOnce());
  443. // verify that original folder did not get wiped (files are still there)
  444. QVERIFY(fakeFolder.currentRemoteState().find("forbidden-move/sub1/file1.txt"));
  445. QVERIFY(fakeFolder.currentRemoteState().find("forbidden-move/sub2/file2.txt"));
  446. // verify that the duplicate folder has been created when trying to rename a folder that has its move permissions forbidden
  447. QVERIFY(fakeFolder.currentRemoteState().find("forbidden-move-new/sub1/file1.txt"));
  448. QVERIFY(fakeFolder.currentRemoteState().find("forbidden-move-new/sub2/file2.txt"));
  449. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  450. }
  451. };
  452. QTEST_GUILESS_MAIN(TestPermissions)
  453. #include "testpermissions.moc"