Merge branch 'improve-connection-manager' into 'master'

Improve connection manager

See merge request eelke/pgLab!5
This commit is contained in:
Eelke Klein 2019-09-01 08:19:22 +00:00
commit 41c1305e3d
23 changed files with 1569 additions and 775 deletions

View file

@ -0,0 +1,198 @@
#include "ConnectionConfigurationWidget.h"
#include "SslModeModel.h"
#include "ConnectionConfig.h"
#include "ConnectionController.h"
#include "ConnectionListModel.h"
#include "util.h"
#include <QApplication>
#include <QDataWidgetMapper>
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QSpinBox>
#include <QComboBox>
#include <QDialog>
#define SET_OBJECT_NAME(var) var->setObjectName(#var)
void ConnectionConfigurationWidget::editExistingInWindow(ConnectionController *ctrl, const ConnectionConfig &cfg)
{
auto w = new ConnectionConfigurationWidget(ctrl->getConnectionTreeModel());
w->setData(cfg);
auto btn_hbox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal);
//auto btn_test = btn_hbox->addButton(tr("Test"), QDialogButtonBox::ActionRole);
auto vbox = new QVBoxLayout;
vbox->addWidget(w);
vbox->addWidget(btn_hbox);
auto win = new QDialog;
win->setWindowTitle(tr("Edit connection configuration"));
win->setLayout(vbox);
win->setAttribute( Qt::WA_DeleteOnClose, true );
win->connect(btn_hbox, &QDialogButtonBox::accepted, [ctrl, w, win] () {
auto [grp, cc] = w->data();
ctrl->getConnectionTreeModel()->save(grp, cc);
win->accept();
});
win->connect(btn_hbox, &QDialogButtonBox::rejected, [win] ()
{
win->reject();
});
win->show();
}
ConnectionConfigurationWidget::ConnectionConfigurationWidget(ConnectionTreeModel *connection_model, QWidget *parent)
: QWidget(parent)
, m_connectionModel(connection_model)
{
lblGroup = new QLabel;
cmbbxGroup = new QComboBox;
cmbbxGroup->setModel(connection_model);
cmbbxGroup->setModelColumn(0);
lblGroup->setBuddy(cmbbxGroup);
lblName = new QLabel;
SET_OBJECT_NAME(lblName);
edtName = new QLineEdit ;
SET_OBJECT_NAME(edtName);
lblName->setBuddy(edtName);
lblHost = new QLabel;
SET_OBJECT_NAME(lblHost);
edtHost = new QLineEdit;
SET_OBJECT_NAME(edtHost);
lblHost->setBuddy(edtHost);
lblPort = new QLabel;
SET_OBJECT_NAME(lblPort);
spinPort = new QSpinBox;
SET_OBJECT_NAME(spinPort);
spinPort->setRange(1, std::numeric_limits<uint16_t>::max());
lblPort->setBuddy(spinPort);
lblUser = new QLabel;
SET_OBJECT_NAME(lblUser);
edtUser = new QLineEdit;
SET_OBJECT_NAME(edtUser);
lblUser->setBuddy(edtUser);
lblDbName = new QLabel;
SET_OBJECT_NAME(lblDbName);
edtDbname = new QLineEdit;
SET_OBJECT_NAME(edtDbname);
lblDbName->setBuddy(edtDbname);
lblSsl = new QLabel;
SET_OBJECT_NAME(lblSsl);
cmbbxSsl = new QComboBox;
SET_OBJECT_NAME(cmbbxSsl);
cmbbxSsl->setModelColumn(0);
auto ssl_model = new SslModeModel(this);
cmbbxSsl->setModel(ssl_model);
lblSsl->setBuddy(cmbbxSsl);
lblCert = new QLabel;
SET_OBJECT_NAME(lblCert);
edtCert = new QLineEdit;
SET_OBJECT_NAME(edtCert);
lblCert->setBuddy(edtCert);
lblKey = new QLabel;
SET_OBJECT_NAME(lblKey);
edtKey = new QLineEdit;
SET_OBJECT_NAME(edtKey);
lblKey->setBuddy(edtKey);
lblRootCert = new QLabel;
SET_OBJECT_NAME(lblRootCert);
edtRootCert = new QLineEdit;
SET_OBJECT_NAME(edtRootCert);
lblRootCert->setBuddy(edtRootCert);
lblCrl = new QLabel;
SET_OBJECT_NAME(lblCrl);
edtCrl = new QLineEdit;
SET_OBJECT_NAME(edtCrl);
lblCrl->setBuddy(edtCrl);
formLayout = new QFormLayout;
formLayout->addRow(lblGroup, cmbbxGroup);
formLayout->addRow(lblName, edtName);
formLayout->addRow(lblHost, edtHost);
formLayout->addRow(lblPort, spinPort);
formLayout->addRow(lblUser, edtUser);
formLayout->addRow(lblDbName, edtDbname);
formLayout->addRow(lblSsl, cmbbxSsl);
formLayout->addRow(lblCert, edtCert);
formLayout->addRow(lblKey, edtKey);
formLayout->addRow(lblRootCert, edtRootCert);
formLayout->addRow(lblCrl, edtCrl);
setLayout(formLayout);
retranslateUi();
}
void ConnectionConfigurationWidget::retranslateUi()
{
lblGroup->setText(QApplication::translate("ConnectionConfigurationWidget", "&Group", nullptr));
lblName->setText(QApplication::translate("ConnectionConfigurationWidget", "&Name", nullptr));
lblHost->setText(QApplication::translate("ConnectionConfigurationWidget", "&Host", nullptr));
lblPort->setText(QApplication::translate("ConnectionConfigurationWidget", "&Port", nullptr));
lblUser->setText(QApplication::translate("ConnectionConfigurationWidget", "&Username", nullptr));
lblDbName->setText(QApplication::translate("ConnectionConfigurationWidget", "&Database", nullptr));
lblSsl->setText(QApplication::translate("ConnectionConfigurationWidget", "&SSL mode", nullptr));
lblCert->setText(QApplication::translate("ConnectionConfigurationWidget", "&Certificate", nullptr));
lblKey->setText(QApplication::translate("ConnectionConfigurationWidget", "&Key", nullptr));
lblRootCert->setText(QApplication::translate("ConnectionConfigurationWidget", "&Root cert.", nullptr));
lblCrl->setText(QApplication::translate("ConnectionConfigurationWidget", "Revocation &list", nullptr));
}
void ConnectionConfigurationWidget::setData(const ConnectionConfig &cfg)
{
auto group = cfg.parent();
if (group) {
auto group_idx = m_connectionModel->findGroup(group->conngroup_id);
cmbbxGroup->setCurrentIndex(group_idx);
}
m_uuid = cfg.uuid();
edtName->setText(stdStrToQ(cfg.name()));
edtHost->setText(stdStrToQ(cfg.host()));
spinPort->setValue(cfg.port());
edtUser->setText(stdStrToQ(cfg.user()));
edtDbname->setText(stdStrToQ(cfg.dbname()));
cmbbxSsl->setCurrentIndex(static_cast<int>(cfg.sslMode()));
edtCert->setText(stdStrToQ(cfg.sslCert()));
edtKey->setText(stdStrToQ(cfg.sslKey()));
edtRootCert->setText(stdStrToQ(cfg.sslRootCert()));
edtCrl->setText(stdStrToQ(cfg.sslCrl()));
}
std::tuple<QString, ConnectionConfig> ConnectionConfigurationWidget::data() const
{
QString group;
group = cmbbxGroup->currentText();
ConnectionConfig cfg;
cfg.setUuid(m_uuid);
cfg.setName(qStrToStd(edtName->text()));
cfg.setHost(qStrToStd(edtHost->text()));
cfg.setPort(static_cast<uint16_t>(spinPort->value()));
cfg.setUser(qStrToStd(edtUser->text()));
cfg.setDbname(qStrToStd(edtDbname->text()));
cfg.setSslMode(static_cast<SslMode>(cmbbxSsl->currentIndex()));
cfg.setSslCert(qStrToStd(edtCert->text()));
cfg.setSslKey(qStrToStd(edtKey->text()));
cfg.setSslRootCert(qStrToStd(edtRootCert->text()));
cfg.setSslCrl(qStrToStd(edtCrl->text()));
return { group, cfg };
}

View file

