Previously only a new password was saved if the save password checkbox was checked, Which always started in the unchecked state. Now when editing existing connection the save password checkbox now reflects if a password has been saved. Only when the password field is edited the program will update the saved password. If the save password checkbox is unchecked then clear the save password.
338 lines
10 KiB
C++
338 lines
10 KiB
C++
#include "ConnectionController.h"
|
|
#include "MasterController.h"
|
|
#include "ConnectionManagerWindow.h"
|
|
#include "ConnectionListModel.h"
|
|
#include "PasswordManager.h"
|
|
#include "DatabaseWindow.h"
|
|
#include "BackupDialog.h"
|
|
#include "PasswordPromptDialog.h"
|
|
#include "ScopeGuard.h"
|
|
#include "ConnectionConfigurationWidget.h"
|
|
#include <QSqlQuery>
|
|
#include <QInputDialog>
|
|
#include <QMessageBox>
|
|
#include <QTimer>
|
|
|
|
ConnectionController::ConnectionController(MasterController *parent)
|
|
: QObject(parent)
|
|
, m_masterController(parent)
|
|
, m_relockTimer(new QTimer(this))
|
|
{
|
|
m_relockTimer->setSingleShot(true);
|
|
m_relockTimer->setTimerType(Qt::VeryCoarseTimer);
|
|
// Force signal to go through queue so when the password manager is relocked after 0msec
|
|
// the code that retrieves the password is garanteed to run before the signal is handled
|
|
// because only after the password is retrieved the loop has a chance to run.
|
|
m_relockTimer->callOnTimeout(this, &ConnectionController::relock, Qt::QueuedConnection);
|
|
}
|
|
|
|
ConnectionController::~ConnectionController()
|
|
{
|
|
delete m_connectionManagerWindow;
|
|
delete m_connectionTreeModel;
|
|
}
|
|
|
|
void ConnectionController::init()
|
|
{
|
|
m_passwordManager = std::make_shared<PasswordManager>();
|
|
|
|
m_connectionTreeModel = new ConnectionTreeModel(this, m_masterController->userConfigDatabase());
|
|
m_connectionTreeModel->load();
|
|
|
|
m_connectionManagerWindow = new ConnectionManagerWindow(m_masterController, nullptr);
|
|
m_connectionManagerWindow->show();
|
|
}
|
|
|
|
void ConnectionController::showConnectionManager()
|
|
{
|
|
m_connectionManagerWindow->show();
|
|
}
|
|
|
|
void ConnectionController::openSqlWindowForConnection(QModelIndex index)
|
|
{
|
|
auto config = ConnectionTreeModel::getConfigFromModelIndex(index);
|
|
if (config) {
|
|
|
|
if (retrieveConnectionPassword(*config)) {
|
|
m_connectionTreeModel->save(*config);
|
|
// TODO instead of directly openening the mainwindow
|
|
// do async connect and only open window when we have
|
|
// working connection
|
|
auto w = new DatabaseWindow(m_masterController, nullptr);
|
|
w->setAttribute( Qt::WA_DeleteOnClose );
|
|
w->setConfig(*config);
|
|
w->showMaximized();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void ConnectionController::openBackupDlgForConnection(QModelIndex index)
|
|
{
|
|
auto config = ConnectionTreeModel::getConfigFromModelIndex(index);
|
|
if (config) {
|
|
if (retrieveConnectionPassword(*config)) {
|
|
m_connectionTreeModel->save(*config);
|
|
auto w = new BackupDialog(nullptr);
|
|
w->setAttribute( Qt::WA_DeleteOnClose );
|
|
w->setConfig(*config);
|
|
w->show();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ConnectionController::createConnection()
|
|
{
|
|
ConnectionConfig cc;
|
|
cc.setUuid(QUuid::createUuid());
|
|
editConfig(cc);
|
|
}
|
|
|
|
void ConnectionController::editConnection(QModelIndex index)
|
|
{
|
|
auto config = ConnectionTreeModel::getConfigFromModelIndex(index);
|
|
if (config) {
|
|
editConfig(*config);
|
|
}
|
|
}
|
|
|
|
void ConnectionController::editCopy(QModelIndex index)
|
|
{
|
|
auto config = ConnectionTreeModel::getConfigFromModelIndex(index);
|
|
if (config) {
|
|
auto cc = *config;
|
|
cc.setUuid(QUuid::createUuid());
|
|
cc.setEncodedPassword({}); // maybe we should decode en reencode?
|
|
editConfig(cc);
|
|
}
|
|
}
|
|
|
|
void ConnectionController::editConfig(ConnectionConfig &cc)
|
|
{
|
|
ConnectionConfigurationWidget::editExistingInWindow(this, cc,
|
|
[this] (ConnectionConfigurationWidget &w) -> void { saveConnection(w); });
|
|
}
|
|
|
|
void ConnectionController::saveConnection(ConnectionConfigurationWidget &w)
|
|
{
|
|
auto cc = w.data();
|
|
auto grp = w.group();
|
|
if (w.savePassword())
|
|
encryptPassword(cc);
|
|
if (w.clearPassword())
|
|
cc.setEncodedPassword({});
|
|
m_connectionTreeModel->save(grp, cc);
|
|
}
|
|
|
|
void ConnectionController::addGroup()
|
|
{
|
|
auto result = QInputDialog::getText(nullptr, tr("Add new connection group"),
|
|
tr("Group name"));
|
|
if (!result.isEmpty()) {
|
|
auto res = m_connectionTreeModel->addGroup(result);
|
|
if (std::holds_alternative<QSqlError>(res)) {
|
|
QMessageBox::critical(nullptr, tr("Add group failed"),
|
|
tr("Failed to add group.\n") +
|
|
std::get<QSqlError>(res).text());
|
|
}
|
|
}
|
|
}
|
|
|
|
void ConnectionController::removeGroup(QModelIndex index)
|
|
{
|
|
auto group = ConnectionTreeModel::getGroupFromModelIndex(index);
|
|
if (group) {
|
|
auto btn = QMessageBox::question(nullptr, tr("Connection group"),
|
|
tr("Remove the selected group and all connections contained in the group?"),
|
|
QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No),
|
|
QMessageBox::NoButton);
|
|
if (btn == QMessageBox::Yes) {
|
|
m_connectionTreeModel->removeGroup(index.row());
|
|
}
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<PasswordManager> ConnectionController::passwordManager()
|
|
{
|
|
return m_passwordManager;
|
|
}
|
|
|
|
bool ConnectionController::retrieveConnectionPassword(ConnectionConfig &cc)
|
|
{
|
|
auto enc_pwd = cc.encodedPassword();
|
|
if (!enc_pwd.isEmpty())
|
|
{
|
|
std::string pw;
|
|
bool result = retrieveFromPasswordManager(getPskId(cc.uuid()),
|
|
std::string_view(enc_pwd.data(), enc_pwd.size()) , pw);
|
|
if (result)
|
|
{
|
|
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.
|
|
QString str = cc.makeLongDescription();
|
|
auto dlg = std::make_unique<PasswordPromptDialog>(PasswordPromptDialog::SaveOption, nullptr);
|
|
dlg->setCaption(tr("Connection password prompt"));
|
|
dlg->setDescription(QString(tr("Please provide password for connection %1")).arg(str));
|
|
int exec_result = dlg->exec();
|
|
|
|
if (exec_result == QDialog::Accepted)
|
|
{
|
|
auto password = dlg->password();
|
|
cc.setPassword(password);
|
|
if (dlg->saveChecked())
|
|
encryptPassword(cc);
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ConnectionController::retrieveFromPasswordManager(const std::string &password_id, const std::string_view &enc_password, std::string &password)
|
|
{
|
|
try
|
|
{
|
|
if (!UnlockPasswordManagerIfNeeded())
|
|
return false;
|
|
|
|
password = m_passwordManager->decrypt(password_id, enc_password);
|
|
return true;
|
|
}
|
|
catch (const PasswordManagerException &ex)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool ConnectionController::encryptPassword(ConnectionConfig &cc)
|
|
{
|
|
if (!UnlockPasswordManagerIfNeeded())
|
|
return false;
|
|
|
|
std::string password = cc.password().toStdString();
|
|
std::string password_id = getPskId(cc.uuid());
|
|
std::string enc_password = m_passwordManager->encrypt(password_id, password);
|
|
cc.setEncodedPassword({ enc_password.data(), static_cast<int>(enc_password.size()) });
|
|
return true;
|
|
}
|
|
|
|
bool ConnectionController::decodeConnectionPassword(QUuid id, QByteArray encoded, QString &out_password)
|
|
{
|
|
std::string password_id = getPskId(id);
|
|
std::string enc(encoded.data(), encoded.size());
|
|
std::string password;
|
|
bool res = retrieveFromPasswordManager(password_id, enc, password);
|
|
if (res)
|
|
out_password = QString::fromStdString(password);
|
|
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()
|
|
{
|
|
auto&& user_cfg_db = m_masterController->userConfigDatabase();
|
|
if (m_passwordManager->initialized(user_cfg_db))
|
|
{
|
|
if (!m_passwordManager->locked())
|
|
return true;
|
|
|
|
while (true)
|
|
{
|
|
PassphraseResult pp_result = PassphrasePrompt();
|
|
if (!pp_result.success)
|
|
break;
|
|
|
|
// user gave OK, if succeeds return true otherwise loop a prompt for password again.
|
|
if (m_passwordManager->openDatabase(user_cfg_db, pp_result.passphrase))
|
|
{
|
|
setRelockTimer(pp_result.rememberForMinutes);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
InitializePasswordManager();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void ConnectionController::setRelockTimer(int rem_minutes)
|
|
{
|
|
if (rem_minutes >= 0) {
|
|
int timeout = rem_minutes * 60 * 1000; /// rem is in minutes, timeout in millisec
|
|
m_relockTimer->start(timeout);
|
|
}
|
|
}
|
|
|
|
PassphraseResult ConnectionController::PassphrasePrompt()
|
|
{
|
|
auto dlg = std::make_unique<PasswordPromptDialog>(PasswordPromptDialog::RememberPassword, nullptr);
|
|
dlg->setCaption(tr("Unlock password manager"));
|
|
dlg->setDescription(tr("Enter password for password manager"));
|
|
int exec_result = dlg->exec();
|
|
bool ok = (exec_result == QDialog::Accepted);
|
|
|
|
PassphraseResult result;
|
|
result.success = ok;
|
|
if (ok) {
|
|
result.passphrase = dlg->password();
|
|
result.rememberForMinutes = dlg->remember();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool ConnectionController::InitializePasswordManager()
|
|
{
|
|
// Ask user for passphrase + confirmation, clearly instruct this is first setup
|
|
// create
|
|
auto dlg = std::make_unique<PasswordPromptDialog>(PasswordPromptDialog::ConfirmPassword, nullptr);
|
|
dlg->setCaption(tr("Password manager setup"));
|
|
dlg->setDescription(tr("Enter a strong password for password manager initialization. A strong key will be "
|
|
"derived from your password and it will be impossible to recover anything from the "
|
|
"password manager without the password you enter here."));
|
|
int exec_result = dlg->exec();
|
|
if (exec_result == QDialog::Accepted) {
|
|
QString passphrase = dlg->password();
|
|
auto&& user_cfg_db = m_masterController->userConfigDatabase();
|
|
if (m_passwordManager->createDatabase(user_cfg_db, passphrase))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::string ConnectionController::getPskId(QUuid connectionid)
|
|
{
|
|
std::string id = "dbpw/";
|
|
id += connectionid.toString().toUtf8().data();
|
|
return id;
|
|
}
|
|
|
|
void ConnectionController::relock()
|
|
{
|
|
m_passwordManager->closeDatabase();
|
|
}
|
|
|
|
|
|
|