TestEntry.cpp 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771
  1. /*
  2. * Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
  3. * Copyright (C) 2013 Felix Geyer <debfx@fobos.de>
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 2 or (at your option)
  8. * version 3 of the License.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. #include <QTest>
  19. #include "TestEntry.h"
  20. #include "core/Clock.h"
  21. #include "core/Group.h"
  22. #include "core/Metadata.h"
  23. #include "core/TimeInfo.h"
  24. #include "crypto/Crypto.h"
  25. QTEST_GUILESS_MAIN(TestEntry)
  26. void TestEntry::initTestCase()
  27. {
  28. QVERIFY(Crypto::init());
  29. }
  30. void TestEntry::testHistoryItemDeletion()
  31. {
  32. QScopedPointer<Entry> entry(new Entry());
  33. QPointer<Entry> historyEntry = new Entry();
  34. entry->addHistoryItem(historyEntry);
  35. QCOMPARE(entry->historyItems().size(), 1);
  36. QList<Entry*> historyEntriesToRemove;
  37. historyEntriesToRemove.append(historyEntry);
  38. entry->removeHistoryItems(historyEntriesToRemove);
  39. QCOMPARE(entry->historyItems().size(), 0);
  40. QVERIFY(historyEntry.isNull());
  41. }
  42. void TestEntry::testCopyDataFrom()
  43. {
  44. QScopedPointer<Entry> entry(new Entry());
  45. entry->setTitle("testtitle");
  46. entry->attributes()->set("attr1", "abc");
  47. entry->attributes()->set("attr2", "def");
  48. entry->attachments()->set("test", "123");
  49. entry->attachments()->set("test2", "456");
  50. AutoTypeAssociations::Association assoc;
  51. assoc.window = "1";
  52. assoc.sequence = "2";
  53. entry->autoTypeAssociations()->add(assoc);
  54. assoc.window = "3";
  55. assoc.sequence = "4";
  56. entry->autoTypeAssociations()->add(assoc);
  57. QScopedPointer<Entry> entry2(new Entry());
  58. entry2->copyDataFrom(entry.data());
  59. QCOMPARE(entry2->title(), QString("testtitle"));
  60. QCOMPARE(entry2->attributes()->value("attr1"), QString("abc"));
  61. QCOMPARE(entry2->attributes()->value("attr2"), QString("def"));
  62. QCOMPARE(entry2->attachments()->keys().size(), 2);
  63. QCOMPARE(entry2->attachments()->value("test"), QByteArray("123"));
  64. QCOMPARE(entry2->attachments()->value("test2"), QByteArray("456"));
  65. QCOMPARE(entry2->autoTypeAssociations()->size(), 2);
  66. QCOMPARE(entry2->autoTypeAssociations()->get(0).window, QString("1"));
  67. QCOMPARE(entry2->autoTypeAssociations()->get(1).window, QString("3"));
  68. }
  69. void TestEntry::testClone()
  70. {
  71. QScopedPointer<Entry> entryOrg(new Entry());
  72. entryOrg->setUuid(QUuid::createUuid());
  73. entryOrg->setTitle("Original Title");
  74. entryOrg->beginUpdate();
  75. entryOrg->setTitle("New Title");
  76. entryOrg->endUpdate();
  77. TimeInfo entryOrgTime = entryOrg->timeInfo();
  78. QDateTime dateTime = Clock::datetimeUtc(60);
  79. entryOrgTime.setCreationTime(dateTime);
  80. entryOrg->setTimeInfo(entryOrgTime);
  81. QScopedPointer<Entry> entryCloneNone(entryOrg->clone(Entry::CloneNoFlags));
  82. QCOMPARE(entryCloneNone->uuid(), entryOrg->uuid());
  83. QCOMPARE(entryCloneNone->title(), QString("New Title"));
  84. QCOMPARE(entryCloneNone->historyItems().size(), 0);
  85. QCOMPARE(entryCloneNone->timeInfo().creationTime(), entryOrg->timeInfo().creationTime());
  86. QScopedPointer<Entry> entryCloneNewUuid(entryOrg->clone(Entry::CloneNewUuid));
  87. QVERIFY(entryCloneNewUuid->uuid() != entryOrg->uuid());
  88. QVERIFY(!entryCloneNewUuid->uuid().isNull());
  89. QCOMPARE(entryCloneNewUuid->title(), QString("New Title"));
  90. QCOMPARE(entryCloneNewUuid->historyItems().size(), 0);
  91. QCOMPARE(entryCloneNewUuid->timeInfo().creationTime(), entryOrg->timeInfo().creationTime());
  92. // Reset modification time
  93. entryOrgTime.setLastModificationTime(Clock::datetimeUtc(60));
  94. entryOrg->setTimeInfo(entryOrgTime);
  95. QScopedPointer<Entry> entryCloneRename(entryOrg->clone(Entry::CloneRenameTitle));
  96. QCOMPARE(entryCloneRename->uuid(), entryOrg->uuid());
  97. QCOMPARE(entryCloneRename->title(), QString("New Title - Clone"));
  98. // Cloning should not modify time info unless explicity requested
  99. QCOMPARE(entryCloneRename->timeInfo(), entryOrg->timeInfo());
  100. QScopedPointer<Entry> entryCloneResetTime(entryOrg->clone(Entry::CloneResetTimeInfo));
  101. QCOMPARE(entryCloneResetTime->uuid(), entryOrg->uuid());
  102. QCOMPARE(entryCloneResetTime->title(), QString("New Title"));
  103. QCOMPARE(entryCloneResetTime->historyItems().size(), 0);
  104. QVERIFY(entryCloneResetTime->timeInfo().creationTime() != entryOrg->timeInfo().creationTime());
  105. // Date back history of original entry
  106. Entry* firstHistoryItem = entryOrg->historyItems()[0];
  107. TimeInfo entryOrgHistoryTimeInfo = firstHistoryItem->timeInfo();
  108. QDateTime datedBackEntryOrgModificationTime = entryOrgHistoryTimeInfo.lastModificationTime().addMSecs(-10);
  109. entryOrgHistoryTimeInfo.setLastModificationTime(datedBackEntryOrgModificationTime);
  110. entryOrgHistoryTimeInfo.setCreationTime(datedBackEntryOrgModificationTime);
  111. firstHistoryItem->setTimeInfo(entryOrgHistoryTimeInfo);
  112. QScopedPointer<Entry> entryCloneHistory(entryOrg->clone(Entry::CloneIncludeHistory | Entry::CloneResetTimeInfo));
  113. QCOMPARE(entryCloneHistory->uuid(), entryOrg->uuid());
  114. QCOMPARE(entryCloneHistory->title(), QString("New Title"));
  115. QCOMPARE(entryCloneHistory->historyItems().size(), entryOrg->historyItems().size());
  116. QCOMPARE(entryCloneHistory->historyItems().at(0)->title(), QString("Original Title"));
  117. QVERIFY(entryCloneHistory->timeInfo().creationTime() != entryOrg->timeInfo().creationTime());
  118. // Timeinfo of history items should not be modified
  119. QList<Entry*> entryOrgHistory = entryOrg->historyItems(), clonedHistory = entryCloneHistory->historyItems();
  120. auto entryOrgHistoryItem = entryOrgHistory.constBegin();
  121. for (auto entryCloneHistoryItem = clonedHistory.constBegin(); entryCloneHistoryItem != clonedHistory.constEnd();
  122. ++entryCloneHistoryItem, ++entryOrgHistoryItem) {
  123. QCOMPARE((*entryOrgHistoryItem)->timeInfo(), (*entryCloneHistoryItem)->timeInfo());
  124. }
  125. Database db;
  126. auto* entryOrgClone = entryOrg->clone(Entry::CloneNoFlags);
  127. entryOrgClone->setGroup(db.rootGroup());
  128. Entry* entryCloneUserRef = entryOrgClone->clone(Entry::CloneUserAsRef);
  129. entryCloneUserRef->setGroup(db.rootGroup());
  130. QCOMPARE(entryCloneUserRef->uuid(), entryOrgClone->uuid());
  131. QCOMPARE(entryCloneUserRef->title(), QString("New Title"));
  132. QCOMPARE(entryCloneUserRef->historyItems().size(), 0);
  133. QCOMPARE(entryCloneUserRef->timeInfo().creationTime(), entryOrgClone->timeInfo().creationTime());
  134. QVERIFY(entryCloneUserRef->attributes()->isReference(EntryAttributes::UserNameKey));
  135. QCOMPARE(entryCloneUserRef->resolvePlaceholder(entryCloneUserRef->username()), entryOrgClone->username());
  136. Entry* entryClonePassRef = entryOrgClone->clone(Entry::ClonePassAsRef);
  137. entryClonePassRef->setGroup(db.rootGroup());
  138. QCOMPARE(entryClonePassRef->uuid(), entryOrgClone->uuid());
  139. QCOMPARE(entryClonePassRef->title(), QString("New Title"));
  140. QCOMPARE(entryClonePassRef->historyItems().size(), 0);
  141. QCOMPARE(entryClonePassRef->timeInfo().creationTime(), entryOrgClone->timeInfo().creationTime());
  142. QVERIFY(entryClonePassRef->attributes()->isReference(EntryAttributes::PasswordKey));
  143. QCOMPARE(entryClonePassRef->resolvePlaceholder(entryCloneUserRef->password()), entryOrg->password());
  144. QCOMPARE(entryClonePassRef->attributes()->referenceUuid(EntryAttributes::PasswordKey), entryOrgClone->uuid());
  145. }
  146. void TestEntry::testResolveUrl()
  147. {
  148. QScopedPointer<Entry> entry(new Entry());
  149. QString testUrl("www.google.com");
  150. QString testCmd("cmd://firefox " + testUrl);
  151. QString testFileUnix("/home/example/test.txt");
  152. QString testFileWindows("c:/WINDOWS/test.txt");
  153. QString testComplexCmd("cmd://firefox --start-now --url 'http://" + testUrl + "' --quit");
  154. QString nonHttpUrl("ftp://google.com");
  155. QString noUrl("random text inserted here");
  156. // Test standard URL's
  157. QCOMPARE(entry->resolveUrl(""), QString(""));
  158. QCOMPARE(entry->resolveUrl(testUrl), "https://" + testUrl);
  159. QCOMPARE(entry->resolveUrl("http://" + testUrl), "http://" + testUrl);
  160. // Test file:// URL's
  161. QCOMPARE(entry->resolveUrl("file://" + testFileUnix), "file://" + testFileUnix);
  162. QCOMPARE(entry->resolveUrl(testFileUnix), "file://" + testFileUnix);
  163. QCOMPARE(entry->resolveUrl("file:///" + testFileWindows), "file:///" + testFileWindows);
  164. QCOMPARE(entry->resolveUrl(testFileWindows), "file:///" + testFileWindows);
  165. // Test cmd:// with no URL
  166. QCOMPARE(entry->resolveUrl("cmd://firefox"), QString(""));
  167. QCOMPARE(entry->resolveUrl("cmd://firefox --no-url"), QString(""));
  168. // Test cmd:// with URL's
  169. QCOMPARE(entry->resolveUrl(testCmd), "https://" + testUrl);
  170. QCOMPARE(entry->resolveUrl(testComplexCmd), "http://" + testUrl);
  171. // Test non-http URL
  172. QCOMPARE(entry->resolveUrl(nonHttpUrl), QString(""));
  173. // Test no URL
  174. QCOMPARE(entry->resolveUrl(noUrl), QString(""));
  175. }
  176. void TestEntry::testResolveUrlPlaceholders()
  177. {
  178. Entry entry;
  179. entry.setUrl("https://user:pw@keepassxc.org:80/path/example.php?q=e&s=t+2#fragment");
  180. QString rmvscm("//user:pw@keepassxc.org:80/path/example.php?q=e&s=t+2#fragment"); // Entry URL without scheme name.
  181. QString scm("https"); // Scheme name of the entry URL.
  182. QString host("keepassxc.org"); // Host component of the entry URL.
  183. QString port("80"); // Port number of the entry URL.
  184. QString path("/path/example.php"); // Path component of the entry URL.
  185. QString query("q=e&s=t+2"); // Query information of the entry URL.
  186. QString userinfo("user:pw"); // User information of the entry URL.
  187. QString username("user"); // User name of the entry URL.
  188. QString password("pw"); // Password of the entry URL.
  189. QString fragment("fragment"); // Fragment of the entry URL.
  190. QCOMPARE(entry.resolvePlaceholder("{URL:RMVSCM}"), rmvscm);
  191. QCOMPARE(entry.resolvePlaceholder("{URL:WITHOUTSCHEME}"), rmvscm);
  192. QCOMPARE(entry.resolvePlaceholder("{URL:SCM}"), scm);
  193. QCOMPARE(entry.resolvePlaceholder("{URL:SCHEME}"), scm);
  194. QCOMPARE(entry.resolvePlaceholder("{URL:HOST}"), host);
  195. QCOMPARE(entry.resolvePlaceholder("{URL:PORT}"), port);
  196. QCOMPARE(entry.resolvePlaceholder("{URL:PATH}"), path);
  197. QCOMPARE(entry.resolvePlaceholder("{URL:QUERY}"), query);
  198. QCOMPARE(entry.resolvePlaceholder("{URL:USERINFO}"), userinfo);
  199. QCOMPARE(entry.resolvePlaceholder("{URL:USERNAME}"), username);
  200. QCOMPARE(entry.resolvePlaceholder("{URL:PASSWORD}"), password);
  201. QCOMPARE(entry.resolvePlaceholder("{URL:FRAGMENT}"), fragment);
  202. }
  203. void TestEntry::testResolveRecursivePlaceholders()
  204. {
  205. Database db;
  206. auto* root = db.rootGroup();
  207. auto* entry1 = new Entry();
  208. entry1->setGroup(root);
  209. entry1->setUuid(QUuid::createUuid());
  210. entry1->setTitle("{USERNAME}");
  211. entry1->setUsername("{PASSWORD}");
  212. entry1->setPassword("{URL}");
  213. entry1->setUrl("{S:CustomTitle}");
  214. entry1->attributes()->set("CustomTitle", "RecursiveValue");
  215. QCOMPARE(entry1->resolveMultiplePlaceholders(entry1->title()), QString("RecursiveValue"));
  216. auto* entry2 = new Entry();
  217. entry2->setGroup(root);
  218. entry2->setUuid(QUuid::createUuid());
  219. entry2->setTitle("Entry2Title");
  220. entry2->setUsername("{S:CustomUserNameAttribute}");
  221. entry2->setPassword(QString("{REF:P@I:%1}").arg(entry1->uuidToHex()));
  222. entry2->setUrl("http://{S:IpAddress}:{S:Port}/{S:Uri}");
  223. entry2->attributes()->set("CustomUserNameAttribute", "CustomUserNameValue");
  224. entry2->attributes()->set("IpAddress", "127.0.0.1");
  225. entry2->attributes()->set("Port", "1234");
  226. entry2->attributes()->set("Uri", "uri/path");
  227. auto* entry3 = new Entry();
  228. entry3->setGroup(root);
  229. entry3->setUuid(QUuid::createUuid());
  230. entry3->setTitle(QString("{REF:T@I:%1}").arg(entry2->uuidToHex()));
  231. entry3->setUsername(QString("{REF:U@I:%1}").arg(entry2->uuidToHex()));
  232. entry3->setPassword(QString("{REF:P@I:%1}").arg(entry2->uuidToHex()));
  233. entry3->setUrl(QString("{REF:A@I:%1}").arg(entry2->uuidToHex()));
  234. QCOMPARE(entry3->resolveMultiplePlaceholders(entry3->title()), QString("Entry2Title"));
  235. QCOMPARE(entry3->resolveMultiplePlaceholders(entry3->username()), QString("CustomUserNameValue"));
  236. QCOMPARE(entry3->resolveMultiplePlaceholders(entry3->password()), QString("RecursiveValue"));
  237. QCOMPARE(entry3->resolveMultiplePlaceholders(entry3->url()), QString("http://127.0.0.1:1234/uri/path"));
  238. auto* entry4 = new Entry();
  239. entry4->setGroup(root);
  240. entry4->setUuid(QUuid::createUuid());
  241. entry4->setTitle(QString("{REF:T@I:%1}").arg(entry3->uuidToHex()));
  242. entry4->setUsername(QString("{REF:U@I:%1}").arg(entry3->uuidToHex()));
  243. entry4->setPassword(QString("{REF:P@I:%1}").arg(entry3->uuidToHex()));
  244. entry4->setUrl(QString("{REF:A@I:%1}").arg(entry3->uuidToHex()));
  245. QCOMPARE(entry4->resolveMultiplePlaceholders(entry4->title()), QString("Entry2Title"));
  246. QCOMPARE(entry4->resolveMultiplePlaceholders(entry4->username()), QString("CustomUserNameValue"));
  247. QCOMPARE(entry4->resolveMultiplePlaceholders(entry4->password()), QString("RecursiveValue"));
  248. QCOMPARE(entry4->resolveMultiplePlaceholders(entry4->url()), QString("http://127.0.0.1:1234/uri/path"));
  249. auto* entry5 = new Entry();
  250. entry5->setGroup(root);
  251. entry5->setUuid(QUuid::createUuid());
  252. entry5->attributes()->set("Scheme", "http");
  253. entry5->attributes()->set("Host", "host.org");
  254. entry5->attributes()->set("Port", "2017");
  255. entry5->attributes()->set("Path", "/some/path");
  256. entry5->attributes()->set("UserName", "username");
  257. entry5->attributes()->set("Password", "password");
  258. entry5->attributes()->set("Query", "q=e&t=s");
  259. entry5->attributes()->set("Fragment", "fragment");
  260. entry5->setUrl("{S:Scheme}://{S:UserName}:{S:Password}@{S:Host}:{S:Port}{S:Path}?{S:Query}#{S:Fragment}");
  261. entry5->setTitle("title+{URL:Path}+{URL:Fragment}+title");
  262. const QString url("http://username:password@host.org:2017/some/path?q=e&t=s#fragment");
  263. QCOMPARE(entry5->resolveMultiplePlaceholders(entry5->url()), url);
  264. QCOMPARE(entry5->resolveMultiplePlaceholders(entry5->title()), QString("title+/some/path+fragment+title"));
  265. auto* entry6 = new Entry();
  266. entry6->setGroup(root);
  267. entry6->setUuid(QUuid::createUuid());
  268. entry6->setTitle(QString("{REF:T@I:%1}").arg(entry3->uuidToHex()));
  269. entry6->setUsername(QString("{TITLE}"));
  270. entry6->setPassword(QString("{PASSWORD}"));
  271. QCOMPARE(entry6->resolvePlaceholder(entry6->title()), QString("Entry2Title"));
  272. QCOMPARE(entry6->resolvePlaceholder(entry6->username()), QString("Entry2Title"));
  273. QCOMPARE(entry6->resolvePlaceholder(entry6->password()), QString("{PASSWORD}"));
  274. auto* entry7 = new Entry();
  275. entry7->setGroup(root);
  276. entry7->setUuid(QUuid::createUuid());
  277. entry7->setTitle(QString("{REF:T@I:%1} and something else").arg(entry3->uuidToHex()));
  278. entry7->setUsername(QString("{TITLE}"));
  279. entry7->setPassword(QString("PASSWORD"));
  280. QCOMPARE(entry7->resolvePlaceholder(entry7->title()), QString("Entry2Title and something else"));
  281. QCOMPARE(entry7->resolvePlaceholder(entry7->username()), QString("Entry2Title and something else"));
  282. QCOMPARE(entry7->resolvePlaceholder(entry7->password()), QString("PASSWORD"));
  283. }
  284. void TestEntry::testResolveReferencePlaceholders()
  285. {
  286. Database db;
  287. auto* root = db.rootGroup();
  288. auto* entry1 = new Entry();
  289. entry1->setGroup(root);
  290. entry1->setUuid(QUuid::createUuid());
  291. entry1->setTitle("Title1");
  292. entry1->setUsername("Username1");
  293. entry1->setPassword("Password1");
  294. entry1->setUrl("Url1");
  295. entry1->setNotes("Notes1");
  296. entry1->attributes()->set("CustomAttribute1", "CustomAttributeValue1");
  297. auto* group = new Group();
  298. group->setParent(root);
  299. auto* entry2 = new Entry();
  300. entry2->setGroup(group);
  301. entry2->setUuid(QUuid::createUuid());
  302. entry2->setTitle("Title2");
  303. entry2->setUsername("Username2");
  304. entry2->setPassword("Password2");
  305. entry2->setUrl("Url2");
  306. entry2->setNotes("Notes2");
  307. entry2->attributes()->set("CustomAttribute2", "CustomAttributeValue2");
  308. auto* entry3 = new Entry();
  309. entry3->setGroup(group);
  310. entry3->setUuid(QUuid::createUuid());
  311. entry3->setTitle("{S:AttributeTitle}");
  312. entry3->setUsername("{S:AttributeUsername}");
  313. entry3->setPassword("{S:AttributePassword}");
  314. entry3->setUrl("{S:AttributeUrl}");
  315. entry3->setNotes("{S:AttributeNotes}");
  316. entry3->attributes()->set("AttributeTitle", "TitleValue");
  317. entry3->attributes()->set("AttributeUsername", "UsernameValue");
  318. entry3->attributes()->set("AttributePassword", "PasswordValue");
  319. entry3->attributes()->set("AttributeUrl", "UrlValue");
  320. entry3->attributes()->set("AttributeNotes", "NotesValue");
  321. auto* tstEntry = new Entry();
  322. tstEntry->setGroup(root);
  323. tstEntry->setUuid(QUuid::createUuid());
  324. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:T@I:%1}").arg(entry1->uuidToHex())), entry1->title());
  325. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:T@T:%1}").arg(entry1->title())), entry1->title());
  326. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:T@U:%1}").arg(entry1->username())), entry1->title());
  327. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:T@P:%1}").arg(entry1->password())), entry1->title());
  328. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:T@A:%1}").arg(entry1->url())), entry1->title());
  329. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:T@N:%1}").arg(entry1->notes())), entry1->title());
  330. QCOMPARE(tstEntry->resolveMultiplePlaceholders(
  331. QString("{REF:T@O:%1}").arg(entry1->attributes()->value("CustomAttribute1"))),
  332. entry1->title());
  333. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:T@I:%1}").arg(entry1->uuidToHex())), entry1->title());
  334. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:T@T:%1}").arg(entry1->title())), entry1->title());
  335. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:U@U:%1}").arg(entry1->username())),
  336. entry1->username());
  337. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:P@P:%1}").arg(entry1->password())),
  338. entry1->password());
  339. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:A@A:%1}").arg(entry1->url())), entry1->url());
  340. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:N@N:%1}").arg(entry1->notes())), entry1->notes());
  341. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:T@I:%1}").arg(entry2->uuidToHex())), entry2->title());
  342. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:T@T:%1}").arg(entry2->title())), entry2->title());
  343. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:T@U:%1}").arg(entry2->username())), entry2->title());
  344. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:T@P:%1}").arg(entry2->password())), entry2->title());
  345. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:T@A:%1}").arg(entry2->url())), entry2->title());
  346. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:T@N:%1}").arg(entry2->notes())), entry2->title());
  347. QCOMPARE(tstEntry->resolveMultiplePlaceholders(
  348. QString("{REF:T@O:%1}").arg(entry2->attributes()->value("CustomAttribute2"))),
  349. entry2->title());
  350. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:T@T:%1}").arg(entry2->title())), entry2->title());
  351. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:U@U:%1}").arg(entry2->username())),
  352. entry2->username());
  353. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:P@P:%1}").arg(entry2->password())),
  354. entry2->password());
  355. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:A@A:%1}").arg(entry2->url())), entry2->url());
  356. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:N@N:%1}").arg(entry2->notes())), entry2->notes());
  357. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:T@I:%1}").arg(entry3->uuidToHex())),
  358. entry3->attributes()->value("AttributeTitle"));
  359. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:U@I:%1}").arg(entry3->uuidToHex())),
  360. entry3->attributes()->value("AttributeUsername"));
  361. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:P@I:%1}").arg(entry3->uuidToHex())),
  362. entry3->attributes()->value("AttributePassword"));
  363. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:A@I:%1}").arg(entry3->uuidToHex())),
  364. entry3->attributes()->value("AttributeUrl"));
  365. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:N@I:%1}").arg(entry3->uuidToHex())),
  366. entry3->attributes()->value("AttributeNotes"));
  367. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:T@I:%1}").arg(entry3->uuidToHex().toUpper())),
  368. entry3->attributes()->value("AttributeTitle"));
  369. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:U@I:%1}").arg(entry3->uuidToHex().toUpper())),
  370. entry3->attributes()->value("AttributeUsername"));
  371. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:P@I:%1}").arg(entry3->uuidToHex().toUpper())),
  372. entry3->attributes()->value("AttributePassword"));
  373. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:A@I:%1}").arg(entry3->uuidToHex().toUpper())),
  374. entry3->attributes()->value("AttributeUrl"));
  375. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:N@I:%1}").arg(entry3->uuidToHex().toUpper())),
  376. entry3->attributes()->value("AttributeNotes"));
  377. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:t@i:%1}").arg(entry3->uuidToHex().toLower())),
  378. entry3->attributes()->value("AttributeTitle"));
  379. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:u@i:%1}").arg(entry3->uuidToHex().toLower())),
  380. entry3->attributes()->value("AttributeUsername"));
  381. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:p@i:%1}").arg(entry3->uuidToHex().toLower())),
  382. entry3->attributes()->value("AttributePassword"));
  383. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:a@i:%1}").arg(entry3->uuidToHex().toLower())),
  384. entry3->attributes()->value("AttributeUrl"));
  385. QCOMPARE(tstEntry->resolveMultiplePlaceholders(QString("{REF:n@i:%1}").arg(entry3->uuidToHex().toLower())),
  386. entry3->attributes()->value("AttributeNotes"));
  387. }
  388. void TestEntry::testResolveNonIdPlaceholdersToUuid()
  389. {
  390. Database db;
  391. auto* root = db.rootGroup();
  392. auto* referencedEntryTitle = new Entry();
  393. referencedEntryTitle->setGroup(root);
  394. referencedEntryTitle->setTitle("myTitle");
  395. referencedEntryTitle->setUuid(QUuid::createUuid());
  396. auto* referencedEntryUsername = new Entry();
  397. referencedEntryUsername->setGroup(root);
  398. referencedEntryUsername->setUsername("myUser");
  399. referencedEntryUsername->setUuid(QUuid::createUuid());
  400. auto* referencedEntryPassword = new Entry();
  401. referencedEntryPassword->setGroup(root);
  402. referencedEntryPassword->setPassword("myPassword");
  403. referencedEntryPassword->setUuid(QUuid::createUuid());
  404. auto* referencedEntryUrl = new Entry();
  405. referencedEntryUrl->setGroup(root);
  406. referencedEntryUrl->setUrl("myUrl");
  407. referencedEntryUrl->setUuid(QUuid::createUuid());
  408. auto* referencedEntryNotes = new Entry();
  409. referencedEntryNotes->setGroup(root);
  410. referencedEntryNotes->setNotes("myNotes");
  411. referencedEntryNotes->setUuid(QUuid::createUuid());
  412. const QList<QChar> placeholders{'T', 'U', 'P', 'A', 'N'};
  413. for (const QChar& searchIn : placeholders) {
  414. const Entry* referencedEntry = nullptr;
  415. QString newEntryNotesRaw("{REF:I@%1:%2}");
  416. switch (searchIn.toLatin1()) {
  417. case 'T':
  418. referencedEntry = referencedEntryTitle;
  419. newEntryNotesRaw = newEntryNotesRaw.arg(searchIn, referencedEntry->title());
  420. break;
  421. case 'U':
  422. referencedEntry = referencedEntryUsername;
  423. newEntryNotesRaw = newEntryNotesRaw.arg(searchIn, referencedEntry->username());
  424. break;
  425. case 'P':
  426. referencedEntry = referencedEntryPassword;
  427. newEntryNotesRaw = newEntryNotesRaw.arg(searchIn, referencedEntry->password());
  428. break;
  429. case 'A':
  430. referencedEntry = referencedEntryUrl;
  431. newEntryNotesRaw = newEntryNotesRaw.arg(searchIn, referencedEntry->url());
  432. break;
  433. case 'N':
  434. referencedEntry = referencedEntryNotes;
  435. newEntryNotesRaw = newEntryNotesRaw.arg(searchIn, referencedEntry->notes());
  436. break;
  437. default:
  438. break;
  439. }
  440. auto* newEntry = new Entry();
  441. newEntry->setGroup(root);
  442. newEntry->setNotes(newEntryNotesRaw);
  443. const QString newEntryNotesResolved = newEntry->resolveMultiplePlaceholders(newEntry->notes());
  444. QCOMPARE(newEntryNotesResolved, referencedEntry->uuidToHex());
  445. }
  446. }
  447. void TestEntry::testResolveClonedEntry()
  448. {
  449. Database db;
  450. auto* root = db.rootGroup();
  451. auto* original = new Entry();
  452. original->setGroup(root);
  453. original->setUuid(QUuid::createUuid());
  454. original->setTitle("Title");
  455. original->setUsername("SomeUsername");
  456. original->setPassword("SomePassword");
  457. QCOMPARE(original->resolveMultiplePlaceholders(original->username()), original->username());
  458. QCOMPARE(original->resolveMultiplePlaceholders(original->password()), original->password());
  459. // Top-level clones.
  460. Entry* clone1 = original->clone(Entry::CloneNewUuid);
  461. clone1->setGroup(root);
  462. Entry* clone2 = original->clone(Entry::CloneUserAsRef | Entry::CloneNewUuid);
  463. clone2->setGroup(root);
  464. Entry* clone3 = original->clone(Entry::ClonePassAsRef | Entry::CloneNewUuid);
  465. clone3->setGroup(root);
  466. Entry* clone4 = original->clone(Entry::CloneUserAsRef | Entry::ClonePassAsRef | Entry::CloneNewUuid);
  467. clone4->setGroup(root);
  468. QCOMPARE(clone1->resolveMultiplePlaceholders(clone1->username()), original->username());
  469. QCOMPARE(clone1->resolveMultiplePlaceholders(clone1->password()), original->password());
  470. QCOMPARE(clone2->resolveMultiplePlaceholders(clone2->username()), original->username());
  471. QCOMPARE(clone2->resolveMultiplePlaceholders(clone2->password()), original->password());
  472. QCOMPARE(clone3->resolveMultiplePlaceholders(clone3->username()), original->username());
  473. QCOMPARE(clone3->resolveMultiplePlaceholders(clone3->password()), original->password());
  474. QCOMPARE(clone4->resolveMultiplePlaceholders(clone4->username()), original->username());
  475. QCOMPARE(clone4->resolveMultiplePlaceholders(clone4->password()), original->password());
  476. // Second-level clones.
  477. Entry* cclone1 = clone4->clone(Entry::CloneNewUuid);
  478. cclone1->setGroup(root);
  479. Entry* cclone2 = clone4->clone(Entry::CloneUserAsRef | Entry::CloneNewUuid);
  480. cclone2->setGroup(root);
  481. Entry* cclone3 = clone4->clone(Entry::ClonePassAsRef | Entry::CloneNewUuid);
  482. cclone3->setGroup(root);
  483. Entry* cclone4 = clone4->clone(Entry::CloneUserAsRef | Entry::ClonePassAsRef | Entry::CloneNewUuid);
  484. cclone4->setGroup(root);
  485. QCOMPARE(cclone1->resolveMultiplePlaceholders(cclone1->username()), original->username());
  486. QCOMPARE(cclone1->resolveMultiplePlaceholders(cclone1->password()), original->password());
  487. QCOMPARE(cclone2->resolveMultiplePlaceholders(cclone2->username()), original->username());
  488. QCOMPARE(cclone2->resolveMultiplePlaceholders(cclone2->password()), original->password());
  489. QCOMPARE(cclone3->resolveMultiplePlaceholders(cclone3->username()), original->username());
  490. QCOMPARE(cclone3->resolveMultiplePlaceholders(cclone3->password()), original->password());
  491. QCOMPARE(cclone4->resolveMultiplePlaceholders(cclone4->username()), original->username());
  492. QCOMPARE(cclone4->resolveMultiplePlaceholders(cclone4->password()), original->password());
  493. // Change the original's attributes and make sure that the changes are tracked.
  494. QString oldUsername = original->username();
  495. QString oldPassword = original->password();
  496. original->setUsername("DifferentUsername");
  497. original->setPassword("DifferentPassword");
  498. QCOMPARE(clone1->resolveMultiplePlaceholders(clone1->username()), oldUsername);
  499. QCOMPARE(clone1->resolveMultiplePlaceholders(clone1->password()), oldPassword);
  500. QCOMPARE(clone2->resolveMultiplePlaceholders(clone2->username()), original->username());
  501. QCOMPARE(clone2->resolveMultiplePlaceholders(clone2->password()), oldPassword);
  502. QCOMPARE(clone3->resolveMultiplePlaceholders(clone3->username()), oldUsername);
  503. QCOMPARE(clone3->resolveMultiplePlaceholders(clone3->password()), original->password());
  504. QCOMPARE(clone4->resolveMultiplePlaceholders(clone4->username()), original->username());
  505. QCOMPARE(clone4->resolveMultiplePlaceholders(clone4->password()), original->password());
  506. QCOMPARE(cclone1->resolveMultiplePlaceholders(cclone1->username()), original->username());
  507. QCOMPARE(cclone1->resolveMultiplePlaceholders(cclone1->password()), original->password());
  508. QCOMPARE(cclone2->resolveMultiplePlaceholders(cclone2->username()), original->username());
  509. QCOMPARE(cclone2->resolveMultiplePlaceholders(cclone2->password()), original->password());
  510. QCOMPARE(cclone3->resolveMultiplePlaceholders(cclone3->username()), original->username());
  511. QCOMPARE(cclone3->resolveMultiplePlaceholders(cclone3->password()), original->password());
  512. QCOMPARE(cclone4->resolveMultiplePlaceholders(cclone4->username()), original->username());
  513. QCOMPARE(cclone4->resolveMultiplePlaceholders(cclone4->password()), original->password());
  514. }
  515. void TestEntry::testIsRecycled()
  516. {
  517. Entry* entry = new Entry();
  518. QVERIFY(!entry->isRecycled());
  519. Database db;
  520. Group* root = db.rootGroup();
  521. QVERIFY(root);
  522. entry->setGroup(root);
  523. QVERIFY(!entry->isRecycled());
  524. QVERIFY(db.metadata()->recycleBinEnabled());
  525. db.recycleEntry(entry);
  526. QVERIFY(entry->isRecycled());
  527. Group* group1 = new Group();
  528. group1->setParent(root);
  529. Entry* entry1 = new Entry();
  530. entry1->setGroup(group1);
  531. QVERIFY(!entry1->isRecycled());
  532. db.recycleGroup(group1);
  533. QVERIFY(entry1->isRecycled());
  534. }
  535. void TestEntry::testMoveUpDown()
  536. {
  537. Database db;
  538. Group* root = db.rootGroup();
  539. QVERIFY(root);
  540. Entry* entry0 = new Entry();
  541. QVERIFY(entry0);
  542. entry0->setGroup(root);
  543. Entry* entry1 = new Entry();
  544. QVERIFY(entry1);
  545. entry1->setGroup(root);
  546. Entry* entry2 = new Entry();
  547. QVERIFY(entry2);
  548. entry2->setGroup(root);
  549. Entry* entry3 = new Entry();
  550. QVERIFY(entry3);
  551. entry3->setGroup(root);
  552. // default order, straight
  553. QCOMPARE(root->entries().at(0), entry0);
  554. QCOMPARE(root->entries().at(1), entry1);
  555. QCOMPARE(root->entries().at(2), entry2);
  556. QCOMPARE(root->entries().at(3), entry3);
  557. entry0->moveDown();
  558. QCOMPARE(root->entries().at(0), entry1);
  559. QCOMPARE(root->entries().at(1), entry0);
  560. QCOMPARE(root->entries().at(2), entry2);
  561. QCOMPARE(root->entries().at(3), entry3);
  562. entry0->moveDown();
  563. QCOMPARE(root->entries().at(0), entry1);
  564. QCOMPARE(root->entries().at(1), entry2);
  565. QCOMPARE(root->entries().at(2), entry0);
  566. QCOMPARE(root->entries().at(3), entry3);
  567. entry0->moveDown();
  568. QCOMPARE(root->entries().at(0), entry1);
  569. QCOMPARE(root->entries().at(1), entry2);
  570. QCOMPARE(root->entries().at(2), entry3);
  571. QCOMPARE(root->entries().at(3), entry0);
  572. // no effect
  573. entry0->moveDown();
  574. QCOMPARE(root->entries().at(0), entry1);
  575. QCOMPARE(root->entries().at(1), entry2);
  576. QCOMPARE(root->entries().at(2), entry3);
  577. QCOMPARE(root->entries().at(3), entry0);
  578. entry0->moveUp();
  579. QCOMPARE(root->entries().at(0), entry1);
  580. QCOMPARE(root->entries().at(1), entry2);
  581. QCOMPARE(root->entries().at(2), entry0);
  582. QCOMPARE(root->entries().at(3), entry3);
  583. entry0->moveUp();
  584. QCOMPARE(root->entries().at(0), entry1);
  585. QCOMPARE(root->entries().at(1), entry0);
  586. QCOMPARE(root->entries().at(2), entry2);
  587. QCOMPARE(root->entries().at(3), entry3);
  588. entry0->moveUp();
  589. QCOMPARE(root->entries().at(0), entry0);
  590. QCOMPARE(root->entries().at(1), entry1);
  591. QCOMPARE(root->entries().at(2), entry2);
  592. QCOMPARE(root->entries().at(3), entry3);
  593. // no effect
  594. entry0->moveUp();
  595. QCOMPARE(root->entries().at(0), entry0);
  596. QCOMPARE(root->entries().at(1), entry1);
  597. QCOMPARE(root->entries().at(2), entry2);
  598. QCOMPARE(root->entries().at(3), entry3);
  599. entry2->moveUp();
  600. QCOMPARE(root->entries().at(0), entry0);
  601. QCOMPARE(root->entries().at(1), entry2);
  602. QCOMPARE(root->entries().at(2), entry1);
  603. QCOMPARE(root->entries().at(3), entry3);
  604. entry0->moveDown();
  605. QCOMPARE(root->entries().at(0), entry2);
  606. QCOMPARE(root->entries().at(1), entry0);
  607. QCOMPARE(root->entries().at(2), entry1);
  608. QCOMPARE(root->entries().at(3), entry3);
  609. entry3->moveUp();
  610. QCOMPARE(root->entries().at(0), entry2);
  611. QCOMPARE(root->entries().at(1), entry0);
  612. QCOMPARE(root->entries().at(2), entry3);
  613. QCOMPARE(root->entries().at(3), entry1);
  614. entry3->moveUp();
  615. QCOMPARE(root->entries().at(0), entry2);
  616. QCOMPARE(root->entries().at(1), entry3);
  617. QCOMPARE(root->entries().at(2), entry0);
  618. QCOMPARE(root->entries().at(3), entry1);
  619. entry2->moveDown();
  620. QCOMPARE(root->entries().at(0), entry3);
  621. QCOMPARE(root->entries().at(1), entry2);
  622. QCOMPARE(root->entries().at(2), entry0);
  623. QCOMPARE(root->entries().at(3), entry1);
  624. entry1->moveUp();
  625. QCOMPARE(root->entries().at(0), entry3);
  626. QCOMPARE(root->entries().at(1), entry2);
  627. QCOMPARE(root->entries().at(2), entry1);
  628. QCOMPARE(root->entries().at(3), entry0);
  629. }
  630. void TestEntry::testPreviousParentGroup()
  631. {
  632. Database db;
  633. auto* root = db.rootGroup();
  634. root->setUuid(QUuid::createUuid());
  635. QVERIFY(!root->uuid().isNull());
  636. auto* group1 = new Group();
  637. group1->setUuid(QUuid::createUuid());
  638. group1->setParent(root);
  639. QVERIFY(!group1->uuid().isNull());
  640. auto* group2 = new Group();
  641. group2->setParent(root);
  642. group2->setUuid(QUuid::createUuid());
  643. QVERIFY(!group2->uuid().isNull());
  644. auto* entry = new Entry();
  645. QVERIFY(entry);
  646. QVERIFY(entry->previousParentGroupUuid().isNull());
  647. QVERIFY(!entry->previousParentGroup());
  648. entry->setGroup(root);
  649. QVERIFY(entry->previousParentGroupUuid().isNull());
  650. QVERIFY(!entry->previousParentGroup());
  651. // Previous parent shouldn't be recorded if new and old parent are the same
  652. entry->setGroup(root);
  653. QVERIFY(entry->previousParentGroupUuid().isNull());
  654. QVERIFY(!entry->previousParentGroup());
  655. entry->setGroup(group1);
  656. QVERIFY(entry->previousParentGroupUuid() == root->uuid());
  657. QVERIFY(entry->previousParentGroup() == root);
  658. entry->setGroup(group2);
  659. QVERIFY(entry->previousParentGroupUuid() == group1->uuid());
  660. QVERIFY(entry->previousParentGroup() == group1);
  661. entry->setGroup(group2);
  662. QVERIFY(entry->previousParentGroupUuid() == group1->uuid());
  663. QVERIFY(entry->previousParentGroup() == group1);
  664. }