@ -0,0 +1,66 @@
#ifndef CONNECTIONCONFIGURATIONWIDGET_H
#define CONNECTIONCONFIGURATIONWIDGET_H
#include <QWidget>
#include <QUuid>
#include <tuple>
class ConnectionController;
class ConnectionConfig;
class QFormLayout;
class QLabel;
class QLineEdit;
class QSpinBox;
class QComboBox;
class QDataWidgetMapper;
class ConnectionTreeModel;
class ConnectionConfigurationWidget : public QWidget
{
Q_OBJECT
public:
static void editExistingInWindow(ConnectionController *ctrl, const ConnectionConfig &cfg);
explicit ConnectionConfigurationWidget(ConnectionTreeModel *connection_model, QWidget *parent = nullptr);
void retranslateUi();
void setData(const ConnectionConfig &cfg);
std::tuple<QString, ConnectionConfig> data() const;
signals:
private:
ConnectionTreeModel *m_connectionModel;
QUuid m_uuid;
QLabel *lblGroup;
QComboBox *cmbbxGroup;
QLabel *lblName;
QLineEdit *edtName;
QLabel *lblHost;
QLineEdit *edtHost;
QLabel *lblPort;
QSpinBox *spinPort;
QLabel *lblUser;
QLineEdit *edtUser;
QLabel *lblDbName;
QLineEdit *edtDbname;
QLabel *lblSsl;
QComboBox *cmbbxSsl;
QLabel *lblCert;
QLineEdit *edtCert;
QLabel *lblKey;
QLineEdit *edtKey;
QLabel *lblRootCert;
QLineEdit *edtRootCert;
QLabel *lblCrl;
QLineEdit *edtCrl;
QFormLayout *formLayout;
public slots:
};
#endif // CONNECTIONCONFIGURATIONWIDGET_H

View file

@ -0,0 +1,262 @@
#include "ConnectionController.h"
#include "MasterController.h"
#include "ConnectionManagerWindow.h"
#include "ConnectionListModel.h"
#include "PasswordManager.h"
#include "DatabaseWindow.h"
#include "ServerWindow.h"
#include "BackupDialog.h"
#include "PasswordPromptDialog.h"
#include "ConnectionConfigurationWidget.h"
#include <QInputDialog>
#include <QMessageBox>
ConnectionController::ConnectionController(MasterController *parent)
: QObject(parent)
, m_masterController(parent)
{}
ConnectionController::~ConnectionController()
{
delete m_connectionManagerWindow;
delete m_connectionTreeModel;
}
void ConnectionController::init()
{
//std::string dbfilename = QDir::toNativeSeparators(GetUserConfigDatabaseName()).toUtf8().data();
//m_userConfigDatabase = std::make_shared<Botan::Sqlite3_Database>(dbfilename);
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();
}
namespace {
ConnectionConfig* getConfigFromModelIndex(QModelIndex index)
{
if (!index.isValid())
return nullptr;
auto node = static_cast<ConnectionNode*>(index.internalPointer());
return dynamic_cast<ConnectionConfig*>(node);
}
ConnectionGroup* getGroupFromModelIndex(QModelIndex index)
{
if (!index.isValid())
return nullptr;
auto node = static_cast<ConnectionNode*>(index.internalPointer());
return dynamic_cast<ConnectionGroup*>(node);
}
}
void ConnectionController::openSqlWindowForConnection(QModelIndex index)
{
auto config = 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 = getConfigFromModelIndex(index);
if (config) {
if (retrieveConnectionPassword(*config)) {
m_connectionTreeModel->save(*config);
auto w = new BackupDialog(nullptr); //new ServerWindow(this, nullptr);
w->setAttribute( Qt::WA_DeleteOnClose );
w->setConfig(*config);
w->show();
}
}
}
void ConnectionController::createConnection()
{
ConnectionConfig cc;
cc.setUuid(QUuid::createUuid());
ConnectionConfigurationWidget::editExistingInWindow(this, cc);
}
void ConnectionController::editConnection(QModelIndex index)
{
auto config = getConfigFromModelIndex(index);
if (config) {
ConnectionConfigurationWidget::editExistingInWindow(this, *config);
}
}
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 = 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());
}
}
}
void ConnectionController::openServerWindowForConnection(QModelIndex index)
{
auto config = getConfigFromModelIndex(index);
if (config) {
if (retrieveConnectionPassword(*config)) {
m_connectionTreeModel->save(*config);
auto w = new ServerWindow(m_masterController, nullptr);
w->setAttribute( Qt::WA_DeleteOnClose );
w->setConfig(*config);
w->show();
}
}
}
bool ConnectionController::retrieveConnectionPassword(ConnectionConfig &cc)
{
auto pw_state = cc.passwordState();
if (pw_state == PasswordState::NotNeeded) {
return true;
}
else if (pw_state == PasswordState::SavedPasswordManager) {
std::string pw;
bool result = getPasswordFromPskdb(getPskId(cc), pw);
if (result) {
cc.setPassword(pw);
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) {
std::string password = dlg->password().toUtf8().data();
cc.setPassword(password);
if (dlg->saveChecked()) {
storePasswordInPskdb(getPskId(cc), password);
cc.setPasswordState(PasswordState::SavedPasswordManager);
}
return true;
}
return false;
}
bool ConnectionController::getPasswordFromPskdb(const std::string &password_id, std::string &password)
{
if (!UnlockPasswordManagerIfNeeded())
return false;
return m_passwordManager->get(password_id, password);
}
bool ConnectionController::storePasswordInPskdb(const std::string &password_id, const std::string password)
{
if (!UnlockPasswordManagerIfNeeded())
return false;
m_passwordManager->set(password_id, password);
return true;
}
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) {
// ask user for passphrase
auto dlg = std::make_unique<PasswordPromptDialog>(nullptr, 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);
if (!ok) {
// leave this retry loop
break;
}
// user gave OK, if succeeds return true otherwise loop a prompt for password again.
if (m_passwordManager->openDatabase(user_cfg_db, dlg->password()))
return true;
}
}
else {
// 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();
if (m_passwordManager->createDatabase(user_cfg_db, passphrase))
return true;
}
}
return false;
}
std::string ConnectionController::getPskId(const ConnectionConfig &cc)
{
std::string id = "dbpw/";
id += cc.uuid().toString().toUtf8().data();
return id;
}

View file

@ -0,0 +1,65 @@
#ifndef CONNECTIONCONTROLLER_H
#define CONNECTIONCONTROLLER_H
#include <QObject>
class MasterController;
class ConnectionConfig;
class ConnectionList;
class ConnectionTreeModel;
class ConnectionManagerWindow;
class PasswordManager;
class ConnectionController : public QObject {
Q_OBJECT
public:
explicit ConnectionController(MasterController *parent = nullptr);
~ConnectionController();
void init();
ConnectionTreeModel *getConnectionTreeModel()
{
return m_connectionTreeModel;
}
void showConnectionManager();
void openSqlWindowForConnection(QModelIndex index);
void openServerWindowForConnection(QModelIndex index);
void openBackupDlgForConnection(QModelIndex index);
/// Starts the form for creating a new conncetion.
/// This function returns immidiatly!
void createConnection();
/// Starts the form for editing a conncetion.
/// This function returns immidiatly!
void editConnection(QModelIndex index);
void addGroup();
void removeGroup(QModelIndex index);
private:
MasterController *m_masterController;
ConnectionList *m_connectionList = nullptr;
ConnectionTreeModel *m_connectionTreeModel = nullptr;
ConnectionManagerWindow *m_connectionManagerWindow = nullptr;
/** Using long lived object so it can remember its master password for sometime
* if the user wishes it.
*/
std::shared_ptr<PasswordManager> m_passwordManager;
/** Retrieves the connection password from the user (directly or through the psk db)
*
*/
bool retrieveConnectionPassword(ConnectionConfig &cc);
bool getPasswordFromPskdb(const std::string &password_id, std::string &password);
bool storePasswordInPskdb(const std::string &password_id, const std::string password);
bool UnlockPasswordManagerIfNeeded();
static std::string getPskId(const ConnectionConfig &cc);
};
#endif // CONNECTIONCONTROLLER_H

View file

