Added the capability to reset the password manager
Also some documentation about the password manager.
This commit is contained in:
parent
f8528143ac
commit
4fa2189b27
17 changed files with 233 additions and 85 deletions
|
|
@ -156,7 +156,19 @@ void PasswordManager::closeDatabase()
|
||||||
|
|
||||||
bool PasswordManager::locked() const
|
bool PasswordManager::locked() const
|
||||||
{
|
{
|
||||||
return m_cryptoEngine == nullptr;
|
return m_cryptoEngine == nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PasswordManager::resetMasterPassword(QSqlDatabase &db)
|
||||||
|
{
|
||||||
|
if (!isPskStoreInitialized(db))
|
||||||
|
return;
|
||||||
|
|
||||||
|
closeDatabase();
|
||||||
|
QSqlQuery del_algo("DELETE FROM " + m_secretAlgoTableName + " WHERE id=1", db);
|
||||||
|
del_algo.exec();
|
||||||
|
QSqlQuery del_hash("DELETE FROM " + m_secretHashTableName + " WHERE id=1", db);
|
||||||
|
del_hash.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string PasswordManager::encrypt(const std::string &name, const std::string &passwd)
|
std::string PasswordManager::encrypt(const std::string &name, const std::string &passwd)
|
||||||
|
|
@ -246,7 +258,7 @@ bool PasswordManager::isPskStoreInitialized(QSqlDatabase& db)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSqlQuery sel_algo("SELECT algo FROM " + m_secretAlgoTableName + " WHERE id=1", db);
|
QSqlQuery sel_algo("SELECT algo FROM " + m_secretAlgoTableName + " WHERE id=1", db);
|
||||||
if (!sel_algo.next()) {
|
if (!sel_algo.next()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -297,3 +309,4 @@ KeyStrengthener PasswordManager::createKeyStrengthener()
|
||||||
key_size
|
key_size
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,64 +14,66 @@
|
||||||
|
|
||||||
namespace Botan {
|
namespace Botan {
|
||||||
|
|
||||||
class Encrypted_PSK_Database;
|
class Encrypted_PSK_Database;
|
||||||
class PasswordHash;
|
class PasswordHash;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class PasswordManagerException: public std::exception {
|
class PasswordManagerException: public std::exception {
|
||||||
public:
|
public:
|
||||||
using std::exception::exception; //(char const* const _Message);
|
using std::exception::exception; //(char const* const _Message);
|
||||||
};
|
};
|
||||||
|
|
||||||
class PasswordManagerLockedException: public PasswordManagerException {
|
class PasswordManagerLockedException: public PasswordManagerException {
|
||||||
public:
|
public:
|
||||||
using PasswordManagerException::PasswordManagerException;
|
using PasswordManagerException::PasswordManagerException;
|
||||||
|
|
||||||
};
|
};
|
||||||
class PasswordCryptoEngine;
|
class PasswordCryptoEngine;
|
||||||
|
|
||||||
class PasswordManager {
|
class PasswordManager {
|
||||||
public:
|
public:
|
||||||
enum Result {
|
enum Result {
|
||||||
Ok,
|
Ok,
|
||||||
Locked,
|
Locked,
|
||||||
Error
|
Error
|
||||||
};
|
};
|
||||||
|
|
||||||
PasswordManager();
|
PasswordManager();
|
||||||
~PasswordManager();
|
~PasswordManager();
|
||||||
|
|
||||||
/** Check if it has been initialized before.
|
/** Check if it has been initialized before.
|
||||||
*
|
*
|
||||||
* If returns false then use createDatabase to set it up
|
* If returns false then use createDatabase to set it up
|
||||||
* else use openDatabase to get access.
|
* else use openDatabase to get access.
|
||||||
*/
|
*/
|
||||||
bool initialized(QSqlDatabase &db);
|
bool initialized(QSqlDatabase &db);
|
||||||
bool createDatabase(QSqlDatabase &db, QString passphrase);
|
bool createDatabase(QSqlDatabase &db, QString passphrase);
|
||||||
/// Opens the PSK database
|
/// Opens the PSK database
|
||||||
bool openDatabase(QSqlDatabase &db, QString passphrase);
|
bool openDatabase(QSqlDatabase &db, QString passphrase);
|
||||||
void closeDatabase();
|
void closeDatabase();
|
||||||
bool locked() const;
|
bool locked() const;
|
||||||
|
void resetMasterPassword(QSqlDatabase &db);
|
||||||
|
|
||||||
std::string encrypt(const std::string &id, const std::string &passwd);
|
|
||||||
std::string decrypt(const std::string &id, const std::string_view &encpwd);
|
std::string encrypt(const std::string &id, const std::string &passwd);
|
||||||
|
std::string decrypt(const std::string &id, const std::string_view &encpwd);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_passwordTableName = "psk_passwd";
|
QString m_passwordTableName = "psk_passwd";
|
||||||
QString m_secretAlgoTableName = "psk_masterkey_algo";
|
QString m_secretAlgoTableName = "psk_masterkey_algo";
|
||||||
QString m_secretHashTableName = "psk_masterkey_hash";
|
QString m_secretHashTableName = "psk_masterkey_hash";
|
||||||
std::unique_ptr<PasswordCryptoEngine> m_cryptoEngine;
|
std::unique_ptr<PasswordCryptoEngine> m_cryptoEngine;
|
||||||
|
|
||||||
bool isPskStoreInitialized(QSqlDatabase& db);
|
bool isPskStoreInitialized(QSqlDatabase& db);
|
||||||
void initializeNewPskStore(QSqlDatabase &db);
|
void initializeNewPskStore(QSqlDatabase &db);
|
||||||
|
|
||||||
/// Get PasswordHash from parameters in database
|
/// Get PasswordHash from parameters in database
|
||||||
KeyStrengthener getKeyStrengthener(QSqlDatabase &db);
|
KeyStrengthener getKeyStrengthener(QSqlDatabase &db);
|
||||||
KeyStrengthener createKeyStrengthener();
|
KeyStrengthener createKeyStrengthener();
|
||||||
|
|
||||||
std::tuple<Botan::secure_vector<uint8_t>, Botan::secure_vector<uint8_t>>
|
std::tuple<Botan::secure_vector<uint8_t>, Botan::secure_vector<uint8_t>>
|
||||||
deriveKey(KeyStrengthener &ks, QString passphrase);
|
deriveKey(KeyStrengthener &ks, QString passphrase);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
7
docs/connectionmanager/index.rst
Normal file
7
docs/connectionmanager/index.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
==================
|
||||||
|
Connection Manager
|
||||||
|
==================
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
|
||||||
|
passwordmanager
|
||||||
35
docs/connectionmanager/passwordmanager.rst
Normal file
35
docs/connectionmanager/passwordmanager.rst
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
================
|
||||||
|
Password manager
|
||||||
|
================
|
||||||
|
|
||||||
|
The connection manager has a build-in password manager which uses encryption
|
||||||
|
to savely store passwords. If you want to use it is up to you. Check the Save
|
||||||
|
password checkbox and the password manager will be used to safely store the
|
||||||
|
password. The first time this happens you will be asked for a password you wish
|
||||||
|
to use for the password manager. Make sure to remember this password cause there
|
||||||
|
is no way to recover it if you loose it.
|
||||||
|
|
||||||
|
When you try to open a connection for which the password was saved the program
|
||||||
|
will prompt you for the password managers password. You can choose how long the
|
||||||
|
program will keep the password manager unlocked. If you cancel this password
|
||||||
|
prompt the program will prompt you for the password of the database connection.
|
||||||
|
|
||||||
|
------------------------------
|
||||||
|
Resetting the password manager
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
Unfortunatly at this time there is no way to change the master password.
|
||||||
|
Neither is it possible to completely reset the password manager other then
|
||||||
|
removing the `pglabuser.db` file.
|
||||||
|
|
||||||
|
--------
|
||||||
|
Security
|
||||||
|
--------
|
||||||
|
|
||||||
|
To make it hard to brute force decode the saved passwords the passwords are not
|
||||||
|
simply encrypted with the master password. Instead a keystrengthening algorithm
|
||||||
|
is applied to your master password combined with a salt. The exact parameters
|
||||||
|
choosen for keystrengthening depends on the speed of your computer. This
|
||||||
|
strengthened key will be used only as a basis for encryption. Each password
|
||||||
|
saved is associated with a random guid, this 128-bit value is combined with
|
||||||
|
your strengthened key to create the key to save the password.
|
||||||
|
|
@ -7,11 +7,13 @@ pgLab User Manual
|
||||||
====================================================
|
====================================================
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 3
|
||||||
:caption: Contents:
|
:caption: Contents:
|
||||||
|
|
||||||
releasenotes
|
releasenotes
|
||||||
installation
|
installation
|
||||||
|
connectionmanager/index
|
||||||
|
internals
|
||||||
|
|
||||||
Indices and tables
|
Indices and tables
|
||||||
==================
|
==================
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,11 @@ Currently only binaries for Windows 64-bit are provided.
|
||||||
Windows
|
Windows
|
||||||
-------
|
-------
|
||||||
|
|
||||||
Downloads can be found `here <https://eelkeklein.stackstorage.com/s/E9xkMGQDFjHv5XN3>`_.
|
Downloads can be found `here
|
||||||
|
<https://eelkeklein.stackstorage.com/s/E9xkMGQDFjHv5XN3>`_.
|
||||||
|
|
||||||
Unpack the contents of the 7zip archive to a folder of your choosing for instance
|
Unpack the contents of the 7zip archive to a folder of your choosing for
|
||||||
`C:\\Program files\\pgLab`. You can run the pgLab.exe from there. If it is complaining
|
instance `C:\\Program files\\pgLab`. You can run the pgLab.exe from there. If
|
||||||
about missing files this is probably because the required Visual C++ Runtime has
|
it is complaining about missing files this is probably because the required
|
||||||
not yet been installed on your machine you can get it from `microsoft <https://aka.ms/vs/17/release/vc_redist.x64.exe>`_.
|
Visual C++ Runtime has not yet been installed on your machine you can get it
|
||||||
|
from `microsoft <https://aka.ms/vs/17/release/vc_redist.x64.exe>`_.
|
||||||
|
|
|
||||||
11
docs/internals.rst
Normal file
11
docs/internals.rst
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
=========
|
||||||
|
Internals
|
||||||
|
=========
|
||||||
|
|
||||||
|
---------
|
||||||
|
User data
|
||||||
|
---------
|
||||||
|
|
||||||
|
All connection configuration information is stored in
|
||||||
|
`%AppData%\pglab\pglab\pglabuser.db` this is an sqlite database.
|
||||||
|
|
||||||
|
|
@ -6,7 +6,9 @@
|
||||||
#include "DatabaseWindow.h"
|
#include "DatabaseWindow.h"
|
||||||
#include "BackupDialog.h"
|
#include "BackupDialog.h"
|
||||||
#include "PasswordPromptDialog.h"
|
#include "PasswordPromptDialog.h"
|
||||||
|
#include "ScopeGuard.h"
|
||||||
#include "ConnectionConfigurationWidget.h"
|
#include "ConnectionConfigurationWidget.h"
|
||||||
|
#include <QSqlQuery>
|
||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
@ -156,33 +158,36 @@ std::shared_ptr<PasswordManager> ConnectionController::passwordManager()
|
||||||
|
|
||||||
bool ConnectionController::retrieveConnectionPassword(ConnectionConfig &cc)
|
bool ConnectionController::retrieveConnectionPassword(ConnectionConfig &cc)
|
||||||
{
|
{
|
||||||
auto enc_pwd = cc.encodedPassword();
|
auto enc_pwd = cc.encodedPassword();
|
||||||
if (!enc_pwd.isEmpty()) {
|
if (!enc_pwd.isEmpty())
|
||||||
std::string pw;
|
{
|
||||||
|
std::string pw;
|
||||||
bool result = retrieveFromPasswordManager(getPskId(cc.uuid()),
|
bool result = retrieveFromPasswordManager(getPskId(cc.uuid()),
|
||||||
std::string_view(enc_pwd.data(), enc_pwd.size()) , pw);
|
std::string_view(enc_pwd.data(), enc_pwd.size()) , pw);
|
||||||
if (result) {
|
if (result)
|
||||||
cc.setPassword(QString::fromUtf8(pw.data(), pw.size()));
|
{
|
||||||
return true;
|
cc.setPassword(QString::fromUtf8(pw.data(), pw.size()));
|
||||||
}
|
return true;
|
||||||
}
|
}
|
||||||
// Geen else hier want als voorgaande blok niet geretourneerd heeft moeten we wachtwoord
|
}
|
||||||
// ook aan de gebruiker vragen zoals hier gebeurd.
|
// Geen else hier want als voorgaande blok niet geretourneerd heeft moeten we wachtwoord
|
||||||
QString str = cc.makeLongDescription();
|
// ook aan de gebruiker vragen zoals hier gebeurd.
|
||||||
auto dlg = std::make_unique<PasswordPromptDialog>(PasswordPromptDialog::SaveOption, nullptr);
|
QString str = cc.makeLongDescription();
|
||||||
dlg->setCaption(tr("Connection password prompt"));
|
auto dlg = std::make_unique<PasswordPromptDialog>(PasswordPromptDialog::SaveOption, nullptr);
|
||||||
dlg->setDescription(QString(tr("Please provide password for connection %1")).arg(str));
|
dlg->setCaption(tr("Connection password prompt"));
|
||||||
int exec_result = dlg->exec();
|
dlg->setDescription(QString(tr("Please provide password for connection %1")).arg(str));
|
||||||
|
int exec_result = dlg->exec();
|
||||||
|
|
||||||
if (exec_result == QDialog::Accepted) {
|
if (exec_result == QDialog::Accepted)
|
||||||
auto password = dlg->password();
|
{
|
||||||
cc.setPassword(password);
|
auto password = dlg->password();
|
||||||
if (dlg->saveChecked()) {
|
cc.setPassword(password);
|
||||||
|
if (dlg->saveChecked())
|
||||||
encryptPassword(cc);
|
encryptPassword(cc);
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ConnectionController::retrieveFromPasswordManager(const std::string &password_id, const std::string_view &enc_password, std::string &password)
|
bool ConnectionController::retrieveFromPasswordManager(const std::string &password_id, const std::string_view &enc_password, std::string &password)
|
||||||
|
|
@ -224,29 +229,48 @@ bool ConnectionController::decodeConnectionPassword(QUuid id, QByteArray encoded
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConnectionController::resetPasswordManager()
|
||||||
|
{
|
||||||
|
auto&& user_cfg_db = m_masterController->userConfigDatabase();
|
||||||
|
user_cfg_db.transaction();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_passwordManager->resetMasterPassword(user_cfg_db);
|
||||||
|
m_connectionTreeModel->clearAllPasswords();
|
||||||
|
user_cfg_db.commit();
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
user_cfg_db.rollback();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool ConnectionController::UnlockPasswordManagerIfNeeded()
|
bool ConnectionController::UnlockPasswordManagerIfNeeded()
|
||||||
{
|
{
|
||||||
auto&& user_cfg_db = m_masterController->userConfigDatabase();
|
auto&& user_cfg_db = m_masterController->userConfigDatabase();
|
||||||
if (m_passwordManager->initialized(user_cfg_db)) {
|
if (m_passwordManager->initialized(user_cfg_db))
|
||||||
if (!m_passwordManager->locked())
|
{
|
||||||
return true;
|
if (!m_passwordManager->locked())
|
||||||
|
return true;
|
||||||
|
|
||||||
while (true) {
|
while (true)
|
||||||
// ask user for passphrase
|
{
|
||||||
PassphraseResult pp_result = PassphrasePrompt();
|
PassphraseResult pp_result = PassphrasePrompt();
|
||||||
if (!pp_result.success)
|
if (!pp_result.success)
|
||||||
break; // leave this retry loop
|
break;
|
||||||
|
|
||||||
// user gave OK, if succeeds return true otherwise loop a prompt for password again.
|
// user gave OK, if succeeds return true otherwise loop a prompt for password again.
|
||||||
if (m_passwordManager->openDatabase(user_cfg_db, pp_result.passphrase)) {
|
if (m_passwordManager->openDatabase(user_cfg_db, pp_result.passphrase))
|
||||||
|
{
|
||||||
setRelockTimer(pp_result.rememberForMinutes);
|
setRelockTimer(pp_result.rememberForMinutes);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
InitializePasswordManager();
|
InitializePasswordManager();
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ class ConnectionTreeModel;
|
||||||
class ConnectionManagerWindow;
|
class ConnectionManagerWindow;
|
||||||
class PasswordManager;
|
class PasswordManager;
|
||||||
class QTimer;
|
class QTimer;
|
||||||
|
class QSqlDatabase;
|
||||||
|
|
||||||
|
|
||||||
class PassphraseResult {
|
class PassphraseResult {
|
||||||
|
|
@ -58,6 +59,7 @@ public:
|
||||||
bool UnlockPasswordManagerIfNeeded();
|
bool UnlockPasswordManagerIfNeeded();
|
||||||
|
|
||||||
bool decodeConnectionPassword(QUuid id, QByteArray encoded, QString &out_password);
|
bool decodeConnectionPassword(QUuid id, QByteArray encoded, QString &out_password);
|
||||||
|
void resetPasswordManager();
|
||||||
private:
|
private:
|
||||||
MasterController *m_masterController;
|
MasterController *m_masterController;
|
||||||
ConnectionList *m_connectionList = nullptr;
|
ConnectionList *m_connectionList = nullptr;
|
||||||
|
|
@ -71,7 +73,6 @@ private:
|
||||||
std::shared_ptr<PasswordManager> m_passwordManager;
|
std::shared_ptr<PasswordManager> m_passwordManager;
|
||||||
|
|
||||||
bool retrieveFromPasswordManager(const std::string &password_id, const std::string_view &enc_password, std::string &password);
|
bool retrieveFromPasswordManager(const std::string &password_id, const std::string_view &enc_password, std::string &password);
|
||||||
// bool updatePasswordManager(const std::string &password_id, const std::string &password, std::string &enc_password);
|
|
||||||
|
|
||||||
/// Expects the plaintext password to be the password that needs encoding.
|
/// Expects the plaintext password to be the password that needs encoding.
|
||||||
bool encryptPassword(ConnectionConfig &cc);
|
bool encryptPassword(ConnectionConfig &cc);
|
||||||
|
|
|
||||||
|
|
@ -351,7 +351,17 @@ void ConnectionTreeModel::save(const QString &group_name, const ConnectionConfig
|
||||||
|
|
||||||
void ConnectionTreeModel::save(const ConnectionConfig &cc)
|
void ConnectionTreeModel::save(const ConnectionConfig &cc)
|
||||||
{
|
{
|
||||||
saveToDb(cc);
|
saveToDb(cc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConnectionTreeModel::clearAllPasswords()
|
||||||
|
{
|
||||||
|
for (auto group : m_groups)
|
||||||
|
for (auto cc : group->connections())
|
||||||
|
{
|
||||||
|
cc->setEncodedPassword({});
|
||||||
|
saveToDb(*cc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::tuple<int, int> ConnectionTreeModel::findConfig(const QUuid uuid) const
|
std::tuple<int, int> ConnectionTreeModel::findConfig(const QUuid uuid) const
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ public:
|
||||||
/** Save changed config, group is not allowed to change
|
/** Save changed config, group is not allowed to change
|
||||||
*/
|
*/
|
||||||
void save(const ConnectionConfig &cc);
|
void save(const ConnectionConfig &cc);
|
||||||
|
void clearAllPasswords();
|
||||||
/// Create a new group in the DB and place in the tree
|
/// Create a new group in the DB and place in the tree
|
||||||
std::variant<int, QSqlError> addGroup(const QString &group_name);
|
std::variant<int, QSqlError> addGroup(const QString &group_name);
|
||||||
std::optional<QSqlError> removeGroup(int row);
|
std::optional<QSqlError> removeGroup(int row);
|
||||||
|
|
|
||||||
|
|
@ -115,3 +115,15 @@ void ConnectionManagerWindow::on_actionManual_triggered()
|
||||||
QDesktopServices::openUrl(QString("https://eelke.gitlab.io/pgLab/#pglab-user-manual"));
|
QDesktopServices::openUrl(QString("https://eelke.gitlab.io/pgLab/#pglab-user-manual"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ConnectionManagerWindow::on_actionReset_password_manager_triggered()
|
||||||
|
{
|
||||||
|
auto warning_response = QMessageBox::warning(this, "pgLab", tr(
|
||||||
|
"Warning you are about to reset the password manager master passwords "
|
||||||
|
"all stored passwords will be lost! Are you shure you wish to continue?"),
|
||||||
|
QMessageBox::Yes | QMessageBox::No);
|
||||||
|
|
||||||
|
if (warning_response == QMessageBox::Yes)
|
||||||
|
m_connectionController->resetPasswordManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,8 @@ private slots:
|
||||||
|
|
||||||
void on_actionManual_triggered();
|
void on_actionManual_triggered();
|
||||||
|
|
||||||
|
void on_actionReset_password_manager_triggered();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::ConnectionManagerWindow *ui;
|
Ui::ConnectionManagerWindow *ui;
|
||||||
MasterController *m_masterController;
|
MasterController *m_masterController;
|
||||||
|
|
|
||||||
|
|
@ -33,11 +33,18 @@
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Fi&le</string>
|
<string>Fi&le</string>
|
||||||
</property>
|
</property>
|
||||||
<addaction name="actionAbout"/>
|
<addaction name="actionReset_password_manager"/>
|
||||||
<addaction name="actionManual"/>
|
|
||||||
<addaction name="actionQuit_application"/>
|
<addaction name="actionQuit_application"/>
|
||||||
</widget>
|
</widget>
|
||||||
|
<widget class="QMenu" name="menuHelp">
|
||||||
|
<property name="title">
|
||||||
|
<string>Help</string>
|
||||||
|
</property>
|
||||||
|
<addaction name="actionManual"/>
|
||||||
|
<addaction name="actionAbout"/>
|
||||||
|
</widget>
|
||||||
<addaction name="menuFile"/>
|
<addaction name="menuFile"/>
|
||||||
|
<addaction name="menuHelp"/>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QStatusBar" name="statusbar"/>
|
<widget class="QStatusBar" name="statusbar"/>
|
||||||
<widget class="QToolBar" name="toolBar">
|
<widget class="QToolBar" name="toolBar">
|
||||||
|
|
@ -181,6 +188,11 @@ QToolButton {
|
||||||
<string>Manual</string>
|
<string>Manual</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionReset_password_manager">
|
||||||
|
<property name="text">
|
||||||
|
<string>Reset password manager</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="resources.qrc"/>
|
<include location="resources.qrc"/>
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ public:
|
||||||
|
|
||||||
using Connections = QVector<std::shared_ptr<ConnectionConfig>>;
|
using Connections = QVector<std::shared_ptr<ConnectionConfig>>;
|
||||||
const Connections& connections() const { return m_connections; }
|
const Connections& connections() const { return m_connections; }
|
||||||
|
Connections& connections() { return m_connections; }
|
||||||
|
|
||||||
void erase(int idx, int count = 1);
|
void erase(int idx, int count = 1);
|
||||||
/// Adds cc to the group and returns the index within the group.
|
/// Adds cc to the group and returns the index within the group.
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,10 @@
|
||||||
---
|
---
|
||||||
|
sections:
|
||||||
|
# The prelude section is implicitly included.
|
||||||
|
- [features, New Features]
|
||||||
|
- [other, Changes]
|
||||||
|
- [issues, Known Issues]
|
||||||
|
- [upgrade, Upgrade Notes]
|
||||||
|
- [security, Security Issues]
|
||||||
|
- [fixes, Bug Fixes]
|
||||||
encoding: utf8
|
encoding: utf8
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Added menu option to reset the password managers master password.
|
||||||
|
This will ofcourse also clear all saved passwords.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue