keepassxc

Форк
0
/
TestBrowser.cpp 
742 строки · 30.9 Кб
1
/*
2
 *  Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
3
 *
4
 *  This program is free software: you can redistribute it and/or modify
5
 *  it under the terms of the GNU General Public License as published by
6
 *  the Free Software Foundation, either version 2 or (at your option)
7
 *  version 3 of the License.
8
 *
9
 *  This program is distributed in the hope that it will be useful,
10
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 *  GNU General Public License for more details.
13
 *
14
 *  You should have received a copy of the GNU General Public License
15
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
 */
17

18
#include "TestBrowser.h"
19

20
#include "browser/BrowserMessageBuilder.h"
21
#include "browser/BrowserSettings.h"
22
#include "core/Group.h"
23
#include "core/Tools.h"
24
#include "crypto/Crypto.h"
25

26
#include <QJsonObject>
27
#include <QTest>
28

29
#include <botan/sodium.h>
30

31
using namespace Botan::Sodium;
32

33
QTEST_GUILESS_MAIN(TestBrowser)
34

35
const QString PUBLICKEY = "UIIPObeoya1G8g1M5omgyoPR/j1mR1HlYHu0wHCgMhA=";
36
const QString SECRETKEY = "B8ei4ZjQJkWzZU2SK/tBsrYRwp+6ztEMf5GFQV+i0yI=";
37
const QString SERVERPUBLICKEY = "lKnbLhrVCOqzEjuNoUz1xj9EZlz8xeO4miZBvLrUPVQ=";
38
const QString SERVERSECRETKEY = "tbPQcghxfOgbmsnEqG2qMIj1W2+nh+lOJcNsHncaz1Q=";
39
const QString NONCE = "zBKdvTjL5bgWaKMCTut/8soM/uoMrFoZ";
40
const QString INCREMENTEDNONCE = "zRKdvTjL5bgWaKMCTut/8soM/uoMrFoZ";
41
const QString CLIENTID = "testClient";
42

43
void TestBrowser::initTestCase()
44
{
45
    QVERIFY(Crypto::init());
46
    m_browserService = browserService();
47
    browserSettings()->setBestMatchOnly(false);
48
}
49

50
void TestBrowser::init()
51
{
52
    m_browserAction.reset(new BrowserAction());
53
}
54

55
/**
56
 * Tests for BrowserAction
57
 */
58

59
void TestBrowser::testChangePublicKeys()
60
{
61
    QJsonObject json;
62
    json["action"] = "change-public-keys";
63
    json["publicKey"] = PUBLICKEY;
64
    json["nonce"] = NONCE;
65

66
    auto response = m_browserAction->processClientMessage(nullptr, json);
67
    QCOMPARE(response["action"].toString(), QString("change-public-keys"));
68
    QCOMPARE(response["publicKey"].toString() == PUBLICKEY, false);
69
    QCOMPARE(response["success"].toString(), TRUE_STR);
70
}
71

72
void TestBrowser::testEncryptMessage()
73
{
74
    QJsonObject message;
75
    message["action"] = "test-action";
76

77
    m_browserAction->m_publicKey = SERVERPUBLICKEY;
78
    m_browserAction->m_secretKey = SERVERSECRETKEY;
79
    m_browserAction->m_clientPublicKey = PUBLICKEY;
80
    auto encrypted = browserMessageBuilder()->encryptMessage(message, NONCE, PUBLICKEY, SERVERSECRETKEY);
81

82
    QCOMPARE(encrypted, QString("+zjtntnk4rGWSl/Ph7Vqip/swvgeupk4lNgHEm2OO3ujNr0OMz6eQtGwjtsj+/rP"));
83
}
84

85
void TestBrowser::testDecryptMessage()
86
{
87
    QString message = "+zjtntnk4rGWSl/Ph7Vqip/swvgeupk4lNgHEm2OO3ujNr0OMz6eQtGwjtsj+/rP";
88
    m_browserAction->m_publicKey = SERVERPUBLICKEY;
89
    m_browserAction->m_secretKey = SERVERSECRETKEY;
90
    m_browserAction->m_clientPublicKey = PUBLICKEY;
91
    auto decrypted = browserMessageBuilder()->decryptMessage(message, NONCE, PUBLICKEY, SERVERSECRETKEY);
92

93
    QCOMPARE(decrypted["action"].toString(), QString("test-action"));
94
}
95

