mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-03-29 17:38:02 -04:00
Merge pull request #2284 from keepassxreboot/refactor/static-init
Refactor browser settings and TOTP
This commit is contained in:
commit
c0aa1ef145
@ -128,7 +128,7 @@ set(keepassx_SOURCES
|
|||||||
gui/SettingsWidget.cpp
|
gui/SettingsWidget.cpp
|
||||||
gui/SearchWidget.cpp
|
gui/SearchWidget.cpp
|
||||||
gui/SortFilterHideProxyModel.cpp
|
gui/SortFilterHideProxyModel.cpp
|
||||||
gui/SetupTotpDialog.cpp
|
gui/TotpSetupDialog.cpp
|
||||||
gui/TotpDialog.cpp
|
gui/TotpDialog.cpp
|
||||||
gui/UnlockDatabaseWidget.cpp
|
gui/UnlockDatabaseWidget.cpp
|
||||||
gui/UnlockDatabaseDialog.cpp
|
gui/UnlockDatabaseDialog.cpp
|
||||||
|
@ -270,8 +270,8 @@ QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QStrin
|
|||||||
QJsonObject BrowserAction::handleGeneratePassword(const QJsonObject& json, const QString& action)
|
QJsonObject BrowserAction::handleGeneratePassword(const QJsonObject& json, const QString& action)
|
||||||
{
|
{
|
||||||
const QString nonce = json.value("nonce").toString();
|
const QString nonce = json.value("nonce").toString();
|
||||||
const QString password = BrowserSettings::generatePassword();
|
const QString password = browserSettings()->generatePassword();
|
||||||
const QString bits = QString::number(BrowserSettings::getbits()); // For some reason this always returns 1140 bits?
|
const QString bits = QString::number(browserSettings()->getbits()); // For some reason this always returns 1140 bits?
|
||||||
|
|
||||||
if (nonce.isEmpty() || password.isEmpty()) {
|
if (nonce.isEmpty() || password.isEmpty()) {
|
||||||
return QJsonObject();
|
return QJsonObject();
|
||||||
|
@ -66,36 +66,36 @@ BrowserOptionDialog::~BrowserOptionDialog()
|
|||||||
|
|
||||||
void BrowserOptionDialog::loadSettings()
|
void BrowserOptionDialog::loadSettings()
|
||||||
{
|
{
|
||||||
BrowserSettings settings;
|
auto settings = browserSettings();
|
||||||
m_ui->enableBrowserSupport->setChecked(settings.isEnabled());
|
m_ui->enableBrowserSupport->setChecked(settings->isEnabled());
|
||||||
|
|
||||||
m_ui->showNotification->setChecked(settings.showNotification());
|
m_ui->showNotification->setChecked(settings->showNotification());
|
||||||
m_ui->bestMatchOnly->setChecked(settings.bestMatchOnly());
|
m_ui->bestMatchOnly->setChecked(settings->bestMatchOnly());
|
||||||
m_ui->unlockDatabase->setChecked(settings.unlockDatabase());
|
m_ui->unlockDatabase->setChecked(settings->unlockDatabase());
|
||||||
m_ui->matchUrlScheme->setChecked(settings.matchUrlScheme());
|
m_ui->matchUrlScheme->setChecked(settings->matchUrlScheme());
|
||||||
|
|
||||||
// hide unimplemented options
|
// hide unimplemented options
|
||||||
// TODO: fix this
|
// TODO: fix this
|
||||||
m_ui->showNotification->hide();
|
m_ui->showNotification->hide();
|
||||||
|
|
||||||
if (settings.sortByUsername()) {
|
if (settings->sortByUsername()) {
|
||||||
m_ui->sortByUsername->setChecked(true);
|
m_ui->sortByUsername->setChecked(true);
|
||||||
} else {
|
} else {
|
||||||
m_ui->sortByTitle->setChecked(true);
|
m_ui->sortByTitle->setChecked(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_ui->alwaysAllowAccess->setChecked(settings.alwaysAllowAccess());
|
m_ui->alwaysAllowAccess->setChecked(settings->alwaysAllowAccess());
|
||||||
m_ui->alwaysAllowUpdate->setChecked(settings.alwaysAllowUpdate());
|
m_ui->alwaysAllowUpdate->setChecked(settings->alwaysAllowUpdate());
|
||||||
m_ui->searchInAllDatabases->setChecked(settings.searchInAllDatabases());
|
m_ui->searchInAllDatabases->setChecked(settings->searchInAllDatabases());
|
||||||
m_ui->supportKphFields->setChecked(settings.supportKphFields());
|
m_ui->supportKphFields->setChecked(settings->supportKphFields());
|
||||||
m_ui->supportBrowserProxy->setChecked(settings.supportBrowserProxy());
|
m_ui->supportBrowserProxy->setChecked(settings->supportBrowserProxy());
|
||||||
m_ui->useCustomProxy->setChecked(settings.useCustomProxy());
|
m_ui->useCustomProxy->setChecked(settings->useCustomProxy());
|
||||||
m_ui->customProxyLocation->setText(settings.customProxyLocation());
|
m_ui->customProxyLocation->setText(settings->customProxyLocation());
|
||||||
m_ui->updateBinaryPath->setChecked(settings.updateBinaryPath());
|
m_ui->updateBinaryPath->setChecked(settings->updateBinaryPath());
|
||||||
m_ui->chromeSupport->setChecked(settings.chromeSupport());
|
m_ui->chromeSupport->setChecked(settings->chromeSupport());
|
||||||
m_ui->chromiumSupport->setChecked(settings.chromiumSupport());
|
m_ui->chromiumSupport->setChecked(settings->chromiumSupport());
|
||||||
m_ui->firefoxSupport->setChecked(settings.firefoxSupport());
|
m_ui->firefoxSupport->setChecked(settings->firefoxSupport());
|
||||||
m_ui->vivaldiSupport->setChecked(settings.vivaldiSupport());
|
m_ui->vivaldiSupport->setChecked(settings->vivaldiSupport());
|
||||||
|
|
||||||
#if defined(KEEPASSXC_DIST_APPIMAGE)
|
#if defined(KEEPASSXC_DIST_APPIMAGE)
|
||||||
m_ui->supportBrowserProxy->setChecked(true);
|
m_ui->supportBrowserProxy->setChecked(true);
|
||||||
@ -113,28 +113,28 @@ void BrowserOptionDialog::loadSettings()
|
|||||||
|
|
||||||
void BrowserOptionDialog::saveSettings()
|
void BrowserOptionDialog::saveSettings()
|
||||||
{
|
{
|
||||||
BrowserSettings settings;
|
auto settings = browserSettings();
|
||||||
settings.setEnabled(m_ui->enableBrowserSupport->isChecked());
|
settings->setEnabled(m_ui->enableBrowserSupport->isChecked());
|
||||||
settings.setShowNotification(m_ui->showNotification->isChecked());
|
settings->setShowNotification(m_ui->showNotification->isChecked());
|
||||||
settings.setBestMatchOnly(m_ui->bestMatchOnly->isChecked());
|
settings->setBestMatchOnly(m_ui->bestMatchOnly->isChecked());
|
||||||
settings.setUnlockDatabase(m_ui->unlockDatabase->isChecked());
|
settings->setUnlockDatabase(m_ui->unlockDatabase->isChecked());
|
||||||
settings.setMatchUrlScheme(m_ui->matchUrlScheme->isChecked());
|
settings->setMatchUrlScheme(m_ui->matchUrlScheme->isChecked());
|
||||||
settings.setSortByUsername(m_ui->sortByUsername->isChecked());
|
settings->setSortByUsername(m_ui->sortByUsername->isChecked());
|
||||||
|
|
||||||
settings.setSupportBrowserProxy(m_ui->supportBrowserProxy->isChecked());
|
settings->setSupportBrowserProxy(m_ui->supportBrowserProxy->isChecked());
|
||||||
settings.setUseCustomProxy(m_ui->useCustomProxy->isChecked());
|
settings->setUseCustomProxy(m_ui->useCustomProxy->isChecked());
|
||||||
settings.setCustomProxyLocation(m_ui->customProxyLocation->text());
|
settings->setCustomProxyLocation(m_ui->customProxyLocation->text());
|
||||||
|
|
||||||
settings.setUpdateBinaryPath(m_ui->updateBinaryPath->isChecked());
|
settings->setUpdateBinaryPath(m_ui->updateBinaryPath->isChecked());
|
||||||
settings.setAlwaysAllowAccess(m_ui->alwaysAllowAccess->isChecked());
|
settings->setAlwaysAllowAccess(m_ui->alwaysAllowAccess->isChecked());
|
||||||
settings.setAlwaysAllowUpdate(m_ui->alwaysAllowUpdate->isChecked());
|
settings->setAlwaysAllowUpdate(m_ui->alwaysAllowUpdate->isChecked());
|
||||||
settings.setSearchInAllDatabases(m_ui->searchInAllDatabases->isChecked());
|
settings->setSearchInAllDatabases(m_ui->searchInAllDatabases->isChecked());
|
||||||
settings.setSupportKphFields(m_ui->supportKphFields->isChecked());
|
settings->setSupportKphFields(m_ui->supportKphFields->isChecked());
|
||||||
|
|
||||||
settings.setChromeSupport(m_ui->chromeSupport->isChecked());
|
settings->setChromeSupport(m_ui->chromeSupport->isChecked());
|
||||||
settings.setChromiumSupport(m_ui->chromiumSupport->isChecked());
|
settings->setChromiumSupport(m_ui->chromiumSupport->isChecked());
|
||||||
settings.setFirefoxSupport(m_ui->firefoxSupport->isChecked());
|
settings->setFirefoxSupport(m_ui->firefoxSupport->isChecked());
|
||||||
settings.setVivaldiSupport(m_ui->vivaldiSupport->isChecked());
|
settings->setVivaldiSupport(m_ui->vivaldiSupport->isChecked());
|
||||||
}
|
}
|
||||||
|
|
||||||
void BrowserOptionDialog::showProxyLocationFileDialog()
|
void BrowserOptionDialog::showProxyLocationFileDialog()
|
||||||
|
@ -34,7 +34,6 @@
|
|||||||
#include "core/PasswordGenerator.h"
|
#include "core/PasswordGenerator.h"
|
||||||
#include "gui/MainWindow.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 KEEPASSXCBROWSER_NAME[] = "KeePassXC-Browser Settings";
|
||||||
static const char ASSOCIATE_KEY_PREFIX[] = "Public Key: ";
|
static const char ASSOCIATE_KEY_PREFIX[] = "Public Key: ";
|
||||||
static const char KEEPASSXCBROWSER_GROUP_NAME[] = "KeePassXC-Browser Passwords";
|
static const char KEEPASSXCBROWSER_GROUP_NAME[] = "KeePassXC-Browser Passwords";
|
||||||
@ -44,6 +43,7 @@ BrowserService::BrowserService(DatabaseTabWidget* parent)
|
|||||||
: m_dbTabWidget(parent)
|
: m_dbTabWidget(parent)
|
||||||
, m_dialogActive(false)
|
, m_dialogActive(false)
|
||||||
, m_bringToFrontRequested(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(databaseLocked(DatabaseWidget*)), this, SLOT(databaseLocked(DatabaseWidget*)));
|
||||||
connect(m_dbTabWidget, SIGNAL(databaseUnlocked(DatabaseWidget*)), this, SLOT(databaseUnlocked(DatabaseWidget*)));
|
connect(m_dbTabWidget, SIGNAL(databaseUnlocked(DatabaseWidget*)), this, SLOT(databaseUnlocked(DatabaseWidget*)));
|
||||||
@ -65,7 +65,7 @@ bool BrowserService::isDatabaseOpened() const
|
|||||||
|
|
||||||
bool BrowserService::openDatabase(bool triggerUnlock)
|
bool BrowserService::openDatabase(bool triggerUnlock)
|
||||||
{
|
{
|
||||||
if (!BrowserSettings::unlockDatabase()) {
|
if (!browserSettings()->unlockDatabase()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,11 +139,11 @@ Entry* BrowserService::getConfigEntry(bool create)
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
entry = db->resolveEntry(KEEPASSXCBROWSER_UUID);
|
entry = db->resolveEntry(m_keepassBrowserUUID);
|
||||||
if (!entry && create) {
|
if (!entry && create) {
|
||||||
entry = new Entry();
|
entry = new Entry();
|
||||||
entry->setTitle(QLatin1String(KEEPASSXCBROWSER_NAME));
|
entry->setTitle(QLatin1String(KEEPASSXCBROWSER_NAME));
|
||||||
entry->setUuid(KEEPASSXCBROWSER_UUID);
|
entry->setUuid(m_keepassBrowserUUID);
|
||||||
entry->setAutoTypeEnabled(false);
|
entry->setAutoTypeEnabled(false);
|
||||||
entry->setGroup(db->rootGroup());
|
entry->setGroup(db->rootGroup());
|
||||||
return entry;
|
return entry;
|
||||||
@ -243,7 +243,7 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id,
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool alwaysAllowAccess = BrowserSettings::alwaysAllowAccess();
|
const bool alwaysAllowAccess = browserSettings()->alwaysAllowAccess();
|
||||||
const QString host = QUrl(url).host();
|
const QString host = QUrl(url).host();
|
||||||
const QString submitHost = QUrl(submitUrl).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) {
|
if (username.compare(login, Qt::CaseSensitive) != 0 || entry->password().compare(password, Qt::CaseSensitive) != 0) {
|
||||||
int dialogResult = QMessageBox::No;
|
int dialogResult = QMessageBox::No;
|
||||||
if (!BrowserSettings::alwaysAllowUpdate()) {
|
if (!browserSettings()->alwaysAllowUpdate()) {
|
||||||
QMessageBox msgBox;
|
QMessageBox msgBox;
|
||||||
msgBox.setWindowTitle(tr("KeePassXC: Update Entry"));
|
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));
|
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();
|
dialogResult = msgBox.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BrowserSettings::alwaysAllowUpdate() || dialogResult == QMessageBox::Yes) {
|
if (browserSettings()->alwaysAllowUpdate() || dialogResult == QMessageBox::Yes) {
|
||||||
entry->beginUpdate();
|
entry->beginUpdate();
|
||||||
entry->setUsername(login);
|
entry->setUsername(login);
|
||||||
entry->setPassword(password);
|
entry->setPassword(password);
|
||||||
@ -396,7 +396,7 @@ QList<Entry*> BrowserService::searchEntries(Database* db, const QString& hostnam
|
|||||||
|
|
||||||
// Ignore entry if port or scheme defined in the URL doesn't match
|
// Ignore entry if port or scheme defined in the URL doesn't match
|
||||||
if ((entryQUrl.port() > 0 && entryQUrl.port() != qUrl.port()) ||
|
if ((entryQUrl.port() > 0 && entryQUrl.port() != qUrl.port()) ||
|
||||||
(BrowserSettings::matchUrlScheme() && entryScheme.compare(qUrl.scheme()) != 0)) {
|
(browserSettings()->matchUrlScheme() && entryScheme.compare(qUrl.scheme()) != 0)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -414,14 +414,14 @@ QList<Entry*> BrowserService::searchEntries(const QString& url, const StringPair
|
|||||||
{
|
{
|
||||||
// Get the list of databases to search
|
// Get the list of databases to search
|
||||||
QList<Database*> databases;
|
QList<Database*> databases;
|
||||||
if (BrowserSettings::searchInAllDatabases()) {
|
if (browserSettings()->searchInAllDatabases()) {
|
||||||
const int count = m_dbTabWidget->count();
|
const int count = m_dbTabWidget->count();
|
||||||
for (int i = 0; i < count; ++i) {
|
for (int i = 0; i < count; ++i) {
|
||||||
if (DatabaseWidget* dbWidget = qobject_cast<DatabaseWidget*>(m_dbTabWidget->widget(i))) {
|
if (DatabaseWidget* dbWidget = qobject_cast<DatabaseWidget*>(m_dbTabWidget->widget(i))) {
|
||||||
if (Database* db = dbWidget->database()) {
|
if (Database* db = dbWidget->database()) {
|
||||||
// Check if database is connected with KeePassXC-Browser
|
// Check if database is connected with KeePassXC-Browser
|
||||||
for (const StringPair keyPair : keyList) {
|
for (const StringPair keyPair : keyList) {
|
||||||
Entry* entry = db->resolveEntry(KEEPASSXCBROWSER_UUID);
|
Entry* entry = db->resolveEntry(m_keepassBrowserUUID);
|
||||||
if (entry) {
|
if (entry) {
|
||||||
QString key = entry->attributes()->value(QLatin1String(ASSOCIATE_KEY_PREFIX) + keyPair.first);
|
QString key = entry->attributes()->value(QLatin1String(ASSOCIATE_KEY_PREFIX) + keyPair.first);
|
||||||
if (!key.isEmpty() && keyPair.second == key) {
|
if (!key.isEmpty() && keyPair.second == key) {
|
||||||
@ -564,18 +564,18 @@ QList<Entry*> BrowserService::sortEntries(QList<Entry*>& pwEntries, const QStrin
|
|||||||
}
|
}
|
||||||
|
|
||||||
QList<Entry*> results;
|
QList<Entry*> results;
|
||||||
QString field = BrowserSettings::sortByTitle() ? "Title" : "UserName";
|
QString field = browserSettings()->sortByTitle() ? "Title" : "UserName";
|
||||||
for (int i = 100; i >= 0; i -= 5) {
|
for (int i = 100; i >= 0; i -= 5) {
|
||||||
if (priorities.count(i) > 0) {
|
if (priorities.count(i) > 0) {
|
||||||
// Sort same priority entries by Title or UserName
|
// Sort same priority entries by Title or UserName
|
||||||
auto entries = priorities.values(i);
|
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) ||
|
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(field), right->attributes()->value(field)) == 0) &&
|
||||||
(QString::localeAwareCompare(left->attributes()->value("UserName"), right->attributes()->value("UserName")) < 0));
|
(QString::localeAwareCompare(left->attributes()->value("UserName"), right->attributes()->value("UserName")) < 0));
|
||||||
});
|
});
|
||||||
results << entries;
|
results << entries;
|
||||||
if (BrowserSettings::bestMatchOnly() && !pwEntries.isEmpty()) {
|
if (browserSettings()->bestMatchOnly() && !pwEntries.isEmpty()) {
|
||||||
// Early out once we find the highest batch of matches
|
// Early out once we find the highest batch of matches
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -642,7 +642,7 @@ QJsonObject BrowserService::prepareEntry(const Entry* entry)
|
|||||||
res["totp"] = entry->totp();
|
res["totp"] = entry->totp();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BrowserSettings::supportKphFields()) {
|
if (browserSettings()->supportKphFields()) {
|
||||||
const EntryAttributes* attr = entry->attributes();
|
const EntryAttributes* attr = entry->attributes();
|
||||||
QJsonArray stringFields;
|
QJsonArray stringFields;
|
||||||
for (const QString& key : attr->keys()) {
|
for (const QString& key : attr->keys()) {
|
||||||
|
@ -107,6 +107,7 @@ private:
|
|||||||
DatabaseTabWidget* const m_dbTabWidget;
|
DatabaseTabWidget* const m_dbTabWidget;
|
||||||
bool m_dialogActive;
|
bool m_dialogActive;
|
||||||
bool m_bringToFrontRequested;
|
bool m_bringToFrontRequested;
|
||||||
|
QUuid m_keepassBrowserUUID;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // BROWSERSERVICE_H
|
#endif // BROWSERSERVICE_H
|
||||||
|
@ -20,9 +20,16 @@
|
|||||||
#include "BrowserSettings.h"
|
#include "BrowserSettings.h"
|
||||||
#include "core/Config.h"
|
#include "core/Config.h"
|
||||||
|
|
||||||
PasswordGenerator BrowserSettings::m_passwordGenerator;
|
BrowserSettings* BrowserSettings::m_instance(nullptr);
|
||||||
PassphraseGenerator BrowserSettings::m_passPhraseGenerator;
|
|
||||||
HostInstaller BrowserSettings::m_hostInstaller;
|
BrowserSettings* BrowserSettings::instance()
|
||||||
|
{
|
||||||
|
if (!m_instance) {
|
||||||
|
m_instance = new BrowserSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_instance;
|
||||||
|
}
|
||||||
|
|
||||||
bool BrowserSettings::isEnabled()
|
bool BrowserSettings::isEnabled()
|
||||||
{
|
{
|
||||||
@ -185,7 +192,7 @@ bool BrowserSettings::chromeSupport()
|
|||||||
void BrowserSettings::setChromeSupport(bool enabled)
|
void BrowserSettings::setChromeSupport(bool enabled)
|
||||||
{
|
{
|
||||||
m_hostInstaller.installBrowser(
|
m_hostInstaller.installBrowser(
|
||||||
HostInstaller::SupportedBrowsers::CHROME, enabled, supportBrowserProxy(), customProxyLocation());
|
HostInstaller::SupportedBrowsers::CHROME, enabled, supportBrowserProxy(), customProxyLocation());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BrowserSettings::chromiumSupport()
|
bool BrowserSettings::chromiumSupport()
|
||||||
@ -196,7 +203,7 @@ bool BrowserSettings::chromiumSupport()
|
|||||||
void BrowserSettings::setChromiumSupport(bool enabled)
|
void BrowserSettings::setChromiumSupport(bool enabled)
|
||||||
{
|
{
|
||||||
m_hostInstaller.installBrowser(
|
m_hostInstaller.installBrowser(
|
||||||
HostInstaller::SupportedBrowsers::CHROMIUM, enabled, supportBrowserProxy(), customProxyLocation());
|
HostInstaller::SupportedBrowsers::CHROMIUM, enabled, supportBrowserProxy(), customProxyLocation());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BrowserSettings::firefoxSupport()
|
bool BrowserSettings::firefoxSupport()
|
||||||
@ -207,7 +214,7 @@ bool BrowserSettings::firefoxSupport()
|
|||||||
void BrowserSettings::setFirefoxSupport(bool enabled)
|
void BrowserSettings::setFirefoxSupport(bool enabled)
|
||||||
{
|
{
|
||||||
m_hostInstaller.installBrowser(
|
m_hostInstaller.installBrowser(
|
||||||
HostInstaller::SupportedBrowsers::FIREFOX, enabled, supportBrowserProxy(), customProxyLocation());
|
HostInstaller::SupportedBrowsers::FIREFOX, enabled, supportBrowserProxy(), customProxyLocation());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BrowserSettings::vivaldiSupport()
|
bool BrowserSettings::vivaldiSupport()
|
||||||
@ -218,7 +225,7 @@ bool BrowserSettings::vivaldiSupport()
|
|||||||
void BrowserSettings::setVivaldiSupport(bool enabled)
|
void BrowserSettings::setVivaldiSupport(bool enabled)
|
||||||
{
|
{
|
||||||
m_hostInstaller.installBrowser(
|
m_hostInstaller.installBrowser(
|
||||||
HostInstaller::SupportedBrowsers::VIVALDI, enabled, supportBrowserProxy(), customProxyLocation());
|
HostInstaller::SupportedBrowsers::VIVALDI, enabled, supportBrowserProxy(), customProxyLocation());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BrowserSettings::passwordUseNumbers()
|
bool BrowserSettings::passwordUseNumbers()
|
||||||
|
@ -27,95 +27,105 @@
|
|||||||
class BrowserSettings
|
class BrowserSettings
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static bool isEnabled();
|
explicit BrowserSettings() = default;
|
||||||
static void setEnabled(bool enabled);
|
static BrowserSettings* instance();
|
||||||
|
|
||||||
static bool showNotification(); // TODO!!
|
bool isEnabled();
|
||||||
static void setShowNotification(bool showNotification);
|
void setEnabled(bool enabled);
|
||||||
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);
|
|
||||||
|
|
||||||
static bool supportBrowserProxy();
|
bool showNotification(); // TODO!!
|
||||||
static void setSupportBrowserProxy(bool enabled);
|
void setShowNotification(bool showNotification);
|
||||||
static bool useCustomProxy();
|
bool bestMatchOnly();
|
||||||
static void setUseCustomProxy(bool enabled);
|
void setBestMatchOnly(bool bestMatchOnly);
|
||||||
static QString customProxyLocation();
|
bool unlockDatabase();
|
||||||
static void setCustomProxyLocation(QString location);
|
void setUnlockDatabase(bool unlockDatabase);
|
||||||
static bool updateBinaryPath();
|
bool matchUrlScheme();
|
||||||
static void setUpdateBinaryPath(bool enabled);
|
void setMatchUrlScheme(bool matchUrlScheme);
|
||||||
static bool chromeSupport();
|
bool sortByUsername();
|
||||||
static void setChromeSupport(bool enabled);
|
void setSortByUsername(bool sortByUsername = true);
|
||||||
static bool chromiumSupport();
|
bool sortByTitle();
|
||||||
static void setChromiumSupport(bool enabled);
|
void setSortByTitle(bool sortByUsertitle = true);
|
||||||
static bool firefoxSupport();
|
bool alwaysAllowAccess();
|
||||||
static void setFirefoxSupport(bool enabled);
|
void setAlwaysAllowAccess(bool alwaysAllowAccess);
|
||||||
static bool vivaldiSupport();
|
bool alwaysAllowUpdate();
|
||||||
static void setVivaldiSupport(bool enabled);
|
void setAlwaysAllowUpdate(bool alwaysAllowUpdate);
|
||||||
|
bool searchInAllDatabases();
|
||||||
|
void setSearchInAllDatabases(bool searchInAllDatabases);
|
||||||
|
bool supportKphFields();
|
||||||
|
void setSupportKphFields(bool supportKphFields);
|
||||||
|
|
||||||
static bool passwordUseNumbers();
|
bool supportBrowserProxy();
|
||||||
static void setPasswordUseNumbers(bool useNumbers);
|
void setSupportBrowserProxy(bool enabled);
|
||||||
static bool passwordUseLowercase();
|
bool useCustomProxy();
|
||||||
static void setPasswordUseLowercase(bool useLowercase);
|
void setUseCustomProxy(bool enabled);
|
||||||
static bool passwordUseUppercase();
|
QString customProxyLocation();
|
||||||
static void setPasswordUseUppercase(bool useUppercase);
|
void setCustomProxyLocation(QString location);
|
||||||
static bool passwordUseSpecial();
|
bool updateBinaryPath();
|
||||||
static void setPasswordUseSpecial(bool useSpecial);
|
void setUpdateBinaryPath(bool enabled);
|
||||||
static bool passwordUseBraces();
|
bool chromeSupport();
|
||||||
static void setPasswordUseBraces(bool useBraces);
|
void setChromeSupport(bool enabled);
|
||||||
static bool passwordUsePunctuation();
|
bool chromiumSupport();
|
||||||
static void setPasswordUsePunctuation(bool usePunctuation);
|
void setChromiumSupport(bool enabled);
|
||||||
static bool passwordUseQuotes();
|
bool firefoxSupport();
|
||||||
static void setPasswordUseQuotes(bool useQuotes);
|
void setFirefoxSupport(bool enabled);
|
||||||
static bool passwordUseDashes();
|
bool vivaldiSupport();
|
||||||
static void setPasswordUseDashes(bool useDashes);
|
void setVivaldiSupport(bool enabled);
|
||||||
static bool passwordUseMath();
|
|
||||||
static void setPasswordUseMath(bool useMath);
|
bool passwordUseNumbers();
|
||||||
static bool passwordUseLogograms();
|
void setPasswordUseNumbers(bool useNumbers);
|
||||||
static void setPasswordUseLogograms(bool useLogograms);
|
bool passwordUseLowercase();
|
||||||
static bool passwordUseEASCII();
|
void setPasswordUseLowercase(bool useLowercase);
|
||||||
static void setPasswordUseEASCII(bool useEASCII);
|
bool passwordUseUppercase();
|
||||||
static bool advancedMode();
|
void setPasswordUseUppercase(bool useUppercase);
|
||||||
static void setAdvancedMode(bool advancedMode);
|
bool passwordUseSpecial();
|
||||||
static QString passwordExcludedChars();
|
void setPasswordUseSpecial(bool useSpecial);
|
||||||
static void setPasswordExcludedChars(QString chars);
|
bool passwordUseBraces();
|
||||||
static int passPhraseWordCount();
|
void setPasswordUseBraces(bool useBraces);
|
||||||
static void setPassPhraseWordCount(int wordCount);
|
bool passwordUsePunctuation();
|
||||||
static QString passPhraseWordSeparator();
|
void setPasswordUsePunctuation(bool usePunctuation);
|
||||||
static void setPassPhraseWordSeparator(QString separator);
|
bool passwordUseQuotes();
|
||||||
static int generatorType();
|
void setPasswordUseQuotes(bool useQuotes);
|
||||||
static void setGeneratorType(int type);
|
bool passwordUseDashes();
|
||||||
static bool passwordEveryGroup();
|
void setPasswordUseDashes(bool useDashes);
|
||||||
static void setPasswordEveryGroup(bool everyGroup);
|
bool passwordUseMath();
|
||||||
static bool passwordExcludeAlike();
|
void setPasswordUseMath(bool useMath);
|
||||||
static void setPasswordExcludeAlike(bool excludeAlike);
|
bool passwordUseLogograms();
|
||||||
static int passwordLength();
|
void setPasswordUseLogograms(bool useLogograms);
|
||||||
static void setPasswordLength(int length);
|
bool passwordUseEASCII();
|
||||||
static PasswordGenerator::CharClasses passwordCharClasses();
|
void setPasswordUseEASCII(bool useEASCII);
|
||||||
static PasswordGenerator::GeneratorFlags passwordGeneratorFlags();
|
bool advancedMode();
|
||||||
static QString generatePassword();
|
void setAdvancedMode(bool advancedMode);
|
||||||
static int getbits();
|
QString passwordExcludedChars();
|
||||||
static void updateBinaryPaths(QString customProxyLocation = QString());
|
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:
|
private:
|
||||||
static PasswordGenerator m_passwordGenerator;
|
static BrowserSettings* m_instance;
|
||||||
static PassphraseGenerator m_passPhraseGenerator;
|
|
||||||
static HostInstaller m_hostInstaller;
|
PasswordGenerator m_passwordGenerator;
|
||||||
|
PassphraseGenerator m_passPhraseGenerator;
|
||||||
|
HostInstaller m_hostInstaller;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline BrowserSettings* browserSettings()
|
||||||
|
{
|
||||||
|
return BrowserSettings::instance();
|
||||||
|
}
|
||||||
|
|
||||||
#endif // BROWSERSETTINGS_H
|
#endif // BROWSERSETTINGS_H
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
#include "HostInstaller.h"
|
#include "HostInstaller.h"
|
||||||
#include "config-keepassx.h"
|
#include "config-keepassx.h"
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
@ -27,35 +28,27 @@
|
|||||||
#include <QProcessEnvironment>
|
#include <QProcessEnvironment>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
|
||||||
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()
|
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) {
|
switch (browser) {
|
||||||
case SupportedBrowsers::CHROME:
|
case SupportedBrowsers::CHROME:
|
||||||
return HostInstaller::TARGET_DIR_CHROME;
|
return TARGET_DIR_CHROME;
|
||||||
case SupportedBrowsers::CHROMIUM:
|
case SupportedBrowsers::CHROMIUM:
|
||||||
return HostInstaller::TARGET_DIR_CHROMIUM;
|
return TARGET_DIR_CHROMIUM;
|
||||||
case SupportedBrowsers::FIREFOX:
|
case SupportedBrowsers::FIREFOX:
|
||||||
return HostInstaller::TARGET_DIR_FIREFOX;
|
return TARGET_DIR_FIREFOX;
|
||||||
case SupportedBrowsers::VIVALDI:
|
case SupportedBrowsers::VIVALDI:
|
||||||
return HostInstaller::TARGET_DIR_VIVALDI;
|
return TARGET_DIR_VIVALDI;
|
||||||
default:
|
default:
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
@ -158,12 +151,12 @@ QString HostInstaller::getPath(SupportedBrowsers browser) const
|
|||||||
userPath = QDir::fromNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DataLocation));
|
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("/", "\\");
|
winPath.replace("/", "\\");
|
||||||
return winPath;
|
return winPath;
|
||||||
#else
|
#else
|
||||||
QString path = getTargetPath(browser);
|
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
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,19 +200,19 @@ QJsonObject HostInstaller::constructFile(SupportedBrowsers browser, const bool&
|
|||||||
#endif // #ifdef KEEPASSXC_DIST_APPIMAGE
|
#endif // #ifdef KEEPASSXC_DIST_APPIMAGE
|
||||||
|
|
||||||
QJsonObject script;
|
QJsonObject script;
|
||||||
script["name"] = HostInstaller::HOST_NAME;
|
script["name"] = HOST_NAME;
|
||||||
script["description"] = "KeePassXC integration with native messaging support";
|
script["description"] = "KeePassXC integration with native messaging support";
|
||||||
script["path"] = path;
|
script["path"] = path;
|
||||||
script["type"] = "stdio";
|
script["type"] = "stdio";
|
||||||
|
|
||||||
QJsonArray arr;
|
QJsonArray arr;
|
||||||
if (browser == SupportedBrowsers::FIREFOX) {
|
if (browser == SupportedBrowsers::FIREFOX) {
|
||||||
for (const QString extension : HostInstaller::ALLOWED_EXTENSIONS) {
|
for (const QString& extension : ALLOWED_EXTENSIONS) {
|
||||||
arr.append(extension);
|
arr.append(extension);
|
||||||
}
|
}
|
||||||
script["allowed_extensions"] = arr;
|
script["allowed_extensions"] = arr;
|
||||||
} else {
|
} else {
|
||||||
for (const QString origin : HostInstaller::ALLOWED_ORIGINS) {
|
for (const QString& origin : ALLOWED_ORIGINS) {
|
||||||
arr.append(origin);
|
arr.append(origin);
|
||||||
}
|
}
|
||||||
script["allowed_origins"] = arr;
|
script["allowed_origins"] = arr;
|
||||||
@ -248,10 +241,5 @@ bool HostInstaller::saveFile(SupportedBrowsers browser, const QJsonObject& scrip
|
|||||||
}
|
}
|
||||||
|
|
||||||
QJsonDocument doc(script);
|
QJsonDocument doc(script);
|
||||||
qint64 bytesWritten = scriptFile.write(doc.toJson());
|
return scriptFile.write(doc.toJson()) >= 0;
|
||||||
if (bytesWritten < 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
@ -55,13 +55,13 @@ private:
|
|||||||
bool saveFile(SupportedBrowsers browser, const QJsonObject& script);
|
bool saveFile(SupportedBrowsers browser, const QJsonObject& script);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const QString HOST_NAME;
|
const QString HOST_NAME;
|
||||||
static const QStringList ALLOWED_EXTENSIONS;
|
const QStringList ALLOWED_EXTENSIONS;
|
||||||
static const QStringList ALLOWED_ORIGINS;
|
const QStringList ALLOWED_ORIGINS;
|
||||||
static const QString TARGET_DIR_CHROME;
|
const QString TARGET_DIR_CHROME;
|
||||||
static const QString TARGET_DIR_CHROMIUM;
|
const QString TARGET_DIR_CHROMIUM;
|
||||||
static const QString TARGET_DIR_FIREFOX;
|
const QString TARGET_DIR_FIREFOX;
|
||||||
static const QString TARGET_DIR_VIVALDI;
|
const QString TARGET_DIR_VIVALDI;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // HOSTINSTALLER_H
|
#endif // HOSTINSTALLER_H
|
||||||
|
@ -30,14 +30,14 @@
|
|||||||
NativeMessagingHost::NativeMessagingHost(DatabaseTabWidget* parent, const bool enabled)
|
NativeMessagingHost::NativeMessagingHost(DatabaseTabWidget* parent, const bool enabled)
|
||||||
: NativeMessagingBase(enabled)
|
: NativeMessagingBase(enabled)
|
||||||
, m_mutex(QMutex::Recursive)
|
, m_mutex(QMutex::Recursive)
|
||||||
, m_browserClients(m_browserService)
|
|
||||||
, m_browserService(parent)
|
, m_browserService(parent)
|
||||||
|
, m_browserClients(m_browserService)
|
||||||
{
|
{
|
||||||
m_localServer.reset(new QLocalServer(this));
|
m_localServer.reset(new QLocalServer(this));
|
||||||
m_localServer->setSocketOptions(QLocalServer::UserAccessOption);
|
m_localServer->setSocketOptions(QLocalServer::UserAccessOption);
|
||||||
m_running.store(false);
|
m_running.store(false);
|
||||||
|
|
||||||
if (BrowserSettings::isEnabled() && !m_running) {
|
if (browserSettings()->isEnabled() && !m_running) {
|
||||||
run();
|
run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,8 +64,8 @@ void NativeMessagingHost::run()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update KeePassXC/keepassxc-proxy binary paths to Native Messaging scripts
|
// Update KeePassXC/keepassxc-proxy binary paths to Native Messaging scripts
|
||||||
if (BrowserSettings::updateBinaryPath()) {
|
if (browserSettings()->updateBinaryPath()) {
|
||||||
BrowserSettings::updateBinaryPaths(BrowserSettings::useCustomProxy() ? BrowserSettings::customProxyLocation()
|
browserSettings()->updateBinaryPaths(browserSettings()->useCustomProxy() ? browserSettings()->customProxyLocation()
|
||||||
: "");
|
: "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ void NativeMessagingHost::run()
|
|||||||
QtConcurrent::run(this, static_cast<void (NativeMessagingHost::*)()>(&NativeMessagingHost::readNativeMessages));
|
QtConcurrent::run(this, static_cast<void (NativeMessagingHost::*)()>(&NativeMessagingHost::readNativeMessages));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (BrowserSettings::supportBrowserProxy()) {
|
if (browserSettings()->supportBrowserProxy()) {
|
||||||
QString serverPath = getLocalServerPath();
|
QString serverPath = getLocalServerPath();
|
||||||
QFile::remove(serverPath);
|
QFile::remove(serverPath);
|
||||||
|
|
||||||
|
@ -58,8 +58,8 @@ private slots:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
QMutex m_mutex;
|
QMutex m_mutex;
|
||||||
BrowserClients m_browserClients;
|
|
||||||
BrowserService m_browserService;
|
BrowserService m_browserService;
|
||||||
|
BrowserClients m_browserClients;
|
||||||
QSharedPointer<QLocalServer> m_localServer;
|
QSharedPointer<QLocalServer> m_localServer;
|
||||||
SocketList m_socketList;
|
SocketList m_socketList;
|
||||||
};
|
};
|
||||||
|
@ -45,8 +45,6 @@ Entry::Entry()
|
|||||||
m_data.iconNumber = DefaultIconNumber;
|
m_data.iconNumber = DefaultIconNumber;
|
||||||
m_data.autoTypeEnabled = true;
|
m_data.autoTypeEnabled = true;
|
||||||
m_data.autoTypeObfuscation = 0;
|
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()), SLOT(updateTotp()));
|
||||||
connect(m_attributes, SIGNAL(modified()), this, SIGNAL(modified()));
|
connect(m_attributes, SIGNAL(modified()), this, SIGNAL(modified()));
|
||||||
@ -347,74 +345,45 @@ const CustomData* Entry::customData() const
|
|||||||
|
|
||||||
bool Entry::hasTotp() const
|
bool Entry::hasTotp() const
|
||||||
{
|
{
|
||||||
return m_attributes->hasKey("TOTP Seed") || m_attributes->hasKey("otp");
|
return !m_data.totpSettings.isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Entry::totp() const
|
QString Entry::totp() const
|
||||||
{
|
{
|
||||||
if (hasTotp()) {
|
if (hasTotp()) {
|
||||||
QString seed = totpSeed();
|
return Totp::generateTotp(m_data.totpSettings);
|
||||||
quint64 time = QDateTime::currentDateTime().toTime_t();
|
|
||||||
QString output = Totp::generateTotp(seed.toLatin1(), time, m_data.totpDigits, m_data.totpStep);
|
|
||||||
|
|
||||||
return QString(output);
|
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void Entry::setTotp(const QString& seed, quint8& step, quint8& digits)
|
void Entry::setTotp(QSharedPointer<Totp::Settings> settings)
|
||||||
{
|
{
|
||||||
beginUpdate();
|
beginUpdate();
|
||||||
if (step == 0) {
|
m_data.totpSettings = settings;
|
||||||
step = Totp::defaultStep;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (digits == 0) {
|
auto text = Totp::writeSettings(m_data.totpSettings);
|
||||||
digits = Totp::defaultDigits;
|
if (m_attributes->hasKey(Totp::ATTRIBUTE_OTP)) {
|
||||||
}
|
m_attributes->set(Totp::ATTRIBUTE_OTP, text, true);
|
||||||
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);
|
|
||||||
} else {
|
} else {
|
||||||
m_attributes->set("TOTP Seed", seed, true);
|
m_attributes->set(Totp::ATTRIBUTE_SEED, m_data.totpSettings->key, true);
|
||||||
if (!enc.shortName.isEmpty()) {
|
m_attributes->set(Totp::ATTRIBUTE_SETTINGS, text);
|
||||||
data = QString("%1;%2").arg(step).arg(enc.shortName);
|
|
||||||
} else {
|
|
||||||
data = QString("%1;%2").arg(step).arg(digits);
|
|
||||||
}
|
|
||||||
m_attributes->set("TOTP Settings", data);
|
|
||||||
}
|
}
|
||||||
endUpdate();
|
endUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Entry::totpSeed() const
|
void Entry::updateTotp()
|
||||||
{
|
{
|
||||||
QString secret = "";
|
if (m_attributes->contains(Totp::ATTRIBUTE_SETTINGS)) {
|
||||||
|
m_data.totpSettings = Totp::parseSettings(m_attributes->value(Totp::ATTRIBUTE_SETTINGS),
|
||||||
if (m_attributes->hasKey("otp")) {
|
m_attributes->value(Totp::ATTRIBUTE_SEED));
|
||||||
secret = m_attributes->value("otp");
|
} else if (m_attributes->contains(Totp::ATTRIBUTE_OTP)) {
|
||||||
} else if (m_attributes->hasKey("TOTP Seed")) {
|
m_data.totpSettings = Totp::parseSettings(m_attributes->value(Totp::ATTRIBUTE_OTP));
|
||||||
secret = m_attributes->value("TOTP Seed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Totp::parseOtpString(secret, m_data.totpDigits, m_data.totpStep);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
quint8 Entry::totpStep() const
|
QSharedPointer<Totp::Settings> Entry::totpSettings() const
|
||||||
{
|
{
|
||||||
return m_data.totpStep;
|
return m_data.totpSettings;
|
||||||
}
|
|
||||||
|
|
||||||
quint8 Entry::totpDigits() const
|
|
||||||
{
|
|
||||||
return m_data.totpDigits;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Entry::setUuid(const QUuid& uuid)
|
void Entry::setUuid(const QUuid& uuid)
|
||||||
@ -725,33 +694,6 @@ void Entry::updateModifiedSinceBegin()
|
|||||||
m_modifiedSinceBegin = true;
|
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<quint8>(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<quint8>(m.captured(2).toUInt());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QString Entry::resolveMultiplePlaceholdersRecursive(const QString& str, int maxDepth) const
|
QString Entry::resolveMultiplePlaceholdersRecursive(const QString& str, int maxDepth) const
|
||||||
{
|
{
|
||||||
if (maxDepth <= 0) {
|
if (maxDepth <= 0) {
|
||||||
|
@ -36,6 +36,9 @@
|
|||||||
|
|
||||||
class Database;
|
class Database;
|
||||||
class Group;
|
class Group;
|
||||||
|
namespace Totp {
|
||||||
|
struct Settings;
|
||||||
|
}
|
||||||
|
|
||||||
enum class EntryReferenceType
|
enum class EntryReferenceType
|
||||||
{
|
{
|
||||||
@ -61,8 +64,7 @@ struct EntryData
|
|||||||
int autoTypeObfuscation;
|
int autoTypeObfuscation;
|
||||||
QString defaultAutoTypeSequence;
|
QString defaultAutoTypeSequence;
|
||||||
TimeInfo timeInfo;
|
TimeInfo timeInfo;
|
||||||
mutable quint8 totpDigits;
|
QSharedPointer<Totp::Settings> totpSettings;
|
||||||
mutable quint8 totpStep;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Entry : public QObject
|
class Entry : public QObject
|
||||||
@ -98,9 +100,7 @@ public:
|
|||||||
QString password() const;
|
QString password() const;
|
||||||
QString notes() const;
|
QString notes() const;
|
||||||
QString totp() const;
|
QString totp() const;
|
||||||
QString totpSeed() const;
|
QSharedPointer<Totp::Settings> totpSettings() const;
|
||||||
quint8 totpDigits() const;
|
|
||||||
quint8 totpStep() const;
|
|
||||||
|
|
||||||
bool hasTotp() const;
|
bool hasTotp() const;
|
||||||
bool isExpired() const;
|
bool isExpired() const;
|
||||||
@ -135,7 +135,7 @@ public:
|
|||||||
void setNotes(const QString& notes);
|
void setNotes(const QString& notes);
|
||||||
void setExpires(const bool& value);
|
void setExpires(const bool& value);
|
||||||
void setExpiryTime(const QDateTime& dateTime);
|
void setExpiryTime(const QDateTime& dateTime);
|
||||||
void setTotp(const QString& seed, quint8& step, quint8& digits);
|
void setTotp(QSharedPointer<Totp::Settings> settings);
|
||||||
|
|
||||||
QList<Entry*> historyItems();
|
QList<Entry*> historyItems();
|
||||||
const QList<Entry*>& historyItems() const;
|
const QList<Entry*>& historyItems() const;
|
||||||
|
@ -47,7 +47,7 @@
|
|||||||
#include "gui/DetailsWidget.h"
|
#include "gui/DetailsWidget.h"
|
||||||
#include "gui/KeePass1OpenWidget.h"
|
#include "gui/KeePass1OpenWidget.h"
|
||||||
#include "gui/MessageBox.h"
|
#include "gui/MessageBox.h"
|
||||||
#include "gui/SetupTotpDialog.h"
|
#include "gui/TotpSetupDialog.h"
|
||||||
#include "gui/TotpDialog.h"
|
#include "gui/TotpDialog.h"
|
||||||
#include "gui/UnlockDatabaseDialog.h"
|
#include "gui/UnlockDatabaseDialog.h"
|
||||||
#include "gui/UnlockDatabaseWidget.h"
|
#include "gui/UnlockDatabaseWidget.h"
|
||||||
@ -444,15 +444,8 @@ void DatabaseWidget::setupTotp()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto setupTotpDialog = new SetupTotpDialog(this, currentEntry);
|
auto setupTotpDialog = new TotpSetupDialog(this, currentEntry);
|
||||||
if (currentEntry->hasTotp()) {
|
connect(setupTotpDialog, SIGNAL(totpUpdated()), SIGNAL(entrySelectionChanged()));
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
setupTotpDialog->open();
|
setupTotpDialog->open();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -938,6 +931,13 @@ void DatabaseWidget::entryActivationSignalReceived(Entry* entry, EntryModel::Mod
|
|||||||
openUrlForEntry(entry);
|
openUrlForEntry(entry);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case EntryModel::Totp:
|
||||||
|
if (entry->hasTotp()) {
|
||||||
|
setClipboardTextAndMinimize(entry->totp());
|
||||||
|
} else {
|
||||||
|
setupTotp();
|
||||||
|
}
|
||||||
|
break;
|
||||||
// TODO: switch to 'Notes' tab in details view/pane
|
// TODO: switch to 'Notes' tab in details view/pane
|
||||||
// case EntryModel::Notes:
|
// case EntryModel::Notes:
|
||||||
// break;
|
// break;
|
||||||
|
@ -39,8 +39,6 @@ DetailsWidget::DetailsWidget(QWidget* parent)
|
|||||||
, m_locked(false)
|
, m_locked(false)
|
||||||
, m_currentEntry(nullptr)
|
, m_currentEntry(nullptr)
|
||||||
, m_currentGroup(nullptr)
|
, m_currentGroup(nullptr)
|
||||||
, m_step(0)
|
|
||||||
, m_totpTimer(nullptr)
|
|
||||||
, m_selectedTabEntry(0)
|
, m_selectedTabEntry(0)
|
||||||
, m_selectedTabGroup(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->entryTotpButton, SIGNAL(toggled(bool)), m_ui->entryTotpWidget, SLOT(setVisible(bool)));
|
||||||
connect(m_ui->entryCloseButton, SIGNAL(toggled(bool)), SLOT(hide()));
|
connect(m_ui->entryCloseButton, SIGNAL(toggled(bool)), SLOT(hide()));
|
||||||
connect(m_ui->entryTabWidget, SIGNAL(tabBarClicked(int)), SLOT(updateTabIndexes()), Qt::QueuedConnection);
|
connect(m_ui->entryTabWidget, SIGNAL(tabBarClicked(int)), SLOT(updateTabIndexes()), Qt::QueuedConnection);
|
||||||
|
connect(&m_totpTimer, SIGNAL(timeout()), this, SLOT(updateTotpLabel()));
|
||||||
|
|
||||||
// Group
|
// Group
|
||||||
m_ui->groupCloseButton->setIcon(filePath()->icon("actions", "dialog-close"));
|
m_ui->groupCloseButton->setIcon(filePath()->icon("actions", "dialog-close"));
|
||||||
@ -65,7 +64,6 @@ DetailsWidget::DetailsWidget(QWidget* parent)
|
|||||||
|
|
||||||
DetailsWidget::~DetailsWidget()
|
DetailsWidget::~DetailsWidget()
|
||||||
{
|
{
|
||||||
deleteTotpTimer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DetailsWidget::setEntry(Entry* selectedEntry)
|
void DetailsWidget::setEntry(Entry* selectedEntry)
|
||||||
@ -146,16 +144,11 @@ void DetailsWidget::updateEntryTotp()
|
|||||||
m_ui->entryTotpButton->setChecked(false);
|
m_ui->entryTotpButton->setChecked(false);
|
||||||
|
|
||||||
if (hasTotp) {
|
if (hasTotp) {
|
||||||
deleteTotpTimer();
|
m_totpTimer.start(1000);
|
||||||
m_totpTimer = new QTimer(m_currentEntry);
|
|
||||||
connect(m_totpTimer, SIGNAL(timeout()), this, SLOT(updateTotpLabel()));
|
|
||||||
m_totpTimer->start(1000);
|
|
||||||
|
|
||||||
m_step = m_currentEntry->totpStep();
|
|
||||||
updateTotpLabel();
|
updateTotpLabel();
|
||||||
} else {
|
} else {
|
||||||
m_ui->entryTotpLabel->clear();
|
m_ui->entryTotpLabel->clear();
|
||||||
stopTotpTimer();
|
m_totpTimer.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,30 +267,16 @@ void DetailsWidget::updateGroupNotesTab()
|
|||||||
m_ui->groupNotesEdit->setText(notes);
|
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()
|
void DetailsWidget::updateTotpLabel()
|
||||||
{
|
{
|
||||||
if (!m_locked && m_currentEntry) {
|
if (!m_locked && m_currentEntry && m_currentEntry->hasTotp()) {
|
||||||
const QString totpCode = m_currentEntry->totp();
|
const QString totpCode = m_currentEntry->totp();
|
||||||
const QString firstHalf = totpCode.left(totpCode.size() / 2);
|
const QString firstHalf = totpCode.left(totpCode.size() / 2);
|
||||||
const QString secondHalf = totpCode.mid(totpCode.size() / 2);
|
const QString secondHalf = totpCode.mid(totpCode.size() / 2);
|
||||||
m_ui->entryTotpLabel->setText(firstHalf + " " + secondHalf);
|
m_ui->entryTotpLabel->setText(firstHalf + " " + secondHalf);
|
||||||
} else {
|
} else {
|
||||||
m_ui->entryTotpLabel->clear();
|
m_ui->entryTotpLabel->clear();
|
||||||
stopTotpTimer();
|
m_totpTimer.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ class DetailsWidget : public QWidget
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
explicit DetailsWidget(QWidget* parent = nullptr);
|
explicit DetailsWidget(QWidget* parent = nullptr);
|
||||||
~DetailsWidget();
|
~DetailsWidget() override;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void setEntry(Entry* selectedEntry);
|
void setEntry(Entry* selectedEntry);
|
||||||
@ -56,8 +56,6 @@ private slots:
|
|||||||
void updateGroupGeneralTab();
|
void updateGroupGeneralTab();
|
||||||
void updateGroupNotesTab();
|
void updateGroupNotesTab();
|
||||||
|
|
||||||
void stopTotpTimer();
|
|
||||||
void deleteTotpTimer();
|
|
||||||
void updateTotpLabel();
|
void updateTotpLabel();
|
||||||
void updateTabIndexes();
|
void updateTabIndexes();
|
||||||
|
|
||||||
@ -71,8 +69,7 @@ private:
|
|||||||
bool m_locked;
|
bool m_locked;
|
||||||
Entry* m_currentEntry;
|
Entry* m_currentEntry;
|
||||||
Group* m_currentGroup;
|
Group* m_currentGroup;
|
||||||
quint8 m_step;
|
QTimer m_totpTimer;
|
||||||
QPointer<QTimer> m_totpTimer;
|
|
||||||
quint8 m_selectedTabEntry;
|
quint8 m_selectedTabEntry;
|
||||||
quint8 m_selectedTabGroup;
|
quint8 m_selectedTabGroup;
|
||||||
};
|
};
|
||||||
|
@ -66,7 +66,7 @@ class BrowserPlugin : public ISettingsPage
|
|||||||
public:
|
public:
|
||||||
BrowserPlugin(DatabaseTabWidget* tabWidget)
|
BrowserPlugin(DatabaseTabWidget* tabWidget)
|
||||||
{
|
{
|
||||||
m_nativeMessagingHost = QSharedPointer<NativeMessagingHost>(new NativeMessagingHost(tabWidget, BrowserSettings::isEnabled()));
|
m_nativeMessagingHost = QSharedPointer<NativeMessagingHost>(new NativeMessagingHost(tabWidget, browserSettings()->isEnabled()));
|
||||||
}
|
}
|
||||||
|
|
||||||
~BrowserPlugin()
|
~BrowserPlugin()
|
||||||
@ -103,7 +103,7 @@ public:
|
|||||||
void saveSettings(QWidget* widget) override
|
void saveSettings(QWidget* widget) override
|
||||||
{
|
{
|
||||||
qobject_cast<BrowserOptionDialog*>(widget)->saveSettings();
|
qobject_cast<BrowserOptionDialog*>(widget)->saveSettings();
|
||||||
if (BrowserSettings::isEnabled()) {
|
if (browserSettings()->isEnabled()) {
|
||||||
m_nativeMessagingHost->run();
|
m_nativeMessagingHost->run();
|
||||||
} else {
|
} else {
|
||||||
m_nativeMessagingHost->stop();
|
m_nativeMessagingHost->stop();
|
||||||
|
@ -1,126 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com>
|
|
||||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
|
||||||
*
|
|
||||||
* 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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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()
|
|
||||||
{
|
|
||||||
}
|
|
@ -1,167 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ui version="4.0">
|
|
||||||
<class>SetupTotpDialog</class>
|
|
||||||
<widget class="QDialog" name="SetupTotpDialog">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>282</width>
|
|
||||||
<height>364</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string>Setup TOTP</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_3">
|
|
||||||
<property name="text">
|
|
||||||
<string>Key:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLineEdit" name="seedEdit"/>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
|
||||||
<item>
|
|
||||||
<widget class="QRadioButton" name="radioDefault">
|
|
||||||
<property name="text">
|
|
||||||
<string>Default RFC 6238 token settings</string>
|
|
||||||
</property>
|
|
||||||
<attribute name="buttonGroup">
|
|
||||||
<string notr="true">settingsButtonGroup</string>
|
|
||||||
</attribute>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QRadioButton" name="radioSteam">
|
|
||||||
<property name="text">
|
|
||||||
<string>Steam token settings</string>
|
|
||||||
</property>
|
|
||||||
<attribute name="buttonGroup">
|
|
||||||
<string notr="true">settingsButtonGroup</string>
|
|
||||||
</attribute>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QRadioButton" name="radioCustom">
|
|
||||||
<property name="text">
|
|
||||||
<string>Use custom settings</string>
|
|
||||||
</property>
|
|
||||||
<attribute name="buttonGroup">
|
|
||||||
<string notr="true">settingsButtonGroup</string>
|
|
||||||
</attribute>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_4">
|
|
||||||
<property name="text">
|
|
||||||
<string>Note: Change these settings only if you know what you are doing.</string>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QFormLayout" name="formLayout_3">
|
|
||||||
<property name="fieldGrowthPolicy">
|
|
||||||
<enum>QFormLayout::ExpandingFieldsGrow</enum>
|
|
||||||
</property>
|
|
||||||
<property name="rowWrapPolicy">
|
|
||||||
<enum>QFormLayout::DontWrapRows</enum>
|
|
||||||
</property>
|
|
||||||
<property name="labelAlignment">
|
|
||||||
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
|
|
||||||
</property>
|
|
||||||
<item row="1" column="0">
|
|
||||||
<widget class="QLabel" name="stepLabel">
|
|
||||||
<property name="enabled">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Time step:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="1">
|
|
||||||
<widget class="QRadioButton" name="radio8Digits">
|
|
||||||
<property name="enabled">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>8 digits</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="1">
|
|
||||||
<widget class="QRadioButton" name="radio6Digits">
|
|
||||||
<property name="enabled">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>6 digits</string>
|
|
||||||
</property>
|
|
||||||
<property name="checked">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="0">
|
|
||||||
<widget class="QLabel" name="digitsLabel">
|
|
||||||
<property name="enabled">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Code size:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="1">
|
|
||||||
<widget class="QSpinBox" name="stepSpinBox">
|
|
||||||
<property name="enabled">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="suffix">
|
|
||||||
<string comment="Seconds"> sec</string>
|
|
||||||
</property>
|
|
||||||
<property name="minimum">
|
|
||||||
<number>1</number>
|
|
||||||
</property>
|
|
||||||
<property name="maximum">
|
|
||||||
<number>60</number>
|
|
||||||
</property>
|
|
||||||
<property name="value">
|
|
||||||
<number>30</number>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="standardButtons">
|
|
||||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<resources/>
|
|
||||||
<connections/>
|
|
||||||
<buttongroups>
|
|
||||||
<buttongroup name="settingsButtonGroup"/>
|
|
||||||
</buttongroups>
|
|
||||||
</ui>
|
|
@ -22,21 +22,25 @@
|
|||||||
#include "core/Config.h"
|
#include "core/Config.h"
|
||||||
#include "gui/Clipboard.h"
|
#include "gui/Clipboard.h"
|
||||||
|
|
||||||
TotpDialog::TotpDialog(DatabaseWidget* parent, Entry* entry)
|
TotpDialog::TotpDialog(QWidget* parent, Entry* entry)
|
||||||
: QDialog(parent)
|
: QDialog(parent)
|
||||||
, m_ui(new Ui::TotpDialog())
|
, m_ui(new Ui::TotpDialog())
|
||||||
, m_totpUpdateTimer(new QTimer(entry))
|
|
||||||
, m_entry(entry)
|
, m_entry(entry)
|
||||||
{
|
{
|
||||||
|
if (!m_entry->hasTotp()) {
|
||||||
|
close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
m_ui->setupUi(this);
|
m_ui->setupUi(this);
|
||||||
|
|
||||||
m_step = m_entry->totpStep();
|
m_step = m_entry->totpSettings()->step;
|
||||||
uCounter = resetCounter();
|
resetCounter();
|
||||||
updateProgressBar();
|
updateProgressBar();
|
||||||
|
|
||||||
connect(m_totpUpdateTimer, SIGNAL(timeout()), this, SLOT(updateProgressBar()));
|
connect(&m_totpUpdateTimer, SIGNAL(timeout()), this, SLOT(updateProgressBar()));
|
||||||
connect(m_totpUpdateTimer, SIGNAL(timeout()), this, SLOT(updateSeconds()));
|
connect(&m_totpUpdateTimer, SIGNAL(timeout()), this, SLOT(updateSeconds()));
|
||||||
m_totpUpdateTimer->start(m_step * 10);
|
m_totpUpdateTimer.start(m_step * 10);
|
||||||
updateTotp();
|
updateTotp();
|
||||||
|
|
||||||
setAttribute(Qt::WA_DeleteOnClose);
|
setAttribute(Qt::WA_DeleteOnClose);
|
||||||
@ -47,6 +51,10 @@ TotpDialog::TotpDialog(DatabaseWidget* parent, Entry* entry)
|
|||||||
connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(copyToClipboard()));
|
connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(copyToClipboard()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TotpDialog::~TotpDialog()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
void TotpDialog::copyToClipboard()
|
void TotpDialog::copyToClipboard()
|
||||||
{
|
{
|
||||||
clipboard()->setText(m_entry->totp());
|
clipboard()->setText(m_entry->totp());
|
||||||
@ -57,13 +65,13 @@ void TotpDialog::copyToClipboard()
|
|||||||
|
|
||||||
void TotpDialog::updateProgressBar()
|
void TotpDialog::updateProgressBar()
|
||||||
{
|
{
|
||||||
if (uCounter < 100) {
|
if (m_counter < 100) {
|
||||||
m_ui->progressBar->setValue(static_cast<int>(100 - uCounter));
|
m_ui->progressBar->setValue(100 - m_counter);
|
||||||
m_ui->progressBar->update();
|
m_ui->progressBar->update();
|
||||||
uCounter++;
|
++m_counter;
|
||||||
} else {
|
} else {
|
||||||
updateTotp();
|
updateTotp();
|
||||||
uCounter = resetCounter();
|
resetCounter();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,16 +89,8 @@ void TotpDialog::updateTotp()
|
|||||||
m_ui->totpLabel->setText(firstHalf + " " + secondHalf);
|
m_ui->totpLabel->setText(firstHalf + " " + secondHalf);
|
||||||
}
|
}
|
||||||
|
|
||||||
double TotpDialog::resetCounter()
|
void TotpDialog::resetCounter()
|
||||||
{
|
{
|
||||||
uint epoch = QDateTime::currentDateTime().toTime_t();
|
uint epoch = QDateTime::currentDateTime().toTime_t();
|
||||||
double counter = qRound(static_cast<double>(epoch % m_step) / m_step * 100);
|
m_counter = static_cast<int>(static_cast<double>(epoch % m_step) / m_step * 100);
|
||||||
return counter;
|
|
||||||
}
|
|
||||||
|
|
||||||
TotpDialog::~TotpDialog()
|
|
||||||
{
|
|
||||||
if (m_totpUpdateTimer) {
|
|
||||||
delete m_totpUpdateTimer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -37,24 +37,23 @@ class TotpDialog : public QDialog
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit TotpDialog(DatabaseWidget* parent = nullptr, Entry* entry = nullptr);
|
explicit TotpDialog(QWidget* parent = nullptr, Entry* entry = nullptr);
|
||||||
~TotpDialog();
|
~TotpDialog() override;
|
||||||
|
|
||||||
private:
|
|
||||||
double uCounter;
|
|
||||||
quint8 m_step = Totp::defaultStep;
|
|
||||||
QScopedPointer<Ui::TotpDialog> m_ui;
|
|
||||||
QPointer<QTimer> m_totpUpdateTimer;
|
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void updateTotp();
|
void updateTotp();
|
||||||
void updateProgressBar();
|
void updateProgressBar();
|
||||||
void updateSeconds();
|
void updateSeconds();
|
||||||
void copyToClipboard();
|
void copyToClipboard();
|
||||||
double resetCounter();
|
|
||||||
|
|
||||||
protected:
|
private:
|
||||||
|
QScopedPointer<Ui::TotpDialog> m_ui;
|
||||||
|
|
||||||
|
void resetCounter();
|
||||||
Entry* m_entry;
|
Entry* m_entry;
|
||||||
|
int m_counter;
|
||||||
|
uint m_step;
|
||||||
|
QTimer m_totpUpdateTimer;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // KEEPASSX_TOTPDIALOG_H
|
#endif // KEEPASSX_TOTPDIALOG_H
|
||||||
|
83
src/gui/TotpSetupDialog.cpp
Normal file
83
src/gui/TotpSetupDialog.cpp
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com>
|
||||||
|
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||||
|
*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -27,33 +27,28 @@
|
|||||||
|
|
||||||
namespace Ui
|
namespace Ui
|
||||||
{
|
{
|
||||||
class SetupTotpDialog;
|
class TotpSetupDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
class SetupTotpDialog : public QDialog
|
class TotpSetupDialog : public QDialog
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit SetupTotpDialog(DatabaseWidget* parent = nullptr, Entry* entry = nullptr);
|
explicit TotpSetupDialog(QWidget* parent = nullptr, Entry* entry = nullptr);
|
||||||
~SetupTotpDialog();
|
~TotpSetupDialog() override;
|
||||||
void setSeed(QString value);
|
void init();
|
||||||
void setStep(quint8 step);
|
|
||||||
void setDigits(quint8 digits);
|
|
||||||
void setSettings(quint8 digits);
|
|
||||||
|
|
||||||
private Q_SLOTS:
|
signals:
|
||||||
void toggleDefault(bool status);
|
void totpUpdated();
|
||||||
void toggleSteam(bool status);
|
|
||||||
|
private slots:
|
||||||
void toggleCustom(bool status);
|
void toggleCustom(bool status);
|
||||||
void setupTotp();
|
void saveSettings();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QScopedPointer<Ui::SetupTotpDialog> m_ui;
|
QScopedPointer<Ui::TotpSetupDialog> m_ui;
|
||||||
|
|
||||||
protected:
|
|
||||||
Entry* m_entry;
|
Entry* m_entry;
|
||||||
DatabaseWidget* m_parent;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // KEEPASSX_SETUPTOTPDIALOG_H
|
#endif // KEEPASSX_SETUPTOTPDIALOG_H
|
182
src/gui/TotpSetupDialog.ui
Normal file
182
src/gui/TotpSetupDialog.ui
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>TotpSetupDialog</class>
|
||||||
|
<widget class="QDialog" name="TotpSetupDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>249</width>
|
||||||
|
<height>248</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Setup TOTP</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>Key:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="seedEdit"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox">
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">border:none</string>
|
||||||
|
</property>
|
||||||
|
<property name="title">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="flat">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QRadioButton" name="radioDefault">
|
||||||
|
<property name="text">
|
||||||
|
<string>Default RFC 6238 token settings</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<attribute name="buttonGroup">
|
||||||
|
<string notr="true">settingsButtonGroup</string>
|
||||||
|
</attribute>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QRadioButton" name="radioSteam">
|
||||||
|
<property name="text">
|
||||||
|
<string>Steam token settings</string>
|
||||||
|
</property>
|
||||||
|
<attribute name="buttonGroup">
|
||||||
|
<string notr="true">settingsButtonGroup</string>
|
||||||
|
</attribute>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QRadioButton" name="radioCustom">
|
||||||
|
<property name="text">
|
||||||
|
<string>Use custom settings</string>
|
||||||
|
</property>
|
||||||
|
<attribute name="buttonGroup">
|
||||||
|
<string notr="true">settingsButtonGroup</string>
|
||||||
|
</attribute>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="customGroup">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true"/>
|
||||||
|
</property>
|
||||||
|
<property name="title">
|
||||||
|
<string>Custom Settings</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QFormLayout" name="formLayout_3">
|
||||||
|
<property name="fieldGrowthPolicy">
|
||||||
|
<enum>QFormLayout::ExpandingFieldsGrow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="rowWrapPolicy">
|
||||||
|
<enum>QFormLayout::DontWrapRows</enum>
|
||||||
|
</property>
|
||||||
|
<property name="labelAlignment">
|
||||||
|
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
|
||||||
|
</property>
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="stepLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Time step:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QSpinBox" name="stepSpinBox">
|
||||||
|
<property name="suffix">
|
||||||
|
<string comment="Seconds"> sec</string>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>60</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>30</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="digitsLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Code size:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QRadioButton" name="radio6Digits">
|
||||||
|
<property name="text">
|
||||||
|
<string>6 digits</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="QRadioButton" name="radio8Digits">
|
||||||
|
<property name="text">
|
||||||
|
<string>8 digits</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
<buttongroups>
|
||||||
|
<buttongroup name="settingsButtonGroup"/>
|
||||||
|
</buttongroups>
|
||||||
|
</ui>
|
@ -28,16 +28,8 @@
|
|||||||
#include "gui/MessageWidget.h"
|
#include "gui/MessageWidget.h"
|
||||||
|
|
||||||
// I wanted to make the CSV import GUI future-proof, so if one day you need a new field,
|
// 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!
|
// 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)
|
CsvImportWidget::CsvImportWidget(QWidget* parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
@ -45,6 +37,10 @@ CsvImportWidget::CsvImportWidget(QWidget* parent)
|
|||||||
, m_parserModel(new CsvParserModel(this))
|
, m_parserModel(new CsvParserModel(this))
|
||||||
, m_comboModel(new QStringListModel(this))
|
, m_comboModel(new QStringListModel(this))
|
||||||
, m_comboMapper(new QSignalMapper(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);
|
m_ui->setupUi(this);
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ private:
|
|||||||
QList<QComboBox*> m_combos;
|
QList<QComboBox*> m_combos;
|
||||||
Database* m_db;
|
Database* m_db;
|
||||||
|
|
||||||
static const QStringList m_columnHeader;
|
const QStringList m_columnHeader;
|
||||||
QStringList m_fieldSeparatorList;
|
QStringList m_fieldSeparatorList;
|
||||||
void configParser();
|
void configParser();
|
||||||
void updateTableview();
|
void updateTableview();
|
||||||
|
@ -30,17 +30,13 @@
|
|||||||
#include "core/Group.h"
|
#include "core/Group.h"
|
||||||
#include "core/Metadata.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)
|
EntryModel::EntryModel(QObject* parent)
|
||||||
: QAbstractTableModel(parent)
|
: QAbstractTableModel(parent)
|
||||||
, m_group(nullptr)
|
, m_group(nullptr)
|
||||||
, m_hideUsernames(false)
|
, m_hideUsernames(false)
|
||||||
, m_hidePasswords(true)
|
, m_hidePasswords(true)
|
||||||
|
, HiddenContentDisplay(QString("\u25cf").repeated(6))
|
||||||
|
, DateFormat(Qt::DefaultLocaleShortDate)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +127,7 @@ int EntryModel::columnCount(const QModelIndex& parent) const
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 12;
|
return 13;
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant EntryModel::data(const QModelIndex& index, int role) const
|
QVariant EntryModel::data(const QModelIndex& index, int role) const
|
||||||
@ -205,16 +201,20 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const
|
|||||||
case Accessed:
|
case Accessed:
|
||||||
result = entry->timeInfo().lastAccessTime().toLocalTime().toString(EntryModel::DateFormat);
|
result = entry->timeInfo().lastAccessTime().toLocalTime().toString(EntryModel::DateFormat);
|
||||||
return result;
|
return result;
|
||||||
case Attachments:
|
case Attachments: {
|
||||||
// Display comma-separated list of attachments
|
// Display comma-separated list of attachments
|
||||||
QList<QString> attachments = entry->attachments()->keys();
|
QList<QString> attachments = entry->attachments()->keys();
|
||||||
for (int i = 0; i < attachments.size(); ++i) {
|
for (int i = 0; i < attachments.size(); ++i) {
|
||||||
if (result.isEmpty()) {
|
if (result.isEmpty()) {
|
||||||
result.append(attachments.at(i));
|
result.append(attachments.at(i));
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
result.append(QString(", ") + attachments.at(i));
|
||||||
}
|
}
|
||||||
result.append(QString(", ") + attachments.at(i));
|
return result;
|
||||||
}
|
}
|
||||||
|
case Totp:
|
||||||
|
result = entry->hasTotp() ? tr("Yes") : "";
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
} else if (role == Qt::UserRole) { // Qt::UserRole is used as sort role, see EntryView::EntryView()
|
} else if (role == Qt::UserRole) { // Qt::UserRole is used as sort role, see EntryView::EntryView()
|
||||||
@ -313,6 +313,8 @@ QVariant EntryModel::headerData(int section, Qt::Orientation orientation, int ro
|
|||||||
return tr("Accessed");
|
return tr("Accessed");
|
||||||
case Attachments:
|
case Attachments:
|
||||||
return tr("Attachments");
|
return tr("Attachments");
|
||||||
|
case Totp:
|
||||||
|
return tr("TOTP");
|
||||||
}
|
}
|
||||||
} else if (role == Qt::DecorationRole) {
|
} else if (role == Qt::DecorationRole) {
|
||||||
if (section == Paperclip) {
|
if (section == Paperclip) {
|
||||||
|
@ -42,7 +42,8 @@ public:
|
|||||||
Modified = 8,
|
Modified = 8,
|
||||||
Accessed = 9,
|
Accessed = 9,
|
||||||
Paperclip = 10,
|
Paperclip = 10,
|
||||||
Attachments = 11
|
Attachments = 11,
|
||||||
|
Totp = 12
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit EntryModel(QObject* parent = nullptr);
|
explicit EntryModel(QObject* parent = nullptr);
|
||||||
@ -100,8 +101,8 @@ private:
|
|||||||
|
|
||||||
QPixmap m_paperClipPixmap;
|
QPixmap m_paperClipPixmap;
|
||||||
|
|
||||||
static const QString HiddenContentDisplay;
|
const QString HiddenContentDisplay;
|
||||||
static const Qt::DateFormat DateFormat;
|
const Qt::DateFormat DateFormat;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // KEEPASSX_ENTRYMODEL_H
|
#endif // KEEPASSX_ENTRYMODEL_H
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
#include "totp.h"
|
#include "totp.h"
|
||||||
#include "core/Base32.h"
|
#include "core/Base32.h"
|
||||||
|
|
||||||
#include <QCryptographicHash>
|
#include <QCryptographicHash>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QMessageAuthenticationCode>
|
#include <QMessageAuthenticationCode>
|
||||||
@ -28,116 +29,119 @@
|
|||||||
#include <QtEndian>
|
#include <QtEndian>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
const quint8 Totp::defaultStep = 30;
|
static QList<Totp::Encoder> encoders {
|
||||||
const quint8 Totp::defaultDigits = 6;
|
{"", "", "0123456789", Totp::DEFAULT_DIGITS, Totp::DEFAULT_STEP, false},
|
||||||
|
{"steam", Totp::STEAM_SHORTNAME, "23456789BCDFGHJKMNPQRTVWXY", Totp::STEAM_DIGITS, Totp::DEFAULT_STEP, true},
|
||||||
/**
|
|
||||||
* 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<quint8, Totp::Encoder> Totp::encoders{
|
|
||||||
{Totp::ENCODER_STEAM, {"steam", "S", "23456789BCDFGHJKMNPQRTVWXY", 5, 30, true}},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
QSharedPointer<Totp::Settings> Totp::parseSettings(const QString& rawSettings, const QString& key)
|
||||||
* 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<QString, quint8> 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<QString, quint8> Totp::nameToEncoder{
|
|
||||||
{"steam", Totp::ENCODER_STEAM},
|
|
||||||
};
|
|
||||||
|
|
||||||
Totp::Totp()
|
|
||||||
{
|
{
|
||||||
}
|
// Create default settings
|
||||||
|
auto settings = createSettings(key, DEFAULT_DIGITS, DEFAULT_STEP);
|
||||||
|
|
||||||
QString Totp::parseOtpString(QString key, quint8& digits, quint8& step)
|
QUrl url(rawSettings);
|
||||||
{
|
|
||||||
QUrl url(key);
|
|
||||||
|
|
||||||
QString seed;
|
|
||||||
uint q_digits, q_step;
|
|
||||||
|
|
||||||
// Default OTP url format
|
|
||||||
if (url.isValid() && url.scheme() == "otpauth") {
|
if (url.isValid() && url.scheme() == "otpauth") {
|
||||||
|
// Default OTP url format
|
||||||
QUrlQuery query(url);
|
QUrlQuery query(url);
|
||||||
|
settings->otpUrl = true;
|
||||||
seed = query.queryItemValue("secret");
|
settings->key = query.queryItemValue("secret");
|
||||||
|
settings->digits = query.queryItemValue("digits").toUInt();
|
||||||
q_digits = query.queryItemValue("digits").toUInt();
|
settings->step = query.queryItemValue("period").toUInt();
|
||||||
if (q_digits == 6 || q_digits == 8) {
|
if (query.hasQueryItem("encoder")) {
|
||||||
digits = q_digits;
|
settings->encoder = getEncoderByName(query.queryItemValue("encoder"));
|
||||||
}
|
|
||||||
|
|
||||||
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];
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Compatibility with "KeeOtp" plugin string format
|
QUrlQuery query(rawSettings);
|
||||||
QRegExp rx("key=(.+)", Qt::CaseInsensitive, QRegExp::RegExp);
|
if (query.hasQueryItem("key")) {
|
||||||
|
// Compatibility with "KeeOtp" plugin
|
||||||
if (rx.exactMatch(key)) {
|
// if settings are changed, will convert to semi-colon format
|
||||||
QUrlQuery query(key);
|
settings->key = query.queryItemValue("key");
|
||||||
|
settings->digits = query.queryItemValue("size").toUInt();
|
||||||
seed = query.queryItemValue("key");
|
settings->step = query.queryItemValue("step").toUInt();
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} 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) {
|
// Bound digits and step
|
||||||
digits = defaultDigits;
|
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) {
|
return settings;
|
||||||
step = defaultStep;
|
|
||||||
}
|
|
||||||
|
|
||||||
return seed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Totp::generateTotp(const QByteArray key,
|
QSharedPointer<Totp::Settings> Totp::createSettings(const QString& key, const uint digits, const uint step,
|
||||||
quint64 time,
|
const QString& encoderShortName)
|
||||||
const quint8 numDigits = defaultDigits,
|
|
||||||
const quint8 step = defaultStep)
|
|
||||||
{
|
{
|
||||||
quint64 current = qToBigEndian(time / step);
|
bool isCustom = digits != DEFAULT_DIGITS || step != DEFAULT_STEP;
|
||||||
|
return QSharedPointer<Totp::Settings>(new Totp::Settings {
|
||||||
|
getEncoderByShortName(encoderShortName), key, false, isCustom, digits, step
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QVariant secret = Base32::decode(Base32::sanitizeInput(key));
|
QString Totp::writeSettings(const QSharedPointer<Totp::Settings> 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);
|
||||||
|
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<Totp::Settings> 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) {
|
||||||
|
// TODO: Replace toTime_t() with toSecsSinceEpoch() when minimum Qt >= 5.8
|
||||||
|
current = qToBigEndian(static_cast<quint64>(QDateTime::currentDateTime().toTime_t()) / step);
|
||||||
|
} else {
|
||||||
|
current = qToBigEndian(time / step);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant secret = Base32::decode(Base32::sanitizeInput(settings->key.toLatin1()));
|
||||||
if (secret.isNull()) {
|
if (secret.isNull()) {
|
||||||
return "Invalid TOTP secret key";
|
return QObject::tr("Invalid Key", "TOTP");
|
||||||
}
|
}
|
||||||
|
|
||||||
QMessageAuthenticationCode code(QCryptographicHash::Sha1);
|
QMessageAuthenticationCode code(QCryptographicHash::Sha1);
|
||||||
@ -155,9 +159,6 @@ QString Totp::generateTotp(const QByteArray key,
|
|||||||
| (hmac[offset + 3] & 0xff);
|
| (hmac[offset + 3] & 0xff);
|
||||||
// clang-format on
|
// 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 direction = -1;
|
||||||
int startpos = digits - 1;
|
int startpos = digits - 1;
|
||||||
if (encoder.reverse) {
|
if (encoder.reverse) {
|
||||||
@ -175,26 +176,34 @@ QString Totp::generateTotp(const QByteArray key,
|
|||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
// See: https://github.com/google/google-authenticator/wiki/Key-Uri-Format
|
Totp::Encoder& Totp::defaultEncoder()
|
||||||
QUrl Totp::generateOtpString(const QString& secret,
|
|
||||||
const QString& type,
|
|
||||||
const QString& issuer,
|
|
||||||
const QString& username,
|
|
||||||
const QString& algorithm,
|
|
||||||
quint8 digits,
|
|
||||||
quint8 step)
|
|
||||||
{
|
{
|
||||||
QUrl keyUri;
|
// The first encoder is always the default
|
||||||
keyUri.setScheme("otpauth");
|
Q_ASSERT(!encoders.empty());
|
||||||
keyUri.setHost(type);
|
return encoders[0];
|
||||||
keyUri.setPath(QString("/%1:%2").arg(issuer).arg(username));
|
}
|
||||||
QUrlQuery parameters;
|
|
||||||
parameters.addQueryItem("secret", secret);
|
Totp::Encoder& Totp::steamEncoder()
|
||||||
parameters.addQueryItem("issuer", issuer);
|
{
|
||||||
parameters.addQueryItem("algorithm", algorithm);
|
return getEncoderByShortName("S");
|
||||||
parameters.addQueryItem("digits", QString::number(digits));
|
}
|
||||||
parameters.addQueryItem("period", QString::number(step));
|
|
||||||
keyUri.setQuery(parameters);
|
Totp::Encoder& Totp::getEncoderByShortName(QString shortName)
|
||||||
|
{
|
||||||
return keyUri;
|
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();
|
||||||
}
|
}
|
||||||
|
@ -22,39 +22,52 @@
|
|||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QtCore/qglobal.h>
|
#include <QtCore/qglobal.h>
|
||||||
|
#include <QtCore/QSharedPointer>
|
||||||
|
|
||||||
class QUrl;
|
class QUrl;
|
||||||
|
|
||||||
class Totp
|
namespace Totp {
|
||||||
|
|
||||||
|
struct Encoder
|
||||||
{
|
{
|
||||||
public:
|
QString name;
|
||||||
Totp();
|
QString shortName;
|
||||||
static QString parseOtpString(QString rawSecret, quint8& digits, quint8& step);
|
QString alphabet;
|
||||||
static QString generateTotp(const QByteArray key, quint64 time, const quint8 numDigits, const quint8 step);
|
uint digits;
|
||||||
static QUrl generateOtpString(const QString& secret,
|
uint step;
|
||||||
const QString& type,
|
bool reverse;
|
||||||
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<quint8, Encoder> encoders;
|
|
||||||
static const QMap<QString, quint8> shortNameToEncoder;
|
|
||||||
static const QMap<QString, quint8> nameToEncoder;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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<Totp::Settings> parseSettings(const QString& rawSettings, const QString& key = {});
|
||||||
|
QSharedPointer<Totp::Settings> createSettings(const QString& key, const uint digits, const uint step,
|
||||||
|
const QString& encoderShortName = {});
|
||||||
|
QString writeSettings(const QSharedPointer<Totp::Settings> settings);
|
||||||
|
|
||||||
|
QString generateTotp(const QSharedPointer<Totp::Settings> settings, const quint64 time = 0ull);
|
||||||
|
|
||||||
|
Encoder& defaultEncoder();
|
||||||
|
Encoder& steamEncoder();
|
||||||
|
Encoder& getEncoderByShortName(QString shortName);
|
||||||
|
Encoder& getEncoderByName(QString name);
|
||||||
|
}
|
||||||
|
|
||||||
#endif // QTOTP_H
|
#endif // QTOTP_H
|
||||||
|
@ -291,11 +291,11 @@ void TestEntryModel::testProxyModel()
|
|||||||
* @author Fonic <https://github.com/fonic>
|
* @author Fonic <https://github.com/fonic>
|
||||||
* Update comparison value of modelProxy->columnCount() to account for
|
* Update comparison value of modelProxy->columnCount() to account for
|
||||||
* additional columns 'Password', 'Notes', 'Expires', 'Created', 'Modified',
|
* additional columns 'Password', 'Notes', 'Expires', 'Created', 'Modified',
|
||||||
* 'Accessed', 'Paperclip' and 'Attachments'
|
* 'Accessed', 'Paperclip', 'Attachments', and TOTP
|
||||||
*/
|
*/
|
||||||
QSignalSpy spyColumnRemove(modelProxy, SIGNAL(columnsAboutToBeRemoved(QModelIndex, int, int)));
|
QSignalSpy spyColumnRemove(modelProxy, SIGNAL(columnsAboutToBeRemoved(QModelIndex, int, int)));
|
||||||
modelProxy->hideColumn(0, true);
|
modelProxy->hideColumn(0, true);
|
||||||
QCOMPARE(modelProxy->columnCount(), 11);
|
QCOMPARE(modelProxy->columnCount(), 12);
|
||||||
QVERIFY(spyColumnRemove.size() >= 1);
|
QVERIFY(spyColumnRemove.size() >= 1);
|
||||||
|
|
||||||
int oldSpyColumnRemoveSize = spyColumnRemove.size();
|
int oldSpyColumnRemoveSize = spyColumnRemove.size();
|
||||||
@ -313,11 +313,11 @@ void TestEntryModel::testProxyModel()
|
|||||||
* @author Fonic <https://github.com/fonic>
|
* @author Fonic <https://github.com/fonic>
|
||||||
* Update comparison value of modelProxy->columnCount() to account for
|
* Update comparison value of modelProxy->columnCount() to account for
|
||||||
* additional columns 'Password', 'Notes', 'Expires', 'Created', 'Modified',
|
* additional columns 'Password', 'Notes', 'Expires', 'Created', 'Modified',
|
||||||
* 'Accessed', 'Paperclip' and 'Attachments'
|
* 'Accessed', 'Paperclip', 'Attachments', and TOTP
|
||||||
*/
|
*/
|
||||||
QSignalSpy spyColumnInsert(modelProxy, SIGNAL(columnsAboutToBeInserted(QModelIndex, int, int)));
|
QSignalSpy spyColumnInsert(modelProxy, SIGNAL(columnsAboutToBeInserted(QModelIndex, int, int)));
|
||||||
modelProxy->hideColumn(0, false);
|
modelProxy->hideColumn(0, false);
|
||||||
QCOMPARE(modelProxy->columnCount(), 12);
|
QCOMPARE(modelProxy->columnCount(), 13);
|
||||||
QVERIFY(spyColumnInsert.size() >= 1);
|
QVERIFY(spyColumnInsert.size() >= 1);
|
||||||
|
|
||||||
int oldSpyColumnInsertSize = spyColumnInsert.size();
|
int oldSpyColumnInsertSize = spyColumnInsert.size();
|
||||||
|
@ -31,138 +31,107 @@ void TestTotp::initTestCase()
|
|||||||
|
|
||||||
void TestTotp::testParseSecret()
|
void TestTotp::testParseSecret()
|
||||||
{
|
{
|
||||||
quint8 digits = 0;
|
// OTP URL Parsing
|
||||||
quint8 step = 0;
|
|
||||||
QString secret = "otpauth://totp/"
|
QString secret = "otpauth://totp/"
|
||||||
"ACME%20Co:john@example.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm="
|
"ACME%20Co:john@example.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm="
|
||||||
"SHA1&digits=6&period=30";
|
"SHA1&digits=6&period=30";
|
||||||
QCOMPARE(Totp::parseOtpString(secret, digits, step), QString("HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ"));
|
auto settings = Totp::parseSettings(secret);
|
||||||
QCOMPARE(digits, quint8(6));
|
QVERIFY(!settings.isNull());
|
||||||
QCOMPARE(step, quint8(30));
|
QCOMPARE(settings->key, QString("HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ"));
|
||||||
|
QCOMPARE(settings->custom, false);
|
||||||
|
QCOMPARE(settings->digits, 6u);
|
||||||
|
QCOMPARE(settings->step, 30u);
|
||||||
|
|
||||||
digits = Totp::defaultDigits;
|
// KeeOTP Parsing
|
||||||
step = Totp::defaultStep;
|
|
||||||
secret = "key=HXDMVJECJJWSRBY%3d&step=25&size=8";
|
secret = "key=HXDMVJECJJWSRBY%3d&step=25&size=8";
|
||||||
QCOMPARE(Totp::parseOtpString(secret, digits, step), QString("HXDMVJECJJWSRBY="));
|
settings = Totp::parseSettings(secret);
|
||||||
QCOMPARE(digits, quint8(8));
|
QVERIFY(!settings.isNull());
|
||||||
QCOMPARE(step, quint8(25));
|
QCOMPARE(settings->key, QString("HXDMVJECJJWSRBY="));
|
||||||
|
QCOMPARE(settings->custom, true);
|
||||||
|
QCOMPARE(settings->digits, 8u);
|
||||||
|
QCOMPARE(settings->step, 25u);
|
||||||
|
|
||||||
digits = 0;
|
// Semi-colon delineated "TOTP Settings"
|
||||||
step = 0;
|
|
||||||
secret = "gezdgnbvgy3tqojqgezdgnbvgy3tqojq";
|
secret = "gezdgnbvgy3tqojqgezdgnbvgy3tqojq";
|
||||||
QCOMPARE(Totp::parseOtpString(secret, digits, step), QString("gezdgnbvgy3tqojqgezdgnbvgy3tqojq"));
|
settings = Totp::parseSettings("30;8", secret);
|
||||||
QCOMPARE(digits, quint8(6));
|
QVERIFY(!settings.isNull());
|
||||||
QCOMPARE(step, quint8(30));
|
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()
|
void TestTotp::testTotpCode()
|
||||||
{
|
{
|
||||||
// Test vectors from RFC 6238
|
// Test vectors from RFC 6238
|
||||||
// https://tools.ietf.org/html/rfc6238#appendix-B
|
// 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;
|
quint64 time = 1234567890;
|
||||||
QString output = Totp::generateTotp(seed, time, 6, 30);
|
QCOMPARE(Totp::generateTotp(settings, time), QString("005924"));
|
||||||
QCOMPARE(output, QString("005924"));
|
|
||||||
|
|
||||||
time = 1111111109;
|
time = 1111111109;
|
||||||
output = Totp::generateTotp(seed, time, 6, 30);
|
QCOMPARE(Totp::generateTotp(settings, time), QString("081804"));
|
||||||
QCOMPARE(output, QString("081804"));
|
|
||||||
|
|
||||||
|
// Test 8 digit TOTP (custom)
|
||||||
|
settings->digits = 8;
|
||||||
|
settings->custom = true;
|
||||||
time = 1111111111;
|
time = 1111111111;
|
||||||
output = Totp::generateTotp(seed, time, 8, 30);
|
QCOMPARE(Totp::generateTotp(settings, time), QString("14050471"));
|
||||||
QCOMPARE(output, QString("14050471"));
|
|
||||||
|
|
||||||
time = 2000000000;
|
time = 2000000000;
|
||||||
output = Totp::generateTotp(seed, time, 8, 30);
|
QCOMPARE(Totp::generateTotp(settings, time), QString("69279037"));
|
||||||
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)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestTotp::testSteamTotp()
|
void TestTotp::testSteamTotp()
|
||||||
{
|
{
|
||||||
quint8 digits = 0;
|
// OTP URL Parsing
|
||||||
quint8 step = 0;
|
|
||||||
QString secret = "otpauth://totp/"
|
QString secret = "otpauth://totp/"
|
||||||
"test:test@example.com?secret=63BEDWCQZKTQWPESARIERL5DTTQFCJTK&issuer=Valve&algorithm="
|
"test:test@example.com?secret=63BEDWCQZKTQWPESARIERL5DTTQFCJTK&issuer=Valve&algorithm="
|
||||||
"SHA1&digits=5&period=30&encoder=steam";
|
"SHA1&digits=5&period=30&encoder=steam";
|
||||||
QCOMPARE(Totp::parseOtpString(secret, digits, step), QString("63BEDWCQZKTQWPESARIERL5DTTQFCJTK"));
|
auto settings = Totp::parseSettings(secret);
|
||||||
QCOMPARE(digits, quint8(Totp::ENCODER_STEAM));
|
|
||||||
QCOMPARE(step, quint8(30));
|
|
||||||
|
|
||||||
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
|
// 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
|
// 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.
|
// from the Steam app's data for use in testing here.
|
||||||
quint64 time = 1511200518;
|
quint64 time = 1511200518;
|
||||||
QCOMPARE(Totp::generateTotp(seed, time, Totp::ENCODER_STEAM, 30), QString("FR8RV"));
|
QCOMPARE(Totp::generateTotp(settings, time), QString("FR8RV"));
|
||||||
time = 1511200714;
|
time = 1511200714;
|
||||||
QCOMPARE(Totp::generateTotp(seed, time, Totp::ENCODER_STEAM, 30), QString("9P3VP"));
|
QCOMPARE(Totp::generateTotp(settings, time), QString("9P3VP"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestTotp::testEntryHistory()
|
void TestTotp::testEntryHistory()
|
||||||
{
|
{
|
||||||
Entry entry;
|
Entry entry;
|
||||||
quint8 step = 16;
|
uint step = 16;
|
||||||
quint8 digits = 6;
|
uint digits = 6;
|
||||||
|
auto settings = Totp::createSettings("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ", digits, step);
|
||||||
|
// Test that entry starts without TOTP
|
||||||
QCOMPARE(entry.historyItems().size(), 0);
|
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);
|
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.historyItems().size(), 2);
|
||||||
|
QCOMPARE(entry.totpSettings()->key, QString("foo"));
|
||||||
}
|
}
|
||||||
|
@ -21,8 +21,6 @@
|
|||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
class Totp;
|
|
||||||
|
|
||||||
class TestTotp : public QObject
|
class TestTotp : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -31,7 +29,6 @@ private slots:
|
|||||||
void initTestCase();
|
void initTestCase();
|
||||||
void testParseSecret();
|
void testParseSecret();
|
||||||
void testTotpCode();
|
void testTotpCode();
|
||||||
void testEncoderData();
|
|
||||||
void testSteamTotp();
|
void testSteamTotp();
|
||||||
void testEntryHistory();
|
void testEntryHistory();
|
||||||
};
|
};
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
#include "gui/MessageBox.h"
|
#include "gui/MessageBox.h"
|
||||||
#include "gui/PasswordEdit.h"
|
#include "gui/PasswordEdit.h"
|
||||||
#include "gui/SearchWidget.h"
|
#include "gui/SearchWidget.h"
|
||||||
#include "gui/SetupTotpDialog.h"
|
#include "gui/TotpSetupDialog.h"
|
||||||
#include "gui/TotpDialog.h"
|
#include "gui/TotpDialog.h"
|
||||||
#include "gui/entry/EditEntryWidget.h"
|
#include "gui/entry/EditEntryWidget.h"
|
||||||
#include "gui/entry/EntryView.h"
|
#include "gui/entry/EntryView.h"
|
||||||
@ -636,7 +636,7 @@ void TestGui::testTotp()
|
|||||||
|
|
||||||
triggerAction("actionEntrySetupTotp");
|
triggerAction("actionEntrySetupTotp");
|
||||||
|
|
||||||
SetupTotpDialog* setupTotpDialog = m_dbWidget->findChild<SetupTotpDialog*>("SetupTotpDialog");
|
TotpSetupDialog* setupTotpDialog = m_dbWidget->findChild<TotpSetupDialog*>("TotpSetupDialog");
|
||||||
|
|
||||||
Tools::wait(100);
|
Tools::wait(100);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user