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 #include <limits>
0020 
0021 Q_DECLARE_METATYPE(std::optional<uint>);
0022 
0023 class SaveHotpTest: public QObject
0024 {
0025     Q_OBJECT
0026 private Q_SLOTS:
0027     void initTestCase(void);
0028     void validHotp(void);
0029     void validHotp_data(void);
0030     void invalidHotp(void);
0031     void invalidHotp_data(void);
0032 private:
0033     accounts::AccountSecret m_secret {&test::fakeRandom};
0034 };
0035 
0036 static void define_test_data(void)
0037 {
0038     QTest::addColumn<QUuid>("id");
0039     QTest::addColumn<QString>("name");
0040     QTest::addColumn<QString>("issuer");
0041     QTest::addColumn<quint64>("counter");
0042     QTest::addColumn<int>("tokenLength");
0043     QTest::addColumn<std::optional<uint>>("offset");
0044     QTest::addColumn<bool>("checksum");
0045     QTest::addColumn<QString>("actualAccountsIni");
0046     QTest::addColumn<QString>("expectedAccountsIni");
0047 }
0048 
0049 static void define_test_case(const char * label, const QUuid &id, const QString &accountName, const QString &issuer, quint64 counter, int tokenLength, const std::optional<uint> &offset, bool checksum, const QString &actualIni, const QString &expectedIni)
0050 {
0051     QTest::newRow(label) << id << accountName << issuer << counter << tokenLength << offset << checksum << actualIni << expectedIni;
0052 }
0053 
0054 void SaveHotpTest::validHotp(void)
0055 {
0056     QFETCH(QUuid, id);
0057     QFETCH(QString, name);
0058     QFETCH(QString, issuer);
0059     QFETCH(quint64, counter);
0060     QFETCH(int, tokenLength);
0061     QFETCH(std::optional<uint>, offset);
0062     QFETCH(bool, checksum);
0063     QFETCH(QString, actualAccountsIni);
0064     QFETCH(QString, expectedAccountsIni);
0065 
0066     const QString actual = test::path(actualAccountsIni);
0067     const QString lock = test::path(actualAccountsIni + QLatin1String(".lock"));
0068     bool actionRun = false;
0069 
0070     const accounts::SettingsProvider settings([&actual, &actionRun](const accounts::PersistenceAction &action) -> void
0071     {
0072         QSettings data(actual, QSettings::IniFormat);
0073         actionRun = true;
0074         action(data);
0075     });
0076 
0077     std::optional<secrets::EncryptedSecret> tokenSecret = test::encrypt(&m_secret, QByteArray("Hello, world!"));
0078     QVERIFY2(tokenSecret, "should be able to encrypt the token secret");
0079 
0080     accounts::SaveHotp uut(settings, id, name, issuer, *tokenSecret, counter, tokenLength, offset, checksum);
0081     QSignalSpy invalidAccount(&uut, &accounts::SaveHotp::invalid);
0082     QSignalSpy savedAccount(&uut, &accounts::SaveHotp::saved);
0083     QSignalSpy jobFinished(&uut, &accounts::SaveHotp::finished);
0084 
0085     uut.run();
0086 
0087     QVERIFY2(test::signal_eventually_emitted_once(savedAccount), "account should be saved");
0088     QVERIFY2(actionRun, "accounts action should have run");
0089 
0090     QFile result(actual);
0091     QVERIFY2(result.exists(), "accounts file should have been created");
0092     QCOMPARE(test::slurp(actual), test::slurp(expectedAccountsIni));
0093 
0094     QFile lockFile(lock);
0095     QVERIFY2(!lockFile.exists(), "lock file should no longer exist");
0096 
0097     QVERIFY2(test::signal_eventually_emitted_once(jobFinished), "job should be finished");
0098     QCOMPARE(invalidAccount.count(), 0);
0099     QCOMPARE(savedAccount.count(), 1);
0100 }
0101 
0102 void SaveHotpTest::invalidHotp(void)
0103 {
0104     QFETCH(QUuid, id);
0105     QFETCH(QString, name);
0106     QFETCH(QString, issuer);
0107     QFETCH(quint64, counter);
0108     QFETCH(int, tokenLength);
0109     QFETCH(std::optional<uint>, offset);
0110     QFETCH(bool, checksum);
0111     QFETCH(QString, actualAccountsIni);
0112     QFETCH(QString, expectedAccountsIni);
0113 
0114     const QString actual = test::path(actualAccountsIni);
0115     const QString lock = test::path(actualAccountsIni + QLatin1String(".lock"));
0116     bool actionRun = false;
0117 
0118     const accounts::SettingsProvider settings([&actual, &actionRun](const accounts::PersistenceAction &action) -> void
0119     {
0120         QSettings data(actual, QSettings::IniFormat);
0121         actionRun = true;
0122         action(data);
0123     });
0124 
0125     std::optional<secrets::EncryptedSecret> tokenSecret = test::encrypt(&m_secret, QByteArray("Hello, world!"));
0126     QVERIFY2(tokenSecret, "should be able to encrypt the token secret");
0127 
0128     accounts::SaveHotp uut(settings, id, name, issuer, *tokenSecret, counter, tokenLength, offset, checksum);
0129     QSignalSpy invalidAccount(&uut, &accounts::SaveHotp::invalid);
0130     QSignalSpy savedAccount(&uut, &accounts::SaveHotp::saved);
0131     QSignalSpy jobFinished(&uut, &accounts::SaveHotp::finished);
0132 
0133     uut.run();
0134 
0135     QVERIFY2(test::signal_eventually_emitted_once(jobFinished), "job should be finished");
0136     QCOMPARE(invalidAccount.count(), 1);
0137     QVERIFY2(!actionRun, "accounts action should not have run");
0138     QCOMPARE(savedAccount.count(), 0);
0139 
0140     QFile result(actual);
0141     QVERIFY2(!result.exists(), "accounts file should not have been created");
0142 
0143     QFile lockFile(lock);
0144     QVERIFY2(!lockFile.exists(), "lock file should not have been created");
0145 }
0146 
0147 void SaveHotpTest::validHotp_data(void)
0148 {
0149     define_test_data();
0150     define_test_case("valid-hotp-sample-1", QUuid("072a645d-6c26-57cc-81eb-d9ef3b9b39e2"), QLatin1String("valid-hotp-sample-1"), QString(), 6U,
0151                      0U, std::nullopt, false,
0152                      QLatin1String("save-valid-hotp-accounts-1.ini"), QLatin1String(":/save-hotp/expected-accounts-1.ini"));
0153     define_test_case("valid-hotp-sample-2", QUuid("437c23aa-2fb0-519a-9a34-a5a2671eea24"), QLatin1String("valid-hotp-sample-2"), QLatin1String("autotests"), 6U,
0154                      0U, std::optional<uint>(12U), true,
0155                      QLatin1String("save-valid-hotp-accounts-2.ini"), QLatin1String(":/save-hotp/expected-accounts-2.ini"));
0156 }
0157 
0158 void SaveHotpTest::invalidHotp_data(void)
0159 {
0160     define_test_data();
0161     define_test_case("null UUID", QUuid(), QLatin1String("null UUID"), QString(), 6U,
0162                      0U, std::nullopt, false,
0163                      QLatin1String("save-hotp-dummy-accounts-1.ini"), QString());
0164     define_test_case("null account name", QUuid("00611bbf-5e0b-5c6a-9847-ad865315ce86"), QString(), QString(), 6U,
0165                      0U, std::nullopt, false,
0166                      QLatin1String("save-hotp-dummy-accounts-2.ini"), QString());
0167     define_test_case("empty account name", QUuid("1e42b907-99d8-5da3-a59b-89b257e49c83"), QLatin1String(""), QString(), 6U,
0168                      0U, std::nullopt, false,
0169                      QLatin1String("save-hotp-dummy-accounts-3.ini"), QString());
0170     define_test_case("empty issuer name", QUuid("533b406b-ad04-5203-a26f-5deb0afeba22"), QLatin1String("empty issuer name"), QLatin1String(""), 6U,
0171                      0U, std::nullopt, false,
0172                      QLatin1String("save-hotp-dummy-accounts-4.ini"), QString());
0173     define_test_case("invalid issuer name", QUuid("1c1ffa42-bb9f-5413-a8a7-6c5b0eb8a36f"), QLatin1String("invalid issuer name"), QLatin1String(":"), 6U,
0174                      0U, std::nullopt, false,
0175                      QLatin1String("save-hotp-dummy-accounts-5.ini"), QString());
0176     define_test_case("tokenLength too small", QUuid("bca12e13-4b5b-5e4e-b162-3b86a6284dea"), QLatin1String("tokenLength too small"), QString(), 5U,
0177                      0U, std::nullopt, false,
0178                      QLatin1String("save-hotp-dummy-accounts-6.ini"), QString());
0179     define_test_case("tokenLength too large", QUuid("5c10d530-fb22-5438-848d-3d4d1f738610"), QLatin1String("tokenLength too large"), QString(), 11U,
0180                      0U, std::nullopt, false,
0181                      QLatin1String("save-hotp-dummy-accounts-7.ini"), QString());
0182     define_test_case("offset too large", QUuid("d0f545da-cc1e-57aa-b793-d856757f33e8"), QLatin1String("offset too large"), QString(), 6U,
0183                      0U, std::optional<uint>(17U), false,
0184                      QLatin1String("save-hotp-dummy-accounts-8.ini"), QString());
0185     define_test_case("offset out of range", QUuid("b31acaca-ee8f-54aa-948e-d67789fbe74c"), QLatin1String("offset out of range"), QString(), 6U,
0186                      0U, std::optional<uint>(std::numeric_limits<uint>::max()), false,
0187                      QLatin1String("save-hotp-dummy-accounts-9.ini"), QString());
0188 }
0189 
0190 void SaveHotpTest::initTestCase(void)
0191 {
0192     QVERIFY2(test::ensureOutputDirectory(), "output directory should be available");
0193     QVERIFY2(test::useDummyPassword(&m_secret), "should be able to set up the master key");
0194 }
0195 
0196 QTEST_MAIN(SaveHotpTest)
0197 
0198 #include "save-hotp.moc"