96
void TestBrowser::testGetBase64FromKey()
97
{
98
    unsigned char pk[crypto_box_PUBLICKEYBYTES];
99

100
    for (unsigned int i = 0; i < crypto_box_PUBLICKEYBYTES; ++i) {
101
        pk[i] = i;
102
    }
103

104
    auto response = browserMessageBuilder()->getBase64FromKey(pk, crypto_box_PUBLICKEYBYTES);
105
    QCOMPARE(response, QString("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="));
106
}
107

108
void TestBrowser::testIncrementNonce()
109
{
110
    auto result = browserMessageBuilder()->incrementNonce(NONCE);
111
    QCOMPARE(result, INCREMENTEDNONCE);
112
}
113

114
void TestBrowser::testBuildResponse()
115
{
116
    const auto object = QJsonObject{{"test", true}};
117
    const QJsonArray arr = {QJsonObject{{"test", true}}};
118
    const auto val = QString("value1");
119

120
    // Note: Passing a const QJsonObject will fail
121
    const Parameters params{
122
        {"test-param-1", val}, {"test-param-2", 2}, {"test-param-3", false}, {"object", object}, {"arr", arr}};
123

124
    const auto action = QString("test-action");
125
    const auto message = browserMessageBuilder()->buildResponse(action, NONCE, params, PUBLICKEY, SERVERSECRETKEY);
126
    QVERIFY(!message.isEmpty());
127
    QCOMPARE(message["action"].toString(), action);
128
    QCOMPARE(message["nonce"].toString(), NONCE);
129

130
    const auto decrypted =
131
        browserMessageBuilder()->decryptMessage(message["message"].toString(), NONCE, PUBLICKEY, SERVERSECRETKEY);
132
    QVERIFY(!decrypted.isEmpty());
133
    QCOMPARE(decrypted["test-param-1"].toString(), QString("value1"));
134
    QCOMPARE(decrypted["test-param-2"].toInt(), 2);
135
    QCOMPARE(decrypted["test-param-3"].toBool(), false);
136

137
    const auto objectResult = decrypted["object"].toObject();
138
    QCOMPARE(objectResult["test"].toBool(), true);
139

140
    const auto arrResult = decrypted["arr"].toArray();
141
    QCOMPARE(arrResult.size(), 1);
142

143
    const auto firstArr = arrResult[0].toObject();
144
    QCOMPARE(firstArr["test"].toBool(), true);
145
}
146

147
void TestBrowser::testSortPriority()
148
{
149
    QFETCH(QString, entryUrl);
150
    QFETCH(QString, siteUrl);
151
    QFETCH(QString, formUrl);
152
    QFETCH(int, expectedScore);
153

154
    QScopedPointer<Entry> entry(new Entry());
155
    entry->setUrl(entryUrl);
156

157
    QCOMPARE(m_browserService->sortPriority(entry->getAllUrls(), siteUrl, formUrl), expectedScore);
158
}
159

160
void TestBrowser::testSortPriority_data()
161
{
162
    const QString siteUrl = "https://github.com/login";
163
    const QString formUrl = "https://github.com/session";
164

165
    QTest::addColumn<QString>("entryUrl");
166
    QTest::addColumn<QString>("siteUrl");
167
    QTest::addColumn<QString>("formUrl");
168
    QTest::addColumn<int>("expectedScore");
169

170
    QTest::newRow("Exact Match") << siteUrl << siteUrl << siteUrl << 100;
171
    QTest::newRow("Exact Match (site)") << siteUrl << siteUrl << formUrl << 100;
172
    QTest::newRow("Exact Match (form)") << siteUrl << "https://github.net" << siteUrl << 100;
173
    QTest::newRow("Exact Match No Trailing Slash") << "https://github.com"
174
                                                   << "https://github.com/" << formUrl << 100;
175
    QTest::newRow("Exact Match No Scheme") << "github.com/login" << siteUrl << formUrl << 100;
176
    QTest::newRow("Exact Match with Query") << "https://github.com/login?test=test#fragment"
177
                                            << "https://github.com/login?test=test" << formUrl << 100;
178

179
    QTest::newRow("Site Query Mismatch") << siteUrl << siteUrl + "?test=test" << formUrl << 90;
180

181
    QTest::newRow("Path Mismatch (site)") << "https://github.com/" << siteUrl << formUrl << 85;
182
    QTest::newRow("Path Mismatch (site) No Scheme") << "github.com" << siteUrl << formUrl << 85;
183
    QTest::newRow("Path Mismatch (form)") << "https://github.com/"
184
                                          << "https://github.net" << formUrl << 85;
185
    QTest::newRow("Path Mismatch (diff parent)") << "https://github.com/keepassxreboot" << siteUrl << formUrl << 80;
186
    QTest::newRow("Path Mismatch (diff parent, form)") << "https://github.com/keepassxreboot"
187
                                                       << "https://github.net" << formUrl << 70;
188

189
    QTest::newRow("Subdomain Mismatch (site)") << siteUrl << "https://sub.github.com/"
190
                                               << "https://github.net/" << 60;
191
    QTest::newRow("Subdomain Mismatch (form)") << siteUrl << "https://github.net/"
192
                                               << "https://sub.github.com/" << 50;
193

194
    QTest::newRow("Scheme Mismatch") << "http://github.com" << siteUrl << formUrl << 0;
195
    QTest::newRow("Scheme Mismatch w/path") << "http://github.com/login" << siteUrl << formUrl << 0;
196
    QTest::newRow("Invalid URL") << "http://github" << siteUrl << formUrl << 0;
197
}
198

