File indexing completed on 2024-04-28 05:50:05

0001 /*
0002  * SPDX-License-Identifier: GPL-3.0-or-later
0003  * SPDX-FileCopyrightText: 2020-2021 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
0004  */
0005 #include "account.h"
0006 #include "account_p.h"
0007 #include "actions_p.h"
0008 
0009 #include "../logging_p.h"
0010 
0011 #include <QTimer>
0012 
0013 KEYSMITH_LOGGER(logger, ".accounts.account")
0014 
0015 namespace accounts
0016 {
0017     Account::Account(AccountPrivate *d, QObject *parent) :
0018         QObject(parent), m_dptr(d)
0019     {
0020     }
0021 
0022     QString Account::name(void) const
0023     {
0024         Q_D(const Account);
0025         return d->name();
0026     }
0027 
0028     QString Account::issuer(void) const
0029     {
0030         Q_D(const Account);
0031         return d->issuer();
0032     }
0033 
0034     QString Account::token(void) const
0035     {
0036         Q_D(const Account);
0037         return d->token();
0038     }
0039 
0040     quint64 Account::counter(void) const
0041     {
0042         Q_D(const Account);
0043         return d->counter();
0044     }
0045 
0046     QDateTime Account::epoch(void) const
0047     {
0048         Q_D(const Account);
0049         return d->epoch();
0050     }
0051 
0052     uint Account::timeStep(void) const
0053     {
0054         Q_D(const Account);
0055         return d->timeStep();
0056     }
0057 
0058     std::optional<uint> Account::offset(void) const
0059     {
0060         Q_D(const Account);
0061         return d->offset();
0062     }
0063 
0064     int Account::tokenLength(void) const
0065     {
0066         Q_D(const Account);
0067         return d->tokenLength();
0068     }
0069 
0070     bool Account::checksum(void) const
0071     {
0072         Q_D(const Account);
0073         return d->checksum();
0074     }
0075 
0076     Account::Hash Account::hash(void) const
0077     {
0078         Q_D(const Account);
0079         return d->hash();
0080     }
0081 
0082     Account::Algorithm Account::algorithm(void) const
0083     {
0084         Q_D(const Account);
0085         return d->algorithm();
0086     }
0087 
0088     void Account::recompute(void)
0089     {
0090         Q_D(Account);
0091         if(d->token().isEmpty() || d->algorithm() != Account::Hotp) {
0092             d->recompute();
0093         }
0094     }
0095 
0096     void Account::setCounter(quint64 value)
0097     {
0098         Q_D(Account);
0099         d->setCounter(value);
0100     }
0101 
0102     void Account::advanceCounter(quint64 by)
0103     {
0104         setCounter(counter() + by);
0105     }
0106 
0107     void Account::remove(void)
0108     {
0109         Q_D(Account);
0110         d->remove();
0111     }
0112 
0113     AccountStorage::AccountStorage(const SettingsProvider &settings, QThread *worker, AccountSecret *secret,
0114                                    QObject *parent) :
0115         QObject(parent),
0116         m_dptr(new AccountStoragePrivate(settings,
0117                                          secret ? secret : new AccountSecret(secrets::defaultSecureRandom, this),
0118                                          this,
0119                                          new Dispatcher(worker, this)))
0120     {
0121         QTimer::singleShot(0, this, &AccountStorage::unlock);
0122     }
0123 
0124     AccountStorage * AccountStorage::open(const SettingsProvider &settings, AccountSecret *secret, QObject *parent)
0125     {
0126         QThread *worker = new QThread(parent);
0127         AccountStorage *storage = new AccountStorage(settings, worker, secret, parent);
0128 
0129         QObject::connect(storage, &AccountStorage::disposed, worker, &QThread::quit);
0130         QObject::connect(worker, &QThread::finished, worker, &QThread::deleteLater);
0131         QObject::connect(worker, &QThread::destroyed, storage, &AccountStorage::deleteLater);
0132         worker->start();
0133 
0134         return storage;
0135     }
0136 
0137     void AccountStorage::unlock(void)
0138     {
0139         Q_D(AccountStorage);
0140         const std::function<void(RequestAccountPassword*)> handler([this](RequestAccountPassword *job) -> void
0141         {
0142             QObject::connect(job, &RequestAccountPassword::unlocked, this, &AccountStorage::load);
0143             QObject::connect(job, &RequestAccountPassword::failed, this, &AccountStorage::handleError);
0144         });
0145         d->unlock(handler);
0146     }
0147 
0148     void AccountStorage::load(void)
0149     {
0150         Q_D(AccountStorage);
0151         const std::function<void(LoadAccounts*)> handler([this](LoadAccounts *job) -> void
0152         {
0153             QObject::connect(job, &LoadAccounts::foundHotp, this, &AccountStorage::handleHotp);
0154             QObject::connect(job, &LoadAccounts::foundTotp, this, &AccountStorage::handleTotp);
0155             QObject::connect(job, &LoadAccounts::finished, this, &AccountStorage::handleLoaded);
0156             QObject::connect(job, &LoadAccounts::failedToLoadAllAccounts, this, &AccountStorage::handleError);
0157         });
0158         d->load(handler);
0159     }
0160 
0161     bool AccountStorage::contains(const QString &fullName) const
0162     {
0163         Q_D(const AccountStorage);
0164         return d->contains(fullName);
0165     }
0166 
0167     bool AccountStorage::contains(const QString &name, const QString &issuer) const
0168     {
0169         return contains(AccountPrivate::toFullName(name, issuer));
0170     }
0171 
0172     Account * AccountStorage::get(const QString &fullName) const
0173     {
0174         Q_D(const AccountStorage);
0175         return d->get(fullName);
0176     }
0177 
0178     Account * AccountStorage::get(const QString &name, const QString &issuer) const
0179     {
0180         return get(AccountPrivate::toFullName(name, issuer));
0181     }
0182 
0183     AccountSecret * AccountStorage::secret(void) const
0184     {
0185         Q_D(const AccountStorage);
0186         return d->secret();
0187     }
0188 
0189     bool AccountStorage::isAccountStillAvailable(const QString &fullName) const
0190     {
0191         Q_D(const AccountStorage);
0192         return d->isAccountStillAvailable(fullName);
0193     }
0194 
0195     bool AccountStorage::isAccountStillAvailable(const QString &name, const QString &issuer) const
0196     {
0197         return isAccountStillAvailable(AccountPrivate::toFullName(name, issuer));
0198     }
0199 
0200     void AccountStorage::addHotp(const QString &name, const QString &issuer, const QString &secret, uint tokenLength,
0201                                  quint64 counter, const std::optional<uint> offset, bool addChecksum)
0202     {
0203         Q_D(AccountStorage);
0204         const std::function<void(SaveHotp*)> handler([this](SaveHotp *job) -> void
0205         {
0206             QObject::connect(job, &SaveHotp::saved, this, &AccountStorage::handleHotp);
0207             QObject::connect(job, &SaveHotp::invalid, this, &AccountStorage::handleError);
0208         });
0209         if (!d->addHotp(handler, name, issuer.isEmpty() ? QString() : issuer, secret, tokenLength, counter, offset, addChecksum)) {
0210             Q_EMIT error();
0211         }
0212     }
0213 
0214     void AccountStorage::addTotp(const QString &name, const QString &issuer, const QString &secret, uint tokenLength,
0215                                  uint timeStep, const QDateTime &epoch, Account::Hash hash)
0216     {
0217         Q_D(AccountStorage);
0218         const std::function<void(SaveTotp*)> handler([this](SaveTotp *job) -> void
0219         {
0220             QObject::connect(job, &SaveTotp::saved, this, &AccountStorage::handleTotp);
0221             QObject::connect(job, &SaveTotp::invalid, this, &AccountStorage::handleError);
0222         });
0223         if (!d->addTotp(handler, name, issuer.isEmpty() ? QString() : issuer, secret, tokenLength, timeStep, epoch, hash)) {
0224             Q_EMIT error();
0225         }
0226     }
0227 
0228     void AccountStorage::accountRemoved(void)
0229     {
0230         Q_D(AccountStorage);
0231 
0232         QObject *from = sender();
0233         Account *account = from ? qobject_cast<Account*>(from) : nullptr;
0234         Q_ASSERT_X(account, Q_FUNC_INFO, "event should be sent by an account");
0235 
0236         const QString fullName = AccountPrivate::toFullName(account->name(), account->issuer());
0237         d->acceptAccountRemoval(fullName);
0238         Q_EMIT removed(fullName);
0239     }
0240 
0241     QVector<QString> AccountStorage::accounts(void) const
0242     {
0243         Q_D(const AccountStorage);
0244         return d->activeAccounts();
0245     }
0246 
0247     void AccountStorage::handleHotp(const QUuid id, const QString &name, const QString &issuer,
0248                                     const QByteArray &secret, const QByteArray &nonce, uint tokenLength,
0249                                     quint64 counter, bool fixedTruncation, uint offset, bool checksum)
0250     {
0251         Q_D(AccountStorage);
0252         if (!d->isStillOpen()) {
0253             qCDebug(logger)
0254                 << "Not handling HOTP account:" << id
0255                 << "Storage no longer open";
0256             return;
0257         }
0258 
0259         if (!isAccountStillAvailable(name, issuer)) {
0260             qCDebug(logger)
0261                 << "Not handling HOTP account:" << id
0262                 << "Account name or issuer not available";
0263             return;
0264         }
0265 
0266         std::optional<secrets::EncryptedSecret> encryptedSecret = secrets::EncryptedSecret::from(secret, nonce);
0267         if (!encryptedSecret) {
0268             qCDebug(logger)
0269                 << "Not handling HOTP account:" << id
0270                 << "Invalid encrypted secret/nonce";
0271             return;
0272         }
0273 
0274         const std::optional<uint> offsetValue = fixedTruncation ? std::optional<uint>((uint) offset) : std::nullopt;
0275         Account *accepted = d->acceptHotpAccount(id, name, issuer,
0276                                                  *encryptedSecret, tokenLength, counter, offsetValue, checksum);
0277         QObject::connect(accepted, &Account::removed, this, &AccountStorage::accountRemoved);
0278 
0279         Q_EMIT added(AccountPrivate::toFullName(name, issuer));
0280     }
0281 
0282     void AccountStorage::handleTotp(const QUuid id, const QString &name, const QString &issuer,
0283                                     const QByteArray &secret, const QByteArray &nonce, uint tokenLength,
0284                                     uint timeStep, const QDateTime &epoch, Account::Hash hash)
0285     {
0286         Q_D(AccountStorage);
0287         if (!d->isStillOpen()) {
0288             qCDebug(logger)
0289                 << "Not handling TOTP account:" << id
0290                 << "Storage no longer open";
0291             return;
0292         }
0293 
0294         if (!isAccountStillAvailable(name, issuer)) {
0295             qCDebug(logger)
0296                 << "Not handling TOTP account:" << id
0297                 << "Account name or issuer not available";
0298             return;
0299         }
0300 
0301         std::optional<secrets::EncryptedSecret> encryptedSecret = secrets::EncryptedSecret::from(secret, nonce);
0302         if (!encryptedSecret) {
0303             qCDebug(logger)
0304                 << "Not handling TOTP account:" << id
0305                 << "Invalid encrypted secret/nonce";
0306             return;
0307         }
0308 
0309         Account *accepted = d->acceptTotpAccount(id, name, issuer,
0310                                                  *encryptedSecret, tokenLength, timeStep, epoch, hash);
0311         QObject::connect(accepted, &Account::removed, this, &AccountStorage::accountRemoved);
0312 
0313         Q_EMIT added(AccountPrivate::toFullName(name, issuer));
0314     }
0315 
0316     void AccountStorage::dispose(void)
0317     {
0318         Q_D(AccountStorage);
0319         d->dispose([this](Null *job) -> void
0320         {
0321             /*
0322              * Use destroyed() instead of finished() to guarantee the Null job has been disposed of before e.g. threads
0323              * are cleaned up. If the QThread is disposed of before the Null job is cleaned up, the job would leak.
0324              */
0325             QObject::connect(job, &Null::destroyed, this, &AccountStorage::handleDisposal);
0326         });
0327     }
0328 
0329     void AccountStorage::handleDisposal(void)
0330     {
0331         Q_D(AccountStorage);
0332         d->acceptDisposal();
0333     }
0334 
0335     bool AccountStorage::hasError(void) const
0336     {
0337         Q_D(const AccountStorage);
0338         return d->hasError();
0339     }
0340 
0341     void AccountStorage::clearError(void)
0342     {
0343         Q_D(AccountStorage);
0344         d->clearError();
0345     }
0346 
0347     void AccountStorage::handleError(void)
0348     {
0349         Q_D(AccountStorage);
0350         d->notifyError();
0351     }
0352 
0353     void AccountStorage::handleLoaded(void)
0354     {
0355         Q_D(AccountStorage);
0356         d->notifyLoaded();
0357     }
0358 
0359     bool AccountStorage::isLoaded(void) const
0360     {
0361         Q_D(const AccountStorage);
0362         return d->isLoaded();
0363     }
0364 }