testpermissions.cpp 26 KB

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