199
void TestBrowser::testSearchEntries()
200
{
201
    auto db = QSharedPointer<Database>::create();
202
    auto* root = db->rootGroup();
203

204
    QStringList urls = {"https://github.com/login_page",
205
                        "https://github.com/login",
206
                        "https://github.com/",
207
                        "github.com/login",
208
                        "http://github.com",
209
                        "http://github.com/login",
210
                        "github.com",
211
                        "github.com/login",
212
                        "https://github", // Invalid URL
213
                        "github.com"};
214

215
    createEntries(urls, root);
216

217
    browserSettings()->setMatchUrlScheme(false);
218
    auto result =
219
        m_browserService->searchEntries(db, "https://github.com", "https://github.com/session"); // db, url, submitUrl
220

221
    QCOMPARE(result.length(), 9);
222
    QCOMPARE(result[0]->url(), QString("https://github.com/login_page"));
223
    QCOMPARE(result[1]->url(), QString("https://github.com/login"));
224
    QCOMPARE(result[2]->url(), QString("https://github.com/"));
225
    QCOMPARE(result[3]->url(), QString("github.com/login"));
226
    QCOMPARE(result[4]->url(), QString("http://github.com"));
227
    QCOMPARE(result[5]->url(), QString("http://github.com/login"));
228

229
    // With matching there should be only 3 results + 4 without a scheme
230
    browserSettings()->setMatchUrlScheme(true);
231
    result = m_browserService->searchEntries(db, "https://github.com", "https://github.com/session");
232
    QCOMPARE(result.length(), 7);
233
    QCOMPARE(result[0]->url(), QString("https://github.com/login_page"));
234
    QCOMPARE(result[1]->url(), QString("https://github.com/login"));
235
    QCOMPARE(result[2]->url(), QString("https://github.com/"));
236
    QCOMPARE(result[3]->url(), QString("github.com/login"));
237
}
238

239
void TestBrowser::testSearchEntriesByPath()
240
{
241
    auto db = QSharedPointer<Database>::create();
242
    auto* root = db->rootGroup();
243

244
    QStringList urlsRoot = {"https://root.example.com/", "root.example.com/login"};
245
    auto entriesRoot = createEntries(urlsRoot, root);
246

247
    auto* groupLevel1 = new Group();
248
    groupLevel1->setParent(root);
249
    groupLevel1->setName("TestGroup1");
250
    QStringList urlsLevel1 = {"https://1.example.com/", "1.example.com/login"};
251
    auto entriesLevel1 = createEntries(urlsLevel1, groupLevel1);
252

253
    auto* groupLevel2 = new Group();
254
    groupLevel2->setParent(groupLevel1);
255
    groupLevel2->setName("TestGroup2");
256
    QStringList urlsLevel2 = {"https://2.example.com/", "2.example.com/login"};
257
    auto entriesLevel2 = createEntries(urlsLevel2, groupLevel2);
258

259
    compareEntriesByPath(db, entriesRoot, "");
260
    compareEntriesByPath(db, entriesLevel1, "TestGroup1/");
261
    compareEntriesByPath(db, entriesLevel2, "TestGroup1/TestGroup2/");
262
}
263

264
void TestBrowser::compareEntriesByPath(QSharedPointer<Database> db, QList<Entry*> entries, QString path)
265
{
266
    for (Entry* entry : entries) {
267
        QString testUrl = "keepassxc://by-path/" + path + entry->title();
268
        /* Look for an entry with that path. First using handleEntry, then through the search */
269
        QCOMPARE(m_browserService->shouldIncludeEntry(entry, testUrl, ""), true);
270
        auto result = m_browserService->searchEntries(db, testUrl, "");
271
        QCOMPARE(result.length(), 1);
272
        QCOMPARE(result[0], entry);
273
    }
274
}
275

