testpermissions.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  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, so the contents were deleted
  184. QVERIFY(!currentLocalState.find("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_"));
  185. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  186. // restore
  187. fakeFolder.remoteModifier().mkdir("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_");
  188. fakeFolder.remoteModifier().insert("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data");
  189. applyPermissionsFromName(fakeFolder.remoteModifier());
  190. QVERIFY(fakeFolder.syncOnce());
  191. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  192. //######################################################################
  193. qInfo( "move a directory in a outside read only folder" );
  194. //Missing directory should be restored
  195. //new directory should be uploaded
  196. fakeFolder.localModifier().rename("readonlyDirectory_PERM_M_/subdir_PERM_CK_", "normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_");
  197. applyPermissionsFromName(fakeFolder.remoteModifier());
  198. QVERIFY(fakeFolder.syncOnce());
  199. currentLocalState = fakeFolder.currentLocalState();
  200. // old name restored
  201. QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/subdir_PERM_CK_"));
  202. // contents moved (had move permissions)
  203. QVERIFY(!currentLocalState.find("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_"));
  204. QVERIFY(!currentLocalState.find("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data"));
  205. // new still exist (and is uploaded)
  206. QVERIFY(currentLocalState.find("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data"));
  207. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  208. // restore for further tests
  209. fakeFolder.remoteModifier().mkdir("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_");
  210. fakeFolder.remoteModifier().insert("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data");
  211. applyPermissionsFromName(fakeFolder.remoteModifier());
  212. QVERIFY(fakeFolder.syncOnce());
  213. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  214. //######################################################################
  215. qInfo( "rename a directory in a read only folder and move a directory to a read-only" );
  216. // do a sync to update the database
  217. applyPermissionsFromName(fakeFolder.remoteModifier());
  218. QVERIFY(fakeFolder.syncOnce());
  219. assertCsyncJournalOk(fakeFolder.syncJournal());
  220. QVERIFY(fakeFolder.currentLocalState().find("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data" ));
  221. //1. rename a directory in a read only folder
  222. //Missing directory should be restored
  223. //new directory should stay but not be uploaded
  224. fakeFolder.localModifier().rename("readonlyDirectory_PERM_M_/subdir_PERM_CK_", "readonlyDirectory_PERM_M_/newname_PERM_CK_" );
  225. //2. move a directory from read to read only (move the directory from previous step)
  226. fakeFolder.localModifier().rename("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_", "readonlyDirectory_PERM_M_/moved_PERM_CK_" );
  227. // error: can't upload to readonly!
  228. QVERIFY(!fakeFolder.syncOnce());
  229. currentLocalState = fakeFolder.currentLocalState();
  230. //1.
  231. // old name restored
  232. QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/subdir_PERM_CK_" ));
  233. // including contents
  234. QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data" ));
  235. // new still exist
  236. QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/newname_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data" ));
  237. // but is not on server: so remove it localy for the future comarison
  238. fakeFolder.localModifier().remove("readonlyDirectory_PERM_M_/newname_PERM_CK_");
  239. //2.
  240. // old removed
  241. QVERIFY(!currentLocalState.find("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_"));
  242. // but still on the server: the rename causing an error meant the deletes didn't execute
  243. QVERIFY(fakeFolder.currentRemoteState().find("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_"));
  244. // new still there
  245. QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/moved_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data" ));
  246. //but not on server
  247. fakeFolder.localModifier().remove("readonlyDirectory_PERM_M_/moved_PERM_CK_");
  248. fakeFolder.remoteModifier().remove("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_");
  249. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  250. //######################################################################
  251. qInfo( "multiple restores of a file create different conflict files" );
  252. fakeFolder.remoteModifier().insert("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data");
  253. applyPermissionsFromName(fakeFolder.remoteModifier());
  254. QVERIFY(fakeFolder.syncOnce());
  255. editReadOnly("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data");
  256. fakeFolder.localModifier().setContents("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data", 's');
  257. //do the sync
  258. applyPermissionsFromName(fakeFolder.remoteModifier());
  259. QVERIFY(fakeFolder.syncOnce());
  260. assertCsyncJournalOk(fakeFolder.syncJournal());
  261. QThread::sleep(1); // make sure changes have different mtime
  262. editReadOnly("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data");
  263. fakeFolder.localModifier().setContents("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data", 'd');
  264. //do the sync
  265. applyPermissionsFromName(fakeFolder.remoteModifier());
  266. QVERIFY(fakeFolder.syncOnce());
  267. assertCsyncJournalOk(fakeFolder.syncJournal());
  268. // there should be two conflict files
  269. currentLocalState = fakeFolder.currentLocalState();
  270. int count = 0;
  271. while (auto i = findConflict(currentLocalState, "readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data")) {
  272. QVERIFY((i->contentChar == 's') || (i->contentChar == 'd'));
  273. fakeFolder.localModifier().remove(i->path());
  274. currentLocalState = fakeFolder.currentLocalState();
  275. count++;
  276. }
  277. QCOMPARE(count, 2);
  278. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  279. }
  280. static void setAllPerm(FileInfo *fi, RemotePermissions perm)
  281. {
  282. fi->permissions = perm;
  283. for (auto &subFi : fi->children)
  284. setAllPerm(&subFi, perm);
  285. }
  286. // What happens if the source can't be moved or the target can't be created?
  287. void testForbiddenMoves()
  288. {
  289. FakeFolder fakeFolder{FileInfo{}};
  290. // Some of this test depends on the order of discovery. With threading
  291. // that order becomes effectively random, but we want to make sure to test
  292. // all cases and thus disable threading.
  293. auto syncOpts = fakeFolder.syncEngine().syncOptions();
  294. syncOpts._parallelNetworkJobs = 1;
  295. fakeFolder.syncEngine().setSyncOptions(syncOpts);
  296. auto &lm = fakeFolder.localModifier();
  297. auto &rm = fakeFolder.remoteModifier();
  298. rm.mkdir("allowed");
  299. rm.mkdir("norename");
  300. rm.mkdir("nomove");
  301. rm.mkdir("nocreatefile");
  302. rm.mkdir("nocreatedir");
  303. rm.mkdir("zallowed"); // order of discovery matters
  304. rm.mkdir("allowed/sub");
  305. rm.mkdir("allowed/sub2");
  306. rm.insert("allowed/file");
  307. rm.insert("allowed/sub/file");
  308. rm.insert("allowed/sub2/file");
  309. rm.mkdir("norename/sub");
  310. rm.insert("norename/file");
  311. rm.insert("norename/sub/file");
  312. rm.mkdir("nomove/sub");
  313. rm.insert("nomove/file");
  314. rm.insert("nomove/sub/file");
  315. rm.mkdir("zallowed/sub");
  316. rm.mkdir("zallowed/sub2");
  317. rm.insert("zallowed/file");
  318. rm.insert("zallowed/sub/file");
  319. rm.insert("zallowed/sub2/file");
  320. setAllPerm(rm.find("norename"), RemotePermissions::fromServerString("WDVCK"));
  321. setAllPerm(rm.find("nomove"), RemotePermissions::fromServerString("WDNCK"));
  322. setAllPerm(rm.find("nocreatefile"), RemotePermissions::fromServerString("WDNVK"));
  323. setAllPerm(rm.find("nocreatedir"), RemotePermissions::fromServerString("WDNVC"));
  324. QVERIFY(fakeFolder.syncOnce());
  325. // Renaming errors
  326. lm.rename("norename/file", "norename/file_renamed");
  327. lm.rename("norename/sub", "norename/sub_renamed");
  328. // Moving errors
  329. lm.rename("nomove/file", "allowed/file_moved");
  330. lm.rename("nomove/sub", "allowed/sub_moved");
  331. // Createfile errors
  332. lm.rename("allowed/file", "nocreatefile/file");
  333. lm.rename("zallowed/file", "nocreatefile/zfile");
  334. lm.rename("allowed/sub", "nocreatefile/sub"); // TODO: probably forbidden because it contains file children?
  335. // Createdir errors
  336. lm.rename("allowed/sub2", "nocreatedir/sub2");
  337. lm.rename("zallowed/sub2", "nocreatedir/zsub2");
  338. // also hook into discovery!!
  339. SyncFileItemVector discovery;
  340. connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToPropagate, this, [&discovery](auto v) { discovery = v; });
  341. ItemCompletedSpy completeSpy(fakeFolder);
  342. QVERIFY(!fakeFolder.syncOnce());
  343. // if renaming doesn't work, just delete+create
  344. QVERIFY(itemInstruction(completeSpy, "norename/file", CSYNC_INSTRUCTION_REMOVE));
  345. QVERIFY(itemInstruction(completeSpy, "norename/sub", CSYNC_INSTRUCTION_NONE));
  346. QVERIFY(discoveryInstruction(discovery, "norename/sub", CSYNC_INSTRUCTION_REMOVE));
  347. QVERIFY(itemInstruction(completeSpy, "norename/file_renamed", CSYNC_INSTRUCTION_NEW));
  348. QVERIFY(itemInstruction(completeSpy, "norename/sub_renamed", CSYNC_INSTRUCTION_NEW));
  349. // the contents can _move_
  350. QVERIFY(itemInstruction(completeSpy, "norename/sub_renamed/file", CSYNC_INSTRUCTION_RENAME));
  351. // simiilarly forbidding moves becomes delete+create
  352. QVERIFY(itemInstruction(completeSpy, "nomove/file", CSYNC_INSTRUCTION_REMOVE));
  353. QVERIFY(itemInstruction(completeSpy, "nomove/sub", CSYNC_INSTRUCTION_NONE));
  354. QVERIFY(discoveryInstruction(discovery, "nomove/sub", CSYNC_INSTRUCTION_REMOVE));
  355. // nomove/sub/file is removed as part of the dir
  356. QVERIFY(itemInstruction(completeSpy, "allowed/file_moved", CSYNC_INSTRUCTION_NEW));
  357. QVERIFY(itemInstruction(completeSpy, "allowed/sub_moved", CSYNC_INSTRUCTION_NEW));
  358. QVERIFY(itemInstruction(completeSpy, "allowed/sub_moved/file", CSYNC_INSTRUCTION_NEW));
  359. // when moving to an invalid target, the targets should be an error
  360. QVERIFY(itemInstruction(completeSpy, "nocreatefile/file", CSYNC_INSTRUCTION_ERROR));
  361. QVERIFY(itemInstruction(completeSpy, "nocreatefile/zfile", CSYNC_INSTRUCTION_ERROR));
  362. QVERIFY(itemInstruction(completeSpy, "nocreatefile/sub", CSYNC_INSTRUCTION_RENAME)); // TODO: What does a real server say?
  363. QVERIFY(itemInstruction(completeSpy, "nocreatedir/sub2", CSYNC_INSTRUCTION_ERROR));
  364. QVERIFY(itemInstruction(completeSpy, "nocreatedir/zsub2", CSYNC_INSTRUCTION_ERROR));
  365. // and the sources of the invalid moves should be restored, not deleted
  366. // (depending on the order of discovery a follow-up sync is needed)
  367. QVERIFY(itemInstruction(completeSpy, "allowed/file", CSYNC_INSTRUCTION_NONE));
  368. QVERIFY(itemInstruction(completeSpy, "allowed/sub2", CSYNC_INSTRUCTION_NONE));
  369. QVERIFY(itemInstruction(completeSpy, "zallowed/file", CSYNC_INSTRUCTION_NEW));
  370. QVERIFY(itemInstruction(completeSpy, "zallowed/sub2", CSYNC_INSTRUCTION_NEW));
  371. QVERIFY(itemInstruction(completeSpy, "zallowed/sub2/file", CSYNC_INSTRUCTION_NEW));
  372. QCOMPARE(fakeFolder.syncEngine().isAnotherSyncNeeded(), ImmediateFollowUp);
  373. // A follow-up sync will restore allowed/file and allowed/sub2 and maintain the nocreatedir/file errors
  374. completeSpy.clear();
  375. QVERIFY(!fakeFolder.syncOnce());
  376. QVERIFY(itemInstruction(completeSpy, "nocreatefile/file", CSYNC_INSTRUCTION_ERROR));
  377. QVERIFY(itemInstruction(completeSpy, "nocreatefile/zfile", CSYNC_INSTRUCTION_ERROR));
  378. QVERIFY(itemInstruction(completeSpy, "nocreatedir/sub2", CSYNC_INSTRUCTION_ERROR));
  379. QVERIFY(itemInstruction(completeSpy, "nocreatedir/zsub2", CSYNC_INSTRUCTION_ERROR));
  380. QVERIFY(itemInstruction(completeSpy, "allowed/file", CSYNC_INSTRUCTION_NEW));
  381. QVERIFY(itemInstruction(completeSpy, "allowed/sub2", CSYNC_INSTRUCTION_NEW));
  382. QVERIFY(itemInstruction(completeSpy, "allowed/sub2/file", CSYNC_INSTRUCTION_NEW));
  383. auto cls = fakeFolder.currentLocalState();
  384. QVERIFY(cls.find("allowed/file"));
  385. QVERIFY(cls.find("allowed/sub2"));
  386. QVERIFY(cls.find("zallowed/file"));
  387. QVERIFY(cls.find("zallowed/sub2"));
  388. QVERIFY(cls.find("zallowed/sub2/file"));
  389. }
  390. // Test for issue #7293
  391. void testAllowedMoveForbiddenDelete() {
  392. FakeFolder fakeFolder{FileInfo{}};
  393. // Some of this test depends on the order of discovery. With threading
  394. // that order becomes effectively random, but we want to make sure to test
  395. // all cases and thus disable threading.
  396. auto syncOpts = fakeFolder.syncEngine().syncOptions();
  397. syncOpts._parallelNetworkJobs = 1;
  398. fakeFolder.syncEngine().setSyncOptions(syncOpts);
  399. auto &lm = fakeFolder.localModifier();
  400. auto &rm = fakeFolder.remoteModifier();
  401. rm.mkdir("changeonly");
  402. rm.mkdir("changeonly/sub1");
  403. rm.insert("changeonly/sub1/file1");
  404. rm.insert("changeonly/sub1/filetorname1a");
  405. rm.insert("changeonly/sub1/filetorname1z");
  406. rm.mkdir("changeonly/sub2");
  407. rm.insert("changeonly/sub2/file2");
  408. rm.insert("changeonly/sub2/filetorname2a");
  409. rm.insert("changeonly/sub2/filetorname2z");
  410. setAllPerm(rm.find("changeonly"), RemotePermissions::fromServerString("NSV"));
  411. QVERIFY(fakeFolder.syncOnce());
  412. lm.rename("changeonly/sub1/filetorname1a", "changeonly/sub1/aaa1_renamed");
  413. lm.rename("changeonly/sub1/filetorname1z", "changeonly/sub1/zzz1_renamed");
  414. lm.rename("changeonly/sub2/filetorname2a", "changeonly/sub2/aaa2_renamed");
  415. lm.rename("changeonly/sub2/filetorname2z", "changeonly/sub2/zzz2_renamed");
  416. lm.rename("changeonly/sub1", "changeonly/aaa");
  417. lm.rename("changeonly/sub2", "changeonly/zzz");
  418. auto expectedState = fakeFolder.currentLocalState();
  419. QVERIFY(fakeFolder.syncOnce());
  420. QCOMPARE(fakeFolder.currentLocalState(), expectedState);
  421. QCOMPARE(fakeFolder.currentRemoteState(), expectedState);
  422. }
  423. };
  424. QTEST_GUILESS_MAIN(TestPermissions)
  425. #include "testpermissions.moc"