pgLab/pglab/ConnectionController.cpp

338 lines
10 KiB
C++
Raw Normal View History

#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);
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 cc;
cc.setUuid(QUuid::createUuid());
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::createUuid());
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();
if (w.savePassword()) {
encryptPassword(cc);
}
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)
{
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().toUtf8().data();
return id;
}
void ConnectionController::relock()
{
m_passwordManager->closeDatabase();
}