276
void TestBrowser::testSearchEntriesByUUID()
277
{
278
    auto db = QSharedPointer<Database>::create();
279
    auto* root = db->rootGroup();
280

281
    /* The URLs don't really matter for this test, we just need some entries */
282
    QStringList urls = {"https://github.com/login_page",
283
                        "https://github.com/login",
284
                        "https://github.com/",
285
                        "github.com/login",
286
                        "http://github.com",
287
                        "http://github.com/login",
288
                        "github.com",
289
                        "github.com/login",
290
                        "https://github",
291
                        "github.com",
292
                        "",
293
                        "not an URL"};
294
    auto entries = createEntries(urls, root);
295

296
    for (Entry* entry : entries) {
297
        QString testUrl = "keepassxc://by-uuid/" + entry->uuidToHex();
298
        /* Look for an entry with that UUID. First using shouldIncludeEntry, then through the search */
299
        QCOMPARE(m_browserService->shouldIncludeEntry(entry, testUrl, ""), true);
300
        auto result = m_browserService->searchEntries(db, testUrl, "");
301
        QCOMPARE(result.length(), 1);
302
        QCOMPARE(result[0], entry);
303
    }
304

305
    /* Test for entries that don't exist */
306
    QStringList uuids = {"00000000000000000000000000000000",
307
                         "00000000000000000000000000000001",
308
                         "00000000000000000000000000000002/",
309
                         "invalid uuid",
310
                         "000000000000000000000000000000000000000"
311
                         "00000000000000000000000"};
312

313
    for (QString uuid : uuids) {
314
        QString testUrl = "keepassxc://by-uuid/" + uuid;
315

316
        for (Entry* entry : entries) {
317
            QCOMPARE(m_browserService->shouldIncludeEntry(entry, testUrl, ""), false);
318
        }
319

320
        auto result = m_browserService->searchEntries(db, testUrl, "");
321
        QCOMPARE(result.length(), 0);
322
    }
323
}
324

325
void TestBrowser::testSearchEntriesByReference()
326
{
327
    auto db = QSharedPointer<Database>::create();
328
    auto* root = db->rootGroup();
329

330
    /* The URLs don't really matter for this test, we just need some entries */
331
    QStringList urls = {"https://subdomain.example.com",
332
                        "example.com", // Only includes a partial URL for references
333
                        "https://another.domain.com", // Additional URL as full reference
334
                        "https://subdomain.somesite.com", // Additional URL as partial reference
335
                        "", // Full reference will be added to https://subdomain.example.com
336
                        "" // Partial reference will be added to https://subdomain.example.com
337
                        "https://www.notincluded.com"}; // Should not show in search
338
    auto entries = createEntries(urls, root);
339

340
    auto firstEntryUuid = entries.first()->uuidToHex();
341
    auto secondEntryUuid = entries[1]->uuidToHex();
342
    auto fullReference = QString("{REF:A@I:%1}").arg(firstEntryUuid);
343
    auto partialReference = QString("https://subdomain.{REF:A@I:%1}").arg(secondEntryUuid);
344
    entries[2]->attributes()->set(EntryAttributes::AdditionalUrlAttribute, fullReference);
345
    entries[3]->attributes()->set(EntryAttributes::AdditionalUrlAttribute, partialReference);
346
    entries[4]->setUrl(fullReference);
347
    entries[5]->setUrl(partialReference);
348

349
    auto result = m_browserService->searchEntries(db, "https://subdomain.example.com", "");
350
    QCOMPARE(result.length(), 6);
351
    QCOMPARE(result[0]->url(), urls[0]);
352
    QCOMPARE(result[1]->url(), urls[1]);
353
    QCOMPARE(result[2]->url(), urls[2]);
354
    QCOMPARE(
355
        result[2]->resolveMultiplePlaceholders(result[2]->attributes()->value(EntryAttributes::AdditionalUrlAttribute)),
356
        urls[0]);
357
    QCOMPARE(result[3]->url(), urls[3]);
358
    QCOMPARE(
359
        result[3]->resolveMultiplePlaceholders(result[3]->attributes()->value(EntryAttributes::AdditionalUrlAttribute)),
360
        urls[0]);
361
    QCOMPARE(result[4]->url(), fullReference);
362
    QCOMPARE(result[4]->resolveMultiplePlaceholders(result[4]->url()), urls[0]); // Should be resolved to the main entry
363
    QCOMPARE(result[5]->url(), partialReference);
364
    QCOMPARE(result[5]->resolveMultiplePlaceholders(result[5]->url()), urls[0]); // Should be resolved to the main entry
365
}
366

