diff options
author | Janek Bevendorff <janek@jbev.net> | 2020-04-26 02:31:38 +0300 |
---|---|---|
committer | Janek Bevendorff <janek@jbev.net> | 2020-05-02 23:30:27 +0300 |
commit | 596d2cf425c3c8495b4c4a58c61798afe9fdd536 (patch) | |
tree | f550255e5355098dd91ba9cd73c7289ac5b352d3 /src/core | |
parent | 5add01243d6c86c6d59d5018f860add377575259 (diff) |
Refactor Config.
Replaces all string configuration options with enum types
that can be checked by the compiler. This prevents spelling
errors, in-place configuration definitions, and inconsistent
default values. The default value config getter signature was
removed in favour of consistently and centrally default-initialised
configuration values.
Individual default values were adjusted for better security,
such as the default password length, which was increased from
16 characters to 32.
The already existing config option deprecation map was extended
by a general migration procedure using configuration versioning.
Settings were split into Roaming and Local settings, which
go to their respective AppData locations on Windows.
Fixes #2574
Fixes #2193
Diffstat (limited to 'src/core')
-rw-r--r-- | src/core/Bootstrap.cpp | 8 | ||||
-rw-r--r-- | src/core/Config.cpp | 502 | ||||
-rw-r--r-- | src/core/Config.h | 166 | ||||
-rw-r--r-- | src/core/Group.cpp | 4 | ||||
-rw-r--r-- | src/core/IconDownloader.cpp | 4 | ||||
-rw-r--r-- | src/core/PasswordGenerator.h | 16 | ||||
-rw-r--r-- | src/core/Resources.cpp | 2 | ||||
-rw-r--r-- | src/core/Translator.cpp | 2 |
8 files changed, 539 insertions, 165 deletions
diff --git a/src/core/Bootstrap.cpp b/src/core/Bootstrap.cpp index 99b950928..3f5a67a05 100644 --- a/src/core/Bootstrap.cpp +++ b/src/core/Bootstrap.cpp @@ -100,7 +100,7 @@ namespace Bootstrap void restoreMainWindowState(MainWindow& mainWindow) { // start minimized if configured - if (config()->get("GUI/MinimizeOnStartup").toBool()) { + if (config()->get(Config::GUI_MinimizeOnStartup).toBool()) { #ifdef Q_OS_WIN mainWindow.showMinimized(); #else @@ -110,14 +110,14 @@ namespace Bootstrap mainWindow.bringToFront(); } - if (config()->get("OpenPreviousDatabasesOnStartup").toBool()) { - const QStringList fileNames = config()->get("LastOpenedDatabases").toStringList(); + if (config()->get(Config::OpenPreviousDatabasesOnStartup).toBool()) { + const QStringList fileNames = config()->get(Config::LastOpenedDatabases).toStringList(); for (const QString& filename : fileNames) { if (!filename.isEmpty() && QFile::exists(filename)) { mainWindow.openDatabase(filename); } } - auto lastActiveFile = config()->get("LastActiveDatabase").toString(); + auto lastActiveFile = config()->get(Config::LastActiveDatabase).toString(); if (!lastActiveFile.isEmpty()) { mainWindow.openDatabase(lastActiveFile); } diff --git a/src/core/Config.cpp b/src/core/Config.cpp index 5965367b1..f874d8fb2 100644 --- a/src/core/Config.cpp +++ b/src/core/Config.cpp @@ -1,6 +1,6 @@ /* + * Copyright (C) 2020 KeePassXC Team <team@keepassxc.org> * Copyright (C) 2011 Felix Geyer <debfx@fobos.de> - * 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 @@ -17,40 +17,202 @@ */ #include "Config.h" +#include "Global.h" #include <QCoreApplication> #include <QDir> +#include <QHash> #include <QSettings> +#include <QSize> #include <QStandardPaths> #include <QTemporaryFile> -/* - * Map of configuration file settings that are either deprecated, or have - * had their name changed. Entries in the map are of the form - * {oldName, newName} - * Set newName to empty string to remove the setting from the file. - */ -static const QMap<QString, QString> deprecationMap = { - // >2.3.4 - {QStringLiteral("security/hidepassworddetails"), QStringLiteral("security/HidePasswordPreviewPanel")}, - // >2.3.4 - {QStringLiteral("GUI/HideDetailsView"), QStringLiteral("GUI/HidePreviewPanel")}, - // >2.3.4 - {QStringLiteral("GUI/DetailSplitterState"), QStringLiteral("GUI/PreviewSplitterState")}, - // >2.3.4 - {QStringLiteral("security/IconDownloadFallbackToGoogle"), QStringLiteral("security/IconDownloadFallback")}, -}; +#define CONFIG_VERSION 1 +#define QS QStringLiteral -QPointer<Config> Config::m_instance(nullptr); +enum ConfigType +{ + Local, + Roaming +}; -QVariant Config::get(const QString& key) +struct ConfigDirective { - return m_settings->value(key, m_defaults.value(key)); -} + QString name; + ConfigType type; + QVariant defaultValue; +}; + +// clang-format off + +/** + * Map of legal config values, their type and default value. + */ +static const QHash<Config::ConfigKey, ConfigDirective> configStrings = { + // General + {Config::SingleInstance,{QS("SingleInstance"), Roaming, true}}, + {Config::RememberLastDatabases,{QS("RememberLastDatabases"), Roaming, true}}, + {Config::NumberOfRememberedLastDatabases,{QS("NumberOfRememberedLastDatabases"), Roaming, 5}}, + {Config::RememberLastKeyFiles,{QS("RememberLastKeyFiles"), Roaming, true}}, + {Config::OpenPreviousDatabasesOnStartup,{QS("OpenPreviousDatabasesOnStartup"), Roaming, true}}, + {Config::AutoSaveAfterEveryChange,{QS("AutoSaveAfterEveryChange"), Roaming, true}}, + {Config::AutoReloadOnChange,{QS("AutoReloadOnChange"), Roaming, true}}, + {Config::AutoSaveOnExit,{QS("AutoSaveOnExit"), Roaming, true}}, + {Config::BackupBeforeSave,{QS("BackupBeforeSave"), Roaming, false}}, + {Config::UseAtomicSaves,{QS("UseAtomicSaves"), Roaming, true}}, + {Config::SearchLimitGroup,{QS("SearchLimitGroup"), Roaming, false}}, + {Config::MinimizeOnOpenUrl,{QS("MinimizeOnOpenUrl"), Roaming, false}}, + {Config::HideWindowOnCopy,{QS("HideWindowOnCopy"), Roaming, false}}, + {Config::MinimizeOnCopy,{QS("MinimizeOnCopy"), Roaming, true}}, + {Config::MinimizeAfterUnlock,{QS("MinimizeAfterUnlock"), Roaming, false}}, + {Config::DropToBackgroundOnCopy,{QS("DropToBackgroundOnCopy"), Roaming, false}}, + {Config::UseGroupIconOnEntryCreation,{QS("UseGroupIconOnEntryCreation"), Roaming, true}}, + {Config::AutoTypeEntryTitleMatch,{QS("AutoTypeEntryTitleMatch"), Roaming, true}}, + {Config::AutoTypeEntryURLMatch,{QS("AutoTypeEntryURLMatch"), Roaming, true}}, + {Config::AutoTypeDelay,{QS("AutoTypeDelay"), Roaming, 25}}, + {Config::AutoTypeStartDelay,{QS("AutoTypeStartDelay"), Roaming, 500}}, + {Config::GlobalAutoTypeKey,{QS("GlobalAutoTypeKey"), Roaming, 0}}, + {Config::GlobalAutoTypeModifiers,{QS("GlobalAutoTypeModifiers"), Roaming, 0}}, + {Config::IgnoreGroupExpansion,{QS("IgnoreGroupExpansion"), Roaming, true}}, + {Config::FaviconDownloadTimeout,{QS("FaviconDownloadTimeout"), Roaming, 10}}, + {Config::UpdateCheckMessageShown,{QS("UpdateCheckMessageShown"), Roaming, true}}, + {Config::UseTouchID,{QS("UseTouchID"), Roaming, false}}, + + {Config::LastDatabases, {QS("LastDatabases"), Local, {}}}, + {Config::LastKeyFiles, {QS("LastKeyFiles"), Local, {}}}, + {Config::LastChallengeResponse, {QS("LastChallengeResponse"), Local, {}}}, + {Config::LastActiveDatabase, {QS("LastActiveDatabase"), Local, {}}}, + {Config::LastOpenedDatabases, {QS("LastOpenedDatabases"), Local, {}}}, + {Config::LastDir, {QS("LastDir"), Local, QDir::homePath()}}, + {Config::LastAttachmentDir, {QS("LastAttachmentDir"), Local, {}}}, + + // GUI + {Config::GUI_Language, {QS("GUI/Language"), Roaming, QS("system")}}, + {Config::GUI_HideToolbar, {QS("GUI/HideToolbar"), Roaming, false}}, + {Config::GUI_MovableToolbar, {QS("GUI/MovableToolbar"), Roaming, false}}, + {Config::GUI_HidePreviewPanel, {QS("GUI/HidePreviewPanel"), Roaming, false}}, + {Config::GUI_ToolButtonStyle, {QS("GUI/ToolButtonStyle"), Roaming, Qt::ToolButtonIconOnly}}, + {Config::GUI_ShowTrayIcon, {QS("GUI/ShowTrayIcon"), Roaming, false}}, + {Config::GUI_DarkTrayIcon, {QS("GUI/DarkTrayIcon"), Roaming, false}}, + {Config::GUI_MinimizeToTray, {QS("GUI/MinimizeToTray"), Roaming, false}}, + {Config::GUI_MinimizeOnStartup, {QS("GUI/MinimizeOnStartup"), Roaming, false}}, + {Config::GUI_MinimizeOnClose, {QS("GUI/MinimizeOnClose"), Roaming, false}}, + {Config::GUI_HideUsernames, {QS("GUI/HideUsernames"), Roaming, false}}, + {Config::GUI_HidePasswords, {QS("GUI/HidePasswords"), Roaming, true}}, + {Config::GUI_AdvancedSettings, {QS("GUI/AdvancedSettings"), Roaming, false}}, + {Config::GUI_MonospaceNotes, {QS("GUI/MonospaceNotes"), Roaming, false}}, + {Config::GUI_ApplicationTheme, {QS("GUI/ApplicationTheme"), Roaming, QS("auto")}}, + {Config::GUI_CheckForUpdates, {QS("GUI/CheckForUpdates"), Roaming, true}}, + {Config::GUI_CheckForUpdatesIncludeBetas, {QS("GUI/CheckForUpdatesIncludeBetas"), Roaming, false}}, + + {Config::GUI_MainWindowGeometry, {QS("GUI/MainWindowGeometry"), Local, {}}}, + {Config::GUI_MainWindowState, {QS("GUI/MainWindowState"), Local, {}}}, + {Config::GUI_ListViewState, {QS("GUI/ListViewState"), Local, {}}}, + {Config::GUI_SearchViewState, {QS("GUI/SearchViewState"), Local, {}}}, + {Config::GUI_SplitterState, {QS("GUI/SplitterState"), Local, {}}}, + {Config::GUI_PreviewSplitterState, {QS("GUI/PreviewSplitterState"), Local, {}}}, + {Config::GUI_AutoTypeSelectDialogSize, {QS("GUI/AutoTypeSelectDialogSize"), Local, QSize(600, 250)}}, + {Config::GUI_CheckForUpdatesNextCheck, {QS("GUI/AutoTypeSelectDialogSize"), Local, 0}}, + + // Security + {Config::Security_ClearClipboard, {QS("Security/ClearClipboard"), Roaming, true}}, + {Config::Security_ClearClipboardTimeout, {QS("Security/ClearClipboardTimeout"), Roaming, 10}}, + {Config::Security_ClearSearch, {QS("Security/ClearSearch"), Roaming, true}}, + {Config::Security_ClearSearchTimeout, {QS("Security/ClearSearchTimeout"), Roaming, 5}}, + {Config::Security_HideNotes, {QS("Security/Security_HideNotes"), Roaming, false}}, + {Config::Security_LockDatabaseIdle, {QS("Security/LockDatabaseIdle"), Roaming, false}}, + {Config::Security_LockDatabaseIdleSeconds, {QS("Security/LockDatabaseIdleSeconds"), Roaming, 240}}, + {Config::Security_LockDatabaseMinimize, {QS("Security/LockDatabaseMinimize"), Roaming, false}}, + {Config::Security_LockDatabaseScreenLock, {QS("Security/LockDatabaseScreenLock"), Roaming, true}}, + {Config::Security_RelockAutoType, {QS("Security/RelockAutoType"), Roaming, false}}, + {Config::Security_PasswordsRepeat, {QS("Security/PasswordsRepeat"), Roaming, false}}, + {Config::Security_PasswordsCleartext, {QS("Security/PasswordsCleartext"), Roaming, false}}, + {Config::Security_PasswordEmptyNoDots, {QS("Security/PasswordEmptyNoDots"), Roaming, true}}, + {Config::Security_HidePasswordPreviewPanel, {QS("Security/HidePasswordPreviewPanel"), Roaming, true}}, + {Config::Security_AutoTypeAsk, {QS("Security/AutotypeAsk"), Roaming, true}}, + {Config::Security_IconDownloadFallback, {QS("Security/IconDownloadFallback"), Roaming, false}}, + {Config::Security_ResetTouchId, {QS("Security/ResetTouchId"), Roaming, false}}, + {Config::Security_ResetTouchIdTimeout, {QS("Security/ResetTouchIdTimeout"), Roaming, 30}}, + {Config::Security_ResetTouchIdScreenlock,{QS("Security/ResetTouchIdScreenlock"), Roaming, true}}, + + // Browser + {Config::Browser_Enabled, {QS("Browser/Enabled"), Roaming, false}}, + {Config::Browser_ShowNotification, {QS("Browser/ShowNotification"), Roaming, true}}, + {Config::Browser_BestMatchOnly, {QS("Browser/BestMatchOnly"), Roaming, false}}, + {Config::Browser_UnlockDatabase, {QS("Browser/UnlockDatabase"), Roaming, true}}, + {Config::Browser_MatchUrlScheme, {QS("Browser/MatchUrlScheme"), Roaming, true}}, + {Config::Browser_SortByUsername, {QS("Browser/SortByUsername"), Roaming, false}}, + {Config::Browser_SupportBrowserProxy, {QS("Browser/SupportBrowserProxy"), Roaming, true}}, + {Config::Browser_UseCustomProxy, {QS("Browser/UseCustomProxy"), Roaming, false}}, + {Config::Browser_CustomProxyLocation, {QS("Browser/CustomProxyLocation"), Roaming, {}}}, + {Config::Browser_UpdateBinaryPath, {QS("Browser/UpdateBinaryPath"), Roaming, true}}, + {Config::Browser_AllowExpiredCredentials, {QS("Browser/AllowExpiredCredentials"), Roaming, false}}, + {Config::Browser_AlwaysAllowAccess, {QS("Browser/AlwaysAllowAccess"), Roaming, false}}, + {Config::Browser_AlwaysAllowUpdate, {QS("Browser/AlwaysAllowUpdate"), Roaming, false}}, + {Config::Browser_HttpAuthPermission, {QS("Browser/HttpAuthPermission"), Roaming, false}}, + {Config::Browser_SearchInAllDatabases, {QS("Browser/SearchInAllDatabases"), Roaming, false}}, + {Config::Browser_SupportKphFields, {QS("Browser/SupportKphFields"), Roaming, true}}, + {Config::Browser_NoMigrationPrompt, {QS("Browser/NoMigrationPrompt"), Roaming, false}}, + + // SSHAgent + {Config::SSHAgent_Enabled, {QS("SSHAgent/Enabled"), Roaming, false}}, + {Config::SSHAgent_UseOpenSSH, {QS("SSHAgent/UseOpenSSH"), Roaming, false}}, + {Config::SSHAgent_AuthSockOverride, {QS("SSHAgent/AuthSockOverride"), Local, {}}}, + + // FdoSecrets + {Config::FdoSecrets_Enabled, {QS("FdoSecrets/Enabled"), Roaming, false}}, + {Config::FdoSecrets_ShowNotification, {QS("FdoSecrets/ShowNotification"), Roaming, true}}, + {Config::FdoSecrets_NoConfirmDeleteItem, {QS("FdoSecrets/NoConfirmDeleteItem"), Roaming, false}}, -QVariant Config::get(const QString& key, const QVariant& defaultValue) + // KeeShare + {Config::KeeShare_QuietSuccess, {QS("KeeShare/QuietSuccess"), Roaming, false}}, + {Config::KeeShare_Own, {QS("KeeShare/Own"), Roaming, {}}}, + {Config::KeeShare_Foreign, {QS("KeeShare/Foreign"), Roaming, {}}}, + {Config::KeeShare_Active, {QS("KeeShare/Active"), Roaming, {}}}, + {Config::KeeShare_LastDir, {QS("KeeShare/LastDir"), Local, QDir::homePath()}}, + {Config::KeeShare_LastKeyDir, {QS("KeeShare/LastKeyDir"), Local, QDir::homePath()}}, + {Config::KeeShare_LastShareDir, {QS("KeeShare/LastShareDir"), Local, QDir::homePath()}}, + + // PasswordGenerator + {Config::PasswordGenerator_LowerCase, {QS("PasswordGenerator/LowerCase"), Roaming, true}}, + {Config::PasswordGenerator_UpperCase, {QS("PasswordGenerator/UpperCase"), Roaming, true}}, + {Config::PasswordGenerator_Numbers, {QS("PasswordGenerator/Numbers"), Roaming, true}}, + {Config::PasswordGenerator_EASCII, {QS("PasswordGenerator/EASCII"), Roaming, false}}, + {Config::PasswordGenerator_AdvancedMode, {QS("PasswordGenerator/AdvancedMode"), Roaming, false}}, + {Config::PasswordGenerator_SpecialChars, {QS("PasswordGenerator/SpecialChars"), Roaming, true}}, + {Config::PasswordGenerator_AdditionalChars, {QS("PasswordGenerator/AdditionalChars"), Roaming, true}}, + {Config::PasswordGenerator_Braces, {QS("PasswordGenerator/Braces"), Roaming, false}}, + {Config::PasswordGenerator_Punctuation, {QS("PasswordGenerator/Punctuation"), Roaming, false}}, + {Config::PasswordGenerator_Quotes, {QS("PasswordGenerator/Quotes"), Roaming, false}}, + {Config::PasswordGenerator_Dashes, {QS("PasswordGenerator/Dashes"), Roaming, false}}, + {Config::PasswordGenerator_Math, {QS("PasswordGenerator/Math"), Roaming, false}}, + {Config::PasswordGenerator_Logograms, {QS("PasswordGenerator/Logograms"), Roaming, false}}, + {Config::PasswordGenerator_ExcludedChars, {QS("PasswordGenerator/ExcludedChars"), Roaming, {}}}, + {Config::PasswordGenerator_ExcludeAlike, {QS("PasswordGenerator/ExcludeAlike"), Roaming, true}}, + {Config::PasswordGenerator_EnsureEvery, {QS("PasswordGenerator/EnsureEvery"), Roaming, true}}, + {Config::PasswordGenerator_Length, {QS("PasswordGenerator/Length"), Roaming, 20}}, + {Config::PasswordGenerator_WordCount, {QS("PasswordGenerator/WordCount"), Roaming, 7}}, + {Config::PasswordGenerator_WordSeparator, {QS("PasswordGenerator/WordSeparator"), Roaming, QS(" ")}}, + {Config::PasswordGenerator_WordList, {QS("PasswordGenerator/WordList"), Roaming, QS("eff_large.wordlist")}}, + {Config::PasswordGenerator_WordCase, {QS("PasswordGenerator/WordCase"), Roaming, 0}}, + {Config::PasswordGenerator_Type, {QS("PasswordGenerator/Type"), Roaming, 0}}, + + // Messages + {Config::Messages_NoLegacyKeyFileWarning, {QS("Messages/NoLegacyKeyFileWarning"), Roaming, false}}, + {Config::Messages_Qt55CompatibilityWarning, {QS("Messages/Messages_Qt55CompatibilityWarning"), Local, false}}}; + +// clang-format on + +QPointer<Config> Config::m_instance(nullptr); + +QVariant Config::get(ConfigKey key) { - return m_settings->value(key, defaultValue); + auto cfg = configStrings[key]; + auto defaultValue = configStrings[key].defaultValue; + if (m_localSettings && cfg.type == Local) { + return m_localSettings->value(cfg.name, defaultValue); + } + return m_settings->value(cfg.name, defaultValue); } bool Config::hasAccessError() @@ -63,18 +225,32 @@ QString Config::getFileName() return m_settings->fileName(); } -void Config::set(const QString& key, const QVariant& value) +void Config::set(ConfigKey key, const QVariant& value) { - if (m_settings->contains(key) && m_settings->value(key) == value) { + if (get(key) == value) { return; } - const bool surpressSignal = !m_settings->contains(key) && m_defaults.value(key) == value; - m_settings->setValue(key, value); + auto cfg = configStrings[key]; + if (cfg.type == Local && m_localSettings) { + m_localSettings->setValue(cfg.name, value); + } else { + m_settings->setValue(cfg.name, value); + } - if (!surpressSignal) { - emit changed(key); + emit changed(key); +} + +void Config::remove(ConfigKey key) +{ + auto cfg = configStrings[key]; + if (cfg.type == Local && m_localSettings) { + m_localSettings->remove(cfg.name); + } else { + m_settings->remove(cfg.name); } + + emit changed(key); } /** @@ -87,38 +263,143 @@ void Config::set(const QString& key, const QVariant& value) void Config::sync() { m_settings->sync(); + if (m_localSettings) { + m_localSettings->sync(); + } } void Config::resetToDefaults() { - for (const auto& setting : m_defaults.keys()) { - m_settings->setValue(setting, m_defaults.value(setting)); + m_settings->clear(); + if (m_localSettings) { + m_localSettings->clear(); } } -void Config::upgrade() +/** + * Map of configuration file settings that are either deprecated, or have + * had their name changed to their new config enum values. + * + * Set a value to Deleted to remove the setting. + */ +static const QHash<QString, Config::ConfigKey> deprecationMap = { + // 2.3.4 + {QS("security/hidepassworddetails"), Config::Security_HidePasswordPreviewPanel}, + {QS("GUI/HideDetailsView"), Config::GUI_HidePreviewPanel}, + {QS("GUI/DetailSplitterState"), Config::GUI_PreviewSplitterState}, + {QS("security/IconDownloadFallbackToGoogle"), Config::Security_IconDownloadFallback}, + + // 2.6.0 + {QS("security/autotypeask"), Config::Security_AutoTypeAsk}, + {QS("security/clearclipboard"), Config::Security_ClearClipboard}, + {QS("security/clearclipboardtimeout"), Config::Security_ClearClipboardTimeout}, + {QS("security/clearsearch"), Config::Security_ClearSearch}, + {QS("security/clearsearchtimeout"), Config::Security_ClearSearchTimeout}, + {QS("security/lockdatabaseidle"), Config::Security_LockDatabaseIdle}, + {QS("security/lockdatabaseidlesec"), Config::Security_LockDatabaseIdleSeconds}, + {QS("security/lockdatabaseminimize"), Config::Security_LockDatabaseMinimize}, + {QS("security/lockdatabasescreenlock"), Config::Security_LockDatabaseScreenLock}, + {QS("security/relockautotype"), Config::Security_RelockAutoType}, + {QS("security/IconDownloadFallback"), Config::Security_IconDownloadFallback}, + {QS("security/passwordscleartext"), Config::Security_PasswordsCleartext}, + {QS("security/passwordemptynodots"), Config::Security_PasswordEmptyNoDots}, + {QS("security/HidePasswordPreviewPanel"), Config::Security_HidePasswordPreviewPanel}, + {QS("security/passwordsrepeat"), Config::Security_PasswordsRepeat}, + {QS("security/hidenotes"), Config::Security_HideNotes}, + {QS("security/resettouchid"), Config::Security_ResetTouchId}, + {QS("security/resettouchidtimeout"), Config::Security_ResetTouchIdTimeout}, + {QS("security/resettouchidscreenlock"), Config::Security_ResetTouchIdScreenlock}, + {QS("KeeShare/Settings.own"), Config::KeeShare_Own}, + {QS("KeeShare/Settings.foreign"), Config::KeeShare_Foreign}, + {QS("KeeShare/Settings.active"), Config::KeeShare_Active}, + {QS("SSHAgent"), Config::SSHAgent_Enabled}, + {QS("SSHAgentOpenSSH"), Config::SSHAgent_UseOpenSSH}, + {QS("SSHAuthSockOverride"), Config::SSHAgent_AuthSockOverride}, + {QS("generator/LowerCase"), Config::PasswordGenerator_LowerCase}, + {QS("generator/UpperCase"), Config::PasswordGenerator_UpperCase}, + {QS("generator/Numbers"), Config::PasswordGenerator_Numbers}, + {QS("generator/EASCII"), Config::PasswordGenerator_EASCII}, + {QS("generator/AdvancedMode"), Config::PasswordGenerator_AdvancedMode}, + {QS("generator/SpecialChars"), Config::PasswordGenerator_SpecialChars}, + {QS("generator/AdditionalChars"), Config::PasswordGenerator_AdditionalChars}, + {QS("generator/Braces"), Config::PasswordGenerator_Braces}, + {QS("generator/Punctuation"), Config::PasswordGenerator_Punctuation}, + {QS("generator/Quotes"), Config::PasswordGenerator_Quotes}, + {QS("generator/Dashes"), Config::PasswordGenerator_Dashes}, + {QS("generator/Math"), Config::PasswordGenerator_Math}, + {QS("generator/Logograms"), Config::PasswordGenerator_Logograms}, + {QS("generator/ExcludedChars"), Config::PasswordGenerator_ExcludedChars}, + {QS("generator/ExcludeAlike"), Config::PasswordGenerator_ExcludeAlike}, + {QS("generator/EnsureEvery"), Config::PasswordGenerator_EnsureEvery}, + {QS("generator/Length"), Config::PasswordGenerator_Length}, + {QS("generator/WordCount"), Config::PasswordGenerator_WordCount}, + {QS("generator/WordSeparator"), Config::PasswordGenerator_WordSeparator}, + {QS("generator/WordList"), Config::PasswordGenerator_WordList}, + {QS("generator/WordCase"), Config::PasswordGenerator_WordCase}, + {QS("generator/Type"), Config::PasswordGenerator_Type}, + {QS("QtErrorMessageShown"), Config::Messages_Qt55CompatibilityWarning}}; + +/** + * Migrate settings from previous versions. + */ +void Config::migrate() { - const auto keys = deprecationMap.keys(); - for (const auto& setting : keys) { + int previousVersion = m_settings->value("ConfigVersion").toInt(); + if (CONFIG_VERSION <= previousVersion) { + return; + } + + // Update renamed keys and drop obsolete keys + for (const auto& setting : deprecationMap.keys()) { + QVariant value; if (m_settings->contains(setting)) { - if (!deprecationMap.value(setting).isEmpty()) { - // Add entry with new name and old entry's value - m_settings->setValue(deprecationMap.value(setting), m_settings->value(setting)); - } + value = m_settings->value(setting); m_settings->remove(setting); + } else if (m_localSettings && m_localSettings->contains(setting)) { + value = m_localSettings->value(setting); + m_localSettings->remove(setting); + } else { + continue; } - } - // > 2.3.4 - if (m_settings->value("AutoSaveAfterEveryChange").toBool()) { - m_settings->setValue("AutoSaveOnExit", true); + if (deprecationMap[setting] == Config::Deleted) { + continue; + } + + set(deprecationMap[setting], value); } - // Setting defaults for 'hide window on copy' behavior, keeping the user's original setting - if (m_settings->value("HideWindowOnCopy").isNull()) { - m_settings->setValue("HideWindowOnCopy", m_settings->value("MinimizeOnCopy").toBool()); - m_settings->setValue("MinimizeOnCopy", true); + // Move local settings to separate file + if (m_localSettings) + for (const auto& setting : asConst(configStrings)) { + if (setting.type == Local && m_settings->contains(setting.name)) { + m_localSettings->setValue(setting.name, m_settings->value(setting.name)); + m_settings->remove(setting.name); + } + } + + // Detailed version migrations + + // pre 2.6.0 (no versioned configs) + if (previousVersion < 1) { + + // 2.3.4 + if (get(AutoSaveAfterEveryChange).toBool()) { + set(AutoSaveOnExit, true); + } + + // Setting defaults for 'hide window on copy' behavior, keeping the user's original setting + if (get(HideWindowOnCopy).isNull()) { + set(HideWindowOnCopy, get(MinimizeOnCopy).toBool()); + set(MinimizeOnCopy, true); + } + + // Reset database columns if upgrading from pre 2.6.0 + remove(GUI_ListViewState); } + + m_settings->setValue("ConfigVersion", CONFIG_VERSION); + sync(); } Config::Config(const QString& fileName, QObject* parent) @@ -130,109 +411,64 @@ Config::Config(const QString& fileName, QObject* parent) Config::Config(QObject* parent) : QObject(parent) { - // Check if portable config is present. If not, find it in user's directory - QString portablePath = QCoreApplication::applicationDirPath() + "/keepassxc.ini"; + // Check if portable config is present (use it also to store local config) + QString portablePath = QDir::fromNativeSeparators(QCoreApplication::applicationDirPath()) + "/keepassxc.ini"; if (QFile::exists(portablePath)) { init(portablePath); - } else { - QString userPath; - QString homePath = QDir::homePath(); - -#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) - // we can't use QStandardPaths on X11 as it uses XDG_DATA_HOME instead of XDG_CONFIG_HOME - QByteArray env = qgetenv("XDG_CONFIG_HOME"); - if (env.isEmpty()) { - userPath = homePath; - userPath += "/.config"; - } else if (env[0] == '/') { - userPath = QFile::decodeName(env); - } else { - userPath = homePath; - userPath += '/'; - userPath += QFile::decodeName(env); - } + return; + } - userPath += "/keepassxc/"; + QString configPath; + QString localConfigPath; + +#if defined(Q_OS_WIN) + configPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + localConfigPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); +#elif defined(Q_OS_MACOS) + configPath = QDir::fromNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); + localConfigPath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); #else - userPath = QDir::fromNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); - // storageLocation() appends the application name ("/keepassxc") to the end - userPath += "/"; + configPath = QDir::fromNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation)); + localConfigPath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); #endif + configPath += "/keepassxc"; + localConfigPath += "/keepassxc"; + #ifdef QT_DEBUG - userPath += "keepassxc_debug.ini"; -#else - userPath += "keepassxc.ini"; + configPath += "_debug"; + localConfigPath += "_debug"; #endif - init(userPath); - } + configPath += ".ini"; + localConfigPath += ".ini"; + + init(QDir::toNativeSeparators(configPath), QDir::toNativeSeparators(localConfigPath)); } Config::~Config() { } -void Config::init(const QString& fileName) +void Config::init(const QString& configFileName, const QString& localConfigFileName) { - m_settings.reset(new QSettings(fileName, QSettings::IniFormat)); - upgrade(); - connect(qApp, &QCoreApplication::aboutToQuit, this, &Config::sync); + // Upgrade from previous KeePassXC version which stores its config + // in AppData/Local on Windows instead of AppData/Roaming. + // Move file to correct location before continuing. + if (!localConfigFileName.isEmpty() && QFile::exists(localConfigFileName) && !QFile::exists(configFileName)) { + QDir().mkpath(QFileInfo(configFileName).absolutePath()); + QFile::copy(localConfigFileName, configFileName); + QFile::remove(localConfigFileName); + QDir().rmdir(QFileInfo(localConfigFileName).absolutePath()); + } - m_defaults.insert("SingleInstance", true); - m_defaults.insert("RememberLastDatabases", true); - m_defaults.insert("NumberOfRememberedLastDatabases", 5); - m_defaults.insert("RememberLastKeyFiles", true); - m_defaults.insert("OpenPreviousDatabasesOnStartup", true); - m_defaults.insert("AutoSaveAfterEveryChange", true); - m_defaults.insert("AutoReloadOnChange", true); - m_defaults.insert("AutoSaveOnExit", true); - m_defaults.insert("BackupBeforeSave", false); - m_defaults.insert("UseAtomicSaves", true); - m_defaults.insert("SearchLimitGroup", false); - m_defaults.insert("MinimizeOnOpenUrl", false); - m_defaults.insert("HideWindowOnCopy", false); - m_defaults.insert("MinimizeOnCopy", true); - m_defaults.insert("MinimizeAfterUnlock", false); - m_defaults.insert("DropToBackgroundOnCopy", false); - m_defaults.insert("UseGroupIconOnEntryCreation", false); - m_defaults.insert("AutoTypeEntryTitleMatch", true); - m_defaults.insert("AutoTypeEntryURLMatch", true); - m_defaults.insert("AutoTypeDelay", 25); - m_defaults.insert("AutoTypeStartDelay", 500); - m_defaults.insert("UseGroupIconOnEntryCreation", true); - m_defaults.insert("IgnoreGroupExpansion", true); - m_defaults.insert("FaviconDownloadTimeout", 10); - m_defaults.insert("security/clearclipboard", true); - m_defaults.insert("security/clearclipboardtimeout", 10); - m_defaults.insert("security/clearsearch", true); - m_defaults.insert("security/clearsearchtimeout", 5); - m_defaults.insert("security/lockdatabaseidle", false); - m_defaults.insert("security/lockdatabaseidlesec", 240); - m_defaults.insert("security/lockdatabaseminimize", false); - m_defaults.insert("security/lockdatabasescreenlock", true); - m_defaults.insert("security/passwordsrepeat", false); - m_defaults.insert("security/passwordscleartext", false); - m_defaults.insert("security/passwordemptynodots", true); - m_defaults.insert("security/HidePasswordPreviewPanel", true); - m_defaults.insert("security/autotypeask", true); - m_defaults.insert("security/IconDownloadFallback", false); - m_defaults.insert("security/resettouchid", false); - m_defaults.insert("security/resettouchidtimeout", 30); - m_defaults.insert("security/resettouchidscreenlock", true); - m_defaults.insert("GUI/Language", "system"); - m_defaults.insert("GUI/HideToolbar", false); - m_defaults.insert("GUI/MovableToolbar", false); - m_defaults.insert("GUI/ToolButtonStyle", Qt::ToolButtonIconOnly); - m_defaults.insert("GUI/ShowTrayIcon", false); - m_defaults.insert("GUI/DarkTrayIcon", false); - m_defaults.insert("GUI/MinimizeToTray", false); - m_defaults.insert("GUI/MinimizeOnClose", false); - m_defaults.insert("GUI/HideUsernames", false); - m_defaults.insert("GUI/HidePasswords", true); - m_defaults.insert("GUI/AdvancedSettings", false); - m_defaults.insert("GUI/MonospaceNotes", false); - m_defaults.insert("GUI/ApplicationTheme", "auto"); + m_settings.reset(new QSettings(configFileName, QSettings::IniFormat)); + if (!localConfigFileName.isEmpty() && configFileName != localConfigFileName) { + m_localSettings.reset(new QSettings(localConfigFileName, QSettings::IniFormat)); + } + + migrate(); + connect(qApp, &QCoreApplication::aboutToQuit, this, &Config::sync); } Config* Config::instance() @@ -264,3 +500,5 @@ void Config::createTempFileInstance() m_instance = new Config(tmpFile->fileName(), qApp); tmpFile->setParent(m_instance); } + +#undef QS diff --git a/src/core/Config.h b/src/core/Config.h index ef6dd6af1..0914f1cbe 100644 --- a/src/core/Config.h +++ b/src/core/Config.h @@ -1,6 +1,6 @@ /* + * Copyright (C) 2020 KeePassXC Team <team@keepassxc.org> * Copyright (C) 2011 Felix Geyer <debfx@fobos.de> - * 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 @@ -32,11 +32,160 @@ class Config : public QObject public: Q_DISABLE_COPY(Config) + enum ConfigKey + { + SingleInstance, + RememberLastDatabases, + NumberOfRememberedLastDatabases, + RememberLastKeyFiles, + OpenPreviousDatabasesOnStartup, + AutoSaveAfterEveryChange, + AutoReloadOnChange, + AutoSaveOnExit, + BackupBeforeSave, + UseAtomicSaves, + SearchLimitGroup, + MinimizeOnOpenUrl, + HideWindowOnCopy, + MinimizeOnCopy, + MinimizeAfterUnlock, + DropToBackgroundOnCopy, + UseGroupIconOnEntryCreation, + AutoTypeEntryTitleMatch, + AutoTypeEntryURLMatch, + AutoTypeDelay, + AutoTypeStartDelay, + GlobalAutoTypeKey, + GlobalAutoTypeModifiers, + IgnoreGroupExpansion, + FaviconDownloadTimeout, + UpdateCheckMessageShown, + UseTouchID, + + LastDatabases, + LastKeyFiles, + LastChallengeResponse, + LastActiveDatabase, + LastOpenedDatabases, + LastDir, + LastAttachmentDir, + + GUI_Language, + GUI_HideToolbar, + GUI_MovableToolbar, + GUI_HidePreviewPanel, + GUI_ToolButtonStyle, + GUI_ShowTrayIcon, + GUI_DarkTrayIcon, + GUI_MinimizeToTray, + GUI_MinimizeOnStartup, + GUI_MinimizeOnClose, + GUI_HideUsernames, + GUI_HidePasswords, + GUI_AdvancedSettings, + GUI_MonospaceNotes, + GUI_ApplicationTheme, + GUI_CheckForUpdates, + GUI_CheckForUpdatesIncludeBetas, + + GUI_MainWindowGeometry, + GUI_MainWindowState, + GUI_ListViewState, + GUI_SearchViewState, + GUI_PreviewSplitterState, + GUI_SplitterState, + GUI_AutoTypeSelectDialogSize, + GUI_CheckForUpdatesNextCheck, + + Security_ClearClipboard, + Security_ClearClipboardTimeout, + Security_ClearSearch, + Security_ClearSearchTimeout, + Security_HideNotes, + Security_LockDatabaseIdle, + Security_LockDatabaseIdleSeconds, + Security_LockDatabaseMinimize, + Security_LockDatabaseScreenLock, + Security_RelockAutoType, + Security_PasswordsRepeat, + Security_PasswordsCleartext, + Security_PasswordEmptyNoDots, + Security_HidePasswordPreviewPanel, + Security_AutoTypeAsk, + Security_IconDownloadFallback, + Security_ResetTouchId, + Security_ResetTouchIdTimeout, + Security_ResetTouchIdScreenlock, + + Browser_Enabled, + Browser_ShowNotification, + Browser_BestMatchOnly, + Browser_UnlockDatabase, + Browser_MatchUrlScheme, + Browser_SortByUsername, + Browser_SupportBrowserProxy, + Browser_UseCustomProxy, + Browser_CustomProxyLocation, + Browser_UpdateBinaryPath, + Browser_AllowExpiredCredentials, + Browser_AlwaysAllowAccess, + Browser_AlwaysAllowUpdate, + Browser_HttpAuthPermission, + Browser_SearchInAllDatabases, + Browser_SupportKphFields, + Browser_NoMigrationPrompt, + + SSHAgent_Enabled, + SSHAgent_UseOpenSSH, + SSHAgent_AuthSockOverride, + + FdoSecrets_Enabled, + FdoSecrets_ShowNotification, + FdoSecrets_NoConfirmDeleteItem, + + KeeShare_QuietSuccess, + KeeShare_Own, + KeeShare_Foreign, + KeeShare_Active, + KeeShare_LastDir, + KeeShare_LastKeyDir, + KeeShare_LastShareDir, + + PasswordGenerator_LowerCase, + PasswordGenerator_UpperCase, + PasswordGenerator_Numbers, + PasswordGenerator_EASCII, + PasswordGenerator_AdvancedMode, + PasswordGenerator_SpecialChars, + PasswordGenerator_AdditionalChars, + PasswordGenerator_Braces, + PasswordGenerator_Punctuation, + PasswordGenerator_Quotes, + PasswordGenerator_Dashes, + PasswordGenerator_Math, + PasswordGenerator_Logograms, + PasswordGenerator_ExcludedChars, + PasswordGenerator_ExcludeAlike, + PasswordGenerator_EnsureEvery, + PasswordGenerator_Length, + PasswordGenerator_WordCount, + PasswordGenerator_WordSeparator, + PasswordGenerator_WordList, + PasswordGenerator_WordCase, + PasswordGenerator_Type, + + Messages_NoLegacyKeyFileWarning, + Messages_Qt55CompatibilityWarning, + + // Special internal value + Deleted + }; + ~Config() override; - QVariant get(const QString& key); - QVariant get(const QString& key, const QVariant& defaultValue); + QVariant get(ConfigKey key); QString getFileName(); - void set(const QString& key, const QVariant& value); + void set(ConfigKey key, const QVariant& value); + void remove(ConfigKey key); bool hasAccessError(); void sync(); void resetToDefaults(); @@ -46,17 +195,18 @@ public: static void createTempFileInstance(); signals: - void changed(const QString& key); + void changed(ConfigKey key); private: - Config(const QString& fileName, QObject* parent); + Config(const QString& fileName, QObject* parent = nullptr); explicit Config(QObject* parent); - void init(const QString& fileName); - void upgrade(); + void init(const QString& configFileName, const QString& localConfigFileName = ""); + void migrate(); static QPointer<Config> m_instance; QScopedPointer<QSettings> m_settings; + QScopedPointer<QSettings> m_localSettings; QHash<QString, QVariant> m_defaults; }; diff --git a/src/core/Group.cpp b/src/core/Group.cpp index eb795f950..acb6d114d 100644 --- a/src/core/Group.cpp +++ b/src/core/Group.cpp @@ -364,7 +364,7 @@ void Group::setExpanded(bool expanded) { if (m_data.isExpanded != expanded) { m_data.isExpanded = expanded; - if (config()->get("IgnoreGroupExpansion").toBool()) { + if (config()->get(Config::IgnoreGroupExpansion).toBool()) { updateTimeinfo(); return; } @@ -1112,7 +1112,7 @@ void Group::applyGroupIconOnCreateTo(Entry* entry) { Q_ASSERT(entry); - if (!config()->get("UseGroupIconOnEntryCreation").toBool()) { + if (!config()->get(Config::UseGroupIconOnEntryCreation).toBool()) { return; } diff --git a/src/core/IconDownloader.cpp b/src/core/IconDownloader.cpp index 1d9bd01ad..1dccc554c 100644 --- a/src/core/IconDownloader.cpp +++ b/src/core/IconDownloader.cpp @@ -103,7 +103,7 @@ void IconDownloader::setUrl(const QString& entryUrl) } // Start with the "fallback" url (if enabled) to try to get the best favicon - if (config()->get("security/IconDownloadFallback", false).toBool()) { + if (config()->get(Config::Security_IconDownloadFallback).toBool()) { QUrl fallbackUrl = QUrl("https://icons.duckduckgo.com"); fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(fullyQualifiedDomain) + ".ico"); m_urlsToTry.append(fallbackUrl); @@ -131,7 +131,7 @@ void IconDownloader::download() } if (!m_timeout.isActive()) { - int timeout = config()->get("FaviconDownloadTimeout", 10).toInt(); + int timeout = config()->get(Config::FaviconDownloadTimeout).toInt(); m_timeout.start(timeout * 1000); // Use the first URL to start the download process diff --git a/src/core/PasswordGenerator.h b/src/core/PasswordGenerator.h index 20681c233..308142563 100644 --- a/src/core/PasswordGenerator.h +++ b/src/core/PasswordGenerator.h @@ -67,23 +67,9 @@ public: QString generatePassword() const; - static const int DefaultLength = 16; + static const int DefaultLength = 32; static const char* DefaultAdditionalChars; static const char* DefaultExcludedChars; - static constexpr bool DefaultLower = (DefaultCharset & LowerLetters) != 0; - static constexpr bool DefaultUpper = (DefaultCharset & UpperLetters) != 0; - static constexpr bool DefaultNumbers = (DefaultCharset & Numbers) != 0; - static constexpr bool DefaultSpecial = (DefaultCharset & SpecialCharacters) != 0; - static constexpr bool DefaultAdvancedMode = (DefaultFlags & AdvancedMode) != 0; - static constexpr bool DefaultBraces = (DefaultCharset & Braces) != 0; - static constexpr bool DefaultPunctuation = (DefaultCharset & Punctuation) != 0; - static constexpr bool DefaultQuotes = (DefaultCharset & Quotes) != 0; - static constexpr bool DefaultDashes = (DefaultCharset & Dashes) != 0; - static constexpr bool DefaultMath = (DefaultCharset & Math) != 0; - static constexpr bool DefaultLogograms = (DefaultCharset & Logograms) != 0; - static constexpr bool DefaultEASCII = (DefaultCharset & EASCII) != 0; - static constexpr bool DefaultLookAlike = (DefaultFlags & ExcludeLookAlike) != 0; - static constexpr bool DefaultFromEveryGroup = (DefaultFlags & CharFromEveryGroup) != 0; private: QVector<PasswordGroup> passwordGroups() const; diff --git a/src/core/Resources.cpp b/src/core/Resources.cpp index 90bc117ae..5f13a7cdf 100644 --- a/src/core/Resources.cpp +++ b/src/core/Resources.cpp @@ -217,7 +217,7 @@ bool Resources::testResourceDir(const QString& dir) bool Resources::useDarkIcon() { - return config()->get("GUI/DarkTrayIcon").toBool(); + return config()->get(Config::GUI_DarkTrayIcon).toBool(); } Resources* Resources::instance() diff --git a/src/core/Translator.cpp b/src/core/Translator.cpp index 28dc6aa68..e90d63837 100644 --- a/src/core/Translator.cpp +++ b/src/core/Translator.cpp @@ -35,7 +35,7 @@ void Translator::installTranslators() { QStringList languages; - QString languageSetting = config()->get("GUI/Language").toString(); + QString languageSetting = config()->get(Config::GUI_Language).toString(); if (languageSetting.isEmpty() || languageSetting == "system") { // NOTE: this is a workaround for the terrible way Qt loads languages // using the QLocale::uiLanguages() approach. Instead, we search each |