@ -1,163 +0,0 @@
#include "ConnectionList.h"
#include "ScopeGuard.h"
#include "util.h"
#include "PasswordManager.h"
#include <QDir>
#include <QStandardPaths>
#include <QSettings>
namespace {
/** Saves a connection configuration.
Before calling this you may want to call beginGroup.
*/
void SaveConnectionConfig(QSettings &settings, const ConnectionConfig &cc)
{
settings.setValue("name", stdStrToQ(cc.name()));
settings.setValue("host", stdStrToQ(cc.host()));
settings.setValue("hostaddr", stdStrToQ(cc.hostAddr()));
settings.setValue("port", cc.port());
settings.setValue("user", stdStrToQ(cc.user()));
//settings.setValue("password", stdStrToQ(cc.password()));
settings.setValue("dbname", stdStrToQ(cc.dbname()));
settings.setValue("sslmode", static_cast<int>(cc.sslMode()));
settings.setValue("sslcert", stdStrToQ(cc.sslCert()));
settings.setValue("sslkey", stdStrToQ(cc.sslKey()));
settings.setValue("sslrootcert", stdStrToQ(cc.sslRootCert()));
settings.setValue("sslcrl", stdStrToQ(cc.sslCrl()));
settings.setValue("passwordState", static_cast<int>(cc.passwordState()));
}
template <typename S, typename T>
bool in_range(T value)
{
return value >= std::numeric_limits<S>::min() && value <= std::numeric_limits<S>::max();
}
void LoadConnectionConfig(QSettings &settings, ConnectionConfig &cc)
{
cc.setName(qvarToStdStr(settings.value("name")));
cc.setHost(qvarToStdStr(settings.value("host")));
cc.setHostAddr(qvarToStdStr(settings.value("hostaddr")));
int p = settings.value("port", 5432).toInt();
if (!in_range<uint16_t>(p)) {
p = 0; // let the user re-enter a valid value
}
cc.setPort(static_cast<uint16_t>(p));
cc.setUser(qvarToStdStr(settings.value("user")));
//cc.setPassword(qvarToStdStr(settings.value("password")));
cc.setDbname(qvarToStdStr(settings.value("dbname")));
cc.setSslMode(static_cast<SslMode>(settings.value("sslmode").toInt()));
cc.setSslCert(qvarToStdStr(settings.value("sslcert")));
cc.setSslKey(qvarToStdStr(settings.value("sslkey")));
cc.setSslRootCert(qvarToStdStr(settings.value("sslrootcert")));
cc.setSslCrl(qvarToStdStr(settings.value("sslcrl")));
PasswordState pwstate;
QVariant v = settings.value("passwordState");
if (v.isNull()) pwstate = PasswordState::NotStored;
else pwstate = static_cast<PasswordState>(v.toInt());
cc.setPasswordState(pwstate);
}
} // end of unnamed namespace
/// \todo should return an expected as creation of the folder can fail
QString ConnectionList::iniFileName()
{
QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
QDir dir(path);
if (!dir.exists()) {
dir.mkpath(".");
}
path += "/connections.ini";
return path;
}
ConnectionList::ConnectionList() = default;
size_t ConnectionList::createNew()
{
ConnectionConfig cc;
cc.setUuid(QUuid::createUuid());
m_connections.push_back(cc);
return m_connections.size()-1;
}
void ConnectionList::remove(size_t idx, size_t count)
{
auto f = m_connections.begin() + static_cast<int>(idx);
auto l = f + static_cast<int>(count);
deleteFromIni(f, l);
m_connections.erase(f, l);
// remove from password save
}
void ConnectionList::deleteFromIni(const t_Connections::iterator &begin, const t_Connections::iterator &end)
{
QString file_name = iniFileName();
QSettings settings(file_name, QSettings::IniFormat);
for (auto i = begin; i != end; ++i) {
settings.remove(i->uuid().toString());
}
}
void ConnectionList::load()
{
QString file_name = iniFileName();
QSettings settings(file_name, QSettings::IniFormat);
auto groups = settings.childGroups();
for (auto&& grp : groups) {
if (grp == "c_IniGroupSecurity") {
// Read security settings
} else {
QUuid uuid(grp);
if ( ! uuid.isNull() ) {
settings.beginGroup(grp);
SCOPE_EXIT { settings.endGroup(); };
ConnectionConfig cc;
cc.setUuid(uuid);
LoadConnectionConfig(settings, cc);
m_connections.push_back(cc);
}
}
}
}
void ConnectionList::save()
{
QString file_name = iniFileName();
QSettings settings(file_name, QSettings::IniFormat);
for (auto& e : m_connections) {
settings.beginGroup(e.uuid().toString());
SCOPE_EXIT { settings.endGroup(); };
SaveConnectionConfig(settings, e);
e.clean();
}
settings.sync();
}
void ConnectionList::save(size_t index)
{
auto& e = m_connections.at(index);
if (e.dirty()) {
QString file_name = iniFileName();
QSettings settings(file_name, QSettings::IniFormat);
settings.beginGroup(e.uuid().toString());
SaveConnectionConfig(settings, e);
e.clean();
settings.sync();
}
}

View file

@ -1,54 +0,0 @@
#ifndef CONNECTIONLIST_H
#define CONNECTIONLIST_H
#include "ConnectionConfig.h"
#include <QString>
#include <QUuid>
#include <vector>
#include "Expected.h"
class ConnectionList {
public:
ConnectionList();
size_t size() const { return m_connections.size(); }
ConnectionConfig& getConfigByIdx(size_t idx)
{
return m_connections.at(idx);
}
void setConfigByIdx(size_t idx, const ConnectionConfig &cc)
{
m_connections[idx] = cc;
}
size_t createNew();
void remove(size_t idx, size_t count);
void load();
void save();
void save(size_t index);
private:
// class LijstElem {
// public:
// QUuid m_uuid; ///< Unique identifier, used as a key for storing password in psk db.
// ConnectionConfig m_config;
// LijstElem(const QUuid id, const ConnectionConfig &cfg)
// : m_uuid(id), m_config(cfg)
// {}
// };
using t_Connections = std::vector<ConnectionConfig>;
t_Connections m_connections;
void deleteFromIni(const t_Connections::iterator &begin, const t_Connections::iterator &end);
static QString iniFileName();
};
#endif // CONNECTIONLIST_H

View file