367
void TestBrowser::testSearchEntriesWithPort()
368
{
369
    auto db = QSharedPointer<Database>::create();
370
    auto* root = db->rootGroup();
371

372
    QStringList urls = {"http://127.0.0.1:443", "http://127.0.0.1:80"};
373

374
    createEntries(urls, root);
375

376
    auto result = m_browserService->searchEntries(db, "http://127.0.0.1:443", "http://127.0.0.1");
377
    QCOMPARE(result.length(), 1);
378
    QCOMPARE(result[0]->url(), QString("http://127.0.0.1:443"));
379
}
380

381
void TestBrowser::testSearchEntriesWithAdditionalURLs()
382
{
383
    auto db = QSharedPointer<Database>::create();
384
    auto* root = db->rootGroup();
385

386
    QStringList urls = {"https://github.com/", "https://www.example.com", "http://domain.com"};
387

388
    auto entries = createEntries(urls, root);
389

390
    // Add an additional URL to the first entry
391
    entries.first()->attributes()->set(EntryAttributes::AdditionalUrlAttribute, "https://keepassxc.org");
392

393
    auto result = m_browserService->searchEntries(db, "https://github.com", "https://github.com/session");
394
    QCOMPARE(result.length(), 1);
395
    QCOMPARE(result[0]->url(), QString("https://github.com/"));
396

397
    // Search the additional URL. It should return the same entry
398
    auto additionalResult = m_browserService->searchEntries(db, "https://keepassxc.org", "https://keepassxc.org");
399
    QCOMPARE(additionalResult.length(), 1);
400
    QCOMPARE(additionalResult[0]->url(), QString("https://github.com/"));
401
}
402

403
void TestBrowser::testInvalidEntries()
404
{
405
    auto db = QSharedPointer<Database>::create();
406
    auto* root = db->rootGroup();
407
    const QString url("https://github.com");
408
    const QString submitUrl("https://github.com/session");
409

410
    QStringList urls = {
411
        "https://github.com/login",
412
        "https:///github.com/", // Extra '/'
413
        "http://github.com/**//*",
414
        "http://*.github.com/login",
415
        "//github.com", // fromUserInput() corrects this one.
416
        "github.com/{}<>",
417
        "http:/example.com",
418
    };
419

420
    createEntries(urls, root);
421

422
    browserSettings()->setMatchUrlScheme(true);
423
    auto result = m_browserService->searchEntries(db, "https://github.com", "https://github.com/session");
424
    QCOMPARE(result.length(), 2);
425
    QCOMPARE(result[0]->url(), QString("https://github.com/login"));
426
    QCOMPARE(result[1]->url(), QString("//github.com"));
427

428
    // Test the URL's directly
429
    QCOMPARE(m_browserService->handleURL(urls[0], url, submitUrl), true);
430
    QCOMPARE(m_browserService->handleURL(urls[1], url, submitUrl), false);
431
    QCOMPARE(m_browserService->handleURL(urls[2], url, submitUrl), false);
432
    QCOMPARE(m_browserService->handleURL(urls[3], url, submitUrl), false);
433
    QCOMPARE(m_browserService->handleURL(urls[4], url, submitUrl), true);
434
    QCOMPARE(m_browserService->handleURL(urls[5], url, submitUrl), false);
435
}
436

