789 lines
27 KiB
C++
789 lines
27 KiB
C++
/*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
* SPDX-FileCopyrightText: 2020-2021 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
|
|
*/
|
|
#include "account_p.h"
|
|
#include "validation.h"
|
|
|
|
#include "../logging_p.h"
|
|
|
|
#include <QObject>
|
|
#include <QTimer>
|
|
|
|
KEYSMITH_LOGGER(logger, ".accounts.account_p")
|
|
|
|
namespace accounts
|
|
{
|
|
QUuid AccountPrivate::id(void) const
|
|
{
|
|
return m_id;
|
|
}
|
|
|
|
std::optional<uint> AccountPrivate::offset(void) const
|
|
{
|
|
return m_offset;
|
|
}
|
|
|
|
QString AccountPrivate::name(void) const
|
|
{
|
|
return m_name;
|
|
}
|
|
|
|
QString AccountPrivate::issuer(void) const
|
|
{
|
|
return m_issuer;
|
|
}
|
|
|
|
QString AccountPrivate::token(void) const
|
|
{
|
|
return m_token;
|
|
}
|
|
|
|
quint64 AccountPrivate::counter(void) const
|
|
{
|
|
return m_counter;
|
|
}
|
|
|
|
QDateTime AccountPrivate::epoch(void) const
|
|
{
|
|
return m_epoch;
|
|
}
|
|
|
|
uint AccountPrivate::timeStep(void) const
|
|
{
|
|
return m_timeStep;
|
|
}
|
|
|
|
Account::Hash AccountPrivate::hash(void) const
|
|
{
|
|
return m_hash;
|
|
}
|
|
|
|
Account::Algorithm AccountPrivate::algorithm(void) const
|
|
{
|
|
return m_algorithm;
|
|
}
|
|
|
|
int AccountPrivate::tokenLength(void) const
|
|
{
|
|
return m_tokenLength;
|
|
}
|
|
|
|
bool AccountPrivate::checksum(void) const
|
|
{
|
|
return m_checksum;
|
|
}
|
|
|
|
void AccountPrivate::setCounter(quint64 counter)
|
|
{
|
|
Q_Q(Account);
|
|
if (!m_is_still_alive) {
|
|
qCDebug(logger)
|
|
<< "Will not set counter for account:" << m_id
|
|
<< "Object marked for death";
|
|
return;
|
|
}
|
|
|
|
if (!m_storage->isStillOpen()) {
|
|
qCDebug(logger)
|
|
<< "Will not set counter for account:" << m_id
|
|
<< "Storage no longer open";
|
|
return;
|
|
}
|
|
|
|
if (m_algorithm != Account::Algorithm::Hotp) {
|
|
qCDebug(logger)
|
|
<< "Will not set counter for account:" << m_id
|
|
<< "Algorithm not applicable:" << m_algorithm;
|
|
return;
|
|
}
|
|
|
|
qCDebug(logger) << "Requesting to store updated details for account:" << m_id;
|
|
SaveHotp *job = new SaveHotp(m_storage->settings(),
|
|
m_id, m_name, m_issuer, m_secret, m_tokenLength,
|
|
counter, m_offset, m_checksum);
|
|
m_actions->queueAndProceed(job, [counter, job, q, this](void) -> void
|
|
{
|
|
new HandleCounterUpdate(this, m_storage, counter, job, q);
|
|
});
|
|
}
|
|
|
|
void AccountPrivate::acceptCounter(quint64 counter)
|
|
{
|
|
Q_Q(Account);
|
|
if (!m_is_still_alive) {
|
|
qCDebug(logger)
|
|
<< "Ignoring counter update for account:" << m_id
|
|
<< "Object marked for death";
|
|
return;
|
|
}
|
|
|
|
if (!m_storage->isStillOpen()) {
|
|
qCDebug(logger)
|
|
<< "Ignoring counter update for account:" << m_id
|
|
<< "Storage no longer open";
|
|
return;
|
|
}
|
|
|
|
if (m_algorithm != Account::Algorithm::Hotp) {
|
|
qCDebug(logger)
|
|
<< "Ignoring counter update for account:" << m_id
|
|
<< "Algorithm not applicable:" << m_algorithm;
|
|
return;
|
|
}
|
|
|
|
if (m_counter != counter) {
|
|
qCDebug(logger) << "Counter is updated for account:" << m_id;
|
|
m_counter = counter;
|
|
Q_EMIT q->updated();
|
|
recompute();
|
|
}
|
|
}
|
|
|
|
void AccountPrivate::acceptTotpTokens(const QString &token, const QString &nextToken,
|
|
const QDateTime &validFrom, const QDateTime &validUntil)
|
|
{
|
|
if (!m_is_still_alive) {
|
|
qCDebug(logger)
|
|
<< "Ignoring token update for account:" << m_id
|
|
<< "Object marked for death";
|
|
return;
|
|
}
|
|
|
|
if (!m_storage->isStillOpen()) {
|
|
qCDebug(logger)
|
|
<< "Ignoring token update for account:" << m_id
|
|
<< "Storage no longer open";
|
|
return;
|
|
}
|
|
|
|
m_nextToken = nextToken;
|
|
m_nextTotpValidFrom = validFrom;
|
|
m_nextTotpValidUntil = validUntil;
|
|
setToken(token);
|
|
}
|
|
|
|
void AccountPrivate::acceptHotpTokens(const QString &token, const QString &nextToken, quint64 nextCounter)
|
|
{
|
|
if (!m_is_still_alive) {
|
|
qCDebug(logger)
|
|
<< "Ignoring token update for account:" << m_id
|
|
<< "Object marked for death";
|
|
return;
|
|
}
|
|
|
|
if (!m_storage->isStillOpen()) {
|
|
qCDebug(logger)
|
|
<< "Ignoring token update for account:" << m_id
|
|
<< "Storage no longer open";
|
|
return;
|
|
}
|
|
|
|
m_nextToken = nextToken;
|
|
m_nextCounter = nextCounter;
|
|
setToken(token);
|
|
}
|
|
|
|
void AccountPrivate::setToken(const QString &token)
|
|
{
|
|
Q_Q(Account);
|
|
if (m_token != token) {
|
|
qCDebug(logger) << "Token is updated for account:" << m_id;
|
|
m_token = token;
|
|
Q_EMIT q->tokenChanged(m_token);
|
|
}
|
|
}
|
|
|
|
void AccountPrivate::shiftTokens(void)
|
|
{
|
|
if (m_nextToken.isEmpty()) {
|
|
qCDebug(logger)
|
|
<< "Not shifting to next token for account:" << m_id
|
|
<< "It is not yet available";
|
|
return;
|
|
}
|
|
|
|
QDateTime now = QDateTime::currentDateTime();
|
|
switch (m_algorithm) {
|
|
case Account::Algorithm::Hotp:
|
|
if (m_counter != m_nextCounter) {
|
|
qCDebug(logger)
|
|
<< "Not shifting to next token for account:" << m_id
|
|
<< "It is not valid anymore";
|
|
return;
|
|
}
|
|
break;
|
|
case Account::Algorithm::Totp:
|
|
if (now < m_nextTotpValidFrom || now > m_nextTotpValidUntil) {
|
|
qCDebug(logger)
|
|
<< "Not shifting to next token for account:" << m_id
|
|
<< "It is not valid at this time";
|
|
return;
|
|
}
|
|
break;
|
|
default:
|
|
Q_ASSERT_X(false, Q_FUNC_INFO, "unknown algorithm value");
|
|
return;
|
|
}
|
|
|
|
setToken(m_nextToken);
|
|
}
|
|
|
|
void AccountPrivate::recompute(void)
|
|
{
|
|
Q_Q(Account);
|
|
if (!m_is_still_alive) {
|
|
qCDebug(logger)
|
|
<< "Will not recompute token for account:" << m_id
|
|
<< "Object marked for death";
|
|
return;
|
|
}
|
|
|
|
if (!m_storage->isStillOpen()) {
|
|
qCDebug(logger)
|
|
<< "Will not recompute token for account:" << m_id
|
|
<< "Storage no longer open";
|
|
return;
|
|
}
|
|
|
|
shiftTokens();
|
|
|
|
qCDebug(logger) << "Requesting recomputed tokens for account:" << m_id;
|
|
ComputeHotp *hotpJob = nullptr;
|
|
ComputeTotp *totpJob = nullptr;
|
|
|
|
switch (m_algorithm) {
|
|
case Account::Algorithm::Hotp:
|
|
hotpJob = new ComputeHotp(m_storage->secret(), m_secret, m_tokenLength, m_counter, m_offset, m_checksum);
|
|
m_actions->queueAndProceed(hotpJob, [hotpJob, q, this](void) -> void {
|
|
new HandleTokenUpdate(this, hotpJob, q);
|
|
});
|
|
break;
|
|
case Account::Algorithm::Totp:
|
|
totpJob = new ComputeTotp(m_storage->secret(), m_secret, m_tokenLength, m_epoch, m_timeStep, m_hash);
|
|
m_actions->queueAndProceed(totpJob, [totpJob, q, this](void) -> void
|
|
{
|
|
new HandleTokenUpdate(this, totpJob, q);
|
|
});
|
|
break;
|
|
default:
|
|
Q_ASSERT_X(false, Q_FUNC_INFO, "unknown algorithm value");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void AccountPrivate::remove(void)
|
|
{
|
|
if (!m_is_still_alive) {
|
|
qCDebug(logger)
|
|
<< "Will not remove account:" << m_id
|
|
<< "Object marked for death";
|
|
return;
|
|
}
|
|
|
|
if (!m_storage->isStillOpen()) {
|
|
qCDebug(logger)
|
|
<< "Will not remove account:" << m_id
|
|
<< "Storage no longer open";
|
|
return;
|
|
}
|
|
|
|
QSet<QString> self;
|
|
self.insert(AccountPrivate::toFullName(m_name, m_issuer));
|
|
m_storage->removeAccounts(self);
|
|
}
|
|
|
|
void AccountPrivate::markForRemoval(void)
|
|
{
|
|
m_is_still_alive = false;
|
|
}
|
|
|
|
bool AccountPrivate::isStillAlive(void) const
|
|
{
|
|
return m_is_still_alive;
|
|
}
|
|
|
|
AccountPrivate::AccountPrivate(const std::function<Account*(AccountPrivate*)> &account,
|
|
AccountStoragePrivate *storage, Dispatcher *dispatcher,
|
|
const QUuid id, const QString &name, const QString &issuer,
|
|
const secrets::EncryptedSecret &secret, uint tokenLength,
|
|
quint64 counter, const std::optional<uint> offset, bool addChecksum) :
|
|
q_ptr(account(this)), m_storage(storage), m_actions(dispatcher), m_is_still_alive(true),
|
|
m_algorithm(Account::Algorithm::Hotp), m_id(id), m_token(QString()), m_nextToken(QString()),
|
|
m_nextTotpValidFrom(QDateTime::fromMSecsSinceEpoch(0)), // not a totp token so does not really matter
|
|
m_nextTotpValidUntil(QDateTime::fromMSecsSinceEpoch(0)), // not a totp token so does not really matter
|
|
m_nextCounter(1ULL), m_name(name), m_issuer(issuer),
|
|
m_secret(secret), m_tokenLength(tokenLength),
|
|
m_counter(counter), m_offset(offset), m_checksum(addChecksum),
|
|
// not a totp token so these values don't really matter
|
|
m_epoch(QDateTime::fromMSecsSinceEpoch(0)), m_timeStep(30), m_hash(Account::Hash::Sha1)
|
|
{
|
|
}
|
|
|
|
AccountPrivate::AccountPrivate(const std::function<Account*(AccountPrivate*)> &account,
|
|
AccountStoragePrivate *storage, Dispatcher *dispatcher,
|
|
const QUuid id, const QString &name, const QString &issuer,
|
|
const secrets::EncryptedSecret &secret, uint tokenLength,
|
|
const QDateTime &epoch, uint timeStep, Account::Hash hash) :
|
|
q_ptr(account(this)), m_storage(storage), m_actions(dispatcher), m_is_still_alive(true),
|
|
m_algorithm(Account::Algorithm::Totp), m_id(id), m_token(QString()), m_nextToken(QString()),
|
|
m_nextTotpValidFrom(epoch), m_nextTotpValidUntil(epoch),
|
|
m_nextCounter(0ULL), // not a hotp token so does not really matter
|
|
m_name(name), m_issuer(issuer), m_secret(secret), m_tokenLength(tokenLength),
|
|
// not a hotp token so these values don't really matter
|
|
m_counter(0ULL), m_offset(std::nullopt), m_checksum(false),
|
|
m_epoch(epoch), m_timeStep(timeStep), m_hash(hash)
|
|
{
|
|
}
|
|
|
|
QVector<QString> AccountStoragePrivate::activeAccounts(void) const
|
|
{
|
|
QVector<QString> active;
|
|
if (!m_is_still_open) {
|
|
qCDebug(logger) << "Not returning accounts: account storage no longer open";
|
|
return active;
|
|
}
|
|
|
|
const QList<QString> all = m_names.keys();
|
|
for (const QString &account : all) {
|
|
const QUuid id = m_names[account];
|
|
if (m_accountsPrivate[id]->isStillAlive()) {
|
|
active.append(account);
|
|
} else {
|
|
qCDebug(logger)
|
|
<< "Not returning account:" << id
|
|
<< "Object marked for death";
|
|
}
|
|
}
|
|
|
|
return active;
|
|
}
|
|
|
|
bool AccountStoragePrivate::isStillOpen(void) const
|
|
{
|
|
return m_is_still_open;
|
|
}
|
|
|
|
bool AccountStoragePrivate::isAccountStillAvailable(const QString &account) const
|
|
{
|
|
if (!m_is_still_open) {
|
|
qCDebug(logger) << "Pretending no name is available: account storage no longer open";
|
|
return false;
|
|
}
|
|
|
|
return !m_names.contains(account);
|
|
}
|
|
|
|
QString AccountPrivate::toFullName(const QString &name, const QString &issuer)
|
|
{
|
|
return issuer.isEmpty() ? name : issuer + QLatin1Char(':') + name;
|
|
}
|
|
|
|
bool AccountStoragePrivate::contains(const QString &account) const
|
|
{
|
|
/*
|
|
* Pretend an account which is marked for removal is already fully purged
|
|
* This lets the behaviour of get() and contains() be mutually consistent
|
|
* without having to hand out pointers which may be about to go stale in get()
|
|
*/
|
|
if (!m_is_still_open) {
|
|
qCDebug(logger) << "Pretending no account exists: account storage no longer open";
|
|
return false;
|
|
}
|
|
|
|
if (!m_names.contains(account)) {
|
|
return false;
|
|
}
|
|
|
|
const QUuid id = m_names[account];
|
|
if (!m_accountsPrivate[id]->isStillAlive()) {
|
|
qCDebug(logger)
|
|
<< "Pretending account does not exist:" << id
|
|
<< "Object marked for death";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
Account * AccountStoragePrivate::get(const QString &account) const
|
|
{
|
|
return contains(account) ? m_accounts[m_names[account]] : nullptr;
|
|
}
|
|
|
|
SettingsProvider AccountStoragePrivate::settings(void) const
|
|
{
|
|
return m_settings;
|
|
}
|
|
|
|
AccountSecret * AccountStoragePrivate::secret(void) const
|
|
{
|
|
return m_secret;
|
|
}
|
|
|
|
void AccountStoragePrivate::removeAccounts(const QSet<QString> &accountNames)
|
|
{
|
|
if (!m_is_still_open) {
|
|
qCDebug(logger) << "Not removing accounts: account storage no longer open";
|
|
return;
|
|
}
|
|
|
|
QSet<QUuid> ids;
|
|
for (const QString &accountName : accountNames) {
|
|
if (m_names.contains(accountName)) {
|
|
const QUuid id = m_names[accountName];
|
|
AccountPrivate *p = m_accountsPrivate[id];
|
|
/*
|
|
* Avoid doing anything with accounts which are already about to be removed from the maps
|
|
*/
|
|
if (p->isStillAlive()) {
|
|
p->markForRemoval();
|
|
ids.insert(id);
|
|
} else {
|
|
qCDebug(logger)
|
|
<< "Not removing account:" << id
|
|
<< "Object marked for death";
|
|
}
|
|
}
|
|
}
|
|
|
|
DeleteAccounts *job = new DeleteAccounts(m_settings, ids);
|
|
m_actions->queueAndProceed(job, [&ids, job, this](void) -> void
|
|
{
|
|
for (const QUuid &id : qAsConst(ids)) {
|
|
Account *account = m_accounts[id];
|
|
QObject::connect(job, &DeleteAccounts::finished, account, &Account::removed);
|
|
}
|
|
});
|
|
}
|
|
|
|
void AccountStoragePrivate::acceptAccountRemoval(const QString &accountName)
|
|
{
|
|
if (!m_names.contains(accountName)) {
|
|
qCDebug(logger) << "Not accepting account removal: account name unknown";
|
|
return;
|
|
}
|
|
|
|
const QUuid id = m_names[accountName];
|
|
qCDebug(logger) << "Handling account cleanup for account:" << id;
|
|
|
|
Account *account = m_accounts[id];
|
|
m_accounts.remove(id);
|
|
m_accountsPrivate.remove(id);
|
|
m_names.remove(accountName);
|
|
m_ids.remove(id);
|
|
QTimer::singleShot(0, account, &accounts::Account::deleteLater);
|
|
}
|
|
|
|
void AccountStoragePrivate::dispose(const std::function<void(Null*)> &handler)
|
|
{
|
|
if (!m_is_still_open) {
|
|
qCDebug(logger) << "Not disposing of storage: account storage no longer open";
|
|
return;
|
|
}
|
|
|
|
qCDebug(logger) << "Disposing of storage";
|
|
|
|
m_is_still_open = false;
|
|
Null *job = new Null();
|
|
m_secret->cancelRequests();
|
|
m_actions->queueAndProceed(job, [job, &handler](void) -> void
|
|
{
|
|
handler(job);
|
|
});
|
|
}
|
|
|
|
void AccountStoragePrivate::acceptDisposal(void)
|
|
{
|
|
Q_Q(AccountStorage);
|
|
if (m_is_still_open) {
|
|
qCDebug(logger) << "Ignoring disposal of storage: account storage is still open";
|
|
return;
|
|
}
|
|
|
|
qCDebug(logger) << "Handling storage disposal";
|
|
|
|
const auto &names = m_names.keys();
|
|
for (const QString &accountName : names) {
|
|
const QUuid id = m_names[accountName];
|
|
qCDebug(logger) << "Handling account cleanup for account:" << id;
|
|
|
|
Account *account = m_accounts[id];
|
|
m_accounts.remove(id);
|
|
m_accountsPrivate.remove(id);
|
|
m_names.remove(accountName);
|
|
m_ids.remove(id);
|
|
QTimer::singleShot(0, account, &accounts::Account::deleteLater);
|
|
}
|
|
QTimer::singleShot(0, m_secret, &accounts::AccountSecret::deleteLater);
|
|
Q_EMIT q->disposed();
|
|
}
|
|
|
|
QUuid AccountStoragePrivate::generateId(const QString &name) const
|
|
{
|
|
QUuid attempt = QUuid::createUuidV5(QUuid(), name);
|
|
while (attempt.isNull() || m_ids.contains(attempt)) {
|
|
attempt = QUuid::createUuid();
|
|
}
|
|
return attempt;
|
|
}
|
|
|
|
std::optional<secrets::EncryptedSecret> AccountStoragePrivate::encrypt(const QString &secret) const
|
|
{
|
|
if (!m_is_still_open) {
|
|
qCDebug(logger) << "Will not encrypt account secret: storage no longer open";
|
|
return std::nullopt;
|
|
}
|
|
|
|
if (!m_secret || !m_secret->key()) {
|
|
qCDebug(logger) << "Will not encrypt account secret: encryption key not available";
|
|
return std::nullopt;
|
|
}
|
|
|
|
QScopedPointer<secrets::SecureMemory> decoded(secrets::decodeBase32(secret));
|
|
if (!decoded) {
|
|
qCDebug(logger) << "Will not encrypt account secret: failed to decode base32";
|
|
return std::nullopt;
|
|
}
|
|
|
|
return m_secret->encrypt(decoded.data());
|
|
}
|
|
|
|
bool AccountStoragePrivate::validateGenericNewToken(const QString &name, const QString &issuer,
|
|
const QString &secret, uint tokenLength) const
|
|
{
|
|
return checkTokenLength(tokenLength) && checkName(name) && checkIssuer(issuer)
|
|
&& isAccountStillAvailable(AccountPrivate::toFullName(name, issuer)) && checkSecret(secret);
|
|
}
|
|
|
|
bool AccountStoragePrivate::addHotp(const std::function<void(SaveHotp*)> &handler,
|
|
const QString &name, const QString &issuer,
|
|
const QString &secret, uint tokenLength,
|
|
quint64 counter, const std::optional<uint> offset, bool checksum)
|
|
{
|
|
if (!m_is_still_open) {
|
|
qCDebug(logger) << "Will not add new HOTP account: storage no longer open";
|
|
return false;
|
|
}
|
|
|
|
if (!validateGenericNewToken(name, issuer, secret, tokenLength) ||
|
|
!checkOffset(offset, QCryptographicHash::Sha1)) {
|
|
qCDebug(logger) << "Will not add new HOTP account: invalid account details";
|
|
return false;
|
|
}
|
|
|
|
std::optional<secrets::EncryptedSecret> encryptedSecret = encrypt(secret);
|
|
if (!encryptedSecret) {
|
|
qCDebug(logger) << "Will not add new HOTP account: failed to encrypt secret";
|
|
return false;
|
|
}
|
|
|
|
QUuid id = generateId(AccountPrivate::toFullName(name, issuer));
|
|
qCDebug(logger) << "Requesting to store details for new HOTP account:" << id;
|
|
|
|
m_ids.insert(id);
|
|
SaveHotp *job = new SaveHotp(m_settings, id, name, issuer, *encryptedSecret, tokenLength,
|
|
counter, offset, checksum);
|
|
m_actions->queueAndProceed(job, [job, &handler](void) -> void
|
|
{
|
|
handler(job);
|
|
});
|
|
return true;
|
|
}
|
|
|
|
bool AccountStoragePrivate::addTotp(const std::function<void(SaveTotp*)> &handler,
|
|
const QString &name, const QString &issuer,
|
|
const QString &secret, uint tokenLength,
|
|
uint timeStep, const QDateTime &epoch, Account::Hash hash)
|
|
{
|
|
if (!m_is_still_open) {
|
|
qCDebug(logger) << "Will not add new TOTP account: storage no longer open";
|
|
return false;
|
|
}
|
|
|
|
if (!validateGenericNewToken(name, issuer, secret, tokenLength) ||
|
|
!checkTimeStep(timeStep) || !checkEpoch(epoch)) {
|
|
qCDebug(logger) << "Will not add new TOTP account: invalid account details";
|
|
return false;
|
|
}
|
|
|
|
std::optional<secrets::EncryptedSecret> encryptedSecret = encrypt(secret);
|
|
if (!encryptedSecret) {
|
|
qCDebug(logger) << "Will not add new TOTP account: failed to encrypt secret";
|
|
return false;
|
|
}
|
|
|
|
QUuid id = generateId(AccountPrivate::toFullName(name, issuer));
|
|
qCDebug(logger) << "Requesting to store details for new TOTP account:" << id;
|
|
|
|
m_ids.insert(id);
|
|
SaveTotp *job = new SaveTotp(m_settings, id, name, issuer, *encryptedSecret, tokenLength,
|
|
timeStep, epoch, hash);
|
|
m_actions->queueAndProceed(job, [job, &handler](void) -> void
|
|
{
|
|
handler(job);
|
|
});
|
|
return true;
|
|
}
|
|
|
|
void AccountStoragePrivate::unlock(const std::function<void(RequestAccountPassword*)> &handler)
|
|
{
|
|
if (!m_is_still_open) {
|
|
qCDebug(logger) << "Will not attempt to unlock accounts: storage no longer open";
|
|
return;
|
|
}
|
|
|
|
qCDebug(logger) << "Requesting to unlock account storage";
|
|
RequestAccountPassword *job = new RequestAccountPassword(m_settings, m_secret);
|
|
m_actions->queueAndProceed(job, [job, &handler](void) -> void
|
|
{
|
|
handler(job);
|
|
});
|
|
}
|
|
|
|
void AccountStoragePrivate::load(const std::function<void(LoadAccounts*)> &handler)
|
|
{
|
|
if (!m_is_still_open) {
|
|
qCDebug(logger) << "Will not load accounts: storage no longer open";
|
|
return;
|
|
}
|
|
|
|
LoadAccounts *job = new LoadAccounts(m_settings, m_secret);
|
|
m_actions->queueAndProceed(job, [job, &handler](void) -> void
|
|
{
|
|
handler(job);
|
|
});
|
|
}
|
|
|
|
Account * AccountStoragePrivate::acceptHotpAccount(const QUuid id, const QString &name, const QString &issuer,
|
|
const secrets::EncryptedSecret &secret, uint tokenLength,
|
|
quint64 counter, const std::optional<uint> offset, bool checksum)
|
|
{
|
|
Q_Q(AccountStorage);
|
|
qCDebug(logger) << "Registering HOTP account:" << id;
|
|
const std::function<Account*(AccountPrivate*)> registration([this, q, &id](AccountPrivate *p) -> Account *
|
|
{
|
|
Account *account = new Account(p, q);
|
|
m_accounts.insert(id, account);
|
|
return account;
|
|
});
|
|
m_ids.insert(id);
|
|
m_names.insert(AccountPrivate::toFullName(name, issuer), id);
|
|
const auto p = new AccountPrivate(registration, this, m_actions,
|
|
id, name, issuer, secret, tokenLength, counter, offset, checksum);
|
|
m_accountsPrivate.insert(id, p);
|
|
|
|
Q_ASSERT_X(m_accounts.contains(id), Q_FUNC_INFO, "account should have been registered");
|
|
return m_accounts[id];
|
|
}
|
|
|
|
Account * AccountStoragePrivate::acceptTotpAccount(const QUuid id, const QString &name, const QString &issuer,
|
|
const secrets::EncryptedSecret &secret, uint tokenLength,
|
|
uint timeStep, const QDateTime &epoch, Account::Hash hash)
|
|
{
|
|
Q_Q(AccountStorage);
|
|
qCDebug(logger) << "Registering TOTP account:" << id;
|
|
const std::function<Account*(AccountPrivate*)> registration([this, q, &id](AccountPrivate *p) -> Account *
|
|
{
|
|
Account *account = new Account(p, q);
|
|
m_accounts.insert(id, account);
|
|
return account;
|
|
});
|
|
m_ids.insert(id);
|
|
m_names.insert(AccountPrivate::toFullName(name, issuer), id);
|
|
const auto p = new AccountPrivate(registration, this, m_actions,
|
|
id, name, issuer, secret, tokenLength, epoch, timeStep, hash);
|
|
m_accountsPrivate.insert(id, p);
|
|
|
|
Q_ASSERT_X(m_accounts.contains(id), Q_FUNC_INFO, "account should have been registered");
|
|
return m_accounts[id];
|
|
}
|
|
|
|
bool AccountStoragePrivate::isLoaded(void) const
|
|
{
|
|
return m_is_loaded;
|
|
}
|
|
|
|
void AccountStoragePrivate::notifyLoaded(void)
|
|
{
|
|
Q_Q(AccountStorage);
|
|
m_is_loaded = true;
|
|
Q_EMIT q->loaded();
|
|
}
|
|
|
|
void AccountStoragePrivate::notifyError(void)
|
|
{
|
|
Q_Q(AccountStorage);
|
|
m_has_error = true;
|
|
Q_EMIT q->error();
|
|
}
|
|
|
|
void AccountStoragePrivate::clearError(void)
|
|
{
|
|
m_has_error = false;
|
|
}
|
|
|
|
bool AccountStoragePrivate::hasError(void) const
|
|
{
|
|
return m_has_error;
|
|
}
|
|
|
|
AccountStoragePrivate::AccountStoragePrivate(const SettingsProvider &settings,
|
|
AccountSecret *secret, AccountStorage *storage, Dispatcher *dispatcher) :
|
|
q_ptr(storage), m_is_loaded(false), m_has_error(false), m_is_still_open(true),
|
|
m_actions(dispatcher), m_settings(settings), m_secret(secret)
|
|
{
|
|
}
|
|
|
|
HandleCounterUpdate::HandleCounterUpdate(AccountPrivate *account, AccountStoragePrivate *storage,
|
|
quint64 counter, SaveHotp *job, QObject *parent) :
|
|
QObject(parent), m_accept_on_finish(true), m_counter(counter), m_account(account), m_storage(storage)
|
|
{
|
|
QObject::connect(job, &SaveHotp::invalid, this, &HandleCounterUpdate::rejected);
|
|
QObject::connect(job, &SaveHotp::finished, this, &HandleCounterUpdate::finished);
|
|
}
|
|
|
|
void HandleCounterUpdate::rejected(void)
|
|
{
|
|
m_accept_on_finish = false;
|
|
m_storage->notifyError();
|
|
}
|
|
|
|
void HandleCounterUpdate::finished(void)
|
|
{
|
|
if (m_accept_on_finish) {
|
|
m_account->acceptCounter(m_counter);
|
|
} else {
|
|
qCWarning(logger)
|
|
<< "Rejecting counter update for account:" << m_account->id()
|
|
<< "Failed to save the updated account details to storage";
|
|
}
|
|
deleteLater();
|
|
}
|
|
|
|
HandleTokenUpdate::HandleTokenUpdate(AccountPrivate *account, ComputeHotp *job, QObject *parent) :
|
|
QObject(parent), m_account(account)
|
|
{
|
|
QObject::connect(job, &ComputeHotp::otp, this, &HandleTokenUpdate::hotp);
|
|
QObject::connect(job, &ComputeHotp::finished, this, &HandleTokenUpdate::deleteLater);
|
|
}
|
|
|
|
HandleTokenUpdate::HandleTokenUpdate(AccountPrivate *account, ComputeTotp *job, QObject *parent) :
|
|
QObject(parent), m_account(account)
|
|
{
|
|
QObject::connect(job, &ComputeTotp::otp, this, &HandleTokenUpdate::totp);
|
|
QObject::connect(job, &ComputeTotp::finished, this, &HandleTokenUpdate::deleteLater);
|
|
}
|
|
|
|
void HandleTokenUpdate::totp(const QString &otp, const QString &nextOtp, const QDateTime &validFrom,
|
|
const QDateTime &validUntil)
|
|
{
|
|
m_account->acceptTotpTokens(otp, nextOtp, validFrom, validUntil);
|
|
}
|
|
|
|
void HandleTokenUpdate::hotp(const QString &otp, const QString &nextOtp, quint64 validUntil)
|
|
{
|
|
m_account->acceptHotpTokens(otp, nextOtp, validUntil);
|
|
}
|
|
}
|