From 53a17c235589dede8390ff68c37806081aa1ae08 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Wed, 5 Sep 2018 16:21:59 -0400 Subject: [PATCH 1/4] Reduce use of static vars in browser plugin * Convert BrowserSettings into instanced class * Moved HostInstaller init into class constructor --- src/browser/BrowserAction.cpp | 4 +- src/browser/BrowserOptionDialog.cpp | 76 ++++++------ src/browser/BrowserService.cpp | 28 ++--- src/browser/BrowserService.h | 1 + src/browser/BrowserSettings.cpp | 21 ++-- src/browser/BrowserSettings.h | 178 +++++++++++++++------------- src/browser/HostInstaller.cpp | 74 +++++------- src/browser/HostInstaller.h | 14 +-- src/browser/NativeMessagingHost.cpp | 10 +- src/browser/NativeMessagingHost.h | 2 +- src/gui/MainWindow.cpp | 4 +- 11 files changed, 209 insertions(+), 203 deletions(-) diff --git a/src/browser/BrowserAction.cpp b/src/browser/BrowserAction.cpp index 2fcf4ee84..60cf8506a 100644 --- a/src/browser/BrowserAction.cpp +++ b/src/browser/BrowserAction.cpp @@ -270,8 +270,8 @@ QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QStrin QJsonObject BrowserAction::handleGeneratePassword(const QJsonObject& json, const QString& action) { const QString nonce = json.value("nonce").toString(); - const QString password = BrowserSettings::generatePassword(); - const QString bits = QString::number(BrowserSettings::getbits()); // For some reason this always returns 1140 bits? + const QString password = browserSettings()->generatePassword(); + const QString bits = QString::number(browserSettings()->getbits()); // For some reason this always returns 1140 bits? if (nonce.isEmpty() || password.isEmpty()) { return QJsonObject(); diff --git a/src/browser/BrowserOptionDialog.cpp b/src/browser/BrowserOptionDialog.cpp index c5923c5d8..e2ed8420b 100644 --- a/src/browser/BrowserOptionDialog.cpp +++ b/src/browser/BrowserOptionDialog.cpp @@ -66,36 +66,36 @@ BrowserOptionDialog::~BrowserOptionDialog() void BrowserOptionDialog::loadSettings() { - BrowserSettings settings; - m_ui->enableBrowserSupport->setChecked(settings.isEnabled()); + auto settings = browserSettings(); + m_ui->enableBrowserSupport->setChecked(settings->isEnabled()); - m_ui->showNotification->setChecked(settings.showNotification()); - m_ui->bestMatchOnly->setChecked(settings.bestMatchOnly()); - m_ui->unlockDatabase->setChecked(settings.unlockDatabase()); - m_ui->matchUrlScheme->setChecked(settings.matchUrlScheme()); + m_ui->showNotification->setChecked(settings->showNotification()); + m_ui->bestMatchOnly->setChecked(settings->bestMatchOnly()); + m_ui->unlockDatabase->setChecked(settings->unlockDatabase()); + m_ui->matchUrlScheme->setChecked(settings->matchUrlScheme()); // hide unimplemented options // TODO: fix this m_ui->showNotification->hide(); - if (settings.sortByUsername()) { + if (settings->sortByUsername()) { m_ui->sortByUsername->setChecked(true); } else { m_ui->sortByTitle->setChecked(true); } - m_ui->alwaysAllowAccess->setChecked(settings.alwaysAllowAccess()); - m_ui->alwaysAllowUpdate->setChecked(settings.alwaysAllowUpdate()); - m_ui->searchInAllDatabases->setChecked(settings.searchInAllDatabases()); - m_ui->supportKphFields->setChecked(settings.supportKphFields()); - m_ui->supportBrowserProxy->setChecked(settings.supportBrowserProxy()); - m_ui->useCustomProxy->setChecked(settings.useCustomProxy()); - m_ui->customProxyLocation->setText(settings.customProxyLocation()); - m_ui->updateBinaryPath->setChecked(settings.updateBinaryPath()); - m_ui->chromeSupport->setChecked(settings.chromeSupport()); - m_ui->chromiumSupport->setChecked(settings.chromiumSupport()); - m_ui->firefoxSupport->setChecked(settings.firefoxSupport()); - m_ui->vivaldiSupport->setChecked(settings.vivaldiSupport()); + m_ui->alwaysAllowAccess->setChecked(settings->alwaysAllowAccess()); + m_ui->alwaysAllowUpdate->setChecked(settings->alwaysAllowUpdate()); + m_ui->searchInAllDatabases->setChecked(settings->searchInAllDatabases()); + m_ui->supportKphFields->setChecked(settings->supportKphFields()); + m_ui->supportBrowserProxy->setChecked(settings->supportBrowserProxy()); + m_ui->useCustomProxy->setChecked(settings->useCustomProxy()); + m_ui->customProxyLocation->setText(settings->customProxyLocation()); + m_ui->updateBinaryPath->setChecked(settings->updateBinaryPath()); + m_ui->chromeSupport->setChecked(settings->chromeSupport()); + m_ui->chromiumSupport->setChecked(settings->chromiumSupport()); + m_ui->firefoxSupport->setChecked(settings->firefoxSupport()); + m_ui->vivaldiSupport->setChecked(settings->vivaldiSupport()); #if defined(KEEPASSXC_DIST_APPIMAGE) m_ui->supportBrowserProxy->setChecked(true); @@ -113,28 +113,28 @@ void BrowserOptionDialog::loadSettings() void BrowserOptionDialog::saveSettings() { - BrowserSettings settings; - settings.setEnabled(m_ui->enableBrowserSupport->isChecked()); - settings.setShowNotification(m_ui->showNotification->isChecked()); - settings.setBestMatchOnly(m_ui->bestMatchOnly->isChecked()); - settings.setUnlockDatabase(m_ui->unlockDatabase->isChecked()); - settings.setMatchUrlScheme(m_ui->matchUrlScheme->isChecked()); - settings.setSortByUsername(m_ui->sortByUsername->isChecked()); + auto settings = browserSettings(); + settings->setEnabled(m_ui->enableBrowserSupport->isChecked()); + settings->setShowNotification(m_ui->showNotification->isChecked()); + settings->setBestMatchOnly(m_ui->bestMatchOnly->isChecked()); + settings->setUnlockDatabase(m_ui->unlockDatabase->isChecked()); + settings->setMatchUrlScheme(m_ui->matchUrlScheme->isChecked()); + settings->setSortByUsername(m_ui->sortByUsername->isChecked()); - settings.setSupportBrowserProxy(m_ui->supportBrowserProxy->isChecked()); - settings.setUseCustomProxy(m_ui->useCustomProxy->isChecked()); - settings.setCustomProxyLocation(m_ui->customProxyLocation->text()); + settings->setSupportBrowserProxy(m_ui->supportBrowserProxy->isChecked()); + settings->setUseCustomProxy(m_ui->useCustomProxy->isChecked()); + settings->setCustomProxyLocation(m_ui->customProxyLocation->text()); - settings.setUpdateBinaryPath(m_ui->updateBinaryPath->isChecked()); - settings.setAlwaysAllowAccess(m_ui->alwaysAllowAccess->isChecked()); - settings.setAlwaysAllowUpdate(m_ui->alwaysAllowUpdate->isChecked()); - settings.setSearchInAllDatabases(m_ui->searchInAllDatabases->isChecked()); - settings.setSupportKphFields(m_ui->supportKphFields->isChecked()); + settings->setUpdateBinaryPath(m_ui->updateBinaryPath->isChecked()); + settings->setAlwaysAllowAccess(m_ui->alwaysAllowAccess->isChecked()); + settings->setAlwaysAllowUpdate(m_ui->alwaysAllowUpdate->isChecked()); + settings->setSearchInAllDatabases(m_ui->searchInAllDatabases->isChecked()); + settings->setSupportKphFields(m_ui->supportKphFields->isChecked()); - settings.setChromeSupport(m_ui->chromeSupport->isChecked()); - settings.setChromiumSupport(m_ui->chromiumSupport->isChecked()); - settings.setFirefoxSupport(m_ui->firefoxSupport->isChecked()); - settings.setVivaldiSupport(m_ui->vivaldiSupport->isChecked()); + settings->setChromeSupport(m_ui->chromeSupport->isChecked()); + settings->setChromiumSupport(m_ui->chromiumSupport->isChecked()); + settings->setFirefoxSupport(m_ui->firefoxSupport->isChecked()); + settings->setVivaldiSupport(m_ui->vivaldiSupport->isChecked()); } void BrowserOptionDialog::showProxyLocationFileDialog() diff --git a/src/browser/BrowserService.cpp b/src/browser/BrowserService.cpp index e2ee6cac9..bba651d56 100644 --- a/src/browser/BrowserService.cpp +++ b/src/browser/BrowserService.cpp @@ -34,7 +34,6 @@ #include "core/PasswordGenerator.h" #include "gui/MainWindow.h" -static const QUuid KEEPASSXCBROWSER_UUID = QUuid::fromRfc4122(QByteArray::fromHex("de887cc3036343b8974b5911b8816224")); static const char KEEPASSXCBROWSER_NAME[] = "KeePassXC-Browser Settings"; static const char ASSOCIATE_KEY_PREFIX[] = "Public Key: "; static const char KEEPASSXCBROWSER_GROUP_NAME[] = "KeePassXC-Browser Passwords"; @@ -44,6 +43,7 @@ BrowserService::BrowserService(DatabaseTabWidget* parent) : m_dbTabWidget(parent) , m_dialogActive(false) , m_bringToFrontRequested(false) + , m_keepassBrowserUUID(QUuid::fromRfc4122(QByteArray::fromHex("de887cc3036343b8974b5911b8816224"))) { connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), this, SLOT(databaseLocked(DatabaseWidget*))); connect(m_dbTabWidget, SIGNAL(databaseUnlocked(DatabaseWidget*)), this, SLOT(databaseUnlocked(DatabaseWidget*))); @@ -65,7 +65,7 @@ bool BrowserService::isDatabaseOpened() const bool BrowserService::openDatabase(bool triggerUnlock) { - if (!BrowserSettings::unlockDatabase()) { + if (!browserSettings()->unlockDatabase()) { return false; } @@ -139,11 +139,11 @@ Entry* BrowserService::getConfigEntry(bool create) return nullptr; } - entry = db->resolveEntry(KEEPASSXCBROWSER_UUID); + entry = db->resolveEntry(m_keepassBrowserUUID); if (!entry && create) { entry = new Entry(); entry->setTitle(QLatin1String(KEEPASSXCBROWSER_NAME)); - entry->setUuid(KEEPASSXCBROWSER_UUID); + entry->setUuid(m_keepassBrowserUUID); entry->setAutoTypeEnabled(false); entry->setGroup(db->rootGroup()); return entry; @@ -243,7 +243,7 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id, return result; } - const bool alwaysAllowAccess = BrowserSettings::alwaysAllowAccess(); + const bool alwaysAllowAccess = browserSettings()->alwaysAllowAccess(); const QString host = QUrl(url).host(); const QString submitHost = QUrl(submitUrl).host(); @@ -358,7 +358,7 @@ void BrowserService::updateEntry(const QString& id, if (username.compare(login, Qt::CaseSensitive) != 0 || entry->password().compare(password, Qt::CaseSensitive) != 0) { int dialogResult = QMessageBox::No; - if (!BrowserSettings::alwaysAllowUpdate()) { + if (!browserSettings()->alwaysAllowUpdate()) { QMessageBox msgBox; msgBox.setWindowTitle(tr("KeePassXC: Update Entry")); msgBox.setText(tr("Do you want to update the information in %1 - %2?").arg(QUrl(url).host()).arg(username)); @@ -371,7 +371,7 @@ void BrowserService::updateEntry(const QString& id, dialogResult = msgBox.exec(); } - if (BrowserSettings::alwaysAllowUpdate() || dialogResult == QMessageBox::Yes) { + if (browserSettings()->alwaysAllowUpdate() || dialogResult == QMessageBox::Yes) { entry->beginUpdate(); entry->setUsername(login); entry->setPassword(password); @@ -396,7 +396,7 @@ QList BrowserService::searchEntries(Database* db, const QString& hostnam // Ignore entry if port or scheme defined in the URL doesn't match if ((entryQUrl.port() > 0 && entryQUrl.port() != qUrl.port()) || - (BrowserSettings::matchUrlScheme() && entryScheme.compare(qUrl.scheme()) != 0)) { + (browserSettings()->matchUrlScheme() && entryScheme.compare(qUrl.scheme()) != 0)) { continue; } @@ -414,14 +414,14 @@ QList BrowserService::searchEntries(const QString& url, const StringPair { // Get the list of databases to search QList databases; - if (BrowserSettings::searchInAllDatabases()) { + if (browserSettings()->searchInAllDatabases()) { const int count = m_dbTabWidget->count(); for (int i = 0; i < count; ++i) { if (DatabaseWidget* dbWidget = qobject_cast(m_dbTabWidget->widget(i))) { if (Database* db = dbWidget->database()) { // Check if database is connected with KeePassXC-Browser for (const StringPair keyPair : keyList) { - Entry* entry = db->resolveEntry(KEEPASSXCBROWSER_UUID); + Entry* entry = db->resolveEntry(m_keepassBrowserUUID); if (entry) { QString key = entry->attributes()->value(QLatin1String(ASSOCIATE_KEY_PREFIX) + keyPair.first); if (!key.isEmpty() && keyPair.second == key) { @@ -564,18 +564,18 @@ QList BrowserService::sortEntries(QList& pwEntries, const QStrin } QList results; - QString field = BrowserSettings::sortByTitle() ? "Title" : "UserName"; + QString field = browserSettings()->sortByTitle() ? "Title" : "UserName"; for (int i = 100; i >= 0; i -= 5) { if (priorities.count(i) > 0) { // Sort same priority entries by Title or UserName auto entries = priorities.values(i); - std::sort(entries.begin(), entries.end(), [&priorities, &field](Entry* left, Entry* right) { + std::sort(entries.begin(), entries.end(), [&field](Entry* left, Entry* right) { return (QString::localeAwareCompare(left->attributes()->value(field), right->attributes()->value(field)) < 0) || ((QString::localeAwareCompare(left->attributes()->value(field), right->attributes()->value(field)) == 0) && (QString::localeAwareCompare(left->attributes()->value("UserName"), right->attributes()->value("UserName")) < 0)); }); results << entries; - if (BrowserSettings::bestMatchOnly() && !pwEntries.isEmpty()) { + if (browserSettings()->bestMatchOnly() && !pwEntries.isEmpty()) { // Early out once we find the highest batch of matches break; } @@ -642,7 +642,7 @@ QJsonObject BrowserService::prepareEntry(const Entry* entry) res["totp"] = entry->totp(); } - if (BrowserSettings::supportKphFields()) { + if (browserSettings()->supportKphFields()) { const EntryAttributes* attr = entry->attributes(); QJsonArray stringFields; for (const QString& key : attr->keys()) { diff --git a/src/browser/BrowserService.h b/src/browser/BrowserService.h index 4c6797d93..10c3a786e 100644 --- a/src/browser/BrowserService.h +++ b/src/browser/BrowserService.h @@ -107,6 +107,7 @@ private: DatabaseTabWidget* const m_dbTabWidget; bool m_dialogActive; bool m_bringToFrontRequested; + QUuid m_keepassBrowserUUID; }; #endif // BROWSERSERVICE_H diff --git a/src/browser/BrowserSettings.cpp b/src/browser/BrowserSettings.cpp index 76163b9d9..f7abdece9 100644 --- a/src/browser/BrowserSettings.cpp +++ b/src/browser/BrowserSettings.cpp @@ -20,9 +20,16 @@ #include "BrowserSettings.h" #include "core/Config.h" -PasswordGenerator BrowserSettings::m_passwordGenerator; -PassphraseGenerator BrowserSettings::m_passPhraseGenerator; -HostInstaller BrowserSettings::m_hostInstaller; +BrowserSettings* BrowserSettings::m_instance(nullptr); + +BrowserSettings* BrowserSettings::instance() +{ + if (!m_instance) { + m_instance = new BrowserSettings(); + } + + return m_instance; +} bool BrowserSettings::isEnabled() { @@ -185,7 +192,7 @@ bool BrowserSettings::chromeSupport() void BrowserSettings::setChromeSupport(bool enabled) { m_hostInstaller.installBrowser( - HostInstaller::SupportedBrowsers::CHROME, enabled, supportBrowserProxy(), customProxyLocation()); + HostInstaller::SupportedBrowsers::CHROME, enabled, supportBrowserProxy(), customProxyLocation()); } bool BrowserSettings::chromiumSupport() @@ -196,7 +203,7 @@ bool BrowserSettings::chromiumSupport() void BrowserSettings::setChromiumSupport(bool enabled) { m_hostInstaller.installBrowser( - HostInstaller::SupportedBrowsers::CHROMIUM, enabled, supportBrowserProxy(), customProxyLocation()); + HostInstaller::SupportedBrowsers::CHROMIUM, enabled, supportBrowserProxy(), customProxyLocation()); } bool BrowserSettings::firefoxSupport() @@ -207,7 +214,7 @@ bool BrowserSettings::firefoxSupport() void BrowserSettings::setFirefoxSupport(bool enabled) { m_hostInstaller.installBrowser( - HostInstaller::SupportedBrowsers::FIREFOX, enabled, supportBrowserProxy(), customProxyLocation()); + HostInstaller::SupportedBrowsers::FIREFOX, enabled, supportBrowserProxy(), customProxyLocation()); } bool BrowserSettings::vivaldiSupport() @@ -218,7 +225,7 @@ bool BrowserSettings::vivaldiSupport() void BrowserSettings::setVivaldiSupport(bool enabled) { m_hostInstaller.installBrowser( - HostInstaller::SupportedBrowsers::VIVALDI, enabled, supportBrowserProxy(), customProxyLocation()); + HostInstaller::SupportedBrowsers::VIVALDI, enabled, supportBrowserProxy(), customProxyLocation()); } bool BrowserSettings::passwordUseNumbers() diff --git a/src/browser/BrowserSettings.h b/src/browser/BrowserSettings.h index 3c79dbed2..3e84ba37d 100644 --- a/src/browser/BrowserSettings.h +++ b/src/browser/BrowserSettings.h @@ -27,95 +27,105 @@ class BrowserSettings { public: - static bool isEnabled(); - static void setEnabled(bool enabled); + explicit BrowserSettings() = default; + static BrowserSettings* instance(); - static bool showNotification(); // TODO!! - static void setShowNotification(bool showNotification); - static bool bestMatchOnly(); - static void setBestMatchOnly(bool bestMatchOnly); - static bool unlockDatabase(); - static void setUnlockDatabase(bool unlockDatabase); - static bool matchUrlScheme(); - static void setMatchUrlScheme(bool matchUrlScheme); - static bool sortByUsername(); - static void setSortByUsername(bool sortByUsername = true); - static bool sortByTitle(); - static void setSortByTitle(bool sortByUsertitle = true); - static bool alwaysAllowAccess(); - static void setAlwaysAllowAccess(bool alwaysAllowAccess); - static bool alwaysAllowUpdate(); - static void setAlwaysAllowUpdate(bool alwaysAllowUpdate); - static bool searchInAllDatabases(); - static void setSearchInAllDatabases(bool searchInAllDatabases); - static bool supportKphFields(); - static void setSupportKphFields(bool supportKphFields); + bool isEnabled(); + void setEnabled(bool enabled); - static bool supportBrowserProxy(); - static void setSupportBrowserProxy(bool enabled); - static bool useCustomProxy(); - static void setUseCustomProxy(bool enabled); - static QString customProxyLocation(); - static void setCustomProxyLocation(QString location); - static bool updateBinaryPath(); - static void setUpdateBinaryPath(bool enabled); - static bool chromeSupport(); - static void setChromeSupport(bool enabled); - static bool chromiumSupport(); - static void setChromiumSupport(bool enabled); - static bool firefoxSupport(); - static void setFirefoxSupport(bool enabled); - static bool vivaldiSupport(); - static void setVivaldiSupport(bool enabled); + bool showNotification(); // TODO!! + void setShowNotification(bool showNotification); + bool bestMatchOnly(); + void setBestMatchOnly(bool bestMatchOnly); + bool unlockDatabase(); + void setUnlockDatabase(bool unlockDatabase); + bool matchUrlScheme(); + void setMatchUrlScheme(bool matchUrlScheme); + bool sortByUsername(); + void setSortByUsername(bool sortByUsername = true); + bool sortByTitle(); + void setSortByTitle(bool sortByUsertitle = true); + bool alwaysAllowAccess(); + void setAlwaysAllowAccess(bool alwaysAllowAccess); + bool alwaysAllowUpdate(); + void setAlwaysAllowUpdate(bool alwaysAllowUpdate); + bool searchInAllDatabases(); + void setSearchInAllDatabases(bool searchInAllDatabases); + bool supportKphFields(); + void setSupportKphFields(bool supportKphFields); - static bool passwordUseNumbers(); - static void setPasswordUseNumbers(bool useNumbers); - static bool passwordUseLowercase(); - static void setPasswordUseLowercase(bool useLowercase); - static bool passwordUseUppercase(); - static void setPasswordUseUppercase(bool useUppercase); - static bool passwordUseSpecial(); - static void setPasswordUseSpecial(bool useSpecial); - static bool passwordUseBraces(); - static void setPasswordUseBraces(bool useBraces); - static bool passwordUsePunctuation(); - static void setPasswordUsePunctuation(bool usePunctuation); - static bool passwordUseQuotes(); - static void setPasswordUseQuotes(bool useQuotes); - static bool passwordUseDashes(); - static void setPasswordUseDashes(bool useDashes); - static bool passwordUseMath(); - static void setPasswordUseMath(bool useMath); - static bool passwordUseLogograms(); - static void setPasswordUseLogograms(bool useLogograms); - static bool passwordUseEASCII(); - static void setPasswordUseEASCII(bool useEASCII); - static bool advancedMode(); - static void setAdvancedMode(bool advancedMode); - static QString passwordExcludedChars(); - static void setPasswordExcludedChars(QString chars); - static int passPhraseWordCount(); - static void setPassPhraseWordCount(int wordCount); - static QString passPhraseWordSeparator(); - static void setPassPhraseWordSeparator(QString separator); - static int generatorType(); - static void setGeneratorType(int type); - static bool passwordEveryGroup(); - static void setPasswordEveryGroup(bool everyGroup); - static bool passwordExcludeAlike(); - static void setPasswordExcludeAlike(bool excludeAlike); - static int passwordLength(); - static void setPasswordLength(int length); - static PasswordGenerator::CharClasses passwordCharClasses(); - static PasswordGenerator::GeneratorFlags passwordGeneratorFlags(); - static QString generatePassword(); - static int getbits(); - static void updateBinaryPaths(QString customProxyLocation = QString()); + bool supportBrowserProxy(); + void setSupportBrowserProxy(bool enabled); + bool useCustomProxy(); + void setUseCustomProxy(bool enabled); + QString customProxyLocation(); + void setCustomProxyLocation(QString location); + bool updateBinaryPath(); + void setUpdateBinaryPath(bool enabled); + bool chromeSupport(); + void setChromeSupport(bool enabled); + bool chromiumSupport(); + void setChromiumSupport(bool enabled); + bool firefoxSupport(); + void setFirefoxSupport(bool enabled); + bool vivaldiSupport(); + void setVivaldiSupport(bool enabled); + + bool passwordUseNumbers(); + void setPasswordUseNumbers(bool useNumbers); + bool passwordUseLowercase(); + void setPasswordUseLowercase(bool useLowercase); + bool passwordUseUppercase(); + void setPasswordUseUppercase(bool useUppercase); + bool passwordUseSpecial(); + void setPasswordUseSpecial(bool useSpecial); + bool passwordUseBraces(); + void setPasswordUseBraces(bool useBraces); + bool passwordUsePunctuation(); + void setPasswordUsePunctuation(bool usePunctuation); + bool passwordUseQuotes(); + void setPasswordUseQuotes(bool useQuotes); + bool passwordUseDashes(); + void setPasswordUseDashes(bool useDashes); + bool passwordUseMath(); + void setPasswordUseMath(bool useMath); + bool passwordUseLogograms(); + void setPasswordUseLogograms(bool useLogograms); + bool passwordUseEASCII(); + void setPasswordUseEASCII(bool useEASCII); + bool advancedMode(); + void setAdvancedMode(bool advancedMode); + QString passwordExcludedChars(); + void setPasswordExcludedChars(QString chars); + int passPhraseWordCount(); + void setPassPhraseWordCount(int wordCount); + QString passPhraseWordSeparator(); + void setPassPhraseWordSeparator(QString separator); + int generatorType(); + void setGeneratorType(int type); + bool passwordEveryGroup(); + void setPasswordEveryGroup(bool everyGroup); + bool passwordExcludeAlike(); + void setPasswordExcludeAlike(bool excludeAlike); + int passwordLength(); + void setPasswordLength(int length); + PasswordGenerator::CharClasses passwordCharClasses(); + PasswordGenerator::GeneratorFlags passwordGeneratorFlags(); + QString generatePassword(); + int getbits(); + void updateBinaryPaths(QString customProxyLocation = QString()); private: - static PasswordGenerator m_passwordGenerator; - static PassphraseGenerator m_passPhraseGenerator; - static HostInstaller m_hostInstaller; + static BrowserSettings* m_instance; + + PasswordGenerator m_passwordGenerator; + PassphraseGenerator m_passPhraseGenerator; + HostInstaller m_hostInstaller; }; +inline BrowserSettings* browserSettings() +{ + return BrowserSettings::instance(); +} + #endif // BROWSERSETTINGS_H diff --git a/src/browser/HostInstaller.cpp b/src/browser/HostInstaller.cpp index 95d202a22..99d09f4f7 100644 --- a/src/browser/HostInstaller.cpp +++ b/src/browser/HostInstaller.cpp @@ -18,6 +18,7 @@ #include "HostInstaller.h" #include "config-keepassx.h" + #include #include #include @@ -27,35 +28,27 @@ #include #include -const QString HostInstaller::HOST_NAME = "org.keepassxc.keepassxc_browser"; -const QStringList HostInstaller::ALLOWED_ORIGINS = QStringList() - << "chrome-extension://iopaggbpplllidnfmcghoonnokmjoicf/" - << "chrome-extension://oboonakemofpalcgghocfoadofidjkkk/"; - -const QStringList HostInstaller::ALLOWED_EXTENSIONS = QStringList() << "keepassxc-browser@keepassxc.org"; - -#if defined(Q_OS_OSX) -const QString HostInstaller::TARGET_DIR_CHROME = "/Library/Application Support/Google/Chrome/NativeMessagingHosts"; -const QString HostInstaller::TARGET_DIR_CHROMIUM = "/Library/Application Support/Chromium/NativeMessagingHosts"; -const QString HostInstaller::TARGET_DIR_FIREFOX = "/Library/Application Support/Mozilla/NativeMessagingHosts"; -const QString HostInstaller::TARGET_DIR_VIVALDI = "/Library/Application Support/Vivaldi/NativeMessagingHosts"; -#elif defined(Q_OS_LINUX) -const QString HostInstaller::TARGET_DIR_CHROME = "/.config/google-chrome/NativeMessagingHosts"; -const QString HostInstaller::TARGET_DIR_CHROMIUM = "/.config/chromium/NativeMessagingHosts"; -const QString HostInstaller::TARGET_DIR_FIREFOX = "/.mozilla/native-messaging-hosts"; -const QString HostInstaller::TARGET_DIR_VIVALDI = "/.config/vivaldi/NativeMessagingHosts"; -#elif defined(Q_OS_WIN) -const QString HostInstaller::TARGET_DIR_CHROME = - "HKEY_CURRENT_USER\\Software\\Google\\Chrome\\NativeMessagingHosts\\" + HostInstaller::HOST_NAME; -const QString HostInstaller::TARGET_DIR_CHROMIUM = - "HKEY_CURRENT_USER\\Software\\Chromium\\NativeMessagingHosts\\" + HostInstaller::HOST_NAME; -const QString HostInstaller::TARGET_DIR_FIREFOX = - "HKEY_CURRENT_USER\\Software\\Mozilla\\NativeMessagingHosts\\" + HostInstaller::HOST_NAME; -const QString HostInstaller::TARGET_DIR_VIVALDI = - "HKEY_CURRENT_USER\\Software\\Vivaldi\\NativeMessagingHosts\\" + HostInstaller::HOST_NAME; -#endif - HostInstaller::HostInstaller() + : HOST_NAME("org.keepassxc.keepassxc_browser") + , ALLOWED_EXTENSIONS(QStringList() << "keepassxc-browser@keepassxc.org") + , ALLOWED_ORIGINS(QStringList() << "chrome-extension://iopaggbpplllidnfmcghoonnokmjoicf/" + << "chrome-extension://oboonakemofpalcgghocfoadofidjkkk/") +#if defined(Q_OS_OSX) + , TARGET_DIR_CHROME("/Library/Application Support/Google/Chrome/NativeMessagingHosts") + , TARGET_DIR_CHROMIUM("/Library/Application Support/Chromium/NativeMessagingHosts") + , TARGET_DIR_FIREFOX("/Library/Application Support/Mozilla/NativeMessagingHosts") + , TARGET_DIR_VIVALDI("/Library/Application Support/Vivaldi/NativeMessagingHosts") +#elif defined(Q_OS_LINUX) + , TARGET_DIR_CHROME("/.config/google-chrome/NativeMessagingHosts") + , TARGET_DIR_CHROMIUM("/.config/chromium/NativeMessagingHosts") + , TARGET_DIR_FIREFOX("/.mozilla/native-messaging-hosts") + , TARGET_DIR_VIVALDI("/.config/vivaldi/NativeMessagingHosts") +#elif defined(Q_OS_WIN) + , TARGET_DIR_CHROME("HKEY_CURRENT_USER\\Software\\Google\\Chrome\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser") + , TARGET_DIR_CHROMIUM("HKEY_CURRENT_USER\\Software\\Chromium\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser") + , TARGET_DIR_FIREFOX("HKEY_CURRENT_USER\\Software\\Mozilla\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser") + , TARGET_DIR_VIVALDI("HKEY_CURRENT_USER\\Software\\Vivaldi\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser") +#endif { } @@ -118,13 +111,13 @@ QString HostInstaller::getTargetPath(SupportedBrowsers browser) const { switch (browser) { case SupportedBrowsers::CHROME: - return HostInstaller::TARGET_DIR_CHROME; + return TARGET_DIR_CHROME; case SupportedBrowsers::CHROMIUM: - return HostInstaller::TARGET_DIR_CHROMIUM; + return TARGET_DIR_CHROMIUM; case SupportedBrowsers::FIREFOX: - return HostInstaller::TARGET_DIR_FIREFOX; + return TARGET_DIR_FIREFOX; case SupportedBrowsers::VIVALDI: - return HostInstaller::TARGET_DIR_VIVALDI; + return TARGET_DIR_VIVALDI; default: return QString(); } @@ -158,12 +151,12 @@ QString HostInstaller::getPath(SupportedBrowsers browser) const userPath = QDir::fromNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); } - QString winPath = QString("%1/%2_%3.json").arg(userPath, HostInstaller::HOST_NAME, getBrowserName(browser)); + QString winPath = QString("%1/%2_%3.json").arg(userPath, HOST_NAME, getBrowserName(browser)); winPath.replace("/", "\\"); return winPath; #else QString path = getTargetPath(browser); - return QString("%1%2/%3.json").arg(QDir::homePath(), path, HostInstaller::HOST_NAME); + return QString("%1%2/%3.json").arg(QDir::homePath(), path, HOST_NAME); #endif } @@ -207,19 +200,19 @@ QJsonObject HostInstaller::constructFile(SupportedBrowsers browser, const bool& #endif // #ifdef KEEPASSXC_DIST_APPIMAGE QJsonObject script; - script["name"] = HostInstaller::HOST_NAME; + script["name"] = HOST_NAME; script["description"] = "KeePassXC integration with native messaging support"; script["path"] = path; script["type"] = "stdio"; QJsonArray arr; if (browser == SupportedBrowsers::FIREFOX) { - for (const QString extension : HostInstaller::ALLOWED_EXTENSIONS) { + for (const QString& extension : ALLOWED_EXTENSIONS) { arr.append(extension); } script["allowed_extensions"] = arr; } else { - for (const QString origin : HostInstaller::ALLOWED_ORIGINS) { + for (const QString& origin : ALLOWED_ORIGINS) { arr.append(origin); } script["allowed_origins"] = arr; @@ -248,10 +241,5 @@ bool HostInstaller::saveFile(SupportedBrowsers browser, const QJsonObject& scrip } QJsonDocument doc(script); - qint64 bytesWritten = scriptFile.write(doc.toJson()); - if (bytesWritten < 0) { - return false; - } - - return true; + return scriptFile.write(doc.toJson()) >= 0; } diff --git a/src/browser/HostInstaller.h b/src/browser/HostInstaller.h index 2fac0609d..3b985c300 100644 --- a/src/browser/HostInstaller.h +++ b/src/browser/HostInstaller.h @@ -55,13 +55,13 @@ private: bool saveFile(SupportedBrowsers browser, const QJsonObject& script); private: - static const QString HOST_NAME; - static const QStringList ALLOWED_EXTENSIONS; - static const QStringList ALLOWED_ORIGINS; - static const QString TARGET_DIR_CHROME; - static const QString TARGET_DIR_CHROMIUM; - static const QString TARGET_DIR_FIREFOX; - static const QString TARGET_DIR_VIVALDI; + const QString HOST_NAME; + const QStringList ALLOWED_EXTENSIONS; + const QStringList ALLOWED_ORIGINS; + const QString TARGET_DIR_CHROME; + const QString TARGET_DIR_CHROMIUM; + const QString TARGET_DIR_FIREFOX; + const QString TARGET_DIR_VIVALDI; }; #endif // HOSTINSTALLER_H diff --git a/src/browser/NativeMessagingHost.cpp b/src/browser/NativeMessagingHost.cpp index 7483b1ca1..fc35bbec5 100644 --- a/src/browser/NativeMessagingHost.cpp +++ b/src/browser/NativeMessagingHost.cpp @@ -30,14 +30,14 @@ NativeMessagingHost::NativeMessagingHost(DatabaseTabWidget* parent, const bool enabled) : NativeMessagingBase(enabled) , m_mutex(QMutex::Recursive) - , m_browserClients(m_browserService) , m_browserService(parent) + , m_browserClients(m_browserService) { m_localServer.reset(new QLocalServer(this)); m_localServer->setSocketOptions(QLocalServer::UserAccessOption); m_running.store(false); - if (BrowserSettings::isEnabled() && !m_running) { + if (browserSettings()->isEnabled() && !m_running) { run(); } @@ -64,8 +64,8 @@ void NativeMessagingHost::run() } // Update KeePassXC/keepassxc-proxy binary paths to Native Messaging scripts - if (BrowserSettings::updateBinaryPath()) { - BrowserSettings::updateBinaryPaths(BrowserSettings::useCustomProxy() ? BrowserSettings::customProxyLocation() + if (browserSettings()->updateBinaryPath()) { + browserSettings()->updateBinaryPaths(browserSettings()->useCustomProxy() ? browserSettings()->customProxyLocation() : ""); } @@ -75,7 +75,7 @@ void NativeMessagingHost::run() QtConcurrent::run(this, static_cast(&NativeMessagingHost::readNativeMessages)); #endif - if (BrowserSettings::supportBrowserProxy()) { + if (browserSettings()->supportBrowserProxy()) { QString serverPath = getLocalServerPath(); QFile::remove(serverPath); diff --git a/src/browser/NativeMessagingHost.h b/src/browser/NativeMessagingHost.h index d94284abf..fde8f051c 100644 --- a/src/browser/NativeMessagingHost.h +++ b/src/browser/NativeMessagingHost.h @@ -58,8 +58,8 @@ private slots: private: QMutex m_mutex; - BrowserClients m_browserClients; BrowserService m_browserService; + BrowserClients m_browserClients; QSharedPointer m_localServer; SocketList m_socketList; }; diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index ec5bfa5a9..d5342cbc0 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -66,7 +66,7 @@ class BrowserPlugin : public ISettingsPage public: BrowserPlugin(DatabaseTabWidget* tabWidget) { - m_nativeMessagingHost = QSharedPointer(new NativeMessagingHost(tabWidget, BrowserSettings::isEnabled())); + m_nativeMessagingHost = QSharedPointer(new NativeMessagingHost(tabWidget, browserSettings()->isEnabled())); } ~BrowserPlugin() @@ -103,7 +103,7 @@ public: void saveSettings(QWidget* widget) override { qobject_cast(widget)->saveSettings(); - if (BrowserSettings::isEnabled()) { + if (browserSettings()->isEnabled()) { m_nativeMessagingHost->run(); } else { m_nativeMessagingHost->stop(); From b74fb3e208f470978d013594c91771bc54e8e4fa Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sat, 8 Sep 2018 17:05:37 -0400 Subject: [PATCH 2/4] Convert private static vars into member vars * CSV Import and Entry Model --- src/gui/csvImport/CsvImportWidget.cpp | 14 +++++--------- src/gui/csvImport/CsvImportWidget.h | 2 +- src/gui/entry/EntryModel.cpp | 8 ++------ src/gui/entry/EntryModel.h | 4 ++-- 4 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/gui/csvImport/CsvImportWidget.cpp b/src/gui/csvImport/CsvImportWidget.cpp index 2997b4c9f..45e0da247 100644 --- a/src/gui/csvImport/CsvImportWidget.cpp +++ b/src/gui/csvImport/CsvImportWidget.cpp @@ -28,16 +28,8 @@ #include "gui/MessageWidget.h" // I wanted to make the CSV import GUI future-proof, so if one day you need a new field, -// all you have to do is uncomment a row or two here, and the GUI will follow: +// all you have to do is add a field to m_columnHeader, and the GUI will follow: // dynamic generation of comboBoxes, labels, placement and so on. Try it for immense fun! -const QStringList CsvImportWidget::m_columnHeader = - QStringList() << QObject::tr("Group") << QObject::tr("Title") << QObject::tr("Username") << QObject::tr("Password") - << QObject::tr("URL") << QObject::tr("Notes") << QObject::tr("Last Modified") - << QObject::tr("Created") - // << QObject::tr("Future field1") - // << QObject::tr("Future field2") - // << QObject::tr("Future field3") - ; CsvImportWidget::CsvImportWidget(QWidget* parent) : QWidget(parent) @@ -45,6 +37,10 @@ CsvImportWidget::CsvImportWidget(QWidget* parent) , m_parserModel(new CsvParserModel(this)) , m_comboModel(new QStringListModel(this)) , m_comboMapper(new QSignalMapper(this)) + , m_columnHeader(QStringList() << QObject::tr("Group") << QObject::tr("Title") << QObject::tr("Username") + << QObject::tr("Password") << QObject::tr("URL") << QObject::tr("Notes") + << QObject::tr("Last Modified") << QObject::tr("Created") + /* << QObject::tr("Future field1") */ ) { m_ui->setupUi(this); diff --git a/src/gui/csvImport/CsvImportWidget.h b/src/gui/csvImport/CsvImportWidget.h index 721a8988f..2a68c95e9 100644 --- a/src/gui/csvImport/CsvImportWidget.h +++ b/src/gui/csvImport/CsvImportWidget.h @@ -66,7 +66,7 @@ private: QList m_combos; Database* m_db; - static const QStringList m_columnHeader; + const QStringList m_columnHeader; QStringList m_fieldSeparatorList; void configParser(); void updateTableview(); diff --git a/src/gui/entry/EntryModel.cpp b/src/gui/entry/EntryModel.cpp index aa61a4068..d3859e498 100644 --- a/src/gui/entry/EntryModel.cpp +++ b/src/gui/entry/EntryModel.cpp @@ -30,17 +30,13 @@ #include "core/Group.h" #include "core/Metadata.h" -// String being displayed when hiding content -const QString EntryModel::HiddenContentDisplay(QString("\u25cf").repeated(6)); - -// Format used to display dates -const Qt::DateFormat EntryModel::DateFormat = Qt::DefaultLocaleShortDate; - EntryModel::EntryModel(QObject* parent) : QAbstractTableModel(parent) , m_group(nullptr) , m_hideUsernames(false) , m_hidePasswords(true) + , HiddenContentDisplay(QString("\u25cf").repeated(6)) + , DateFormat(Qt::DefaultLocaleShortDate) { } diff --git a/src/gui/entry/EntryModel.h b/src/gui/entry/EntryModel.h index 4fc765044..1965066a1 100644 --- a/src/gui/entry/EntryModel.h +++ b/src/gui/entry/EntryModel.h @@ -100,8 +100,8 @@ private: QPixmap m_paperClipPixmap; - static const QString HiddenContentDisplay; - static const Qt::DateFormat DateFormat; + const QString HiddenContentDisplay; + const Qt::DateFormat DateFormat; }; #endif // KEEPASSX_ENTRYMODEL_H From 1dc9f10c7f0b95dbd8eb37865dec89fcc4a52931 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Wed, 5 Sep 2018 16:20:57 -0400 Subject: [PATCH 3/4] Complete refactor of TOTP integration * Eliminate TOTP logic from GUI elements * Consolidate TOTP functionality under the Totp namespace * Eliminate guessing about state and encoders * Increased test cases * Add entry view column for TOTP [#2132] * General code cleanup, reduction of unnecessary steps, separation of concerns * Rename SetupTotpDialog to TotpSetupDialog for consistency --- src/CMakeLists.txt | 2 +- src/core/Entry.cpp | 92 ++----- src/core/Entry.h | 12 +- src/gui/DatabaseWidget.cpp | 20 +- src/gui/DetailsWidget.cpp | 31 +-- src/gui/DetailsWidget.h | 7 +- src/gui/SetupTotpDialog.cpp | 126 ---------- src/gui/SetupTotpDialog.ui | 167 ------------- src/gui/TotpDialog.cpp | 42 ++-- src/gui/TotpDialog.h | 19 +- src/gui/TotpSetupDialog.cpp | 83 +++++++ .../{SetupTotpDialog.h => TotpSetupDialog.h} | 27 +-- src/gui/TotpSetupDialog.ui | 182 ++++++++++++++ src/gui/entry/EntryModel.cpp | 24 +- src/gui/entry/EntryModel.h | 3 +- src/totp/totp.cpp | 227 +++++++++--------- src/totp/totp.h | 71 +++--- tests/TestEntryModel.cpp | 8 +- tests/TestTotp.cpp | 151 +++++------- tests/TestTotp.h | 3 - tests/gui/TestGui.cpp | 4 +- 21 files changed, 585 insertions(+), 716 deletions(-) delete mode 100644 src/gui/SetupTotpDialog.cpp delete mode 100644 src/gui/SetupTotpDialog.ui create mode 100644 src/gui/TotpSetupDialog.cpp rename src/gui/{SetupTotpDialog.h => TotpSetupDialog.h} (68%) create mode 100644 src/gui/TotpSetupDialog.ui diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f1389c55d..ea09d43b9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -128,7 +128,7 @@ set(keepassx_SOURCES gui/SettingsWidget.cpp gui/SearchWidget.cpp gui/SortFilterHideProxyModel.cpp - gui/SetupTotpDialog.cpp + gui/TotpSetupDialog.cpp gui/TotpDialog.cpp gui/UnlockDatabaseWidget.cpp gui/UnlockDatabaseDialog.cpp diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index 598ea8497..4233d89e3 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -45,8 +45,6 @@ Entry::Entry() m_data.iconNumber = DefaultIconNumber; m_data.autoTypeEnabled = true; m_data.autoTypeObfuscation = 0; - m_data.totpStep = Totp::defaultStep; - m_data.totpDigits = Totp::defaultDigits; connect(m_attributes, SIGNAL(modified()), SLOT(updateTotp())); connect(m_attributes, SIGNAL(modified()), this, SIGNAL(modified())); @@ -347,74 +345,45 @@ const CustomData* Entry::customData() const bool Entry::hasTotp() const { - return m_attributes->hasKey("TOTP Seed") || m_attributes->hasKey("otp"); + return !m_data.totpSettings.isNull(); } QString Entry::totp() const { if (hasTotp()) { - QString seed = totpSeed(); - quint64 time = QDateTime::currentDateTime().toTime_t(); - QString output = Totp::generateTotp(seed.toLatin1(), time, m_data.totpDigits, m_data.totpStep); - - return QString(output); + return Totp::generateTotp(m_data.totpSettings); } return {}; } -void Entry::setTotp(const QString& seed, quint8& step, quint8& digits) +void Entry::setTotp(QSharedPointer settings) { beginUpdate(); - if (step == 0) { - step = Totp::defaultStep; - } + m_data.totpSettings = settings; - if (digits == 0) { - digits = Totp::defaultDigits; - } - QString data; - - const Totp::Encoder& enc = Totp::encoders.value(digits, Totp::defaultEncoder); - - if (m_attributes->hasKey("otp")) { - data = QString("key=%1&step=%2&size=%3").arg(seed).arg(step).arg(enc.digits == 0 ? digits : enc.digits); - if (!enc.name.isEmpty()) { - data.append("&enocder=").append(enc.name); - } - m_attributes->set("otp", data, true); + auto text = Totp::writeSettings(m_data.totpSettings); + if (m_attributes->hasKey(Totp::ATTRIBUTE_OTP)) { + m_attributes->set(Totp::ATTRIBUTE_OTP, text, true); } else { - m_attributes->set("TOTP Seed", seed, true); - if (!enc.shortName.isEmpty()) { - data = QString("%1;%2").arg(step).arg(enc.shortName); - } else { - data = QString("%1;%2").arg(step).arg(digits); - } - m_attributes->set("TOTP Settings", data); + m_attributes->set(Totp::ATTRIBUTE_SEED, m_data.totpSettings->key, true); + m_attributes->set(Totp::ATTRIBUTE_SETTINGS, text); } endUpdate(); } -QString Entry::totpSeed() const +void Entry::updateTotp() { - QString secret = ""; - - if (m_attributes->hasKey("otp")) { - secret = m_attributes->value("otp"); - } else if (m_attributes->hasKey("TOTP Seed")) { - secret = m_attributes->value("TOTP Seed"); + if (m_attributes->contains(Totp::ATTRIBUTE_SETTINGS)) { + m_data.totpSettings = Totp::parseSettings(m_attributes->value(Totp::ATTRIBUTE_SETTINGS), + m_attributes->value(Totp::ATTRIBUTE_SEED)); + } else if (m_attributes->contains(Totp::ATTRIBUTE_OTP)) { + m_data.totpSettings = Totp::parseSettings(m_attributes->value(Totp::ATTRIBUTE_OTP)); } - - return Totp::parseOtpString(secret, m_data.totpDigits, m_data.totpStep); } -quint8 Entry::totpStep() const +QSharedPointer Entry::totpSettings() const { - return m_data.totpStep; -} - -quint8 Entry::totpDigits() const -{ - return m_data.totpDigits; + return m_data.totpSettings; } void Entry::setUuid(const QUuid& uuid) @@ -725,33 +694,6 @@ void Entry::updateModifiedSinceBegin() m_modifiedSinceBegin = true; } -/** - * Update TOTP data whenever entry attributes have changed. - */ -void Entry::updateTotp() -{ - m_data.totpDigits = Totp::defaultDigits; - m_data.totpStep = Totp::defaultStep; - - if (!m_attributes->hasKey("TOTP Settings")) { - return; - } - - // this regex must be kept in sync with the set of allowed short names Totp::shortNameToEncoder - QRegularExpression rx(QString("(\\d+);((?:\\d+)|S)")); - QRegularExpressionMatch m = rx.match(m_attributes->value("TOTP Settings")); - if (!m.hasMatch()) { - return; - } - - m_data.totpStep = static_cast(m.captured(1).toUInt()); - if (Totp::shortNameToEncoder.contains(m.captured(2))) { - m_data.totpDigits = Totp::shortNameToEncoder[m.captured(2)]; - } else { - m_data.totpDigits = static_cast(m.captured(2).toUInt()); - } -} - QString Entry::resolveMultiplePlaceholdersRecursive(const QString& str, int maxDepth) const { if (maxDepth <= 0) { diff --git a/src/core/Entry.h b/src/core/Entry.h index de6a4b398..aa2426c5e 100644 --- a/src/core/Entry.h +++ b/src/core/Entry.h @@ -36,6 +36,9 @@ class Database; class Group; +namespace Totp { + struct Settings; +} enum class EntryReferenceType { @@ -61,8 +64,7 @@ struct EntryData int autoTypeObfuscation; QString defaultAutoTypeSequence; TimeInfo timeInfo; - mutable quint8 totpDigits; - mutable quint8 totpStep; + QSharedPointer totpSettings; }; class Entry : public QObject @@ -98,9 +100,7 @@ public: QString password() const; QString notes() const; QString totp() const; - QString totpSeed() const; - quint8 totpDigits() const; - quint8 totpStep() const; + QSharedPointer totpSettings() const; bool hasTotp() const; bool isExpired() const; @@ -135,7 +135,7 @@ public: void setNotes(const QString& notes); void setExpires(const bool& value); void setExpiryTime(const QDateTime& dateTime); - void setTotp(const QString& seed, quint8& step, quint8& digits); + void setTotp(QSharedPointer settings); QList historyItems(); const QList& historyItems() const; diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index fde356fd5..c96c9000c 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -47,7 +47,7 @@ #include "gui/DetailsWidget.h" #include "gui/KeePass1OpenWidget.h" #include "gui/MessageBox.h" -#include "gui/SetupTotpDialog.h" +#include "gui/TotpSetupDialog.h" #include "gui/TotpDialog.h" #include "gui/UnlockDatabaseDialog.h" #include "gui/UnlockDatabaseWidget.h" @@ -444,15 +444,8 @@ void DatabaseWidget::setupTotp() return; } - auto setupTotpDialog = new SetupTotpDialog(this, currentEntry); - if (currentEntry->hasTotp()) { - setupTotpDialog->setSeed(currentEntry->totpSeed()); - setupTotpDialog->setStep(currentEntry->totpStep()); - setupTotpDialog->setDigits(currentEntry->totpDigits()); - // now that all settings are set, decide whether it's default, steam or custom - setupTotpDialog->setSettings(currentEntry->totpDigits()); - } - + auto setupTotpDialog = new TotpSetupDialog(this, currentEntry); + connect(setupTotpDialog, SIGNAL(totpUpdated()), SIGNAL(entrySelectionChanged())); setupTotpDialog->open(); } @@ -938,6 +931,13 @@ void DatabaseWidget::entryActivationSignalReceived(Entry* entry, EntryModel::Mod openUrlForEntry(entry); } break; + case EntryModel::Totp: + if (entry->hasTotp()) { + setClipboardTextAndMinimize(entry->totp()); + } else { + setupTotp(); + } + break; // TODO: switch to 'Notes' tab in details view/pane // case EntryModel::Notes: // break; diff --git a/src/gui/DetailsWidget.cpp b/src/gui/DetailsWidget.cpp index 70286b9d7..ff8861172 100644 --- a/src/gui/DetailsWidget.cpp +++ b/src/gui/DetailsWidget.cpp @@ -39,8 +39,6 @@ DetailsWidget::DetailsWidget(QWidget* parent) , m_locked(false) , m_currentEntry(nullptr) , m_currentGroup(nullptr) - , m_step(0) - , m_totpTimer(nullptr) , m_selectedTabEntry(0) , m_selectedTabGroup(0) { @@ -56,6 +54,7 @@ DetailsWidget::DetailsWidget(QWidget* parent) connect(m_ui->entryTotpButton, SIGNAL(toggled(bool)), m_ui->entryTotpWidget, SLOT(setVisible(bool))); connect(m_ui->entryCloseButton, SIGNAL(toggled(bool)), SLOT(hide())); connect(m_ui->entryTabWidget, SIGNAL(tabBarClicked(int)), SLOT(updateTabIndexes()), Qt::QueuedConnection); + connect(&m_totpTimer, SIGNAL(timeout()), this, SLOT(updateTotpLabel())); // Group m_ui->groupCloseButton->setIcon(filePath()->icon("actions", "dialog-close")); @@ -65,7 +64,6 @@ DetailsWidget::DetailsWidget(QWidget* parent) DetailsWidget::~DetailsWidget() { - deleteTotpTimer(); } void DetailsWidget::setEntry(Entry* selectedEntry) @@ -146,16 +144,11 @@ void DetailsWidget::updateEntryTotp() m_ui->entryTotpButton->setChecked(false); if (hasTotp) { - deleteTotpTimer(); - m_totpTimer = new QTimer(m_currentEntry); - connect(m_totpTimer, SIGNAL(timeout()), this, SLOT(updateTotpLabel())); - m_totpTimer->start(1000); - - m_step = m_currentEntry->totpStep(); + m_totpTimer.start(1000); updateTotpLabel(); } else { m_ui->entryTotpLabel->clear(); - stopTotpTimer(); + m_totpTimer.stop(); } } @@ -274,30 +267,16 @@ void DetailsWidget::updateGroupNotesTab() m_ui->groupNotesEdit->setText(notes); } -void DetailsWidget::stopTotpTimer() -{ - if (m_totpTimer) { - m_totpTimer->stop(); - } -} - -void DetailsWidget::deleteTotpTimer() -{ - if (m_totpTimer) { - delete m_totpTimer; - } -} - void DetailsWidget::updateTotpLabel() { - if (!m_locked && m_currentEntry) { + if (!m_locked && m_currentEntry && m_currentEntry->hasTotp()) { const QString totpCode = m_currentEntry->totp(); const QString firstHalf = totpCode.left(totpCode.size() / 2); const QString secondHalf = totpCode.mid(totpCode.size() / 2); m_ui->entryTotpLabel->setText(firstHalf + " " + secondHalf); } else { m_ui->entryTotpLabel->clear(); - stopTotpTimer(); + m_totpTimer.stop(); } } diff --git a/src/gui/DetailsWidget.h b/src/gui/DetailsWidget.h index 3a4c277d9..ba42e5278 100644 --- a/src/gui/DetailsWidget.h +++ b/src/gui/DetailsWidget.h @@ -33,7 +33,7 @@ class DetailsWidget : public QWidget public: explicit DetailsWidget(QWidget* parent = nullptr); - ~DetailsWidget(); + ~DetailsWidget() override; public slots: void setEntry(Entry* selectedEntry); @@ -56,8 +56,6 @@ private slots: void updateGroupGeneralTab(); void updateGroupNotesTab(); - void stopTotpTimer(); - void deleteTotpTimer(); void updateTotpLabel(); void updateTabIndexes(); @@ -71,8 +69,7 @@ private: bool m_locked; Entry* m_currentEntry; Group* m_currentGroup; - quint8 m_step; - QPointer m_totpTimer; + QTimer m_totpTimer; quint8 m_selectedTabEntry; quint8 m_selectedTabGroup; }; diff --git a/src/gui/SetupTotpDialog.cpp b/src/gui/SetupTotpDialog.cpp deleted file mode 100644 index ef7ee9e7c..000000000 --- a/src/gui/SetupTotpDialog.cpp +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com> - * Copyright (C) 2017 KeePassXC Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 or (at your option) - * version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "SetupTotpDialog.h" -#include "totp/totp.h" -#include "ui_SetupTotpDialog.h" - -SetupTotpDialog::SetupTotpDialog(DatabaseWidget* parent, Entry* entry) - : QDialog(parent) - , m_ui(new Ui::SetupTotpDialog()) -{ - m_entry = entry; - m_parent = parent; - - m_ui->setupUi(this); - setAttribute(Qt::WA_DeleteOnClose); - - this->setFixedSize(this->sizeHint()); - - connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(close())); - connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(setupTotp())); - connect(m_ui->radioDefault, SIGNAL(toggled(bool)), SLOT(toggleDefault(bool))); - connect(m_ui->radioSteam, SIGNAL(toggled(bool)), SLOT(toggleSteam(bool))); - connect(m_ui->radioCustom, SIGNAL(toggled(bool)), SLOT(toggleCustom(bool))); -} - -void SetupTotpDialog::setupTotp() -{ - quint8 digits; - - if (m_ui->radioSteam->isChecked()) { - digits = Totp::ENCODER_STEAM; - } else if (m_ui->radio8Digits->isChecked()) { - digits = 8; - } else { - digits = 6; - } - - quint8 step = m_ui->stepSpinBox->value(); - QString seed = Totp::parseOtpString(m_ui->seedEdit->text(), digits, step); - m_entry->setTotp(seed, step, digits); - emit m_parent->entrySelectionChanged(); - close(); -} - -void SetupTotpDialog::toggleDefault(bool status) -{ - if (status) { - setStep(Totp::defaultStep); - setDigits(Totp::defaultDigits); - } -} - -void SetupTotpDialog::toggleSteam(bool status) -{ - if (status) { - setStep(Totp::defaultStep); - setDigits(Totp::ENCODER_STEAM); - } -} - -void SetupTotpDialog::toggleCustom(bool status) -{ - m_ui->digitsLabel->setEnabled(status); - m_ui->radio6Digits->setEnabled(status); - m_ui->radio8Digits->setEnabled(status); - - m_ui->stepLabel->setEnabled(status); - m_ui->stepSpinBox->setEnabled(status); -} - -void SetupTotpDialog::setSeed(QString value) -{ - m_ui->seedEdit->setText(value); -} - -void SetupTotpDialog::setSettings(quint8 digits) -{ - quint8 step = m_ui->stepSpinBox->value(); - - bool isDefault = ((step == Totp::defaultStep) && (digits == Totp::defaultDigits)); - bool isSteam = (digits == Totp::ENCODER_STEAM); - - if (isSteam) { - m_ui->radioSteam->setChecked(true); - } else if (isDefault) { - m_ui->radioDefault->setChecked(true); - } else { - m_ui->radioCustom->setChecked(true); - } -} - -void SetupTotpDialog::setStep(quint8 step) -{ - m_ui->stepSpinBox->setValue(step); -} - -void SetupTotpDialog::setDigits(quint8 digits) -{ - if (digits == 8) { - m_ui->radio8Digits->setChecked(true); - m_ui->radio6Digits->setChecked(false); - } else { - m_ui->radio6Digits->setChecked(true); - m_ui->radio8Digits->setChecked(false); - } -} - -SetupTotpDialog::~SetupTotpDialog() -{ -} diff --git a/src/gui/SetupTotpDialog.ui b/src/gui/SetupTotpDialog.ui deleted file mode 100644 index 327ca6d0a..000000000 --- a/src/gui/SetupTotpDialog.ui +++ /dev/null @@ -1,167 +0,0 @@ - - - SetupTotpDialog - - - - 0 - 0 - 282 - 364 - - - - Setup TOTP - - - - - - - - Key: - - - - - - - - - - - - - - Default RFC 6238 token settings - - - settingsButtonGroup - - - - - - - Steam token settings - - - settingsButtonGroup - - - - - - - Use custom settings - - - settingsButtonGroup - - - - - - - - - Note: Change these settings only if you know what you are doing. - - - true - - - - - - - QFormLayout::ExpandingFieldsGrow - - - QFormLayout::DontWrapRows - - - Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing - - - - - false - - - Time step: - - - - - - - false - - - 8 digits - - - - - - - false - - - 6 digits - - - true - - - - - - - false - - - Code size: - - - - - - - false - - - sec - - - 1 - - - 60 - - - 30 - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - - - diff --git a/src/gui/TotpDialog.cpp b/src/gui/TotpDialog.cpp index 75cf6a482..c2de9adbd 100644 --- a/src/gui/TotpDialog.cpp +++ b/src/gui/TotpDialog.cpp @@ -22,21 +22,25 @@ #include "core/Config.h" #include "gui/Clipboard.h" -TotpDialog::TotpDialog(DatabaseWidget* parent, Entry* entry) +TotpDialog::TotpDialog(QWidget* parent, Entry* entry) : QDialog(parent) , m_ui(new Ui::TotpDialog()) - , m_totpUpdateTimer(new QTimer(entry)) , m_entry(entry) { + if (!m_entry->hasTotp()) { + close(); + return; + } + m_ui->setupUi(this); - m_step = m_entry->totpStep(); - uCounter = resetCounter(); + m_step = m_entry->totpSettings()->step; + resetCounter(); updateProgressBar(); - connect(m_totpUpdateTimer, SIGNAL(timeout()), this, SLOT(updateProgressBar())); - connect(m_totpUpdateTimer, SIGNAL(timeout()), this, SLOT(updateSeconds())); - m_totpUpdateTimer->start(m_step * 10); + connect(&m_totpUpdateTimer, SIGNAL(timeout()), this, SLOT(updateProgressBar())); + connect(&m_totpUpdateTimer, SIGNAL(timeout()), this, SLOT(updateSeconds())); + m_totpUpdateTimer.start(m_step * 10); updateTotp(); setAttribute(Qt::WA_DeleteOnClose); @@ -47,6 +51,10 @@ TotpDialog::TotpDialog(DatabaseWidget* parent, Entry* entry) connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(copyToClipboard())); } +TotpDialog::~TotpDialog() +{ +} + void TotpDialog::copyToClipboard() { clipboard()->setText(m_entry->totp()); @@ -57,13 +65,13 @@ void TotpDialog::copyToClipboard() void TotpDialog::updateProgressBar() { - if (uCounter < 100) { - m_ui->progressBar->setValue(static_cast(100 - uCounter)); + if (m_counter < 100) { + m_ui->progressBar->setValue(100 - m_counter); m_ui->progressBar->update(); - uCounter++; + ++m_counter; } else { updateTotp(); - uCounter = resetCounter(); + resetCounter(); } } @@ -81,16 +89,8 @@ void TotpDialog::updateTotp() m_ui->totpLabel->setText(firstHalf + " " + secondHalf); } -double TotpDialog::resetCounter() +void TotpDialog::resetCounter() { uint epoch = QDateTime::currentDateTime().toTime_t(); - double counter = qRound(static_cast(epoch % m_step) / m_step * 100); - return counter; -} - -TotpDialog::~TotpDialog() -{ - if (m_totpUpdateTimer) { - delete m_totpUpdateTimer; - } + m_counter = static_cast(static_cast(epoch % m_step) / m_step * 100); } diff --git a/src/gui/TotpDialog.h b/src/gui/TotpDialog.h index e002cb82a..0ab035185 100644 --- a/src/gui/TotpDialog.h +++ b/src/gui/TotpDialog.h @@ -37,24 +37,23 @@ class TotpDialog : public QDialog Q_OBJECT public: - explicit TotpDialog(DatabaseWidget* parent = nullptr, Entry* entry = nullptr); - ~TotpDialog(); - -private: - double uCounter; - quint8 m_step = Totp::defaultStep; - QScopedPointer m_ui; - QPointer m_totpUpdateTimer; + explicit TotpDialog(QWidget* parent = nullptr, Entry* entry = nullptr); + ~TotpDialog() override; private Q_SLOTS: void updateTotp(); void updateProgressBar(); void updateSeconds(); void copyToClipboard(); - double resetCounter(); -protected: +private: + QScopedPointer m_ui; + + void resetCounter(); Entry* m_entry; + int m_counter; + uint m_step; + QTimer m_totpUpdateTimer; }; #endif // KEEPASSX_TOTPDIALOG_H diff --git a/src/gui/TotpSetupDialog.cpp b/src/gui/TotpSetupDialog.cpp new file mode 100644 index 000000000..69290e7a3 --- /dev/null +++ b/src/gui/TotpSetupDialog.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com> + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "TotpSetupDialog.h" +#include "totp/totp.h" +#include "ui_TotpSetupDialog.h" + +TotpSetupDialog::TotpSetupDialog(QWidget* parent, Entry* entry) + : QDialog(parent) + , m_ui(new Ui::TotpSetupDialog()) + , m_entry(entry) +{ + m_ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + setFixedSize(sizeHint()); + + connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(close())); + connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(saveSettings())); + connect(m_ui->radioCustom, SIGNAL(toggled(bool)), SLOT(toggleCustom(bool))); + + init(); +} + +TotpSetupDialog::~TotpSetupDialog() +{ +} + +void TotpSetupDialog::saveSettings() +{ + QString encShortName; + uint digits = Totp::DEFAULT_DIGITS; + if (m_ui->radio8Digits->isChecked()) { + digits = 8; + } else if (m_ui->radioSteam->isChecked()) { + digits = Totp::STEAM_DIGITS; + encShortName = Totp::STEAM_SHORTNAME; + } + + auto settings = Totp::createSettings(m_ui->seedEdit->text(), digits, m_ui->stepSpinBox->value(), encShortName); + m_entry->setTotp(settings); + emit totpUpdated(); + close(); +} + +void TotpSetupDialog::toggleCustom(bool status) +{ + m_ui->customGroup->setEnabled(status); +} + +void TotpSetupDialog::init() +{ + auto settings = m_entry->totpSettings(); + if (!settings.isNull()) { + m_ui->seedEdit->setText(settings->key); + m_ui->stepSpinBox->setValue(settings->step); + + if (settings->encoder.shortName == Totp::STEAM_SHORTNAME) { + m_ui->radioSteam->setChecked(true); + } else if (settings->custom) { + m_ui->radioCustom->setChecked(true); + if (settings->digits == 8) { + m_ui->radio8Digits->setChecked(true); + } else { + m_ui->radio6Digits->setChecked(true); + } + } + } +} diff --git a/src/gui/SetupTotpDialog.h b/src/gui/TotpSetupDialog.h similarity index 68% rename from src/gui/SetupTotpDialog.h rename to src/gui/TotpSetupDialog.h index 7c34bb5f1..905b2a2ca 100644 --- a/src/gui/SetupTotpDialog.h +++ b/src/gui/TotpSetupDialog.h @@ -27,33 +27,28 @@ namespace Ui { - class SetupTotpDialog; + class TotpSetupDialog; } -class SetupTotpDialog : public QDialog +class TotpSetupDialog : public QDialog { Q_OBJECT public: - explicit SetupTotpDialog(DatabaseWidget* parent = nullptr, Entry* entry = nullptr); - ~SetupTotpDialog(); - void setSeed(QString value); - void setStep(quint8 step); - void setDigits(quint8 digits); - void setSettings(quint8 digits); + explicit TotpSetupDialog(QWidget* parent = nullptr, Entry* entry = nullptr); + ~TotpSetupDialog() override; + void init(); -private Q_SLOTS: - void toggleDefault(bool status); - void toggleSteam(bool status); +signals: + void totpUpdated(); + +private slots: void toggleCustom(bool status); - void setupTotp(); + void saveSettings(); private: - QScopedPointer m_ui; - -protected: + QScopedPointer m_ui; Entry* m_entry; - DatabaseWidget* m_parent; }; #endif // KEEPASSX_SETUPTOTPDIALOG_H diff --git a/src/gui/TotpSetupDialog.ui b/src/gui/TotpSetupDialog.ui new file mode 100644 index 000000000..7b3e9318b --- /dev/null +++ b/src/gui/TotpSetupDialog.ui @@ -0,0 +1,182 @@ + + + TotpSetupDialog + + + + 0 + 0 + 249 + 248 + + + + Setup TOTP + + + + + + 5 + + + 5 + + + + + Key: + + + + + + + + + + + + border:none + + + + + + false + + + false + + + + + + Default RFC 6238 token settings + + + true + + + settingsButtonGroup + + + + + + + Steam token settings + + + settingsButtonGroup + + + + + + + Use custom settings + + + settingsButtonGroup + + + + + + + + + + false + + + + + + Custom Settings + + + + QFormLayout::ExpandingFieldsGrow + + + QFormLayout::DontWrapRows + + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + + 5 + + + 5 + + + + + Time step: + + + + + + + sec + + + 1 + + + 60 + + + 30 + + + + + + + Code size: + + + + + + + 6 digits + + + true + + + + + + + 8 digits + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + diff --git a/src/gui/entry/EntryModel.cpp b/src/gui/entry/EntryModel.cpp index d3859e498..fe1f70476 100644 --- a/src/gui/entry/EntryModel.cpp +++ b/src/gui/entry/EntryModel.cpp @@ -127,7 +127,7 @@ int EntryModel::columnCount(const QModelIndex& parent) const return 0; } - return 12; + return 13; } QVariant EntryModel::data(const QModelIndex& index, int role) const @@ -201,16 +201,20 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const case Accessed: result = entry->timeInfo().lastAccessTime().toLocalTime().toString(EntryModel::DateFormat); return result; - case Attachments: - // Display comma-separated list of attachments - QList attachments = entry->attachments()->keys(); - for (int i = 0; i < attachments.size(); ++i) { - if (result.isEmpty()) { - result.append(attachments.at(i)); - continue; + case Attachments: { + // Display comma-separated list of attachments + QList attachments = entry->attachments()->keys(); + for (int i = 0; i < attachments.size(); ++i) { + if (result.isEmpty()) { + result.append(attachments.at(i)); + continue; + } + result.append(QString(", ") + attachments.at(i)); } - result.append(QString(", ") + attachments.at(i)); + return result; } + case Totp: + result = entry->hasTotp() ? tr("Yes") : ""; return result; } } else if (role == Qt::UserRole) { // Qt::UserRole is used as sort role, see EntryView::EntryView() @@ -309,6 +313,8 @@ QVariant EntryModel::headerData(int section, Qt::Orientation orientation, int ro return tr("Accessed"); case Attachments: return tr("Attachments"); + case Totp: + return tr("TOTP"); } } else if (role == Qt::DecorationRole) { if (section == Paperclip) { diff --git a/src/gui/entry/EntryModel.h b/src/gui/entry/EntryModel.h index 1965066a1..e8c90f7e4 100644 --- a/src/gui/entry/EntryModel.h +++ b/src/gui/entry/EntryModel.h @@ -42,7 +42,8 @@ public: Modified = 8, Accessed = 9, Paperclip = 10, - Attachments = 11 + Attachments = 11, + Totp = 12 }; explicit EntryModel(QObject* parent = nullptr); diff --git a/src/totp/totp.cpp b/src/totp/totp.cpp index d187bd0f6..286d383ab 100644 --- a/src/totp/totp.cpp +++ b/src/totp/totp.cpp @@ -18,6 +18,7 @@ #include "totp.h" #include "core/Base32.h" + #include #include #include @@ -28,114 +29,107 @@ #include #include -const quint8 Totp::defaultStep = 30; -const quint8 Totp::defaultDigits = 6; - -/** - * Custom encoder types. Each should be unique and >= 128 and < 255 - * Values have no meaning outside of keepassxc - */ -/** - * Encoder for Steam Guard TOTP - */ -const quint8 Totp::ENCODER_STEAM = 254; - -const Totp::Encoder Totp::defaultEncoder = {"", "", "0123456789", 0, 0, false}; -const QMap Totp::encoders{ - {Totp::ENCODER_STEAM, {"steam", "S", "23456789BCDFGHJKMNPQRTVWXY", 5, 30, true}}, +static QList encoders { + {"", "", "0123456789", Totp::DEFAULT_DIGITS, Totp::DEFAULT_STEP, false}, + {"steam", Totp::STEAM_SHORTNAME, "23456789BCDFGHJKMNPQRTVWXY", Totp::STEAM_DIGITS, Totp::DEFAULT_STEP, true}, }; -/** - * These map the second field of the "TOTP Settings" field to our internal encoder number - * that overloads the digits field. Make sure that the key matches the shortName value - * in the corresponding Encoder - * NOTE: when updating this map, a corresponding edit to the settings regex must be made - * in Entry::totpSeed() - */ -const QMap Totp::shortNameToEncoder{ - {"S", Totp::ENCODER_STEAM}, -}; -/** - * These map the "encoder=" URL parameter of the "otp" field to our internal encoder number - * that overloads the digits field. Make sure that the key matches the name value - * in the corresponding Encoder - */ -const QMap Totp::nameToEncoder{ - {"steam", Totp::ENCODER_STEAM}, -}; - -Totp::Totp() +QSharedPointer Totp::parseSettings(const QString& rawSettings, const QString& key) { -} + // Create default settings + auto settings = createSettings(key, DEFAULT_DIGITS, DEFAULT_STEP); -QString Totp::parseOtpString(QString key, quint8& digits, quint8& step) -{ - QUrl url(key); - - QString seed; - uint q_digits, q_step; - - // Default OTP url format + QUrl url(rawSettings); if (url.isValid() && url.scheme() == "otpauth") { + // Default OTP url format QUrlQuery query(url); - - seed = query.queryItemValue("secret"); - - q_digits = query.queryItemValue("digits").toUInt(); - if (q_digits == 6 || q_digits == 8) { - digits = q_digits; - } - - q_step = query.queryItemValue("period").toUInt(); - if (q_step > 0 && q_step <= 60) { - step = q_step; - } - QString encName = query.queryItemValue("encoder"); - if (!encName.isEmpty() && nameToEncoder.contains(encName)) { - digits = nameToEncoder[encName]; + settings->otpUrl = true; + settings->key = query.queryItemValue("secret"); + settings->digits = query.queryItemValue("digits").toUInt(); + settings->step = query.queryItemValue("period").toUInt(); + if (query.hasQueryItem("encoder")) { + settings->encoder = getEncoderByName(query.queryItemValue("encoder")); } } else { - // Compatibility with "KeeOtp" plugin string format - QRegExp rx("key=(.+)", Qt::CaseInsensitive, QRegExp::RegExp); - - if (rx.exactMatch(key)) { - QUrlQuery query(key); - - seed = query.queryItemValue("key"); - q_digits = query.queryItemValue("size").toUInt(); - if (q_digits == 6 || q_digits == 8) { - digits = q_digits; - } - - q_step = query.queryItemValue("step").toUInt(); - if (q_step > 0 && q_step <= 60) { - step = q_step; - } - + QUrlQuery query(rawSettings); + if (query.hasQueryItem("key")) { + // Compatibility with "KeeOtp" plugin + // if settings are changed, will convert to semi-colon format + settings->key = query.queryItemValue("key"); + settings->digits = query.queryItemValue("size").toUInt(); + settings->step = query.queryItemValue("step").toUInt(); } else { - seed = key; + // Parse semi-colon separated values ([step];[digits|S]) + auto vars = rawSettings.split(";"); + if (vars.size() >= 2) { + if (vars[1] == STEAM_SHORTNAME) { + // Explicit steam encoder + settings->encoder = steamEncoder(); + } else { + // Extract step and digits + settings->step = vars[0].toUInt(); + settings->digits = vars[1].toUInt(); + } + } } } - if (digits == 0) { - digits = defaultDigits; + // Bound digits and step + settings->digits = qMax(1u, settings->digits); + settings->step = qBound(1u, settings->step, 60u); + + // Detect custom settings, used by setup GUI + if (settings->encoder.shortName != STEAM_SHORTNAME + && (settings->digits != DEFAULT_DIGITS || settings->step != DEFAULT_STEP)) { + settings->custom = true; } - if (step == 0) { - step = defaultStep; - } - - return seed; + return settings; } -QString Totp::generateTotp(const QByteArray key, - quint64 time, - const quint8 numDigits = defaultDigits, - const quint8 step = defaultStep) +QSharedPointer Totp::createSettings(const QString& key, const uint digits, const uint step, + const QString& encoderShortName) { - quint64 current = qToBigEndian(time / step); + bool isCustom = digits != DEFAULT_DIGITS || step != DEFAULT_STEP; + return QSharedPointer(new Totp::Settings { + getEncoderByShortName(encoderShortName), key, false, isCustom, digits, step + }); +} - QVariant secret = Base32::decode(Base32::sanitizeInput(key)); +QString Totp::writeSettings(const QSharedPointer settings) +{ + // OTP Url output + if (settings->otpUrl) { + auto urlstring = QString("key=%1&step=%2&size=%3").arg(settings->key).arg(settings->step).arg(settings->digits); + if (!settings->encoder.name.isEmpty()) { + urlstring.append("&encoder=").append(settings->encoder.name); + } + return urlstring; + } + + // Semicolon output [step];[encoder] + if (!settings->encoder.shortName.isEmpty()) { + return QString("%1;%2").arg(settings->step).arg(settings->encoder.shortName); + } + + // Semicolon output [step];[digits] + return QString("%1;%2").arg(settings->step).arg(settings->digits); +} + +QString Totp::generateTotp(const QSharedPointer settings, const quint64 time) +{ + const Encoder& encoder = settings->encoder; + uint step = settings->custom ? settings->step : encoder.step; + uint digits = settings->custom ? settings->digits : encoder.digits; + + quint64 current; + if (time == 0) { + current = qToBigEndian(QDateTime::currentDateTime().toTime_t() / step); + } else { + current = qToBigEndian(time / step); + } + + QVariant secret = Base32::decode(Base32::sanitizeInput(settings->key.toLatin1())); if (secret.isNull()) { return "Invalid TOTP secret key"; } @@ -155,9 +149,6 @@ QString Totp::generateTotp(const QByteArray key, | (hmac[offset + 3] & 0xff); // clang-format on - const Encoder& encoder = encoders.value(numDigits, defaultEncoder); - // if encoder.digits is 0, we need to use the passed-in number of digits (default encoder) - quint8 digits = encoder.digits == 0 ? numDigits : encoder.digits; int direction = -1; int startpos = digits - 1; if (encoder.reverse) { @@ -175,26 +166,34 @@ QString Totp::generateTotp(const QByteArray key, return retval; } -// See: https://github.com/google/google-authenticator/wiki/Key-Uri-Format -QUrl Totp::generateOtpString(const QString& secret, - const QString& type, - const QString& issuer, - const QString& username, - const QString& algorithm, - quint8 digits, - quint8 step) +Totp::Encoder& Totp::defaultEncoder() { - QUrl keyUri; - keyUri.setScheme("otpauth"); - keyUri.setHost(type); - keyUri.setPath(QString("/%1:%2").arg(issuer).arg(username)); - QUrlQuery parameters; - parameters.addQueryItem("secret", secret); - parameters.addQueryItem("issuer", issuer); - parameters.addQueryItem("algorithm", algorithm); - parameters.addQueryItem("digits", QString::number(digits)); - parameters.addQueryItem("period", QString::number(step)); - keyUri.setQuery(parameters); - - return keyUri; + // The first encoder is always the default + Q_ASSERT(!encoders.empty()); + return encoders[0]; +} + +Totp::Encoder& Totp::steamEncoder() +{ + return getEncoderByShortName("S"); +} + +Totp::Encoder& Totp::getEncoderByShortName(QString shortName) +{ + for (auto& encoder : encoders) { + if (encoder.shortName == shortName) { + return encoder; + } + } + return defaultEncoder(); +} + +Totp::Encoder& Totp::getEncoderByName(QString name) +{ + for (auto& encoder : encoders) { + if (encoder.name == name) { + return encoder; + } + } + return defaultEncoder(); } diff --git a/src/totp/totp.h b/src/totp/totp.h index 1c159e290..b4a592918 100644 --- a/src/totp/totp.h +++ b/src/totp/totp.h @@ -22,39 +22,52 @@ #include #include #include +#include class QUrl; -class Totp +namespace Totp { + +struct Encoder { -public: - Totp(); - static QString parseOtpString(QString rawSecret, quint8& digits, quint8& step); - static QString generateTotp(const QByteArray key, quint64 time, const quint8 numDigits, const quint8 step); - static QUrl generateOtpString(const QString& secret, - const QString& type, - const QString& issuer, - const QString& username, - const QString& algorithm, - quint8 digits, - quint8 step); - static const quint8 defaultStep; - static const quint8 defaultDigits; - struct Encoder - { - QString name; - QString shortName; - QString alphabet; - quint8 digits; - quint8 step; - bool reverse; - }; - static const Encoder defaultEncoder; - // custom encoder values that overload the digits field - static const quint8 ENCODER_STEAM; - static const QMap encoders; - static const QMap shortNameToEncoder; - static const QMap nameToEncoder; + QString name; + QString shortName; + QString alphabet; + uint digits; + uint step; + bool reverse; }; +struct Settings +{ + Totp::Encoder encoder; + QString key; + bool otpUrl; + bool custom; + uint digits; + uint step; +}; + +constexpr uint DEFAULT_STEP = 30u; +constexpr uint DEFAULT_DIGITS = 6u; +constexpr uint STEAM_DIGITS = 5u; +static const QString STEAM_SHORTNAME = "S"; + +static const QString ATTRIBUTE_OTP = "otp"; +static const QString ATTRIBUTE_SEED = "TOTP Seed"; +static const QString ATTRIBUTE_SETTINGS = "TOTP Settings"; + +QSharedPointer parseSettings(const QString& rawSettings, const QString& key = {}); +QSharedPointer createSettings(const QString& key, const uint digits, const uint step, + const QString& encoderShortName = {}); +QString writeSettings(const QSharedPointer settings); + +QString generateTotp(const QSharedPointer settings, const quint64 time = 0ull); + +Encoder& defaultEncoder(); +Encoder& steamEncoder(); +Encoder& getEncoderByShortName(QString shortName); +Encoder& getEncoderByName(QString name); +} + #endif // QTOTP_H diff --git a/tests/TestEntryModel.cpp b/tests/TestEntryModel.cpp index 3378a9f9a..49939e256 100644 --- a/tests/TestEntryModel.cpp +++ b/tests/TestEntryModel.cpp @@ -291,11 +291,11 @@ void TestEntryModel::testProxyModel() * @author Fonic * Update comparison value of modelProxy->columnCount() to account for * additional columns 'Password', 'Notes', 'Expires', 'Created', 'Modified', - * 'Accessed', 'Paperclip' and 'Attachments' + * 'Accessed', 'Paperclip', 'Attachments', and TOTP */ QSignalSpy spyColumnRemove(modelProxy, SIGNAL(columnsAboutToBeRemoved(QModelIndex, int, int))); modelProxy->hideColumn(0, true); - QCOMPARE(modelProxy->columnCount(), 11); + QCOMPARE(modelProxy->columnCount(), 12); QVERIFY(spyColumnRemove.size() >= 1); int oldSpyColumnRemoveSize = spyColumnRemove.size(); @@ -313,11 +313,11 @@ void TestEntryModel::testProxyModel() * @author Fonic * Update comparison value of modelProxy->columnCount() to account for * additional columns 'Password', 'Notes', 'Expires', 'Created', 'Modified', - * 'Accessed', 'Paperclip' and 'Attachments' + * 'Accessed', 'Paperclip', 'Attachments', and TOTP */ QSignalSpy spyColumnInsert(modelProxy, SIGNAL(columnsAboutToBeInserted(QModelIndex, int, int))); modelProxy->hideColumn(0, false); - QCOMPARE(modelProxy->columnCount(), 12); + QCOMPARE(modelProxy->columnCount(), 13); QVERIFY(spyColumnInsert.size() >= 1); int oldSpyColumnInsertSize = spyColumnInsert.size(); diff --git a/tests/TestTotp.cpp b/tests/TestTotp.cpp index a91ae00b3..f4de2c6ad 100644 --- a/tests/TestTotp.cpp +++ b/tests/TestTotp.cpp @@ -31,138 +31,107 @@ void TestTotp::initTestCase() void TestTotp::testParseSecret() { - quint8 digits = 0; - quint8 step = 0; + // OTP URL Parsing QString secret = "otpauth://totp/" "ACME%20Co:john@example.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=" "SHA1&digits=6&period=30"; - QCOMPARE(Totp::parseOtpString(secret, digits, step), QString("HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ")); - QCOMPARE(digits, quint8(6)); - QCOMPARE(step, quint8(30)); + auto settings = Totp::parseSettings(secret); + QVERIFY(!settings.isNull()); + QCOMPARE(settings->key, QString("HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ")); + QCOMPARE(settings->custom, false); + QCOMPARE(settings->digits, 6u); + QCOMPARE(settings->step, 30u); - digits = Totp::defaultDigits; - step = Totp::defaultStep; + // KeeOTP Parsing secret = "key=HXDMVJECJJWSRBY%3d&step=25&size=8"; - QCOMPARE(Totp::parseOtpString(secret, digits, step), QString("HXDMVJECJJWSRBY=")); - QCOMPARE(digits, quint8(8)); - QCOMPARE(step, quint8(25)); + settings = Totp::parseSettings(secret); + QVERIFY(!settings.isNull()); + QCOMPARE(settings->key, QString("HXDMVJECJJWSRBY=")); + QCOMPARE(settings->custom, true); + QCOMPARE(settings->digits, 8u); + QCOMPARE(settings->step, 25u); - digits = 0; - step = 0; + // Semi-colon delineated "TOTP Settings" secret = "gezdgnbvgy3tqojqgezdgnbvgy3tqojq"; - QCOMPARE(Totp::parseOtpString(secret, digits, step), QString("gezdgnbvgy3tqojqgezdgnbvgy3tqojq")); - QCOMPARE(digits, quint8(6)); - QCOMPARE(step, quint8(30)); + settings = Totp::parseSettings("30;8", secret); + QVERIFY(!settings.isNull()); + QCOMPARE(settings->key, QString("gezdgnbvgy3tqojqgezdgnbvgy3tqojq")); + QCOMPARE(settings->custom, true); + QCOMPARE(settings->digits, 8u); + QCOMPARE(settings->step, 30u); + + // Bare secret (no "TOTP Settings" attribute) + secret = "gezdgnbvgy3tqojqgezdgnbvgy3tqojq"; + settings = Totp::parseSettings("", secret); + QVERIFY(!settings.isNull()); + QCOMPARE(settings->key, QString("gezdgnbvgy3tqojqgezdgnbvgy3tqojq")); + QCOMPARE(settings->custom, false); + QCOMPARE(settings->digits, 6u); + QCOMPARE(settings->step, 30u); } void TestTotp::testTotpCode() { // Test vectors from RFC 6238 // https://tools.ietf.org/html/rfc6238#appendix-B + auto settings = Totp::createSettings("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ", Totp::DEFAULT_DIGITS, Totp::DEFAULT_STEP); - QByteArray seed = QString("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ").toLatin1(); - + // Test 6 digit TOTP (default) quint64 time = 1234567890; - QString output = Totp::generateTotp(seed, time, 6, 30); - QCOMPARE(output, QString("005924")); + QCOMPARE(Totp::generateTotp(settings, time), QString("005924")); time = 1111111109; - output = Totp::generateTotp(seed, time, 6, 30); - QCOMPARE(output, QString("081804")); + QCOMPARE(Totp::generateTotp(settings, time), QString("081804")); + // Test 8 digit TOTP (custom) + settings->digits = 8; + settings->custom = true; time = 1111111111; - output = Totp::generateTotp(seed, time, 8, 30); - QCOMPARE(output, QString("14050471")); + QCOMPARE(Totp::generateTotp(settings, time), QString("14050471")); time = 2000000000; - output = Totp::generateTotp(seed, time, 8, 30); - QCOMPARE(output, QString("69279037")); -} - -void TestTotp::testEncoderData() -{ - for (quint8 key : Totp::encoders.keys()) { - const Totp::Encoder& enc = Totp::encoders.value(key); - QVERIFY2( - enc.digits != 0, - qPrintable( - QString("Custom encoders cannot have zero-value for digits field: %1(%2)").arg(enc.name).arg(key))); - QVERIFY2(!enc.name.isEmpty(), - qPrintable(QString("Custom encoders must have a name: %1(%2)").arg(enc.name).arg(key))); - QVERIFY2(!enc.shortName.isEmpty(), - qPrintable(QString("Custom encoders must have a shortName: %1(%2)").arg(enc.name).arg(key))); - QVERIFY2(Totp::shortNameToEncoder.contains(enc.shortName), - qPrintable(QString("No shortNameToEncoder entry found for custom encoder: %1(%2) %3") - .arg(enc.name) - .arg(key) - .arg(enc.shortName))); - QVERIFY2(Totp::shortNameToEncoder[enc.shortName] == key, - qPrintable(QString("shortNameToEncoder doesn't reference this custome encoder: %1(%2) %3") - .arg(enc.name) - .arg(key) - .arg(enc.shortName))); - QVERIFY2(Totp::nameToEncoder.contains(enc.name), - qPrintable(QString("No nameToEncoder entry found for custom encoder: %1(%2) %3") - .arg(enc.name) - .arg(key) - .arg(enc.shortName))); - QVERIFY2(Totp::nameToEncoder[enc.name] == key, - qPrintable(QString("nameToEncoder doesn't reference this custome encoder: %1(%2) %3") - .arg(enc.name) - .arg(key) - .arg(enc.shortName))); - } - - for (const QString& key : Totp::nameToEncoder.keys()) { - quint8 value = Totp::nameToEncoder.value(key); - QVERIFY2(Totp::encoders.contains(value), - qPrintable(QString("No custom encoder found for encoder named %1(%2)").arg(value).arg(key))); - QVERIFY2(Totp::encoders[value].name == key, - qPrintable( - QString("nameToEncoder doesn't reference the right custom encoder: %1(%2)").arg(value).arg(key))); - } - - for (const QString& key : Totp::shortNameToEncoder.keys()) { - quint8 value = Totp::shortNameToEncoder.value(key); - QVERIFY2(Totp::encoders.contains(value), - qPrintable(QString("No custom encoder found for short-name encoder %1(%2)").arg(value).arg(key))); - QVERIFY2( - Totp::encoders[value].shortName == key, - qPrintable( - QString("shortNameToEncoder doesn't reference the right custom encoder: %1(%2)").arg(value).arg(key))); - } + QCOMPARE(Totp::generateTotp(settings, time), QString("69279037")); } void TestTotp::testSteamTotp() { - quint8 digits = 0; - quint8 step = 0; + // OTP URL Parsing QString secret = "otpauth://totp/" "test:test@example.com?secret=63BEDWCQZKTQWPESARIERL5DTTQFCJTK&issuer=Valve&algorithm=" "SHA1&digits=5&period=30&encoder=steam"; - QCOMPARE(Totp::parseOtpString(secret, digits, step), QString("63BEDWCQZKTQWPESARIERL5DTTQFCJTK")); - QCOMPARE(digits, quint8(Totp::ENCODER_STEAM)); - QCOMPARE(step, quint8(30)); + auto settings = Totp::parseSettings(secret); - QByteArray seed = QString("63BEDWCQZKTQWPESARIERL5DTTQFCJTK").toLatin1(); + QCOMPARE(settings->key, QString("63BEDWCQZKTQWPESARIERL5DTTQFCJTK")); + QCOMPARE(settings->encoder.shortName, Totp::STEAM_SHORTNAME); + QCOMPARE(settings->digits, Totp::STEAM_DIGITS); + QCOMPARE(settings->step, 30u); // These time/value pairs were created by running the Steam Guard function of the // Steam mobile app with a throw-away steam account. The above secret was extracted // from the Steam app's data for use in testing here. quint64 time = 1511200518; - QCOMPARE(Totp::generateTotp(seed, time, Totp::ENCODER_STEAM, 30), QString("FR8RV")); + QCOMPARE(Totp::generateTotp(settings, time), QString("FR8RV")); time = 1511200714; - QCOMPARE(Totp::generateTotp(seed, time, Totp::ENCODER_STEAM, 30), QString("9P3VP")); + QCOMPARE(Totp::generateTotp(settings, time), QString("9P3VP")); } void TestTotp::testEntryHistory() { Entry entry; - quint8 step = 16; - quint8 digits = 6; + uint step = 16; + uint digits = 6; + auto settings = Totp::createSettings("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ", digits, step); + // Test that entry starts without TOTP QCOMPARE(entry.historyItems().size(), 0); - entry.setTotp("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ", step, digits); + QVERIFY(!entry.hasTotp()); + // Add TOTP to entry + entry.setTotp(settings); QCOMPARE(entry.historyItems().size(), 1); - entry.setTotp("foo", step, digits); + QVERIFY(entry.hasTotp()); + QCOMPARE(entry.totpSettings()->key, QString("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ")); + // Change key and verify settings changed + settings->key = "foo"; + entry.setTotp(settings); QCOMPARE(entry.historyItems().size(), 2); + QCOMPARE(entry.totpSettings()->key, QString("foo")); } diff --git a/tests/TestTotp.h b/tests/TestTotp.h index 784eb8f22..92fa7a0e1 100644 --- a/tests/TestTotp.h +++ b/tests/TestTotp.h @@ -21,8 +21,6 @@ #include -class Totp; - class TestTotp : public QObject { Q_OBJECT @@ -31,7 +29,6 @@ private slots: void initTestCase(); void testParseSecret(); void testTotpCode(); - void testEncoderData(); void testSteamTotp(); void testEntryHistory(); }; diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 5833a241b..930dac561 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -56,7 +56,7 @@ #include "gui/MessageBox.h" #include "gui/PasswordEdit.h" #include "gui/SearchWidget.h" -#include "gui/SetupTotpDialog.h" +#include "gui/TotpSetupDialog.h" #include "gui/TotpDialog.h" #include "gui/entry/EditEntryWidget.h" #include "gui/entry/EntryView.h" @@ -636,7 +636,7 @@ void TestGui::testTotp() triggerAction("actionEntrySetupTotp"); - SetupTotpDialog* setupTotpDialog = m_dbWidget->findChild("SetupTotpDialog"); + TotpSetupDialog* setupTotpDialog = m_dbWidget->findChild("TotpSetupDialog"); Tools::wait(100); From 823a91622062102420401e24a3647de091ce86f1 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sat, 15 Sep 2018 12:08:33 -0400 Subject: [PATCH 4/4] Fix TOTP epoch time; add null checks for settings --- src/totp/totp.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/totp/totp.cpp b/src/totp/totp.cpp index 286d383ab..8d924d579 100644 --- a/src/totp/totp.cpp +++ b/src/totp/totp.cpp @@ -98,6 +98,10 @@ QSharedPointer Totp::createSettings(const QString& key, const ui QString Totp::writeSettings(const QSharedPointer settings) { + if (settings.isNull()) { + return {}; + } + // OTP Url output if (settings->otpUrl) { auto urlstring = QString("key=%1&step=%2&size=%3").arg(settings->key).arg(settings->step).arg(settings->digits); @@ -118,20 +122,26 @@ QString Totp::writeSettings(const QSharedPointer settings) QString Totp::generateTotp(const QSharedPointer settings, const quint64 time) { + Q_ASSERT(!settings.isNull()); + if (settings.isNull()) { + return QObject::tr("Invalid Settings", "TOTP"); + } + const Encoder& encoder = settings->encoder; uint step = settings->custom ? settings->step : encoder.step; uint digits = settings->custom ? settings->digits : encoder.digits; quint64 current; if (time == 0) { - current = qToBigEndian(QDateTime::currentDateTime().toTime_t() / step); + // TODO: Replace toTime_t() with toSecsSinceEpoch() when minimum Qt >= 5.8 + current = qToBigEndian(static_cast(QDateTime::currentDateTime().toTime_t()) / step); } else { current = qToBigEndian(time / step); } QVariant secret = Base32::decode(Base32::sanitizeInput(settings->key.toLatin1())); if (secret.isNull()) { - return "Invalid TOTP secret key"; + return QObject::tr("Invalid Key", "TOTP"); } QMessageAuthenticationCode code(QCryptographicHash::Sha1);