437
void TestBrowser::testSubdomainsAndPaths()
438
{
439
    auto db = QSharedPointer<Database>::create();
440
    auto* root = db->rootGroup();
441

442
    QStringList urls = {
443
        "https://www.github.com/login/page.xml",
444
        "https://login.github.com/",
445
        "https://github.com",
446
        "http://www.github.com",
447
        "http://login.github.com/pathtonowhere",
448
        ".github.com", // Invalid URL
449
        "www.github.com/",
450
        "https://github", // Invalid URL
451
        "https://hub.com" // Should not return
452
    };
453

454
    createEntries(urls, root);
455

456
    browserSettings()->setMatchUrlScheme(false);
457
    auto result = m_browserService->searchEntries(db, "https://github.com", "https://github.com/session");
458
    QCOMPARE(result.length(), 1);
459
    QCOMPARE(result[0]->url(), QString("https://github.com"));
460

461
    // With www subdomain
462
    result = m_browserService->searchEntries(db, "https://www.github.com", "https://www.github.com/session");
463
    QCOMPARE(result.length(), 4);
464
    QCOMPARE(result[0]->url(), QString("https://www.github.com/login/page.xml"));
465
    QCOMPARE(result[1]->url(), QString("https://github.com")); // Accepts any subdomain
466
    QCOMPARE(result[2]->url(), QString("http://www.github.com"));
467
    QCOMPARE(result[3]->url(), QString("www.github.com/"));
468

469
    // With www subdomain omitted
470
    root->setCustomDataTriState(BrowserService::OPTION_OMIT_WWW, Group::Enable);
471
    result = m_browserService->searchEntries(db, "https://github.com", "https://github.com/session");
472
    root->setCustomDataTriState(BrowserService::OPTION_OMIT_WWW, Group::Inherit);
473
    QCOMPARE(result.length(), 4);
474
    QCOMPARE(result[0]->url(), QString("https://www.github.com/login/page.xml"));
475
    QCOMPARE(result[1]->url(), QString("https://github.com"));
476
    QCOMPARE(result[2]->url(), QString("http://www.github.com"));
477
    QCOMPARE(result[3]->url(), QString("www.github.com/"));
478

479
    // With scheme matching there should be only 1 result
480
    browserSettings()->setMatchUrlScheme(true);
481
    result = m_browserService->searchEntries(db, "https://github.com", "https://github.com/session");
482
    QCOMPARE(result.length(), 1);
483
    QCOMPARE(result[0]->url(), QString("https://github.com"));
484

485
    // Test site with subdomain in the site URL
486
    QStringList entryURLs = {
487
        "https://accounts.example.com",
488
        "https://accounts.example.com/path",
489
        "https://subdomain.example.com/",
490
        "https://another.accounts.example.com/",
491
        "https://another.subdomain.example.com/",
492
        "https://example.com/",
493
        "https://example" // Invalid URL
494
    };
495

496
    createEntries(entryURLs, root);
497

498
    result = m_browserService->searchEntries(db, "https://accounts.example.com/", "https://accounts.example.com/");
499
    QCOMPARE(result.length(), 3);
500
    QCOMPARE(result[0]->url(), QString("https://accounts.example.com"));
501
    QCOMPARE(result[1]->url(), QString("https://accounts.example.com/path"));
502
    QCOMPARE(result[2]->url(), QString("https://example.com/")); // Accepts any subdomain
503

504
    result = m_browserService->searchEntries(
505
        db, "https://another.accounts.example.com/", "https://another.accounts.example.com/");
506
    QCOMPARE(result.length(), 4);
507
    QCOMPARE(result[0]->url(),
508
             QString("https://accounts.example.com")); // Accepts any subdomain under accounts.example.com
509
    QCOMPARE(result[1]->url(), QString("https://accounts.example.com/path"));
510
    QCOMPARE(result[2]->url(), QString("https://another.accounts.example.com/"));
511
    QCOMPARE(result[3]->url(), QString("https://example.com/")); // Accepts one or more subdomains
512

513
    // Test local files. It should be a direct match.
514
    QStringList localFiles = {"file:///Users/testUser/tests/test.html"};
515

516
    createEntries(localFiles, root);
517

518
    // With local files, url is always set to the file scheme + ://. Submit URL holds the actual URL.
519
    result = m_browserService->searchEntries(db, "file://", "file:///Users/testUser/tests/test.html");
520
    QCOMPARE(result.length(), 1);
521
}
522

523
QList<Entry*> TestBrowser::createEntries(QStringList& urls, Group* root) const
524
{
525
    QList<Entry*> entries;
526
    for (int i = 0; i < urls.length(); ++i) {
527
        auto entry = new Entry();
528
        entry->setGroup(root);
529
        entry->beginUpdate();
530
        entry->setUrl(urls[i]);
531
        entry->setUsername(QString("User %1").arg(i));
532
        entry->setUuid(QUuid::createUuid());
533
        entry->setTitle(QString("Name_%1").arg(entry->uuidToHex()));
534
        entry->endUpdate();
535
        entries.push_back(entry);
536
    }
537

538
    return entries;
539
}
540

