pgLab/pglab/ConnectionController.cpp

351 lines
10 KiB
C++
Raw Permalink Normal View History

#include "ConnectionController.h"
#include "MasterController.h"
#include "ConnectionManagerWindow.h"
#include "ConnectionListModel.h"
#include "utils/PasswordManager.h"
#include "DatabaseWindow.h"
#include "BackupDialog.h"
#include "PasswordPromptDialog.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);
}
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);
openSqlWindowForConnection(*config);
}
}
}
void ConnectionController::openSqlWindowForConnection(const ConnectionConfig &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);
2021-04-10 14:44:10 +02:00
auto w = new BackupDialog(nullptr);
w->setAttribute( Qt::WA_DeleteOnClose );
w->setConfig(*config);
w->show();
}
}
}
void ConnectionController::createConnection(ConnectionConfig *init)
{
ConnectionConfig cc;
if (init)
{
cc = *init;
cc.setUuid(QUuid());
}
2021-07-04 20:07:20 +02:00
editConfig(cc);
}
void ConnectionController::editConnection(QModelIndex index)
{
auto config = ConnectionTreeModel::getConfigFromModelIndex(index);
if (config) {
2021-07-04 20:07:20 +02:00
editConfig(*config);
}
}
void ConnectionController::editCopy(QModelIndex index)
{
auto config = ConnectionTreeModel::getConfigFromModelIndex(index);
if (config) {
auto cc = *config;
cc.setUuid(QUuid());
cc.setEncodedPassword({}); // maybe we should decode en reencode?
2021-07-04 20:07:20 +02:00
editConfig(cc);
}
}
2021-07-04 20:07:20 +02:00
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();
bool isNew = cc.uuid().isNull();
if (isNew)
{
cc.setUuid(QUuid::createUuid());
}
if (w.savePasswordEnabled())
{
if (isNew || w.passwordIsChanged())
encryptPassword(cc);
}
else
{
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()) {
try
{
m_connectionTreeModel->addGroup(result);
}
catch (const SQLiteException &ex) {
QMessageBox::critical(nullptr, tr("Add group failed"),
tr("Failed to add group.\n") +
QString(ex.what()));
}
}
}
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;
}
2025-02-23 16:52:39 +01:00
catch (const PasswordManagerException &)
{
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()
{
SQLiteConnection& user_cfg_db = m_masterController->userConfigDatabase();
SQLiteTransaction tx(user_cfg_db);
m_passwordManager->resetMasterPassword(user_cfg_db);
m_connectionTreeModel->clearAllPasswords();
tx.Commit();
}
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)
{
2021-07-04 20:07:20 +02:00
PassphraseResult pp_result = PassphrasePrompt();
if (!pp_result.success)
break;
2021-07-04 20:07:20 +02:00
// user gave OK, if succeeds return true otherwise loop a prompt for password again.
if (m_passwordManager->openDatabase(user_cfg_db, pp_result.passphrase))
{
2021-07-04 20:07:20 +02:00
setRelockTimer(pp_result.rememberForMinutes);
return true;
}
}
}
else
{
2021-07-04 20:07:20 +02:00
InitializePasswordManager();
return true;
}
return false;
}
2021-07-04 20:07:20 +02:00
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(QUuid::WithBraces).toUtf8().data();
return id;
}
void ConnectionController::relock()
{
m_passwordManager->closeDatabase();
}