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"