541
void TestBrowser::testBestMatchingCredentials()
542
{
543
    auto db = QSharedPointer<Database>::create();
544
    auto* root = db->rootGroup();
545

546
    // Test with simple URL entries
547
    QStringList urls = {"https://github.com/loginpage", "https://github.com/justsomepage", "https://github.com/"};
548

549
    auto entries = createEntries(urls, root);
550

551
    browserSettings()->setBestMatchOnly(true);
552

553
    QString siteUrl = "https://github.com/loginpage";
554
    auto result = m_browserService->searchEntries(db, siteUrl, siteUrl);
555
    auto sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
556
    QCOMPARE(sorted.size(), 1);
557
    QCOMPARE(sorted[0]->url(), siteUrl);
558

559
    siteUrl = "https://github.com/justsomepage";
560
    result = m_browserService->searchEntries(db, siteUrl, siteUrl);
561
    sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
562
    QCOMPARE(sorted.size(), 1);
563
    QCOMPARE(sorted[0]->url(), siteUrl);
564

565
    siteUrl = "https://github.com/";
566
    result = m_browserService->searchEntries(db, siteUrl, siteUrl);
567
    sorted = m_browserService->sortEntries(entries, siteUrl, siteUrl);
568
    QCOMPARE(sorted.size(), 1);
569
    QCOMPARE(sorted[0]->url(), siteUrl);
570

571
    // Without best-matching the URL with the path should be returned first
572
    browserSettings()->setBestMatchOnly(false);
573
    siteUrl = "https://github.com/loginpage";
574
    result = m_browserService->searchEntries(db, siteUrl, siteUrl);
575
    sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
576
    QCOMPARE(sorted.size(), 3);
577
    QCOMPARE(sorted[0]->url(), siteUrl);
578

579
    // Test with subdomains
580
    QStringList subdomainsUrls = {"https://sub.github.com/loginpage",
581
                                  "https://sub.github.com/justsomepage",
582
                                  "https://bus.github.com/justsomepage",
583
                                  "https://subdomain.example.com/",
584
                                  "https://subdomain.example.com",
585
                                  "https://example.com"};
586

587
    entries = createEntries(subdomainsUrls, root);
588

589
    browserSettings()->setBestMatchOnly(true);
590
    siteUrl = "https://sub.github.com/justsomepage";
591
    result = m_browserService->searchEntries(db, siteUrl, siteUrl);
592
    sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
593
    QCOMPARE(sorted.size(), 1);
594
    QCOMPARE(sorted[0]->url(), siteUrl);
595

596
    siteUrl = "https://github.com/justsomepage";
597
    result = m_browserService->searchEntries(db, siteUrl, siteUrl);
598
    sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
599
    QCOMPARE(sorted.size(), 1);
600
    QCOMPARE(sorted[0]->url(), siteUrl);
601

602
    siteUrl = "https://sub.github.com/justsomepage?wehavesomeextra=here";
603
    result = m_browserService->searchEntries(db, siteUrl, siteUrl);
604
    sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
605
    QCOMPARE(sorted.size(), 1);
606
    QCOMPARE(sorted[0]->url(), QString("https://sub.github.com/justsomepage"));
607

608
    // The matching should not care if there's a / path or not.
609
    siteUrl = "https://subdomain.example.com/";
610
    result = m_browserService->searchEntries(db, siteUrl, siteUrl);
611
    sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
612
    QCOMPARE(sorted.size(), 2);
613
    QCOMPARE(sorted[0]->url(), QString("https://subdomain.example.com"));
614
    QCOMPARE(sorted[1]->url(), QString("https://subdomain.example.com/"));
615

616
    // Entries with https://example.com should be still returned even if the site URL has a subdomain. Those have the
617
    // best match.
618
    db = QSharedPointer<Database>::create();
619
    root = db->rootGroup();
620
    QStringList domainUrls = {"https://example.com", "https://example.com", "https://other.example.com"};
621
    entries = createEntries(domainUrls, root);
622
    siteUrl = "https://subdomain.example.com";
623
    result = m_browserService->searchEntries(db, siteUrl, siteUrl);
624
    sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
625

626
    QCOMPARE(sorted.size(), 2);
627
    QCOMPARE(sorted[0]->url(), QString("https://example.com"));
628
    QCOMPARE(sorted[1]->url(), QString("https://example.com"));
629

630
    // https://github.com/keepassxreboot/keepassxc/issues/4754
631
    db = QSharedPointer<Database>::create();
632
    root = db->rootGroup();
633
    QStringList fooUrls = {"https://example.com/foo", "https://example.com/bar"};
634
    entries = createEntries(fooUrls, root);
635

636
    for (const auto& url : fooUrls) {
637
        result = m_browserService->searchEntries(db, url, url);
638
        sorted = m_browserService->sortEntries(result, url, url);
639
        QCOMPARE(sorted.size(), 1);
640
        QCOMPARE(sorted[0]->url(), QString(url));
641
    }
642

643
    // https://github.com/keepassxreboot/keepassxc/issues/4734
644
    db = QSharedPointer<Database>::create();
645
    root = db->rootGroup();
646
    QStringList testUrls = {"http://some.domain.tld/somePath", "http://some.domain.tld/otherPath"};
647
    entries = createEntries(testUrls, root);
648

649
    for (const auto& url : testUrls) {
650
        result = m_browserService->searchEntries(db, url, url);
651
        sorted = m_browserService->sortEntries(result, url, url);
652
        QCOMPARE(sorted.size(), 1);
653
        QCOMPARE(sorted[0]->url(), QString(url));
654
    }
655
}
656