@ -1,14 +1,158 @@
#include "ConnectionListModel.h"
#include "ConnectionList.h"
#include "ScopeGuard.h"
#include "util.h"
#include <botan/cryptobox.h>
#include <QDir>
#include <QSettings>
#include <QSqlDatabase>
#include <QSqlError>
#include <QSqlQuery>
#include <QStandardPaths>
ConnectionListModel::ConnectionListModel(ConnectionList *conns, QObject *parent)
namespace {
const char * const q_create_table_conngroup =
R"__(
CREATE TABLE IF NOT EXISTS conngroup (
conngroup_id INTEGER PRIMARY KEY,
gname TEXT NOT NULL UNIQUE
);)__";
const char * const q_create_table_connection =
R"__(
CREATE TABLE IF NOT EXISTS connection (
uuid TEXT PRIMARY KEY,
cname TEXT NOT NULL,
conngroup_id INTEGER NOT NULL,
host TEXT NOT NULL,
hostaddr TEXT NOT NULL,
port INTEGER NOT NULL,
user TEXT NOT NULL,
dbname TEXT NOT NULL,
sslmode INTEGER NOT NULL,
sslcert TEXT NOT NULL,
sslkey TEXT NOT NULL,
sslrootcert TEXT NOT NULL,
sslcrl TEXT NOT NULL,
passwordstate INTEGER NOT NULL
);)__";
const char * const q_insert_or_replace_into_connection =
R"__(INSERT OR REPLACE INTO connection
VALUES (:uuid, :name, :conngroup_id, :host, :hostaddr, :port, :user, :dbname,
:sslmode, :sslcert, :sslkey, :sslrootcert, :sslcrl, :passwordstate);
)__" ;
std::tuple<bool, QSqlError> InitConnectionTables(QSqlDatabase &db)
{
QSqlQuery q_create_table(db);
q_create_table.prepare(q_create_table_conngroup);
if (!q_create_table.exec()) {
auto err = q_create_table.lastError();
return { false, err };
}
q_create_table.prepare(q_create_table_connection);
if (!q_create_table.exec()) {
auto err = q_create_table.lastError();
return { false, err };
}
return {true, {}};
}
std::optional<QSqlError> SaveConnectionConfig(QSqlDatabase &db, const ConnectionConfig &cc, int conngroup_id)
{
QSqlQuery q(db);
q.prepare(q_insert_or_replace_into_connection);
q.bindValue(":uuid", cc.uuid().toString());
q.bindValue(":name", stdStrToQ(cc.name()));
q.bindValue(":conngroup_id", conngroup_id);
q.bindValue(":host", stdStrToQ(cc.host()));
q.bindValue(":hostaddr", stdStrToQ(cc.hostAddr()));
q.bindValue(":port", cc.port());
q.bindValue(":user", stdStrToQ(cc.user()));
q.bindValue(":dbname", stdStrToQ(cc.dbname()));
q.bindValue(":sslmode", static_cast<int>(cc.sslMode()));
q.bindValue(":sslcert", stdStrToQ(cc.sslCert()));
q.bindValue(":sslkey", stdStrToQ(cc.sslKey()));
q.bindValue(":sslrootcert", stdStrToQ(cc.sslRootCert()));
q.bindValue(":sslcrl", stdStrToQ(cc.sslCrl()));
q.bindValue(":passwordstate", static_cast<int>(cc.passwordState()));
if (!q.exec()) {
return q.lastError();
}
return {};
}
/** Saves a connection configuration.
Before calling this you may want to call beginGroup.
*/
// void SaveConnectionConfig(QSettings &settings, const ConnectionConfig &cc)
// {
// settings.setValue("name", stdStrToQ(cc.name()));
// settings.setValue("host", stdStrToQ(cc.host()));
// settings.setValue("hostaddr", stdStrToQ(cc.hostAddr()));
// settings.setValue("port", cc.port());
// settings.setValue("user", stdStrToQ(cc.user()));
// //settings.setValue("password", stdStrToQ(cc.password()));
// settings.setValue("dbname", stdStrToQ(cc.dbname()));
// settings.setValue("sslmode", static_cast<int>(cc.sslMode()));
// settings.setValue("sslcert", stdStrToQ(cc.sslCert()));
// settings.setValue("sslkey", stdStrToQ(cc.sslKey()));
// settings.setValue("sslrootcert", stdStrToQ(cc.sslRootCert()));
// settings.setValue("sslcrl", stdStrToQ(cc.sslCrl()));
// settings.setValue("passwordState", static_cast<int>(cc.passwordState()));
// }
// template <typename S, typename T>
// bool in_range(T value)
// {
// return value >= std::numeric_limits<S>::min() && value <= std::numeric_limits<S>::max();
// }
// void LoadConnectionConfig(QSettings &settings, ConnectionConfig &cc)
// {
// cc.setName(qvarToStdStr(settings.value("name")));
// cc.setHost(qvarToStdStr(settings.value("host")));
// cc.setHostAddr(qvarToStdStr(settings.value("hostaddr")));
// int p = settings.value("port", 5432).toInt();
// if (!in_range<uint16_t>(p)) {
// p = 0; // let the user re-enter a valid value
// }
// cc.setPort(static_cast<uint16_t>(p));
// cc.setUser(qvarToStdStr(settings.value("user")));
// //cc.setPassword(qvarToStdStr(settings.value("password")));
// cc.setDbname(qvarToStdStr(settings.value("dbname")));
// cc.setSslMode(static_cast<SslMode>(settings.value("sslmode").toInt()));
// cc.setSslCert(qvarToStdStr(settings.value("sslcert")));
// cc.setSslKey(qvarToStdStr(settings.value("sslkey")));
// cc.setSslRootCert(qvarToStdStr(settings.value("sslrootcert")));
// cc.setSslCrl(qvarToStdStr(settings.value("sslcrl")));
// PasswordState pwstate;
// QVariant v = settings.value("passwordState");
// if (v.isNull()) pwstate = PasswordState::NotStored;
// else pwstate = static_cast<PasswordState>(v.toInt());
// cc.setPasswordState(pwstate);
// }
} // end of unnamed namespace
#if false
ConnectionListModel::ConnectionListModel(QObject *parent)
: QAbstractListModel(parent)
, m_connections(conns)
{
}
@ -18,7 +162,7 @@ int ConnectionListModel::rowCount(const QModelIndex &parent) const
{
int result = 0;
if (parent == QModelIndex()) {
result = m_connections->size();
result = m_connections.size();
}
return result;
}
@ -34,7 +178,7 @@ QVariant ConnectionListModel::data(const QModelIndex &index, int role) const
if (role == Qt::DisplayRole || role == Qt::EditRole) {
int row = index.row();
int col = index.column();
const ConnectionConfig& cfg = m_connections->getConfigByIdx(row);
const ConnectionConfig& cfg = m_connections.at(row);
switch (col) {
case Description:
result = makeLongDescription(cfg);
@ -72,7 +216,7 @@ bool ConnectionListModel::setData(const QModelIndex &index, const QVariant &valu
// auto& elem = m_connections.at(row);
// elem.m_dirty = true;
// ConnectionConfig& cfg = elem.m_config;
ConnectionConfig& cfg = m_connections->getConfigByIdx(row);
ConnectionConfig& cfg = m_connections[row];
if (col > 0) {
result = true;
}
@ -109,7 +253,7 @@ Qt::ItemFlags ConnectionListModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags result;
int row = index.row();
if (row >= 0 && row < (int)m_connections->size()) {
if (row >= 0 && row < m_connections.size()) {
result = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
if (index.column() != Description)
result |= Qt::ItemIsEditable;
@ -133,75 +277,495 @@ QString ConnectionListModel::makeLongDescription(const ConnectionConfig &cfg)
return stdStrToQ(result);
}
void ConnectionListModel::newItem()
{
int i = m_connections->createNew();
auto idx = createIndex(i, 0);
emit dataChanged(idx, idx);
}
Expected<ConnectionConfig> ConnectionListModel::get(size_t row)
{
if (row < m_connections->size()) {
return m_connections->getConfigByIdx(row);
}
return Expected<ConnectionConfig>::fromException(std::out_of_range("Invalid row"));
}
#include <boost/assert.hpp>
template <typename T>
size_t as_size_t(T t);
template <>
size_t as_size_t(int t)
{
BOOST_ASSERT(t >= 0);
return static_cast<size_t>(t);
}
template <>
size_t as_size_t(long t)
{
BOOST_ASSERT(t >= 0);
return static_cast<size_t>(t);
}
//void ConnectionListModel::del(const int idx)
bool ConnectionListModel::removeRows(int row, int count, const QModelIndex &parent)
{
bool result = false;
if (row >= 0 && row < (int)m_connections->size()) {
if (row >= 0 && row < m_connections.size()) {
beginRemoveRows(parent, row, row + count -1);
SCOPE_EXIT { endRemoveRows(); };
m_connections->remove(as_size_t(row), as_size_t(count));
QString file_name = iniFileName();
QSettings settings(file_name, QSettings::IniFormat);
for (int idx = 0; idx < count; ++idx) {
auto&& cc = m_connections[idx+row];
settings.remove(cc.uuid().toString());
}
settings.sync();
m_connections.remove(row, count);
result = true;
}
return result;
}
//void ConnectionListModel::load()
//void ConnectionListModel::newItem()
//{
// m_connections->load();
//// int i = m_connections->createNew();
//// auto idx = createIndex(i, 0);
//// emit dataChanged(idx, idx);
//}
Expected<ConnectionConfig> ConnectionListModel::get(int row)
{
if (row < m_connections.size()) {
return m_connections.at(row);
}
return Expected<ConnectionConfig>::fromException(std::out_of_range("Invalid row"));
}
void ConnectionListModel::load()
{
QString file_name = iniFileName();
QSettings settings(file_name, QSettings::IniFormat);
auto groups = settings.childGroups();
for (auto&& grp : groups) {
if (grp == "c_IniGroupSecurity") {
// Read security settings
} else {
QUuid uuid(grp);
if ( ! uuid.isNull() ) {
settings.beginGroup(grp);
SCOPE_EXIT { settings.endGroup(); };
ConnectionConfig cc;
cc.setUuid(uuid);
LoadConnectionConfig(settings, cc);
m_connections.push_back(cc);
}
}
}
}
void ConnectionListModel::save()
{
m_connections->save();
QString file_name = iniFileName();
QSettings settings(file_name, QSettings::IniFormat);
for (auto& e : m_connections) {
settings.beginGroup(e.uuid().toString());
SCOPE_EXIT { settings.endGroup(); };
SaveConnectionConfig(settings, e);
e.clean();
}
settings.sync();
}
void ConnectionListModel::save(size_t index)
void ConnectionListModel::save(int index)
{
m_connections->save(index);
auto& e = m_connections[index];
if (e.dirty()) {
QString file_name = iniFileName();
QSettings settings(file_name, QSettings::IniFormat);
settings.beginGroup(e.uuid().toString());
SaveConnectionConfig(settings, e);
e.clean();
settings.sync();
}
}
void ConnectionListModel::save(size_t index, const ConnectionConfig &cc)
void ConnectionListModel::save(const ConnectionConfig &cc)
{
m_connections->setConfigByIdx(index, cc);
m_connections->save(index);
auto find_res = std::find(m_connections.begin(), m_connections.end(), cc.uuid());
int i;
if (find_res == m_connections.end()) {
m_connections.push_back(cc);
i = m_connections.size() - 1;
}
else {
*find_res = cc;
i = find_res - m_connections.begin();
}
emit dataChanged(createIndex(i, 0), createIndex(i, ColCount-1));
save(i);
}
QString ConnectionListModel::iniFileName()
{
QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
QDir dir(path);
if (!dir.exists()) {
dir.mkpath(".");
}
path += "/connections.ini";
return path;
}
#endif
ConnectionTreeModel::ConnectionTreeModel(QObject *parent, QSqlDatabase &db)
: QAbstractItemModel(parent)
, m_db(db)
{
}
void ConnectionTreeModel::load()
{
InitConnectionTables(m_db);
QSqlQuery q(m_db);
q.prepare("SELECT conngroup_id, gname FROM conngroup;");
if (!q.exec()) {
// auto err = q_create_table.lastError();
// return { false, err };
throw std::runtime_error("Loading groups failed");
}
while (q.next()) {
int id = q.value(0).toInt();
QString name = q.value(1).toString();
auto g = std::make_shared<ConnectionGroup>();
g->conngroup_id = id;
g->name = name;
m_groups.push_back(g);
}
q.prepare("SELECT uuid, cname, conngroup_id, host, hostaddr, port, "
" user, dbname, sslmode, sslcert, sslkey, sslrootcert, sslcrl, "
" passwordstate "
"FROM connection ORDER BY conngroup_id, cname;");
if (!q.exec()) {
// auto err = q_create_table.lastError();
// return { false, err };
throw std::runtime_error("Loading groups failed");
}
while (q.next()) {
auto cc = std::make_shared<ConnectionConfig>();
cc->setUuid(q.value(0).toUuid());
cc->setName(qvarToStdStr(q.value(1)));
cc->setHost(qvarToStdStr(q.value(3)));
cc->setHostAddr(qvarToStdStr(q.value(4)));
cc->setPort(static_cast<uint16_t>(q.value(5).toInt()));
cc->setUser(qvarToStdStr(q.value(6)));
cc->setDbname(qvarToStdStr(q.value(7)));
cc->setSslMode(static_cast<SslMode>(q.value(8).toInt()));
cc->setSslCert(qvarToStdStr(q.value(9)));
cc->setSslKey(qvarToStdStr(q.value(10)));
cc->setSslRootCert(qvarToStdStr(q.value(11)));
cc->setSslCrl(qvarToStdStr(q.value(12)));
cc->setPasswordState(static_cast<PasswordState>(q.value(13).toInt()));
int group_id = q.value(2).toInt();
auto find_res = std::find_if(m_groups.begin(), m_groups.end(),
[group_id] (auto item) { return item->conngroup_id == group_id; });
if (find_res != m_groups.end()) {
(*find_res)->add(cc);
}
else {
throw std::runtime_error("conngroup missing");
}
}
// auto g1 = std::make_shared<ConnectionGroup>();
// g1->name = "Testing";
// for (int i = 1; i < 3; ++i) {
// auto cc = std::make_shared<ConnectionConfig>();
// cc->setUuid(QUuid::createUuid());
// cc->setName("testconn " + std::to_string(i));
// g1->add(cc);
// }
// auto g2 = std::make_shared<ConnectionGroup>();
// g2->name = "Production";
// for (int i = 1; i < 4; ++i) {
// auto cc = std::make_shared<ConnectionConfig>();
// cc->setUuid(QUuid::createUuid());
// cc->setName("prodconn " + std::to_string(i));
// g2->add(cc);
// }
// m_groups = { g1, g2 };
}
QVariant ConnectionTreeModel::data(const QModelIndex &index, int role) const
{
// Code below assumes two level tree groups/connections
// it will fail for nested groups
QVariant v;
auto privdata = static_cast<ConnectionNode*>(index.internalPointer());
if (auto group = dynamic_cast<ConnectionGroup*>(privdata); group != nullptr) {
// This is a group
if (role == Qt::DisplayRole) {
if (index.column() == Name) {
v = group->name;
}
}
}
else if (auto conn = dynamic_cast<ConnectionConfig*>(privdata); conn != nullptr) {
// This is a connection
if (role == Qt::DisplayRole) {
switch (index.column()) {
case Name: v = stdStrToQ(conn->name()); break;
case Host: v = stdStrToQ(conn->host()); break;
case Port: v = conn->port(); break;
case User: v = stdStrToQ(conn->user()); break;
case DbName: v= stdStrToQ(conn->dbname()); break;
}
}
}
return v;
}
QVariant ConnectionTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
{
QVariant v;
if (orientation == Qt::Horizontal) {
if (role == Qt::DisplayRole) {
switch (section) {
case Name: v = tr("Name"); break;
case Host: v = tr("Host"); break;
case Port: v = tr("Port"); break;
case User: v = tr("User"); break;
case DbName: v= tr("Database"); break;
}
}
}
return v;
}
QModelIndex ConnectionTreeModel::index(int row, int column, const QModelIndex &parent) const
{
if (!hasIndex(row, column, parent))
return {};
const ConnectionNode *node = nullptr;
if (parent.isValid()) {
auto privdata = static_cast<ConnectionNode*>(parent.internalPointer());
if (auto group = dynamic_cast<ConnectionGroup*>(privdata); group != nullptr) {
node = group->connections().at(row).get();
}
else {
throw std::logic_error("Should never ask for a child index of a connectionconfig");
}
}
else {
node = m_groups[row].get();
}
return createIndex(row, column, const_cast<ConnectionNode*>(node));
// void *p = nullptr;
// if (parent.isValid()) {
// auto cg = static_cast<ConnectionGroup *>(parent.internalPointer());
// auto cc = &cg->connections().at(row);
// p = const_cast<ConnectionConfig*>(cc);
// }
// else {
// p = const_cast<ConnectionGroup*>(&m_groups.at(row));
// }
// return createIndex(row, column, p);
}
QModelIndex ConnectionTreeModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
return {};
auto privdata = static_cast<ConnectionNode*>(index.internalPointer());
if (auto group = dynamic_cast<ConnectionGroup*>(privdata); group != nullptr) {
return {};
}
else if (auto config = dynamic_cast<ConnectionConfig*>(privdata); config != nullptr) {
auto p = config->parent();
auto find_res = std::find_if(m_groups.begin(), m_groups.end(), [p] (auto item) -> bool { return *p == *item; });
if (find_res != m_groups.end()) {
return createIndex(find_res - m_groups.begin(), 0,
const_cast<ConnectionGroup*>(config->parent()));
}
}
throw std::logic_error("Should never get here");
}
int ConnectionTreeModel::rowCount(const QModelIndex &parent) const
{
int result = 0;
if (parent.isValid()) {
// if (parent.column() <= 0) {
// result = m_groups[parent.row()].connections().size();
// }
// else {
// result = 1;
// }
auto privdata = static_cast<ConnectionNode*>(parent.internalPointer());
if (auto group = dynamic_cast<ConnectionGroup*>(privdata); group != nullptr) {
result = group->connections().size();
}
else if (auto config = dynamic_cast<ConnectionConfig*>(privdata); config != nullptr) {
result = 0;
}
}
else {
result = m_groups.size();
}
return result;
}
int ConnectionTreeModel::columnCount(const QModelIndex &) const
{
return ColCount;
}
bool ConnectionTreeModel::removeRows(int row, int count, const QModelIndex &parent)
{
if (parent.isValid() && count == 1) {
// should be a group
auto grp = m_groups[parent.row()];
for (int i = 0; i < count; ++i) {
QUuid uuid = grp->connections().at(row + i)->uuid();
QSqlQuery q(m_db);
q.prepare(
"DELETE FROM connection "
" WHERE uuid=:uuid");
q.bindValue(":uuid", uuid);
if (!q.exec()) {
auto err = q.lastError();
throw std::runtime_error("QqlError");
}
}
beginRemoveRows(parent, row, row + count - 1);
SCOPE_EXIT { endRemoveRows(); };
grp->erase(row, count);
}
}
void ConnectionTreeModel::save(const QString &group_name, const ConnectionConfig &cc)
{
auto [grp_idx, conn_idx] = findConfig(cc.uuid());
if (grp_idx >= 0) {
auto grp = m_groups[grp_idx];
if (grp->name == group_name) {
// update config
grp->update(conn_idx, cc);
// send change event
auto node = grp->connections().at(conn_idx);
dataChanged(
createIndex(conn_idx, 0, node.get()),
createIndex(conn_idx, ColCount-1, node.get()));
saveToDb(*node);
return;
}
else {
auto parent = createIndex(grp_idx, 0, grp.get());
beginRemoveRows(parent, conn_idx, conn_idx);
SCOPE_EXIT { endRemoveRows(); };
grp->erase(conn_idx);
}
}
// Here we can assume we have to find the new group or create a new group
// because if the connection was in the right group the function has already returned.
// We assume the model is in sync with the DB as the DB should not be shared!
int new_grp_idx = findGroup(group_name);
if (new_grp_idx < 0) {
// Group not found we are g
auto add_grp_res = addGroup(group_name);
if (std::holds_alternative<int>(add_grp_res)) {
new_grp_idx = std::get<int>(add_grp_res);
}
else {
throw std::runtime_error("SqlError1");
}
}
auto new_grp = m_groups[new_grp_idx];
auto parent = createIndex(new_grp_idx, 0, new_grp.get());
auto idx = new_grp->connections().size();
beginInsertRows(parent, idx, idx);
SCOPE_EXIT { endInsertRows(); };
auto node = std::make_shared<ConnectionConfig>(cc);
new_grp->add(node);
auto save_res = saveToDb(*node);
if (save_res) {
throw std::runtime_error("SqlError2");
}
}
void ConnectionTreeModel::save(const ConnectionConfig &cc)
{
saveToDb(cc);
}
std::tuple<int, int> ConnectionTreeModel::findConfig(const QUuid uuid) const
{
int group_idx = -1, connection_idx = -1;
for (int grp_idx = 0; grp_idx < m_groups.size(); ++grp_idx) {
auto && grp = m_groups[grp_idx];
auto && conns = grp->connections();
auto find_res = std::find_if(conns.begin(), conns.end(),
[&uuid] (auto item) -> bool { return item->uuid() == uuid; });
if (find_res != conns.end()) {
group_idx = grp_idx;
connection_idx = find_res - conns.begin();
break;
}
}
return { group_idx, connection_idx };
}
int ConnectionTreeModel::findGroup(QString name) const
{
for (int idx = 0; idx < m_groups.size(); ++idx) {
if (m_groups[idx]->name == name) return idx;
}
return -1;
}
std::variant<int, QSqlError> ConnectionTreeModel::addGroup(QString group_name)
{
QSqlQuery q(m_db);
q.prepare("INSERT INTO conngroup (gname) VALUES (:name)");
q.bindValue(":name", group_name);
if (!q.exec()) {
auto err = q.lastError();
return { err };
}
auto cg = std::make_shared<ConnectionGroup>();
cg->conngroup_id = q.lastInsertId().toInt();
cg->name = group_name;
int row = m_groups.size();
beginInsertRows({}, row, row);
SCOPE_EXIT { endInsertRows(); };
m_groups.push_back(cg);
return row;
}
std::optional<QSqlError> ConnectionTreeModel::removeGroup(int row)
{
beginRemoveRows({}, row, row);
SCOPE_EXIT { endRemoveRows(); };
auto id = m_groups[row]->conngroup_id;
QSqlQuery q(m_db);
q.prepare("DELETE FROM connection WHERE conngroup_id=:id");
q.bindValue(":id", id);
if (!q.exec()) {
auto err = q.lastError();
return { err };
}
q.prepare("DELETE FROM conngroup WHERE conngroup_id=:id");
q.bindValue(":id", id);
if (!q.exec()) {
auto err = q.lastError();
return { err };
}
m_groups.remove(row);
return {};
}
int ConnectionTreeModel::findGroup(int conngroup_id) const
{
auto find_res = std::find_if(m_groups.begin(), m_groups.end(),
[conngroup_id] (auto item) { return item->conngroup_id == conngroup_id; });
if (find_res == m_groups.end())
return -1;
return find_res - m_groups.begin();
}
std::optional<QSqlError> ConnectionTreeModel::saveToDb(const ConnectionConfig &cc)
{
return SaveConnectionConfig(m_db, cc, cc.parent()->conngroup_id);
}

View file

@ -8,9 +8,77 @@
#include "ConnectionConfig.h"
#include "Expected.h"
#include <optional>
#include <variant>
#include <QVector>
class ConnectionList;
#include <QSqlError>
class QSqlDatabase;
class ConnectionTreeModel : public QAbstractItemModel {
Q_OBJECT
public:
enum Columns {
Name,
Host,
Port,
User,
DbName,
ColCount
};
ConnectionTreeModel(QObject *parent, QSqlDatabase &db);
void load();
QVariant data(const QModelIndex &index, int role) const override;
// Qt::ItemFlags flags(const QModelIndex &index) const override;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
QModelIndex index(int row, int column,
const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
/** Matches cc to the list by looking at its uuid.
*
* If it is not in the list it is added. If the uuid is in the list that entry is updated.
* If the group has been changed it is moved to the right group.
* In both cases the data is also directly written to long term storage.
*/
void save(const QString &group, const ConnectionConfig &cc);
/** Save changed config, group is not allowed to change
*/
void save(const ConnectionConfig &cc);
/// Create a new group in the DB and place in the tree
std::variant<int, QSqlError> addGroup(QString group_name);
std::optional<QSqlError> removeGroup(int row);
int findGroup(int conngroup_id) const;
private:
using Groups = QVector<std::shared_ptr<ConnectionGroup>>;
QSqlDatabase &m_db;
Groups m_groups;
/// Finds the connection with the specified uuid and returns
/// { group_index, connection_index }
std::tuple<int, int> findConfig(const QUuid uuid) const;
int findGroup(QString name) const;
std::optional<QSqlError> saveToDb(const ConnectionConfig &cc);
};
#if false
/** \brief Model class for the list of connections.
*
* This class also allows for the editing of the list.
@ -30,30 +98,41 @@ public:
ColCount
};
ConnectionListModel(ConnectionList *conns, QObject *parent);
ConnectionListModel(QObject *parent);
ConnectionListModel(const ConnectionListModel&) = delete;
~ConnectionListModel() override;
// BEGIN Model/View related functions
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
virtual int columnCount(const QModelIndex &/*parent*/) const override;
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
// virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
void newItem();
Expected<ConnectionConfig> get(size_t row);
virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
// END Model/View related functions
Expected<ConnectionConfig> get(int row);
void load();
// Writes all entries to storage
void save();
void save(size_t index);
void save(size_t index, const ConnectionConfig &cc);
// Writes the specified entry to storage
void save(int index);
/** Matches cc to the list by looking at its uuid.
*
* If it is not in the list it is added. If the uuid is in the list that entry is updated.
* In both cases the data is also directly written to long term storage.
*/
void save(const ConnectionConfig &cc);
static QString makeLongDescription(const ConnectionConfig &cfg);
private:
ConnectionList *m_connections;
using ConnectionList = QVector<ConnectionConfig>;
ConnectionList m_connections;
QString iniFileName();
};
#endif
#endif // CONNECTIONLISTMODEL_H

View file

@ -1,8 +1,7 @@
#include "ConnectionManagerWindow.h"
#include "ui_ConnectionManagerWindow.h"
//#include "mainwindow.h"
#include "MasterController.h"
#include <QDataWidgetMapper>
#include "ConnectionController.h"
#include <QMessageBox>
#include <QStandardItemModel>
#include "ConnectionListModel.h"
@ -11,17 +10,6 @@
#include <QDir>
#include <QStandardPaths>
QString pskFileName()
{
QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
QDir dir(path);
if (!dir.exists()) {
dir.mkpath(".");
}
path += "/psk.ini";
return path;
}
ConnectionManagerWindow::ConnectionManagerWindow(MasterController *master, QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::ConnectionManagerWindow)
@ -29,86 +17,49 @@ ConnectionManagerWindow::ConnectionManagerWindow(MasterController *master, QWidg
, m_connectionController(master->connectionController())
{
ui->setupUi(this);
ui->listView->setModel(m_connectionController->getConnectionListModel());
setupWidgetMappings();
connect(ui->listView->selectionModel(),
SIGNAL(currentChanged(QModelIndex,QModelIndex)),
this, SLOT(on_currentChanged(QModelIndex,QModelIndex)));
ui->treeView->setModel(m_connectionController->getConnectionTreeModel());
connect(ui->treeView, &QTreeView::activated, this,
&ConnectionManagerWindow::connectionActivated);
}
ConnectionManagerWindow::~ConnectionManagerWindow()
{
delete ui;
delete m_mapper;
}
void ConnectionManagerWindow::on_actionAdd_Connection_triggered()
{
auto clm = m_connectionController->getConnectionListModel();
clm->newItem();
// Select the new row
auto idx = clm->index(clm->rowCount() - 1, 0);
ui->listView->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::Select);
}
void ConnectionManagerWindow::on_currentChanged(const QModelIndex &current,
const QModelIndex &)
{
int currow = current.row();
auto clm = m_connectionController->getConnectionListModel();
if (prevSelection)
clm->save(*prevSelection);
m_mapper->setCurrentIndex(currow);
if (currow >= 0)
prevSelection = static_cast<size_t>(currow);
else
prevSelection.reset();
m_connectionController->createConnection();
}
void ConnectionManagerWindow::on_actionDelete_connection_triggered()
{
auto ci = ui->listView->selectionModel()->currentIndex();
auto ci = ui->treeView->selectionModel()->currentIndex();
if (ci.isValid()) {
auto node = static_cast<ConnectionNode*>(ci.internalPointer());
auto cc = dynamic_cast<ConnectionConfig*>(node);
if (cc) {
auto res = QMessageBox::question(this, "pglab",
tr("Are you sure you want to remove this connection?"), QMessageBox::Yes, QMessageBox::No);
if (res == QMessageBox::Yes) {
auto clm = m_connectionController->getConnectionListModel();
clm->removeRow(ci.row());
auto cm = m_connectionController->getConnectionTreeModel();
cm->removeRow(ci.row(), ci.parent());
}
}
}
void ConnectionManagerWindow::setupWidgetMappings()
{
auto clm = m_connectionController->getConnectionListModel();
m_mapper = new QDataWidgetMapper(this);
m_mapper->setModel(clm);
m_mapper->addMapping(ui->edtName, 1);
m_mapper->addMapping(ui->edtHost, 2);
m_mapper->addMapping(ui->spinPort, 3);
m_mapper->addMapping(ui->edtUser, 4);
m_mapper->addMapping(ui->edtDbname, 6);
m_mapper->toFirst();
}
void ConnectionManagerWindow::on_actionConnect_triggered()
{
auto ci = ui->listView->selectionModel()->currentIndex();
if (ci.isValid()) {
auto r = static_cast<size_t>(ci.row());
m_connectionController->openSqlWindowForConnection(r);
}
auto ci = ui->treeView->selectionModel()->currentIndex();
m_connectionController->openSqlWindowForConnection(ci);
}
void ConnectionManagerWindow::on_actionQuit_application_triggered()
{
auto res = QMessageBox::question(this, "pglab",
tr("Close ALL windows?"), QMessageBox::Yes, QMessageBox::No);
tr("Close all windows?"), QMessageBox::Yes, QMessageBox::No);
if (res == QMessageBox::Yes) {
QApplication::quit();
}
@ -116,20 +67,36 @@ void ConnectionManagerWindow::on_actionQuit_application_triggered()
void ConnectionManagerWindow::on_actionBackup_database_triggered()
{
auto ci = ui->listView->selectionModel()->currentIndex();
m_connectionController->openBackupDlgForConnection(ci.row());
auto ci = ui->treeView->selectionModel()->currentIndex();
m_connectionController->openBackupDlgForConnection(ci);
}
void ConnectionManagerWindow::on_actionManage_server_triggered()
{
auto ci = ui->listView->selectionModel()->currentIndex();
m_connectionController->openServerWindowForConnection(ci.row());
auto ci = ui->treeView->selectionModel()->currentIndex();
m_connectionController->openServerWindowForConnection(ci);
}
void ConnectionManagerWindow::on_listView_activated(const QModelIndex &index)
void ConnectionManagerWindow::connectionActivated(const QModelIndex &index)
{
if (index.isValid()) {
auto r = static_cast<size_t>(index.row());
m_connectionController->openSqlWindowForConnection(r);
m_connectionController->openSqlWindowForConnection(index);
}
}
void ConnectionManagerWindow::on_actionConfigure_connection_triggered()
{
auto ci = ui->treeView->selectionModel()->currentIndex();
m_connectionController->editConnection(ci);
}
void ConnectionManagerWindow::on_actionAdd_group_triggered()
{
m_connectionController->addGroup();
}
void ConnectionManagerWindow::on_actionRemove_group_triggered()
{
auto ci = ui->treeView->selectionModel()->currentIndex();
m_connectionController->removeGroup(ci);
}

View file

@ -11,7 +11,6 @@ class ConnectionManagerWindow;
class ConnectionConfig;
class ConnectionController;
class MasterController;
class QDataWidgetMapper;
class QStandardItemModel;
/** \brief Class that holds glue code for the ConnectionManager UI.
@ -25,24 +24,24 @@ public:
private slots:
void on_actionAdd_Connection_triggered();
void on_currentChanged(const QModelIndex &current, const QModelIndex &previous);
void on_actionDelete_connection_triggered();
void on_actionConnect_triggered();
void on_actionQuit_application_triggered();
void on_actionBackup_database_triggered();
void on_actionManage_server_triggered();
void on_listView_activated(const QModelIndex &index);
void connectionActivated(const QModelIndex &index);
void on_actionConfigure_connection_triggered();
void on_actionAdd_group_triggered();
void on_actionRemove_group_triggered();
private:
Ui::ConnectionManagerWindow *ui;
QDataWidgetMapper *m_mapper = nullptr;
MasterController *m_masterController;
ConnectionController *m_connectionController;
std::optional<size_t> prevSelection;
void setupWidgetMappings();
};
#endif // CONNECTIONMANAGERWINDOW_H

View file

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<width>413</width>
<height>600</height>
</rect>
</property>
@ -16,161 +16,7 @@
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QListView" name="listView">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="uniformItemSizes">
<bool>true</bool>
</property>
</widget>
<widget class="QWidget" name="layoutWidget">
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="edtName"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Host</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="edtHost"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Port</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="spinPort">
<property name="maximum">
<number>65535</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Username</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="edtUser"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Database</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="edtDbname"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>SSL</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QComboBox" name="cmbbxSsl">
<property name="currentIndex">
<number>2</number>
</property>
<item>
<property name="text">
<string>reject</string>
</property>
</item>
<item>
<property name="text">
<string>allow</string>
</property>
</item>
<item>
<property name="text">
<string>prefer</string>
</property>
</item>
<item>
<property name="text">
<string>require</string>
</property>
</item>
<item>
<property name="text">
<string>verify-ca</string>
</property>
</item>
<item>
<property name="text">
<string>verify-full</string>
</property>
</item>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Certificate</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLineEdit" name="edtCert"/>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Key</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLineEdit" name="edtKey"/>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Root cert.</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QLineEdit" name="edtRootCert"/>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Revocation list</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QLineEdit" name="edtCrl"/>
</item>
</layout>
</widget>
</widget>
<widget class="QTreeView" name="treeView"/>
</item>
</layout>
</widget>
@ -179,7 +25,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<width>413</width>
<height>20</height>
</rect>
</property>
@ -222,11 +68,14 @@ QToolButton {
</attribute>
<addaction name="actionConnect"/>
<addaction name="actionManage_server"/>
<addaction name="actionBackup_database"/>
<addaction name="separator"/>
<addaction name="actionAdd_group"/>
<addaction name="actionRemove_group"/>
<addaction name="actionAdd_Connection"/>
<addaction name="actionConfigure_connection"/>
<addaction name="actionDelete_connection"/>
<addaction name="separator"/>
<addaction name="actionBackup_database"/>
</widget>
<action name="actionAdd_Connection">
<property name="icon">
@ -285,6 +134,36 @@ QToolButton {
<string>Manage server</string>
</property>
</action>
<action name="actionConfigure_connection">
<property name="icon">
<iconset>
<normalon>:/icons/server_configuration.png</normalon>
</iconset>
</property>
<property name="text">
<string>Configure connection</string>
</property>
</action>
<action name="actionAdd_group">
<property name="icon">
<iconset>
<normalon>:/icons/folder_add.png</normalon>
</iconset>
</property>
<property name="text">
<string>Add group</string>
</property>
</action>
<action name="actionRemove_group">
<property name="icon">
<iconset>
<normalon>:/icons/folder_delete.png</normalon>
</iconset>
</property>
<property name="text">
<string>Remove group</string>
</property>
</action>
</widget>
<resources>
<include location="resources.qrc"/>

View file

@ -3,6 +3,7 @@
#include "CrudTab.h"
#include "widgets/CatalogTablesPage.h"
#include "OpenDatabase.h"
#include "ConnectionController.h"
#include "MasterController.h"
#include "TaskExecutor.h"
#include <QAction>

View file

@ -1,16 +1,8 @@
#include "MasterController.h"
#include "ConnectionManagerWindow.h"
#include "ConnectionList.h"
#include "ConnectionListModel.h"
#include "PasswordManager.h"
#include "DatabaseWindow.h"
#include "ServerWindow.h"
#include "BackupDialog.h"
#include "PasswordPromptDialog.h"
#include "ConnectionController.h"
#include <QDebug>
#include <QDir>
#include <QStandardPaths>
#include <botan/sqlite3.h>
namespace {
@ -60,193 +52,3 @@ QSqlDatabase& MasterController::userConfigDatabase()
}
ConnectionController::ConnectionController(MasterController *parent)
: QObject(parent)
, m_masterController(parent)
{}
ConnectionController::~ConnectionController()
{
delete m_connectionManagerWindow;
delete m_connectionListModel;
delete m_connectionList;
}
void ConnectionController::init()
{
//std::string dbfilename = QDir::toNativeSeparators(GetUserConfigDatabaseName()).toUtf8().data();
//m_userConfigDatabase = std::make_shared<Botan::Sqlite3_Database>(dbfilename);
m_passwordManager = std::make_shared<PasswordManager>();
m_connectionList = new ConnectionList;
m_connectionList->load();
m_connectionListModel = new ConnectionListModel(m_connectionList, this);
m_connectionManagerWindow = new ConnectionManagerWindow(m_masterController, nullptr);
m_connectionManagerWindow->show();
}
void ConnectionController::showConnectionManager()
{
m_connectionManagerWindow->show();
}
void ConnectionController::openSqlWindowForConnection(size_t connection_index)
{
auto res = m_connectionListModel->get(connection_index);
if (res.valid()) {
auto cc = res.get();
if (retrieveConnectionPassword(cc)) {
m_connectionListModel->save(connection_index, cc);
// 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(cc);
w->showMaximized();
}
}
}
void ConnectionController::openBackupDlgForConnection(size_t connection_index)
{
auto res = m_connectionListModel->get(connection_index);
if (res.valid()) {
auto cc = res.get();
if (retrieveConnectionPassword(cc)) {
m_connectionListModel->save(connection_index, cc);
auto w = new BackupDialog(nullptr); //new ServerWindow(this, nullptr);
w->setAttribute( Qt::WA_DeleteOnClose );
w->setConfig(cc);
w->show();
}
}
}
void ConnectionController::openServerWindowForConnection(size_t connection_index)
{
auto res = m_connectionListModel->get(connection_index);
if (res.valid()) {
auto cc = res.get();
if (retrieveConnectionPassword(cc)) {
m_connectionListModel->save(connection_index, cc);
auto w = new ServerWindow(m_masterController, nullptr);
w->setAttribute( Qt::WA_DeleteOnClose );
w->setConfig(cc);
w->show();
}
}
}
bool ConnectionController::retrieveConnectionPassword(ConnectionConfig &cc)
{
auto pw_state = cc.passwordState();
if (pw_state == PasswordState::NotNeeded) {
return true;
}
else if (pw_state == PasswordState::SavedPasswordManager) {
std::string pw;
bool result = getPasswordFromPskdb(getPskId(cc), pw);
if (result) {
cc.setPassword(pw);
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 = ConnectionListModel::makeLongDescription(cc);
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) {
std::string password = dlg->password().toUtf8().data();
cc.setPassword(password);
if (dlg->saveChecked()) {
storePasswordInPskdb(getPskId(cc), password);
cc.setPasswordState(PasswordState::SavedPasswordManager);
}
return true;
}
return false;
}
bool ConnectionController::getPasswordFromPskdb(const std::string &password_id, std::string &password)
{
if (!UnlockPasswordManagerIfNeeded())
return false;
return m_passwordManager->get(password_id, password);
}
bool ConnectionController::storePasswordInPskdb(const std::string &password_id, const std::string password)
{
if (!UnlockPasswordManagerIfNeeded())
return false;
m_passwordManager->set(password_id, password);
return true;
}
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) {
// ask user for passphrase
auto dlg = std::make_unique<PasswordPromptDialog>(nullptr, 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);
if (!ok) {
// leave this retry loop
break;
}
// user gave OK, if succeeds return true otherwise loop a prompt for password again.
if (m_passwordManager->openDatabase(user_cfg_db, dlg->password()))
return true;
}
}
else {
// 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();
if (m_passwordManager->createDatabase(user_cfg_db, passphrase))
return true;
}
}
return false;
}
std::string ConnectionController::getPskId(const ConnectionConfig &cc)
{
std::string id = "dbpw/";
id += cc.uuid().toString().toUtf8().data();
return id;
}

View file

@ -8,15 +8,6 @@
#include <map>
#include <memory>
//namespace Botan {
// class Sqlite3_Database;
//}
class ConnectionConfig;
class ConnectionList;
class ConnectionListModel;
class ConnectionManagerWindow;
class PasswordManager;
class ConnectionController;
/** \brief Controller class responsible for all things global.
@ -43,47 +34,5 @@ private:
ConnectionController* m_connectionController = nullptr;
};
class ConnectionController : public QObject {
Q_OBJECT
public:
explicit ConnectionController(MasterController *parent = nullptr);
~ConnectionController();
void init();
ConnectionListModel *getConnectionListModel()
{
return m_connectionListModel;
}
void showConnectionManager();
void openSqlWindowForConnection(size_t connection_index);
void openServerWindowForConnection(size_t connection_index);
void openBackupDlgForConnection(size_t connection_index);
private:
MasterController *m_masterController;
ConnectionList *m_connectionList = nullptr;
ConnectionListModel *m_connectionListModel = nullptr;
ConnectionManagerWindow *m_connectionManagerWindow = nullptr;
/** Using long lived object so it can remember its master password for sometime
* if the user wishes it.
*/
std::shared_ptr<PasswordManager> m_passwordManager;
/** Retrieves the connection password from the user (directly or through the psk db)
*
*/
bool retrieveConnectionPassword(ConnectionConfig &cc);
bool getPasswordFromPskdb(const std::string &password_id, std::string &password);
bool storePasswordInPskdb(const std::string &password_id, const std::string password);
bool UnlockPasswordManagerIfNeeded();
static std::string getPskId(const ConnectionConfig &cc);
};
#endif // MASTERCONTROLLER_H

69
pglab/SslModeModel.cpp Normal file
View file

@ -0,0 +1,69 @@
#include "SslModeModel.h"
SslModeModel::SslModeModel(QObject *parent)
: QAbstractListModel(parent)
{}
int SslModeModel::rowCount(const QModelIndex &) const
{
return 6;
}
int SslModeModel::columnCount(const QModelIndex &) const
{
return ColCount;
}
QVariant SslModeModel::data(const QModelIndex &index, int role) const
{
QVariant v;
if (role == Qt::DisplayRole) {
switch (index.column()) {
case Name:
switch(index.row()) {
case 0:
v = tr("disable");
break;
case 1:
v = tr("allow");
break;
case 2:
v = tr("prefer");
break;
case 3:
v = tr("require");
break;
case 4:
v = tr("verify_ca");
break;
case 5:
v = tr("verify_full");
break;
}
break;
case Description:
switch(index.row()) {
case 0:
v = tr("try a non encrypted connection only");
break;
case 1:
v = tr("try no encryption first then try encrypted");
break;
case 2:
v = tr("try encrypted first then not encrypted");
break;
case 3:
v = tr("require an encrypted connection");
break;
case 4:
v = tr("verify encryption certificate has a valid signature");
break;
case 5:
v = tr("verify encryption certificate has a valid signature and matches the host");
break;
}
break;
}
}
return v;
}

20
pglab/SslModeModel.h Normal file
View file

@ -0,0 +1,20 @@
#ifndef SSLMODEMODEL_H
#define SSLMODEMODEL_H
#include <QAbstractListModel>
class SslModeModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Column { Name, Description, ColCount };
SslModeModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
};
#endif // SSLMODEMODEL_H

BIN
pglab/icons/folder_add.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -21,6 +21,8 @@ DEFINES += _WIN32_WINNT=0x0501
win32:RC_ICONS += pglab.ico
SOURCES += main.cpp\
ConnectionConfigurationWidget.cpp \
ConnectionController.cpp \
NotificationListWidget.cpp \
NotificationModel.cpp \
NotificationService.cpp \
@ -30,6 +32,7 @@ SOURCES += main.cpp\
CreateDatabaseDialog.cpp \
ConnectionManagerWindow.cpp \
ConnectionListModel.cpp \
SslModeModel.cpp \
stopwatch.cpp \
TuplesResultWidget.cpp \
BackupDialog.cpp \
@ -40,7 +43,6 @@ SOURCES += main.cpp\
ServerWindow.cpp \
DatabasesTableModel.cpp \
RolesTableModel.cpp \
ConnectionList.cpp \
ProcessStdioWidget.cpp \
GlobalIoService.cpp \
ResultTableModelUtil.cpp \
@ -85,6 +87,8 @@ PropertyProxyModel.cpp \
widgets/CatalogSequencesPage.cpp
HEADERS += \
ConnectionConfigurationWidget.h \
ConnectionController.h \
IDatabaseWindow.h \
NotificationListWidget.h \
NotificationModel.h \
@ -95,6 +99,7 @@ HEADERS += \
CreateDatabaseDialog.h \
ConnectionManagerWindow.h \
ConnectionListModel.h \
SslModeModel.h \
stopwatch.h \
TuplesResultWidget.h \
BackupDialog.h \
@ -105,7 +110,6 @@ HEADERS += \
ServerWindow.h \
DatabasesTableModel.h \
RolesTableModel.h \
ConnectionList.h \
ProcessStdioWidget.h \
GlobalIoService.h \
ResultTableModelUtil.h \

View file

@ -28,5 +28,8 @@
<file>icons/constraints/primarykey.png</file>
<file>icons/constraints/unique.png</file>
<file>icons/constraints/index.png</file>
<file>icons/server_configuration.png</file>
<file>icons/folder_add.png</file>
<file>icons/folder_delete.png</file>
</qresource>
</RCC>

View file

@ -57,6 +57,16 @@ ConnectionConfig::ConnectionConfig()
: m_applicationName(QCoreApplication::applicationName().toUtf8().data())
{}
const ConnectionGroup *ConnectionConfig::parent() const
{
return m_group;
}
void ConnectionConfig::setParent(ConnectionGroup *grp)
{
m_group = grp;
}
void ConnectionConfig::setUuid(const QUuid &uuid)
{
@ -291,6 +301,21 @@ void ConnectionConfig::clean()
m_dirty = false;
}
QString ConnectionConfig::makeLongDescription() const
{
std::string result(name());
result += " (";
result += user();
result += "@";
result += host();
result += ":";
result += std::to_string(port());
result += "/";
result += dbname();
result += ")";
return stdStrToQ(result);
}
/*
PGHOST behaves the same as the host connection parameter.
@ -347,3 +372,22 @@ void ConnectionConfig::strToEnv(QProcessEnvironment &env, const QString &var, co
else
env.insert(var, stdStrToQ(val));
}
void ConnectionGroup::erase(int idx, int count)
{
m_connections.remove(idx, count);
}
int ConnectionGroup::add(std::shared_ptr<ConnectionConfig> cc)
{
cc->setParent(this);
m_connections.push_back(cc);
return m_connections.size() - 1;
}
void ConnectionGroup::update(int idx, const ConnectionConfig &cc)
{
auto node = m_connections[idx];
*node = cc;
node->setParent(this);
}

View file

@ -2,6 +2,7 @@
#define CONNECTION_H
#include <QUuid>
#include <QVector>
#include <vector>
#include <string>
@ -24,9 +25,43 @@ enum class PasswordState {
class QProcessEnvironment;
class QString;
class ConnectionConfig {
class ConnectionConfig;
/** Base class for ConnectionGroup and ConnectionConfig
* to enable the use of RTTI in the tree model class.
*/
class ConnectionNode {
public:
ConnectionConfig();
virtual ~ConnectionNode() = default;
};
class ConnectionGroup: public ConnectionNode {
public:
int conngroup_id;
QString name;
using Connections = QVector<std::shared_ptr<ConnectionConfig>>;
const Connections& connections() const { return m_connections; }
void erase(int idx, int count = 1);
/// Adds cc to the group and returns the index within the group.
int add(std::shared_ptr<ConnectionConfig> cc);
void update(int idx, const ConnectionConfig &cc);
bool operator==(const ConnectionGroup &rhs) const {
return conngroup_id == rhs.conngroup_id
&& name == rhs.name;
}
private:
Connections m_connections;
};
class ConnectionConfig: public ConnectionNode {
public:
ConnectionConfig(); // Default object containing invalid uuid
const ConnectionGroup* parent() const;
void setParent(ConnectionGroup *grp);
void setUuid(const QUuid &uuid);
const QUuid &uuid() const;
@ -79,6 +114,10 @@ public:
bool dirty() const;
void clean();
bool operator==(QUuid id) const { return m_uuid == id; }
QString makeLongDescription() const;
private:
QUuid m_uuid;
std::string m_name;
@ -100,6 +139,7 @@ private:
PasswordState m_passwordState = PasswordState::NotStored;
bool m_dirty = false;
ConnectionGroup* m_group;
static void strToEnv(QProcessEnvironment &env, const QString &var, const std::string &val);