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 }