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

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_p.h"
0006 #include "validation.h"
0007 
0008 #include "../logging_p.h"
0009 
0010 #include <QObject>
0011 #include <QTimer>
0012 
0013 KEYSMITH_LOGGER(logger, ".accounts.account_p")
0014 
0015 namespace accounts
0016 {
0017     QUuid AccountPrivate::id(void) const
0018     {
0019         return m_id;
0020     }
0021 
0022     std::optional<uint> AccountPrivate::offset(void) const
0023     {
0024         return m_offset;
0025     }
0026 
0027     QString AccountPrivate::name(void) const
0028     {
0029         return m_name;
0030     }
0031 
0032     QString AccountPrivate::issuer(void) const
0033     {
0034         return m_issuer;
0035     }
0036 
0037     QString AccountPrivate::token(void) const
0038     {
0039         return m_token;
0040     }
0041 
0042     quint64 AccountPrivate::counter(void) const
0043     {
0044         return m_counter;
0045     }
0046 
0047     QDateTime AccountPrivate::epoch(void) const
0048     {
0049         return m_epoch;
0050     }
0051 
0052     uint AccountPrivate::timeStep(void) const
0053     {
0054         return m_timeStep;
0055     }
0056 
0057     Account::Hash AccountPrivate::hash(void) const
0058     {
0059         return m_hash;
0060     }
0061 
0062     Account::Algorithm AccountPrivate::algorithm(void) const
0063     {
0064         return m_algorithm;
0065     }
0066 
0067     int AccountPrivate::tokenLength(void) const
0068     {
0069         return m_tokenLength;
0070     }
0071 
0072     bool AccountPrivate::checksum(void) const
0073     {
0074         return m_checksum;
0075     }
0076 
0077     void AccountPrivate::setCounter(quint64 counter)
0078     {
0079         Q_Q(Account);
0080         if (!m_is_still_alive) {
0081             qCDebug(logger)
0082                 << "Will not set counter for account:" << m_id
0083                 << "Object marked for death";
0084             return;
0085         }
0086 
0087         if (!m_storage->isStillOpen()) {
0088             qCDebug(logger)
0089                 << "Will not set counter for account:" << m_id
0090                 << "Storage no longer open";
0091             return;
0092         }
0093 
0094         if (m_algorithm != Account::Algorithm::Hotp) {
0095             qCDebug(logger)
0096                 << "Will not set counter for account:" << m_id
0097                 << "Algorithm not applicable:" << m_algorithm;
0098             return;
0099         }
0100 
0101         qCDebug(logger) << "Requesting to store updated details for account:" << m_id;
0102         SaveHotp *job = new SaveHotp(m_storage->settings(),
0103                                      m_id, m_name, m_issuer, m_secret, m_tokenLength,
0104                                      counter, m_offset, m_checksum);
0105         m_actions->queueAndProceed(job, [counter, job, q, this](void) -> void
0106         {
0107             new HandleCounterUpdate(this, m_storage, counter, job, q);
0108         });
0109     }
0110 
0111     void AccountPrivate::acceptCounter(quint64 counter)
0112     {
0113         Q_Q(Account);
0114         if (!m_is_still_alive) {
0115             qCDebug(logger)
0116                 << "Ignoring counter update for account:" << m_id
0117                 << "Object marked for death";
0118             return;
0119         }
0120 
0121         if (!m_storage->isStillOpen()) {
0122             qCDebug(logger)
0123                 << "Ignoring counter update for account:" << m_id
0124                 << "Storage no longer open";
0125             return;
0126         }
0127 
0128         if (m_algorithm != Account::Algorithm::Hotp) {
0129             qCDebug(logger)
0130                 << "Ignoring counter update for account:" << m_id
0131                 << "Algorithm not applicable:" << m_algorithm;
0132             return;
0133         }
0134 
0135         if (m_counter != counter) {
0136             qCDebug(logger) << "Counter is updated for account:" << m_id;
0137             m_counter = counter;
0138             Q_EMIT q->updated();
0139             recompute();
0140         }
0141     }
0142 
0143     void AccountPrivate::acceptTotpTokens(const QString &token, const QString &nextToken,
0144                                           const QDateTime &validFrom, const QDateTime &validUntil)
0145     {
0146         if (!m_is_still_alive) {
0147             qCDebug(logger)
0148                 << "Ignoring token update for account:" << m_id
0149                 << "Object marked for death";
0150             return;
0151         }
0152 
0153         if (!m_storage->isStillOpen()) {
0154             qCDebug(logger)
0155                 << "Ignoring token update for account:" << m_id
0156                 << "Storage no longer open";
0157             return;
0158         }
0159 
0160         m_nextToken = nextToken;
0161         m_nextTotpValidFrom = validFrom;
0162         m_nextTotpValidUntil = validUntil;
0163         setToken(token);
0164     }
0165 
0166     void AccountPrivate::acceptHotpTokens(const QString &token, const QString &nextToken, quint64 nextCounter)
0167     {
0168         if (!m_is_still_alive) {
0169             qCDebug(logger)
0170                 << "Ignoring token update for account:" << m_id
0171                 << "Object marked for death";
0172             return;
0173         }
0174 
0175         if (!m_storage->isStillOpen()) {
0176             qCDebug(logger)
0177                 << "Ignoring token update for account:" << m_id
0178                 << "Storage no longer open";
0179             return;
0180         }
0181 
0182         m_nextToken = nextToken;
0183         m_nextCounter = nextCounter;
0184         setToken(token);
0185     }
0186 
0187     void AccountPrivate::setToken(const QString &token)
0188     {
0189         Q_Q(Account);
0190         if (m_token != token) {
0191             qCDebug(logger) << "Token is updated for account:" << m_id;
0192             m_token = token;
0193             Q_EMIT q->tokenChanged(m_token);
0194         }
0195     }
0196 
0197     void AccountPrivate::shiftTokens(void)
0198     {
0199         if (m_nextToken.isEmpty()) {
0200             qCDebug(logger)
0201                 << "Not shifting to next token for account:" << m_id
0202                 << "It is not yet available";
0203             return;
0204         }
0205 
0206         QDateTime now = QDateTime::currentDateTime();
0207         switch (m_algorithm) {
0208         case Account::Algorithm::Hotp:
0209             if (m_counter != m_nextCounter) {
0210                 qCDebug(logger)
0211                     << "Not shifting to next token for account:" << m_id
0212                     << "It is not valid anymore";
0213                 return;
0214             }
0215             break;
0216         case Account::Algorithm::Totp:
0217             if (now < m_nextTotpValidFrom || now > m_nextTotpValidUntil) {
0218                 qCDebug(logger)
0219                     << "Not shifting to next token for account:" << m_id
0220                     << "It is not valid at this time";
0221                 return;
0222             }
0223             break;
0224         default:
0225             Q_ASSERT_X(false, Q_FUNC_INFO, "unknown algorithm value");
0226             return;
0227         }
0228 
0229         setToken(m_nextToken);
0230     }
0231 
0232     void AccountPrivate::recompute(void)
0233     {
0234         Q_Q(Account);
0235         if (!m_is_still_alive) {
0236             qCDebug(logger)
0237                 << "Will not recompute token for account:" << m_id
0238                 << "Object marked for death";
0239             return;
0240         }
0241 
0242         if (!m_storage->isStillOpen()) {
0243             qCDebug(logger)
0244                 << "Will not recompute token for account:" << m_id
0245                 << "Storage no longer open";
0246             return;
0247         }
0248 
0249         shiftTokens();
0250 
0251         qCDebug(logger) << "Requesting recomputed tokens for account:" << m_id;
0252         ComputeHotp *hotpJob = nullptr;
0253         ComputeTotp *totpJob = nullptr;
0254 
0255         switch (m_algorithm) {
0256         case Account::Algorithm::Hotp:
0257             hotpJob = new ComputeHotp(m_storage->secret(), m_secret, m_tokenLength, m_counter, m_offset, m_checksum);
0258             m_actions->queueAndProceed(hotpJob, [hotpJob, q, this](void) -> void {
0259                 new HandleTokenUpdate(this, hotpJob, q);
0260             });
0261             break;
0262         case Account::Algorithm::Totp:
0263             totpJob = new ComputeTotp(m_storage->secret(), m_secret, m_tokenLength, m_epoch, m_timeStep, m_hash);
0264             m_actions->queueAndProceed(totpJob, [totpJob, q, this](void) -> void
0265             {
0266                 new HandleTokenUpdate(this, totpJob, q);
0267             });
0268             break;
0269         default:
0270             Q_ASSERT_X(false, Q_FUNC_INFO, "unknown algorithm value");
0271             break;
0272         }
0273     }
0274 
0275     void AccountPrivate::remove(void)
0276     {
0277         if (!m_is_still_alive) {
0278             qCDebug(logger)
0279                 << "Will not remove account:" << m_id
0280                 << "Object marked for death";
0281             return;
0282         }
0283 
0284         if (!m_storage->isStillOpen()) {
0285             qCDebug(logger)
0286                 << "Will not remove account:" << m_id
0287                 << "Storage no longer open";
0288             return;
0289         }
0290 
0291         QSet<QString> self;
0292         self.insert(AccountPrivate::toFullName(m_name, m_issuer));
0293         m_storage->removeAccounts(self);
0294     }
0295 
0296     void AccountPrivate::markForRemoval(void)
0297     {
0298         m_is_still_alive = false;
0299     }
0300 
0301     bool AccountPrivate::isStillAlive(void) const
0302     {
0303         return m_is_still_alive;
0304     }
0305 
0306     AccountPrivate::AccountPrivate(const std::function<Account*(AccountPrivate*)> &account,
0307                                    AccountStoragePrivate *storage, Dispatcher *dispatcher,
0308                                    const QUuid id, const QString &name, const QString &issuer,
0309                                    const secrets::EncryptedSecret &secret, uint tokenLength,
0310                                    quint64 counter, const std::optional<uint> offset, bool addChecksum) :
0311         q_ptr(account(this)), m_storage(storage), m_actions(dispatcher), m_is_still_alive(true),
0312         m_algorithm(Account::Algorithm::Hotp), m_id(id), m_token(QString()), m_nextToken(QString()),
0313         m_nextTotpValidFrom(QDateTime::fromMSecsSinceEpoch(0)), // not a totp token so does not really matter
0314         m_nextTotpValidUntil(QDateTime::fromMSecsSinceEpoch(0)), // not a totp token so does not really matter
0315         m_nextCounter(1ULL), m_name(name), m_issuer(issuer),
0316         m_secret(secret), m_tokenLength(tokenLength),
0317         m_counter(counter), m_offset(offset), m_checksum(addChecksum),
0318         // not a totp token so these values don't really matter
0319         m_epoch(QDateTime::fromMSecsSinceEpoch(0)), m_timeStep(30), m_hash(Account::Hash::Sha1)
0320     {
0321     }
0322 
0323     AccountPrivate::AccountPrivate(const std::function<Account*(AccountPrivate*)> &account,
0324                                    AccountStoragePrivate *storage, Dispatcher *dispatcher,
0325                                    const QUuid id, const QString &name, const QString &issuer,
0326                                    const secrets::EncryptedSecret &secret, uint tokenLength,
0327                                    const QDateTime &epoch, uint timeStep, Account::Hash hash) :
0328         q_ptr(account(this)), m_storage(storage), m_actions(dispatcher), m_is_still_alive(true),
0329         m_algorithm(Account::Algorithm::Totp), m_id(id), m_token(QString()), m_nextToken(QString()),
0330         m_nextTotpValidFrom(epoch), m_nextTotpValidUntil(epoch),
0331         m_nextCounter(0ULL), // not a hotp token so does not really matter
0332         m_name(name), m_issuer(issuer), m_secret(secret), m_tokenLength(tokenLength),
0333         // not a hotp token so these values don't really matter
0334         m_counter(0ULL), m_offset(std::nullopt), m_checksum(false),
0335         m_epoch(epoch), m_timeStep(timeStep), m_hash(hash)
0336     {
0337     }
0338 
0339     QVector<QString> AccountStoragePrivate::activeAccounts(void) const
0340     {
0341         QVector<QString> active;
0342         if (!m_is_still_open) {
0343             qCDebug(logger) << "Not returning accounts: account storage no longer open";
0344             return active;
0345         }
0346 
0347         const QList<QString> all = m_names.keys();
0348         for (const QString &account : all) {
0349             const QUuid id = m_names[account];
0350             if (m_accountsPrivate[id]->isStillAlive()) {
0351                 active.append(account);
0352             } else {
0353                 qCDebug(logger)
0354                     << "Not returning account:" << id
0355                     << "Object marked for death";
0356             }
0357         }
0358 
0359         return active;
0360     }
0361 
0362     bool AccountStoragePrivate::isStillOpen(void) const
0363     {
0364         return m_is_still_open;
0365     }
0366 
0367     bool AccountStoragePrivate::isAccountStillAvailable(const QString &account) const
0368     {
0369         if (!m_is_still_open) {
0370             qCDebug(logger) << "Pretending no name is available: account storage no longer open";
0371             return false;
0372         }
0373 
0374         return !m_names.contains(account);
0375     }
0376 
0377     QString AccountPrivate::toFullName(const QString &name, const QString &issuer)
0378     {
0379         return issuer.isEmpty() ? name : issuer + QLatin1Char(':') + name;
0380     }
0381 
0382     bool AccountStoragePrivate::contains(const QString &account) const
0383     {
0384         /*
0385          * Pretend an account which is marked for removal is already fully purged
0386          * This lets the behaviour of get() and contains() be mutually consistent
0387          * without having to hand out pointers which may be about to go stale in get()
0388          */
0389         if (!m_is_still_open) {
0390             qCDebug(logger) << "Pretending no account exists: account storage no longer open";
0391             return false;
0392         }
0393 
0394         if (!m_names.contains(account)) {
0395             return false;
0396         }
0397 
0398         const QUuid id = m_names[account];
0399         if (!m_accountsPrivate[id]->isStillAlive()) {
0400             qCDebug(logger)
0401                 << "Pretending account does not exist:" << id
0402                 << "Object marked for death";
0403             return false;
0404         }
0405 
0406         return true;
0407     }
0408 
0409     Account * AccountStoragePrivate::get(const QString &account) const
0410     {
0411         return contains(account) ? m_accounts[m_names[account]] : nullptr;
0412     }
0413 
0414     SettingsProvider AccountStoragePrivate::settings(void) const
0415     {
0416         return m_settings;
0417     }
0418 
0419     AccountSecret * AccountStoragePrivate::secret(void) const
0420     {
0421         return m_secret;
0422     }
0423 
0424     void AccountStoragePrivate::removeAccounts(const QSet<QString> &accountNames)
0425     {
0426         if (!m_is_still_open) {
0427             qCDebug(logger) << "Not removing accounts: account storage no longer open";
0428             return;
0429         }
0430 
0431         QSet<QUuid> ids;
0432         for (const QString &accountName : accountNames) {
0433             if (m_names.contains(accountName)) {
0434                 const QUuid id = m_names[accountName];
0435                 AccountPrivate *p = m_accountsPrivate[id];
0436                 /*
0437                  * Avoid doing anything with accounts which are already about to be removed from the maps
0438                  */
0439                 if (p->isStillAlive()) {
0440                     p->markForRemoval();
0441                     ids.insert(id);
0442                 } else {
0443                     qCDebug(logger)
0444                         << "Not removing account:" << id
0445                         << "Object marked for death";
0446                 }
0447             }
0448         }
0449 
0450         DeleteAccounts *job = new DeleteAccounts(m_settings, ids);
0451         m_actions->queueAndProceed(job, [&ids, job, this](void) -> void
0452         {
0453             for (const QUuid &id : qAsConst(ids)) {
0454                 Account *account = m_accounts[id];
0455                 QObject::connect(job, &DeleteAccounts::finished, account, &Account::removed);
0456             }
0457         });
0458     }
0459 
0460     void AccountStoragePrivate::acceptAccountRemoval(const QString &accountName)
0461     {
0462         if (!m_names.contains(accountName)) {
0463             qCDebug(logger) << "Not accepting account removal: account name unknown";
0464             return;
0465         }
0466 
0467         const QUuid id = m_names[accountName];
0468         qCDebug(logger) << "Handling account cleanup for account:" << id;
0469 
0470         Account *account = m_accounts[id];
0471         m_accounts.remove(id);
0472         m_accountsPrivate.remove(id);
0473         m_names.remove(accountName);
0474         m_ids.remove(id);
0475         QTimer::singleShot(0, account, &accounts::Account::deleteLater);
0476     }
0477 
0478     void AccountStoragePrivate::dispose(const std::function<void(Null*)> &handler)
0479     {
0480         if (!m_is_still_open) {
0481             qCDebug(logger) << "Not disposing of storage: account storage no longer open";
0482             return;
0483         }
0484 
0485         qCDebug(logger) << "Disposing of storage";
0486 
0487         m_is_still_open = false;
0488         Null *job = new Null();
0489         m_secret->cancelRequests();
0490         m_actions->queueAndProceed(job, [job, &handler](void) -> void
0491         {
0492             handler(job);
0493         });
0494     }
0495 
0496     void AccountStoragePrivate::acceptDisposal(void)
0497     {
0498         Q_Q(AccountStorage);
0499         if (m_is_still_open) {
0500             qCDebug(logger) << "Ignoring disposal of storage: account storage is still open";
0501             return;
0502         }
0503 
0504         qCDebug(logger) << "Handling storage disposal";
0505 
0506         const auto &names = m_names.keys();
0507         for (const QString &accountName : names) {
0508             const QUuid id = m_names[accountName];
0509             qCDebug(logger) << "Handling account cleanup for account:" << id;
0510 
0511             Account *account = m_accounts[id];
0512             m_accounts.remove(id);
0513             m_accountsPrivate.remove(id);
0514             m_names.remove(accountName);
0515             m_ids.remove(id);
0516             QTimer::singleShot(0, account, &accounts::Account::deleteLater);
0517         }
0518         QTimer::singleShot(0, m_secret, &accounts::AccountSecret::deleteLater);
0519         Q_EMIT q->disposed();
0520     }
0521 
0522     QUuid AccountStoragePrivate::generateId(const QString &name) const
0523     {
0524         QUuid attempt = QUuid::createUuidV5(QUuid(), name);
0525         while (attempt.isNull() || m_ids.contains(attempt)) {
0526             attempt = QUuid::createUuid();
0527         }
0528         return attempt;
0529     }
0530 
0531     std::optional<secrets::EncryptedSecret> AccountStoragePrivate::encrypt(const QString &secret) const
0532     {
0533         if (!m_is_still_open) {
0534             qCDebug(logger) << "Will not encrypt account secret: storage no longer open";
0535             return std::nullopt;
0536         }
0537 
0538         if (!m_secret || !m_secret->key()) {
0539             qCDebug(logger) << "Will not encrypt account secret: encryption key not available";
0540             return std::nullopt;
0541         }
0542 
0543         QScopedPointer<secrets::SecureMemory> decoded(secrets::decodeBase32(secret));
0544         if (!decoded) {
0545             qCDebug(logger) << "Will not encrypt account secret: failed to decode base32";
0546             return std::nullopt;
0547         }
0548 
0549         return m_secret->encrypt(decoded.data());
0550     }
0551 
0552     bool AccountStoragePrivate::validateGenericNewToken(const QString &name, const QString &issuer,
0553                                                         const QString &secret, uint tokenLength) const
0554     {
0555         return checkTokenLength(tokenLength) && checkName(name) && checkIssuer(issuer)
0556             && isAccountStillAvailable(AccountPrivate::toFullName(name, issuer)) && checkSecret(secret);
0557     }
0558 
0559     bool AccountStoragePrivate::addHotp(const std::function<void(SaveHotp*)> &handler,
0560                                         const QString &name, const QString &issuer,
0561                                         const QString &secret, uint tokenLength,
0562                                         quint64 counter, const std::optional<uint> offset, bool checksum)
0563     {
0564         if (!m_is_still_open) {
0565             qCDebug(logger) << "Will not add new HOTP account: storage no longer open";
0566             return false;
0567         }
0568 
0569         if (!validateGenericNewToken(name, issuer, secret, tokenLength) ||
0570             !checkOffset(offset, QCryptographicHash::Sha1)) {
0571             qCDebug(logger) << "Will not add new HOTP account: invalid account details";
0572             return false;
0573         }
0574 
0575         std::optional<secrets::EncryptedSecret> encryptedSecret = encrypt(secret);
0576         if (!encryptedSecret) {
0577             qCDebug(logger) << "Will not add new HOTP account: failed to encrypt secret";
0578             return false;
0579         }
0580 
0581         QUuid id = generateId(AccountPrivate::toFullName(name, issuer));
0582         qCDebug(logger) << "Requesting to store details for new HOTP account:" << id;
0583 
0584         m_ids.insert(id);
0585         SaveHotp *job = new SaveHotp(m_settings, id, name, issuer, *encryptedSecret, tokenLength,
0586                                      counter, offset, checksum);
0587         m_actions->queueAndProceed(job, [job, &handler](void) -> void
0588         {
0589             handler(job);
0590         });
0591         return true;
0592     }
0593 
0594     bool AccountStoragePrivate::addTotp(const std::function<void(SaveTotp*)> &handler,
0595                                         const QString &name, const QString &issuer,
0596                                         const QString &secret, uint tokenLength,
0597                                         uint timeStep, const QDateTime &epoch, Account::Hash hash)
0598     {
0599         if (!m_is_still_open) {
0600             qCDebug(logger) << "Will not add new TOTP account: storage no longer open";
0601             return false;
0602         }
0603 
0604         if (!validateGenericNewToken(name, issuer, secret, tokenLength) ||
0605             !checkTimeStep(timeStep) || !checkEpoch(epoch)) {
0606             qCDebug(logger) << "Will not add new TOTP account: invalid account details";
0607             return false;
0608         }
0609 
0610         std::optional<secrets::EncryptedSecret> encryptedSecret = encrypt(secret);
0611         if (!encryptedSecret) {
0612             qCDebug(logger) << "Will not add new TOTP account: failed to encrypt secret";
0613             return false;
0614         }
0615 
0616         QUuid id = generateId(AccountPrivate::toFullName(name, issuer));
0617         qCDebug(logger) << "Requesting to store details for new TOTP account:" << id;
0618 
0619         m_ids.insert(id);
0620         SaveTotp *job = new SaveTotp(m_settings, id, name, issuer, *encryptedSecret, tokenLength,
0621                                      timeStep, epoch, hash);
0622         m_actions->queueAndProceed(job, [job, &handler](void) -> void
0623         {
0624             handler(job);
0625         });
0626         return true;
0627     }
0628 
0629     void AccountStoragePrivate::unlock(const std::function<void(RequestAccountPassword*)> &handler)
0630     {
0631         if (!m_is_still_open) {
0632             qCDebug(logger) << "Will not attempt to unlock accounts: storage no longer open";
0633             return;
0634         }
0635 
0636         qCDebug(logger) << "Requesting to unlock account storage";
0637         RequestAccountPassword *job = new RequestAccountPassword(m_settings, m_secret);
0638         m_actions->queueAndProceed(job, [job, &handler](void) -> void
0639         {
0640             handler(job);
0641         });
0642     }
0643 
0644     void AccountStoragePrivate::load(const std::function<void(LoadAccounts*)> &handler)
0645     {
0646         if (!m_is_still_open) {
0647             qCDebug(logger) << "Will not load accounts: storage no longer open";
0648             return;
0649         }
0650 
0651         LoadAccounts *job = new LoadAccounts(m_settings, m_secret);
0652         m_actions->queueAndProceed(job, [job, &handler](void) -> void
0653         {
0654             handler(job);
0655         });
0656     }
0657 
0658     Account * AccountStoragePrivate::acceptHotpAccount(const QUuid id, const QString &name, const QString &issuer,
0659                                                        const secrets::EncryptedSecret &secret, uint tokenLength,
0660                                                        quint64 counter, const std::optional<uint> offset, bool checksum)
0661     {
0662         Q_Q(AccountStorage);
0663         qCDebug(logger) << "Registering HOTP account:" << id;
0664         const std::function<Account*(AccountPrivate*)> registration([this, q, &id](AccountPrivate *p) -> Account *
0665         {
0666             Account *account = new Account(p, q);
0667             m_accounts.insert(id, account);
0668             return account;
0669         });
0670         m_ids.insert(id);
0671         m_names.insert(AccountPrivate::toFullName(name, issuer), id);
0672         const auto p = new AccountPrivate(registration, this, m_actions,
0673                                           id, name, issuer, secret, tokenLength, counter, offset, checksum);
0674         m_accountsPrivate.insert(id, p);
0675 
0676         Q_ASSERT_X(m_accounts.contains(id), Q_FUNC_INFO, "account should have been registered");
0677         return m_accounts[id];
0678     }
0679 
0680     Account * AccountStoragePrivate::acceptTotpAccount(const QUuid id, const QString &name, const QString &issuer,
0681                                                        const secrets::EncryptedSecret &secret, uint tokenLength,
0682                                                        uint timeStep, const QDateTime &epoch, Account::Hash hash)
0683     {
0684         Q_Q(AccountStorage);
0685         qCDebug(logger) << "Registering TOTP account:" << id;
0686         const std::function<Account*(AccountPrivate*)> registration([this, q, &id](AccountPrivate *p) -> Account *
0687         {
0688             Account *account = new Account(p, q);
0689             m_accounts.insert(id, account);
0690             return account;
0691         });
0692         m_ids.insert(id);
0693         m_names.insert(AccountPrivate::toFullName(name, issuer), id);
0694         const auto p = new AccountPrivate(registration, this, m_actions,
0695                                           id, name, issuer, secret, tokenLength, epoch, timeStep, hash);
0696         m_accountsPrivate.insert(id, p);
0697 
0698         Q_ASSERT_X(m_accounts.contains(id), Q_FUNC_INFO, "account should have been registered");
0699         return m_accounts[id];
0700     }
0701 
0702     bool AccountStoragePrivate::isLoaded(void) const
0703     {
0704         return m_is_loaded;
0705     }
0706 
0707     void AccountStoragePrivate::notifyLoaded(void)
0708     {
0709         Q_Q(AccountStorage);
0710         m_is_loaded = true;
0711         Q_EMIT q->loaded();
0712     }
0713 
0714     void AccountStoragePrivate::notifyError(void)
0715     {
0716         Q_Q(AccountStorage);
0717         m_has_error = true;
0718         Q_EMIT q->error();
0719     }
0720 
0721     void AccountStoragePrivate::clearError(void)
0722     {
0723         m_has_error = false;
0724     }
0725 
0726     bool AccountStoragePrivate::hasError(void) const
0727     {
0728         return m_has_error;
0729     }
0730 
0731     AccountStoragePrivate::AccountStoragePrivate(const SettingsProvider &settings,
0732                                                  AccountSecret *secret, AccountStorage *storage, Dispatcher *dispatcher) :
0733         q_ptr(storage), m_is_loaded(false), m_has_error(false), m_is_still_open(true),
0734         m_actions(dispatcher), m_settings(settings), m_secret(secret)
0735     {
0736     }
0737 
0738     HandleCounterUpdate::HandleCounterUpdate(AccountPrivate *account, AccountStoragePrivate *storage,
0739                                              quint64 counter, SaveHotp *job, QObject *parent) :
0740         QObject(parent), m_accept_on_finish(true), m_counter(counter), m_account(account), m_storage(storage)
0741     {
0742         QObject::connect(job, &SaveHotp::invalid, this, &HandleCounterUpdate::rejected);
0743         QObject::connect(job, &SaveHotp::finished, this, &HandleCounterUpdate::finished);
0744     }
0745 
0746     void HandleCounterUpdate::rejected(void)
0747     {
0748         m_accept_on_finish = false;
0749         m_storage->notifyError();
0750     }
0751 
0752     void HandleCounterUpdate::finished(void)
0753     {
0754         if (m_accept_on_finish) {
0755             m_account->acceptCounter(m_counter);
0756         } else {
0757             qCWarning(logger)
0758                 << "Rejecting counter update for account:" << m_account->id()
0759                 << "Failed to save the updated account details to storage";
0760         }
0761         deleteLater();
0762     }
0763 
0764     HandleTokenUpdate::HandleTokenUpdate(AccountPrivate *account, ComputeHotp *job, QObject *parent) :
0765         QObject(parent), m_account(account)
0766     {
0767         QObject::connect(job, &ComputeHotp::otp, this, &HandleTokenUpdate::hotp);
0768         QObject::connect(job, &ComputeHotp::finished, this, &HandleTokenUpdate::deleteLater);
0769     }
0770 
0771     HandleTokenUpdate::HandleTokenUpdate(AccountPrivate *account, ComputeTotp *job, QObject *parent) :
0772         QObject(parent), m_account(account)
0773     {
0774         QObject::connect(job, &ComputeTotp::otp, this, &HandleTokenUpdate::totp);
0775         QObject::connect(job, &ComputeTotp::finished, this, &HandleTokenUpdate::deleteLater);
0776     }
0777 
0778     void HandleTokenUpdate::totp(const QString &otp, const QString &nextOtp, const QDateTime &validFrom,
0779                                  const QDateTime &validUntil)
0780     {
0781         m_account->acceptTotpTokens(otp, nextOtp, validFrom, validUntil);
0782     }
0783 
0784     void HandleTokenUpdate::hotp(const QString &otp, const QString &nextOtp, quint64 validUntil)
0785     {
0786         m_account->acceptHotpTokens(otp, nextOtp, validUntil);
0787     }
0788 }