657
void TestBrowser::testBestMatchingWithAdditionalURLs()
658
{
659
    auto db = QSharedPointer<Database>::create();
660
    auto* root = db->rootGroup();
661

662
    QStringList urls = {"https://github.com/loginpage", "https://test.github.com/", "https://github.com/"};
663

664
    auto entries = createEntries(urls, root);
665
    browserSettings()->setBestMatchOnly(true);
666

667
    // Add an additional URL to the first entry
668
    entries.first()->attributes()->set(EntryAttributes::AdditionalUrlAttribute, "https://test.github.com/anotherpage");
669

670
    // The first entry should be triggered
671
    auto result = m_browserService->searchEntries(
672
        db, "https://test.github.com/anotherpage", "https://test.github.com/anotherpage");
673
    auto sorted = m_browserService->sortEntries(
674
        result, "https://test.github.com/anotherpage", "https://test.github.com/anotherpage");
675
    QCOMPARE(sorted.length(), 1);
676
    QCOMPARE(sorted[0]->url(), urls[0]);
677
}
678

679
void TestBrowser::testRestrictBrowserKey()
680
{
681
    auto db = QSharedPointer<Database>::create();
682
    auto* root = db->rootGroup();
683

684
    // Group 0 (root): No browser key restriction given
685
    QStringList urlsRoot = {"https://example.com/0"};
686
    auto entriesRoot = createEntries(urlsRoot, root);
687

688
    // Group 1: restricted to browser with 'key1'
689
    auto* group1 = new Group();
690
    group1->setParent(root);
691
    group1->setName("TestGroup1");
692
    group1->customData()->set(BrowserService::OPTION_RESTRICT_KEY, "key1");
693
    QStringList urls1 = {"https://example.com/1"};
694
    auto entries1 = createEntries(urls1, group1);
695

696
    // Group 2: restricted to browser with 'key2'
697
    auto* group2 = new Group();
698
    group2->setParent(root);
699
    group2->setName("TestGroup2");
700
    group2->customData()->set(BrowserService::OPTION_RESTRICT_KEY, "key2");
701
    QStringList urls2 = {"https://example.com/2"};
702
    auto entries2 = createEntries(urls2, group2);
703

704
    // Group 2b: inherits parent group (2) restriction
705
    auto* group2b = new Group();
706
    group2b->setParent(group2);
707
    group2b->setName("TestGroup2b");
708
    QStringList urls2b = {"https://example.com/2b"};
709
    auto entries2b = createEntries(urls2b, group2b);
710

711
    // Group 3: inherits parent group (root) - any browser can see
712
    auto* group3 = new Group();
713
    group3->setParent(root);
714
    group3->setName("TestGroup3");
715
    QStringList urls3 = {"https://example.com/3"};
716
    auto entries3 = createEntries(urls3, group3);
717

718
    // Browser 'key0': Groups 1 and 2 are excluded, so entries 0 and 3 will be found
719
    auto siteUrl = QString("https://example.com");
720
    auto result = m_browserService->searchEntries(db, siteUrl, siteUrl, {"key0"});
721
    auto sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
722
    QCOMPARE(sorted.size(), 2);
723
    QCOMPARE(sorted[0]->url(), QString("https://example.com/3"));
724
    QCOMPARE(sorted[1]->url(), QString("https://example.com/0"));
725

726
    // Browser 'key1': Group 2 will be excluded, so entries 0, 1, and 3 will be found
727
    result = m_browserService->searchEntries(db, siteUrl, siteUrl, {"key1"});
728
    sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
729
    QCOMPARE(sorted.size(), 3);
730
    QCOMPARE(sorted[0]->url(), QString("https://example.com/3"));
731
    QCOMPARE(sorted[1]->url(), QString("https://example.com/1"));
732
    QCOMPARE(sorted[2]->url(), QString("https://example.com/0"));
733

734
    // Browser 'key2': Group 1 will be excluded, so entries 0, 2, 2b, 3 will be found
735
    result = m_browserService->searchEntries(db, siteUrl, siteUrl, {"key2"});
736
    sorted = m_browserService->sortEntries(result, siteUrl, siteUrl);
737
    QCOMPARE(sorted.size(), 4);
738
    QCOMPARE(sorted[0]->url(), QString("https://example.com/3"));
739
    QCOMPARE(sorted[1]->url(), QString("https://example.com/2b"));
740
    QCOMPARE(sorted[2]->url(), QString("https://example.com/2"));
741
    QCOMPARE(sorted[3]->url(), QString("https://example.com/0"));
742
}
743

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.