365 lines
11 KiB
C++
365 lines
11 KiB
C++
/*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
* SPDX-FileCopyrightText: 2020-2021 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
|
|
*/
|
|
#include "account.h"
|
|
#include "account_p.h"
|
|
#include "actions_p.h"
|
|
|
|
#include "../logging_p.h"
|
|
|
|
#include <QTimer>
|
|
|
|
KEYSMITH_LOGGER(logger, ".accounts.account")
|
|
|
|
namespace accounts
|
|
{
|
|
Account::Account(AccountPrivate *d, QObject *parent) :
|
|
QObject(parent), m_dptr(d)
|
|
{
|
|
}
|
|
|
|
QString Account::name(void) const
|
|
{
|
|
Q_D(const Account);
|
|
return d->name();
|
|
}
|
|
|
|
QString Account::issuer(void) const
|
|
{
|
|
Q_D(const Account);
|
|
return d->issuer();
|
|
}
|
|
|
|
QString Account::token(void) const
|
|
{
|
|
Q_D(const Account);
|
|
return d->token();
|
|
}
|
|
|
|
quint64 Account::counter(void) const
|
|
{
|
|
Q_D(const Account);
|
|
return d->counter();
|
|
}
|
|
|
|
QDateTime Account::epoch(void) const
|
|
{
|
|
Q_D(const Account);
|
|
return d->epoch();
|
|
}
|
|
|
|
uint Account::timeStep(void) const
|
|
{
|
|
Q_D(const Account);
|
|
return d->timeStep();
|
|
}
|
|
|
|
std::optional<uint> Account::offset(void) const
|
|
{
|
|
Q_D(const Account);
|
|
return d->offset();
|
|
}
|
|
|
|
int Account::tokenLength(void) const
|
|
{
|
|
Q_D(const Account);
|
|
return d->tokenLength();
|
|
}
|
|
|
|
bool Account::checksum(void) const
|
|
{
|
|
Q_D(const Account);
|
|
return d->checksum();
|
|
}
|
|
|
|
Account::Hash Account::hash(void) const
|
|
{
|
|
Q_D(const Account);
|
|
return d->hash();
|
|
}
|
|
|
|
Account::Algorithm Account::algorithm(void) const
|
|
{
|
|
Q_D(const Account);
|
|
return d->algorithm();
|
|
}
|
|
|
|
void Account::recompute(void)
|
|
{
|
|
Q_D(Account);
|
|
if(d->token().isEmpty() || d->algorithm() != Account::Hotp) {
|
|
d->recompute();
|
|
}
|
|
}
|
|
|
|
void Account::setCounter(quint64 value)
|
|
{
|
|
Q_D(Account);
|
|
d->setCounter(value);
|
|
}
|
|
|
|
void Account::advanceCounter(quint64 by)
|
|
{
|
|
setCounter(counter() + by);
|
|
}
|
|
|
|
void Account::remove(void)
|
|
{
|
|
Q_D(Account);
|
|
d->remove();
|
|
}
|
|
|
|
AccountStorage::AccountStorage(const SettingsProvider &settings, QThread *worker, AccountSecret *secret,
|
|
QObject *parent) :
|
|
QObject(parent),
|
|
m_dptr(new AccountStoragePrivate(settings,
|
|
secret ? secret : new AccountSecret(secrets::defaultSecureRandom, this),
|
|
this,
|
|
new Dispatcher(worker, this)))
|
|
{
|
|
QTimer::singleShot(0, this, &AccountStorage::unlock);
|
|
}
|
|
|
|
AccountStorage * AccountStorage::open(const SettingsProvider &settings, AccountSecret *secret, QObject *parent)
|
|
{
|
|
QThread *worker = new QThread(parent);
|
|
AccountStorage *storage = new AccountStorage(settings, worker, secret, parent);
|
|
|
|
QObject::connect(storage, &AccountStorage::disposed, worker, &QThread::quit);
|
|
QObject::connect(worker, &QThread::finished, worker, &QThread::deleteLater);
|
|
QObject::connect(worker, &QThread::destroyed, storage, &AccountStorage::deleteLater);
|
|
worker->start();
|
|
|
|
return storage;
|
|
}
|
|
|
|
void AccountStorage::unlock(void)
|
|
{
|
|
Q_D(AccountStorage);
|
|
const std::function<void(RequestAccountPassword*)> handler([this](RequestAccountPassword *job) -> void
|
|
{
|
|
QObject::connect(job, &RequestAccountPassword::unlocked, this, &AccountStorage::load);
|
|
QObject::connect(job, &RequestAccountPassword::failed, this, &AccountStorage::handleError);
|
|
});
|
|
d->unlock(handler);
|
|
}
|
|
|
|
void AccountStorage::load(void)
|
|
{
|
|
Q_D(AccountStorage);
|
|
const std::function<void(LoadAccounts*)> handler([this](LoadAccounts *job) -> void
|
|
{
|
|
QObject::connect(job, &LoadAccounts::foundHotp, this, &AccountStorage::handleHotp);
|
|
QObject::connect(job, &LoadAccounts::foundTotp, this, &AccountStorage::handleTotp);
|
|
QObject::connect(job, &LoadAccounts::finished, this, &AccountStorage::handleLoaded);
|
|
QObject::connect(job, &LoadAccounts::failedToLoadAllAccounts, this, &AccountStorage::handleError);
|
|
});
|
|
d->load(handler);
|
|
}
|
|
|
|
bool AccountStorage::contains(const QString &fullName) const
|
|
{
|
|
Q_D(const AccountStorage);
|
|
return d->contains(fullName);
|
|
}
|
|
|
|
bool AccountStorage::contains(const QString &name, const QString &issuer) const
|
|
{
|
|
return contains(AccountPrivate::toFullName(name, issuer));
|
|
}
|
|
|
|
Account * AccountStorage::get(const QString &fullName) const
|
|
{
|
|
Q_D(const AccountStorage);
|
|
return d->get(fullName);
|
|
}
|
|
|
|
Account * AccountStorage::get(const QString &name, const QString &issuer) const
|
|
{
|
|
return get(AccountPrivate::toFullName(name, issuer));
|
|
}
|
|
|
|
AccountSecret * AccountStorage::secret(void) const
|
|
{
|
|
Q_D(const AccountStorage);
|
|
return d->secret();
|
|
}
|
|
|
|
bool AccountStorage::isAccountStillAvailable(const QString &fullName) const
|
|
{
|
|
Q_D(const AccountStorage);
|
|
return d->isAccountStillAvailable(fullName);
|
|
}
|
|
|
|
bool AccountStorage::isAccountStillAvailable(const QString &name, const QString &issuer) const
|
|
{
|
|
return isAccountStillAvailable(AccountPrivate::toFullName(name, issuer));
|
|
}
|
|
|
|
void AccountStorage::addHotp(const QString &name, const QString &issuer, const QString &secret, uint tokenLength,
|
|
quint64 counter, const std::optional<uint> offset, bool addChecksum)
|
|
{
|
|
Q_D(AccountStorage);
|
|
const std::function<void(SaveHotp*)> handler([this](SaveHotp *job) -> void
|
|
{
|
|
QObject::connect(job, &SaveHotp::saved, this, &AccountStorage::handleHotp);
|
|
QObject::connect(job, &SaveHotp::invalid, this, &AccountStorage::handleError);
|
|
});
|
|
if (!d->addHotp(handler, name, issuer.isEmpty() ? QString() : issuer, secret, tokenLength, counter, offset, addChecksum)) {
|
|
Q_EMIT error();
|
|
}
|
|
}
|
|
|
|
void AccountStorage::addTotp(const QString &name, const QString &issuer, const QString &secret, uint tokenLength,
|
|
uint timeStep, const QDateTime &epoch, Account::Hash hash)
|
|
{
|
|
Q_D(AccountStorage);
|
|
const std::function<void(SaveTotp*)> handler([this](SaveTotp *job) -> void
|
|
{
|
|
QObject::connect(job, &SaveTotp::saved, this, &AccountStorage::handleTotp);
|
|
QObject::connect(job, &SaveTotp::invalid, this, &AccountStorage::handleError);
|
|
});
|
|
if (!d->addTotp(handler, name, issuer.isEmpty() ? QString() : issuer, secret, tokenLength, timeStep, epoch, hash)) {
|
|
Q_EMIT error();
|
|
}
|
|
}
|
|
|
|
void AccountStorage::accountRemoved(void)
|
|
{
|
|
Q_D(AccountStorage);
|
|
|
|
QObject *from = sender();
|
|
Account *account = from ? qobject_cast<Account*>(from) : nullptr;
|
|
Q_ASSERT_X(account, Q_FUNC_INFO, "event should be sent by an account");
|
|
|
|
const QString fullName = AccountPrivate::toFullName(account->name(), account->issuer());
|
|
d->acceptAccountRemoval(fullName);
|
|
Q_EMIT removed(fullName);
|
|
}
|
|
|
|
QVector<QString> AccountStorage::accounts(void) const
|
|
{
|
|
Q_D(const AccountStorage);
|
|
return d->activeAccounts();
|
|
}
|
|
|
|
void AccountStorage::handleHotp(const QUuid id, const QString &name, const QString &issuer,
|
|
const QByteArray &secret, const QByteArray &nonce, uint tokenLength,
|
|
quint64 counter, bool fixedTruncation, uint offset, bool checksum)
|
|
{
|
|
Q_D(AccountStorage);
|
|
if (!d->isStillOpen()) {
|
|
qCDebug(logger)
|
|
<< "Not handling HOTP account:" << id
|
|
<< "Storage no longer open";
|
|
return;
|
|
}
|
|
|
|
if (!isAccountStillAvailable(name, issuer)) {
|
|
qCDebug(logger)
|
|
<< "Not handling HOTP account:" << id
|
|
<< "Account name or issuer not available";
|
|
return;
|
|
}
|
|
|
|
std::optional<secrets::EncryptedSecret> encryptedSecret = secrets::EncryptedSecret::from(secret, nonce);
|
|
if (!encryptedSecret) {
|
|
qCDebug(logger)
|
|
<< "Not handling HOTP account:" << id
|
|
<< "Invalid encrypted secret/nonce";
|
|
return;
|
|
}
|
|
|
|
const std::optional<uint> offsetValue = fixedTruncation ? std::optional<uint>((uint) offset) : std::nullopt;
|
|
Account *accepted = d->acceptHotpAccount(id, name, issuer,
|
|
*encryptedSecret, tokenLength, counter, offsetValue, checksum);
|
|
QObject::connect(accepted, &Account::removed, this, &AccountStorage::accountRemoved);
|
|
|
|
Q_EMIT added(AccountPrivate::toFullName(name, issuer));
|
|
}
|
|
|
|
void AccountStorage::handleTotp(const QUuid id, const QString &name, const QString &issuer,
|
|
const QByteArray &secret, const QByteArray &nonce, uint tokenLength,
|
|
uint timeStep, const QDateTime &epoch, Account::Hash hash)
|
|
{
|
|
Q_D(AccountStorage);
|
|
if (!d->isStillOpen()) {
|
|
qCDebug(logger)
|
|
<< "Not handling TOTP account:" << id
|
|
<< "Storage no longer open";
|
|
return;
|
|
}
|
|
|
|
if (!isAccountStillAvailable(name, issuer)) {
|
|
qCDebug(logger)
|
|
<< "Not handling TOTP account:" << id
|
|
<< "Account name or issuer not available";
|
|
return;
|
|
}
|
|
|
|
std::optional<secrets::EncryptedSecret> encryptedSecret = secrets::EncryptedSecret::from(secret, nonce);
|
|
if (!encryptedSecret) {
|
|
qCDebug(logger)
|
|
<< "Not handling TOTP account:" << id
|
|
<< "Invalid encrypted secret/nonce";
|
|
return;
|
|
}
|
|
|
|
Account *accepted = d->acceptTotpAccount(id, name, issuer,
|
|
*encryptedSecret, tokenLength, timeStep, epoch, hash);
|
|
QObject::connect(accepted, &Account::removed, this, &AccountStorage::accountRemoved);
|
|
|
|
Q_EMIT added(AccountPrivate::toFullName(name, issuer));
|
|
}
|
|
|
|
void AccountStorage::dispose(void)
|
|
{
|
|
Q_D(AccountStorage);
|
|
d->dispose([this](Null *job) -> void
|
|
{
|
|
/*
|
|
* Use destroyed() instead of finished() to guarantee the Null job has been disposed of before e.g. threads
|
|
* are cleaned up. If the QThread is disposed of before the Null job is cleaned up, the job would leak.
|
|
*/
|
|
QObject::connect(job, &Null::destroyed, this, &AccountStorage::handleDisposal);
|
|
});
|
|
}
|
|
|
|
void AccountStorage::handleDisposal(void)
|
|
{
|
|
Q_D(AccountStorage);
|
|
d->acceptDisposal();
|
|
}
|
|
|
|
bool AccountStorage::hasError(void) const
|
|
{
|
|
Q_D(const AccountStorage);
|
|
return d->hasError();
|
|
}
|
|
|
|
void AccountStorage::clearError(void)
|
|
{
|
|
Q_D(AccountStorage);
|
|
d->clearError();
|
|
}
|
|
|
|
void AccountStorage::handleError(void)
|
|
{
|
|
Q_D(AccountStorage);
|
|
d->notifyError();
|
|
}
|
|
|
|
void AccountStorage::handleLoaded(void)
|
|
{
|
|
Q_D(AccountStorage);
|
|
d->notifyLoaded();
|
|
}
|
|
|
|
bool AccountStorage::isLoaded(void) const
|
|
{
|
|
Q_D(const AccountStorage);
|
|
return d->isLoaded();
|
|
}
|
|
}
|