File indexing completed on 2024-05-12 05:52:47
0001 /* 0002 * SPDX-License-Identifier: GPL-3.0-or-later 0003 * SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com> 0004 */ 0005 #include "account/actions_p.h" 0006 0007 #include "../test-utils/output.h" 0008 #include "../test-utils/secret.h" 0009 #include "../../test-utils/spy.h" 0010 #include "../../secrets/test-utils/random.h" 0011 0012 #include <QFile> 0013 #include <QSignalSpy> 0014 #include <QString> 0015 #include <QTest> 0016 #include <QUuid> 0017 #include <QtDebug> 0018 0019 class SaveTotpTest: public QObject 0020 { 0021 Q_OBJECT 0022 private Q_SLOTS: 0023 void initTestCase(void); 0024 void validTotp(void); 0025 void validTotp_data(void); 0026 void invalidTotp(void); 0027 void invalidTotp_data(void); 0028 private: 0029 accounts::AccountSecret m_secret {&test::fakeRandom}; 0030 }; 0031 0032 static void define_test_data(void) 0033 { 0034 QTest::addColumn<QUuid>("id"); 0035 QTest::addColumn<QString>("name"); 0036 QTest::addColumn<QString>("issuer"); 0037 QTest::addColumn<uint>("timeStep"); 0038 QTest::addColumn<int>("tokenLength"); 0039 QTest::addColumn<QDateTime>("epoch"); 0040 QTest::addColumn<accounts::Account::Hash>("hash"); 0041 QTest::addColumn<qint64>("now"); 0042 QTest::addColumn<QString>("actualAccountsIni"); 0043 QTest::addColumn<QString>("expectedAccountsIni"); 0044 } 0045 0046 static void define_test_case(const char * label, const QUuid &id, const QString &accountName, const QString &issuer, uint timeStep, int tokenLength, const QDateTime &epoch, accounts::Account::Hash hash, qint64 now, const QString &actualIni, const QString &expectedIni) 0047 { 0048 QTest::newRow(label) << id << accountName << issuer << timeStep << tokenLength << epoch << hash << now << actualIni << expectedIni; 0049 } 0050 0051 void SaveTotpTest::validTotp(void) 0052 { 0053 QFETCH(QUuid, id); 0054 QFETCH(QString, name); 0055 QFETCH(QString, issuer); 0056 QFETCH(uint, timeStep); 0057 QFETCH(int, tokenLength); 0058 QFETCH(QDateTime, epoch); 0059 QFETCH(accounts::Account::Hash, hash); 0060 QFETCH(qint64, now); 0061 QFETCH(QString, actualAccountsIni); 0062 QFETCH(QString, expectedAccountsIni); 0063 0064 const std::function<qint64(void)> clock([now](void) -> qint64 0065 { 0066 return now; 0067 }); 0068 0069 const QString actual = test::path(actualAccountsIni); 0070 const QString lock = test::path(actualAccountsIni + QLatin1String(".lock")); 0071 bool actionRun = false; 0072 0073 const accounts::SettingsProvider settings([&actual, &actionRun](const accounts::PersistenceAction &action) -> void 0074 { 0075 QSettings data(actual, QSettings::IniFormat); 0076 actionRun = true; 0077 action(data); 0078 }); 0079 0080 std::optional<secrets::EncryptedSecret> tokenSecret = test::encrypt(&m_secret, QByteArray("Hello, world!")); 0081 QVERIFY2(tokenSecret, "should be able to encrypt the token secret"); 0082 0083 accounts::SaveTotp uut(settings, id, name, issuer, *tokenSecret, timeStep, tokenLength, epoch, hash, clock); 0084 QSignalSpy invalidAccount(&uut, &accounts::SaveTotp::invalid); 0085 QSignalSpy savedAccount(&uut, &accounts::SaveTotp::saved); 0086 QSignalSpy jobFinished(&uut, &accounts::SaveTotp::finished); 0087 0088 uut.run(); 0089 0090 QVERIFY2(test::signal_eventually_emitted_once(savedAccount), "account should be saved"); 0091 QVERIFY2(actionRun, "accounts action should have run"); 0092 0093 QFile result(actual); 0094 QVERIFY2(result.exists(), "accounts file should have been created"); 0095 QCOMPARE(test::slurp(actual), test::slurp(expectedAccountsIni)); 0096 0097 QFile lockFile(lock); 0098 QVERIFY2(!lockFile.exists(), "lock file should no longer exist"); 0099 0100 QVERIFY2(test::signal_eventually_emitted_once(jobFinished), "job should be finished"); 0101 QCOMPARE(invalidAccount.count(), 0); 0102 QCOMPARE(savedAccount.count(), 1); 0103 } 0104 0105 void SaveTotpTest::invalidTotp(void) 0106 { 0107 QFETCH(QUuid, id); 0108 QFETCH(QString, name); 0109 QFETCH(QString, issuer); 0110 QFETCH(uint, timeStep); 0111 QFETCH(int, tokenLength); 0112 QFETCH(QDateTime, epoch); 0113 QFETCH(accounts::Account::Hash, hash); 0114 QFETCH(qint64, now); 0115 QFETCH(QString, actualAccountsIni); 0116 QFETCH(QString, expectedAccountsIni); 0117 0118 const std::function<qint64(void)> clock([now](void) -> qint64 0119 { 0120 return now; 0121 }); 0122 0123 const QString actual = test::path(actualAccountsIni); 0124 const QString lock = test::path(actualAccountsIni + QLatin1String(".lock")); 0125 bool actionRun = false; 0126 0127 const accounts::SettingsProvider settings([&actual, &actionRun](const accounts::PersistenceAction &action) -> void 0128 { 0129 QSettings data(actual, QSettings::IniFormat); 0130 actionRun = true; 0131 action(data); 0132 }); 0133 0134 std::optional<secrets::EncryptedSecret> tokenSecret = test::encrypt(&m_secret, QByteArray("Hello, world!")); 0135 QVERIFY2(tokenSecret, "should be able to encrypt the token secret"); 0136 0137 accounts::SaveTotp uut(settings, id, name, issuer, *tokenSecret, timeStep, tokenLength, epoch, hash, clock); 0138 QSignalSpy invalidAccount(&uut, &accounts::SaveTotp::invalid); 0139 QSignalSpy savedAccount(&uut, &accounts::SaveTotp::saved); 0140 QSignalSpy jobFinished(&uut, &accounts::SaveTotp::finished); 0141 0142 uut.run(); 0143 0144 QVERIFY2(test::signal_eventually_emitted_once(jobFinished), "job should be finished"); 0145 QCOMPARE(invalidAccount.count(), 1); 0146 QVERIFY2(!actionRun, "accounts action should not have run"); 0147 QCOMPARE(savedAccount.count(), 0); 0148 0149 QFile result(actual); 0150 QVERIFY2(!result.exists(), "accounts file should not have been created"); 0151 0152 QFile lockFile(lock); 0153 QVERIFY2(!lockFile.exists(), "lock file should not have been created"); 0154 } 0155 0156 void SaveTotpTest::validTotp_data(void) 0157 { 0158 define_test_data(); 0159 define_test_case("valid-totp-sample-1", QUuid("534cc72e-e9ec-5e39-a1ff-9f017c9be8cc"), QLatin1String("valid-totp-sample-1"), QString(), 6U, 0160 30U, QDateTime::fromMSecsSinceEpoch(0), accounts::Account::Hash::Sha1, 0161 0LL, QLatin1String("save-valid-totp-accounts-1.ini"), QLatin1String(":/save-totp/expected-accounts-1.ini")); 0162 define_test_case("valid-totp-sample-2", QUuid("6537d6a5-005e-5a92-b560-b09df3c2e676"), QLatin1String("valid-totp-sample-2"), QLatin1String("autotests"), 6U, 0163 30U, QDateTime::fromMSecsSinceEpoch(1'234'567'890LL), accounts::Account::Hash::Sha512, 0164 2'000'000'000LL, QLatin1String("save-valid-totp-accounts-2.ini"), QLatin1String(":/save-totp/expected-accounts-2.ini")); 0165 } 0166 0167 void SaveTotpTest::invalidTotp_data(void) 0168 { 0169 define_test_data(); 0170 define_test_case("null UUID", QUuid(), QLatin1String("null UUID"), QString(), 6U, 0171 30U, QDateTime::fromMSecsSinceEpoch(0LL), accounts::Account::Hash::Sha1, 0172 0LL, QLatin1String("save-totp-dummy-accounts-1.ini"), QString()); 0173 define_test_case("null account name", QUuid("00611bbf-5e0b-5c6a-9847-ad865315ce86"), QString(), QString(), 6U, 0174 30U, QDateTime::fromMSecsSinceEpoch(0LL), accounts::Account::Hash::Sha1, 0175 0LL, QLatin1String("save-totp-dummy-accounts-2.ini"), QString()); 0176 define_test_case("empty account name", QUuid("1e42b907-99d8-5da3-a59b-89b257e49c83"), QLatin1String(""), QString(), 6U, 0177 30U, QDateTime::fromMSecsSinceEpoch(0LL), accounts::Account::Hash::Sha1, 0178 0LL, QLatin1String("save-totp-dummy-accounts-3.ini"), QString()); 0179 define_test_case("empty issuer name", QUuid("533b406b-ad04-5203-a26f-5deb0afeba22"), QLatin1String("empty issuer name"), QLatin1String(""), 6U, 0180 30U, QDateTime::fromMSecsSinceEpoch(0LL), accounts::Account::Hash::Sha1, 0181 0LL, QLatin1String("save-totp-dummy-accounts-4.ini"), QString()); 0182 define_test_case("empty issuer name", QUuid("1c1ffa42-bb9f-5413-a8a7-6c5b0eb8a36f"), QLatin1String("invalid issuer name"), QLatin1String(":"), 6U, 0183 30U, QDateTime::fromMSecsSinceEpoch(0LL), accounts::Account::Hash::Sha1, 0184 0LL, QLatin1String("save-totp-dummy-accounts-5.ini"), QString()); 0185 define_test_case("timeStep too small", QUuid("5ab8749b-f973-5f48-a70e-c261ebd0521a"), QLatin1String("timeStep too small"), QString(), 6U, 0186 0U, QDateTime::fromMSecsSinceEpoch(0LL), accounts::Account::Hash::Sha1, 0187 0LL, QLatin1String("save-totp-dummy-accounts-6.ini"), QString()); 0188 define_test_case("tokenLength too small", QUuid("bca12e13-4b5b-5e4e-b162-3b86a6284dea"), QLatin1String("tokenLength too small"), QString(), 5U, 0189 30U, QDateTime::fromMSecsSinceEpoch(0LL), accounts::Account::Hash::Sha1, 0190 0LL, QLatin1String("save-totp-dummy-accounts-7.ini"), QString()); 0191 define_test_case("tokenLength too large", QUuid("5c10d530-fb22-5438-848d-3d4d1f738610"), QLatin1String("tokenLength too large"), QString(), 11U, 0192 30U, QDateTime::fromMSecsSinceEpoch(0LL), accounts::Account::Hash::Sha1, 0193 0LL, QLatin1String("save-totp-dummy-accounts-8.ini"), QString()); 0194 define_test_case("null/invalid epoch datetime", QUuid("e719ed90-e0c0-5510-81c1-ccfd7a5e962c"), QLatin1String("null/invalid epoch datetime"), QString(), 6U, 0195 30U, QDateTime(), accounts::Account::Hash::Sha1, 0196 2'000'000'000LL, QLatin1String("save-totp-dummy-accounts-9.ini"), QString()); 0197 define_test_case("future epoch datetime", QUuid("0e03a2ed-8e49-54ac-8e46-20536078e5a1"), QLatin1String("future epoch datetime"), QString(), 6U, 0198 30U, QDateTime::fromMSecsSinceEpoch(1'234'567'890LL), accounts::Account::Hash::Sha1, 0199 0LL, QLatin1String("save-totp-dummy-accounts-10.ini"), QString()); 0200 } 0201 0202 void SaveTotpTest::initTestCase(void) 0203 { 0204 QVERIFY2(test::ensureOutputDirectory(), "output directory should be available"); 0205 QVERIFY2(test::useDummyPassword(&m_secret), "should be able to set up the master key"); 0206 } 0207 0208 QTEST_MAIN(SaveTotpTest) 0209 0210 #include "save-totp.moc"