From eefcb8fd5171f880911de619d8cde1177c73b269 Mon Sep 17 00:00:00 2001 From: eelke Date: Sun, 7 Oct 2018 20:57:52 +0200 Subject: [PATCH 1/8] Add (inherited) to tablespace name when table is using the default tablespace set for the database. --- pglablib/PgDatabaseCatalog.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pglablib/PgDatabaseCatalog.cpp b/pglablib/PgDatabaseCatalog.cpp index 9e8a754..a90b96a 100644 --- a/pglablib/PgDatabaseCatalog.cpp +++ b/pglablib/PgDatabaseCatalog.cpp @@ -75,9 +75,13 @@ QString getTablespaceDisplayString(const PgDatabaseCatalog &cat, Oid oid) if (oid == 0) { auto dbname = cat.getDBName(); oid = cat.databases()->getByName(dbname).tablespace; + auto ts = cat.tablespaces()->getByKey(oid); + return ts.name + " (inherited)"; + } + else { + auto ts = cat.tablespaces()->getByKey(oid); + return ts.name; } - auto ts = cat.tablespaces()->getByKey(oid); - return ts.name; } QString getTypeDisplayString(const PgDatabaseCatalog &cat, Oid oid, int32_t typmod) From b8cfb223be63ef0d28f97778224d7c4b64204477 Mon Sep 17 00:00:00 2001 From: eelke Date: Mon, 8 Oct 2018 18:43:56 +0200 Subject: [PATCH 2/8] Switch of wordwrap in CodeEditor and SqlCodePreview --- pglab/CodeEditor.cpp | 2 ++ pglab/SqlCodePreview.cpp | 1 + 2 files changed, 3 insertions(+) diff --git a/pglab/CodeEditor.cpp b/pglab/CodeEditor.cpp index 64a1df8..02d1c4e 100644 --- a/pglab/CodeEditor.cpp +++ b/pglab/CodeEditor.cpp @@ -19,6 +19,8 @@ CodeEditor::CodeEditor(QWidget *parent) connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine())); connect(this, SIGNAL(textChanged()), this, SLOT(onTextChanged())); + setWordWrapMode(QTextOption::NoWrap); + updateGutterAreaWidth(0); highlightCurrentLine(); } diff --git a/pglab/SqlCodePreview.cpp b/pglab/SqlCodePreview.cpp index 3a65be1..c7eb62a 100644 --- a/pglab/SqlCodePreview.cpp +++ b/pglab/SqlCodePreview.cpp @@ -8,6 +8,7 @@ SqlCodePreview::SqlCodePreview(QWidget *parent) auto&& config = UserConfiguration::instance(); setFont(config->codeFont()); setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); + setWordWrapMode(QTextOption::NoWrap); m_highlighter = new SqlSyntaxHighlighter(document()); } From 38ae5f50e425a472b61013bd0177c6cd5ba95363 Mon Sep 17 00:00:00 2001 From: eelke Date: Sat, 20 Oct 2018 10:58:42 +0200 Subject: [PATCH 3/8] Moved code from TablesPage into seperate PropetiesPage component Clears up the TablePage and makes rhe propertypage reusable. --- pglab/PropertiesPage.cpp | 25 +++++++++++++++++++++++++ pglab/PropertiesPage.h | 28 ++++++++++++++++++++++++++++ pglab/TablesPage.cpp | 28 +++++++++++----------------- pglab/TablesPage.h | 5 ++++- pglab/TablesPage.ui | 12 +----------- pglab/TriggerPage.cpp | 3 --- pglab/TriggerPage.h | 2 -- pglab/pglab.pro | 6 ++++-- 8 files changed, 73 insertions(+), 36 deletions(-) create mode 100644 pglab/PropertiesPage.cpp create mode 100644 pglab/PropertiesPage.h diff --git a/pglab/PropertiesPage.cpp b/pglab/PropertiesPage.cpp new file mode 100644 index 0000000..6ab4d06 --- /dev/null +++ b/pglab/PropertiesPage.cpp @@ -0,0 +1,25 @@ +#include "PropertiesPage.h" +#include "PgLabItemDelegate.h" +#include "PropertyProxyModel.h" +#include "ResultTableModelUtil.h" +#include "SqlCodePreview.h" +#include + +PropertiesPage::PropertiesPage(QWidget *parent) : QSplitter(parent) +{ + m_tableView = new QTableView(this); +// m_definitionView = new SqlCodePreview(this); + addWidget(m_tableView); +// addWidget(m_definitionView); + + SetTableViewDefault(m_tableView); + + m_propertyProxyModel = new PropertyProxyModel(this); + m_tableView->setModel(m_propertyProxyModel); + + auto item_delegate = new PgLabItemDelegate(this); + m_tableView->setItemDelegate(item_delegate); +} + + +//property_model->setSourceModel(m_tablesModel); diff --git a/pglab/PropertiesPage.h b/pglab/PropertiesPage.h new file mode 100644 index 0000000..e169eba --- /dev/null +++ b/pglab/PropertiesPage.h @@ -0,0 +1,28 @@ +#ifndef PROPERTIESPAGE_H +#define PROPERTIESPAGE_H + +#include + +class QTableView; +class SqlCodePreview; +class PgDatabaseCatalog; +class PropertyProxyModel; + +class PropertiesPage : public QSplitter +{ + Q_OBJECT +public: + explicit PropertiesPage(QWidget *parent = nullptr); + +signals: + +public slots: + +private: + QTableView *m_tableView = nullptr; +// SqlCodePreview *m_definitionView = nullptr; + PropertyProxyModel *m_propertyProxyModel = nullptr; + +}; + +#endif // PROPERTIESPAGE_H diff --git a/pglab/TablesPage.cpp b/pglab/TablesPage.cpp index 97bfb23..a64fc33 100644 --- a/pglab/TablesPage.cpp +++ b/pglab/TablesPage.cpp @@ -9,7 +9,7 @@ #include "IndexModel.h" #include "MainWindow.h" #include "PgLabItemDelegate.h" -#include "PropertyProxyModel.h" +#include "PropertiesPage.h" #include "ResultTableModelUtil.h" #include "SqlFormattingUtils.h" #include "SqlSyntaxHighlighter.h" @@ -54,14 +54,6 @@ TablesPage::TablesPage(MainWindow *parent) ui->indexesTable->setItemDelegate(pglab_delegate); ui->indexesTable->setItemDelegateForColumn(0, icon_delegate); - // Properties - PropertyProxyModel* property_model = new PropertyProxyModel(this); - property_model->setSourceModel(m_tablesModel); - SetTableViewDefault(ui->tablePropertiesTable); - ui->tablePropertiesTable->setModel(property_model); - ui->tablePropertiesTable->setItemDelegate(pglab_delegate); - - // Set code editor fonts QFont code_font = UserConfiguration::instance()->codeFont(); ui->constraintSqlEdit->setFont(code_font); @@ -71,14 +63,11 @@ TablesPage::TablesPage(MainWindow *parent) // Connect signals // --------------- // Table selection - connect(ui->tableListTable->selectionModel(), &QItemSelectionModel::currentRowChanged, - property_model, &PropertyProxyModel::setActiveRow); +// connect(ui->tableListTable->selectionModel(), &QItemSelectionModel::currentRowChanged, +// property_model, &PropertyProxyModel::setActiveRow); connect(ui->tableListTable->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &TablesPage::tableListTable_currentRowChanged); -// connect(ui->constraintsTable->selectionModel(), &QItemSelectionModel::currentRowChanged, this, -// &TablesPage::constraintsTable_currentRowChanged); - connect(ui->constraintsTable->selectionModel(), &QItemSelectionModel::selectionChanged, this, &TablesPage::constraintsTable_selectionChanged); connect(ui->constraintsTable->model(), &QAbstractItemModel::modelReset, this, @@ -92,8 +81,12 @@ TablesPage::TablesPage(MainWindow *parent) &TablesPage::indexesTable_modelReset); // Non designer based code + m_propertiesPage = new PropertiesPage(this); + m_propertiesTab = addDetailTab(m_propertiesPage); + m_triggerPage = new TriggerPage(this); - m_triggerTab = addDetailTab(m_triggerPage, tr("Triggers")); + m_triggerTab = addDetailTab(m_triggerPage); + retranslateUi(false); } @@ -103,16 +96,17 @@ void TablesPage::retranslateUi(bool all) if (all) ui->retranslateUi(this); + ui->twDetails->setTabText(ui->twDetails->indexOf(m_propertiesTab), QApplication::translate("TablesPage", "Properties", nullptr)); ui->twDetails->setTabText(ui->twDetails->indexOf(m_triggerTab), QApplication::translate("TablesPage", "Triggers", nullptr)); } -QWidget* TablesPage::addDetailTab(QWidget *contents, QString caption) +QWidget* TablesPage::addDetailTab(QWidget *contents) { auto tab = new QWidget(); auto verticalLayout = new QVBoxLayout(tab); verticalLayout->addWidget(contents); - ui->twDetails->addTab(tab, caption); + ui->twDetails->addTab(tab, ""); return tab; } diff --git a/pglab/TablesPage.h b/pglab/TablesPage.h index 81c437a..23d2443 100644 --- a/pglab/TablesPage.h +++ b/pglab/TablesPage.h @@ -16,6 +16,7 @@ class PgDatabaseCatalog; class NamespaceFilterWidget; class IndexModel; class MainWindow; +class PropertiesPage; class TriggerPage; class TablesPage : public QWidget @@ -30,6 +31,8 @@ public: private: Ui::TablesPage *ui; MainWindow *m_window; + QWidget *m_propertiesTab; + PropertiesPage *m_propertiesPage; QWidget *m_triggerTab; TriggerPage *m_triggerPage; std::shared_ptr m_catalog; @@ -40,7 +43,7 @@ private: //NamespaceFilterWidget* m_namespaceFilterWidget; void retranslateUi(bool all = true); - QWidget* addDetailTab(QWidget *contents, QString caption); + QWidget* addDetailTab(QWidget *contents); private slots: void tableListTable_currentRowChanged(const QModelIndex ¤t, const QModelIndex &previous); diff --git a/pglab/TablesPage.ui b/pglab/TablesPage.ui index 93c8b26..b562cda 100644 --- a/pglab/TablesPage.ui +++ b/pglab/TablesPage.ui @@ -38,7 +38,7 @@ - 3 + 2 @@ -86,16 +86,6 @@ - - - Properties - - - - - - - diff --git a/pglab/TriggerPage.cpp b/pglab/TriggerPage.cpp index 3eecfff..add7b1f 100644 --- a/pglab/TriggerPage.cpp +++ b/pglab/TriggerPage.cpp @@ -21,9 +21,6 @@ TriggerPage::TriggerPage(QWidget *parent) SetTableViewDefault(m_tableView); - QFont code_font = UserConfiguration::instance()->codeFont(); - m_definitionView->setFont(code_font); - m_model = new TriggerTableModel(this); m_sortFilterProxy = new CustomFilterSortModel(this); m_sortFilterProxy->setSourceModel(m_model); diff --git a/pglab/TriggerPage.h b/pglab/TriggerPage.h index 35cc4e3..00bb9ee 100644 --- a/pglab/TriggerPage.h +++ b/pglab/TriggerPage.h @@ -1,11 +1,9 @@ #ifndef TRIGGERPAGE_H #define TRIGGERPAGE_H -#include #include #include -class QSplitter; class QTableView; class SqlCodePreview; class PgDatabaseCatalog; diff --git a/pglab/pglab.pro b/pglab/pglab.pro index afb2036..7313d8a 100644 --- a/pglab/pglab.pro +++ b/pglab/pglab.pro @@ -82,7 +82,8 @@ PropertyProxyModel.cpp \ TriggerTableModel.cpp \ TriggerPage.cpp \ SqlCodePreview.cpp \ - CustomFilterSortModel.cpp + CustomFilterSortModel.cpp \ + PropertiesPage.cpp HEADERS += \ QueryResultModel.h \ @@ -135,7 +136,8 @@ CustomDataRole.h \ TriggerTableModel.h \ TriggerPage.h \ SqlCodePreview.h \ - CustomFilterSortModel.h + CustomFilterSortModel.h \ + PropertiesPage.h FORMS += mainwindow.ui \ ConnectionManagerWindow.ui \ From d4d8316917ba0a9bc8a57dc50a0194cfcd53f1a7 Mon Sep 17 00:00:00 2001 From: eelke Date: Sun, 21 Oct 2018 13:46:58 +0200 Subject: [PATCH 4/8] Collection of minor code improvements. Program should still behave exactly the same. --- core/CsvWriter.cpp | 3 +-- core/ExplainTreeModelItem.cpp | 8 ++++---- core/ExplainTreeModelItem.h | 6 +++--- core/SqlAstNode.cpp | 5 +---- pglab/IconColumnDelegate.cpp | 3 +-- pglab/PgLabItemDelegate.cpp | 3 ++- 6 files changed, 12 insertions(+), 16 deletions(-) diff --git a/core/CsvWriter.cpp b/core/CsvWriter.cpp index b51ace3..ec37f86 100644 --- a/core/CsvWriter.cpp +++ b/core/CsvWriter.cpp @@ -1,7 +1,6 @@ #include "CsvWriter.h" -CsvWriter::CsvWriter() -{} +CsvWriter::CsvWriter() = default; CsvWriter::CsvWriter(QTextStream *output) : m_output(output) diff --git a/core/ExplainTreeModelItem.cpp b/core/ExplainTreeModelItem.cpp index 418eba7..5f71489 100644 --- a/core/ExplainTreeModelItem.cpp +++ b/core/ExplainTreeModelItem.cpp @@ -71,7 +71,7 @@ ExplainRoot::SPtr ExplainRoot::createFromJson(Json::Value &json) auto res = std::make_shared(); // Explain always seems to be an array with one element if (json.isArray()) { - if (json.size() > 0) { + if (!json.empty()) { Json::Value &explain = json[0]; Json::Value &plan = explain["Plan"]; @@ -89,7 +89,7 @@ ExplainTreeModelItem::ExplainTreeModelItem() = default; ExplainTreeModelItem::~ExplainTreeModelItem() = default; -void ExplainTreeModelItem::appendChild(ItemPtr child) +void ExplainTreeModelItem::appendChild(const ItemPtr &child) { child->setParent(shared_from_this()); m_childItems.push_back(child); @@ -132,7 +132,7 @@ int ExplainTreeModelItem::row() const return idx; } -void ExplainTreeModelItem::setParent(ItemPtr parent) +void ExplainTreeModelItem::setParent(const ItemPtr &parent) { m_parentItem = parent; } @@ -306,7 +306,7 @@ ExplainTreeModelItemPtr ExplainTreeModelItem::parent() float ExplainTreeModelItem::exclusiveTime() const { float tt = inclusiveTime(); - for (auto c : m_childItems) { + for (auto&& c : m_childItems) { tt -= c->inclusiveTime(); } return tt; diff --git a/core/ExplainTreeModelItem.h b/core/ExplainTreeModelItem.h index 30cdb7f..f800874 100644 --- a/core/ExplainTreeModelItem.h +++ b/core/ExplainTreeModelItem.h @@ -56,14 +56,14 @@ public: ExplainTreeModelItem(const ExplainTreeModelItem &rhs) = delete; ExplainTreeModelItem &operator=(const ExplainTreeModelItem &rhs) = delete; - void appendChild(ItemPtr child); + void appendChild(const ItemPtr &child); ExplainTreeModelItemPtr child(int row); int childCount() const; // int columnCount() const; // QVariant data(int column) const; int row() const; - void setParent(ItemPtr parent); + void setParent(const ItemPtr &parent); ItemPtr parent(); @@ -112,7 +112,7 @@ public: std::weak_ptr m_parentItem; QString nodeType; - bool parallelAware; // 9.6 + bool parallelAware = false; // 9.6 QString strategy; QString joinType; float startupCost = 0.f; diff --git a/core/SqlAstNode.cpp b/core/SqlAstNode.cpp index 19c59b4..a6a1c5d 100644 --- a/core/SqlAstNode.cpp +++ b/core/SqlAstNode.cpp @@ -2,7 +2,4 @@ using namespace SqlAst; -Node::Node() -{ - -} +Node::Node() = default; diff --git a/pglab/IconColumnDelegate.cpp b/pglab/IconColumnDelegate.cpp index c73adb5..c095e96 100644 --- a/pglab/IconColumnDelegate.cpp +++ b/pglab/IconColumnDelegate.cpp @@ -42,8 +42,7 @@ QSize IconColumnDelegate::sizeHint(const QStyleOptionViewItem &, void IconColumnDelegate::clearCache() { for (auto &e : m_Icons) - delete e. - second; + delete e.second; m_Icons.clear(); } diff --git a/pglab/PgLabItemDelegate.cpp b/pglab/PgLabItemDelegate.cpp index 73ea803..52afeaf 100644 --- a/pglab/PgLabItemDelegate.cpp +++ b/pglab/PgLabItemDelegate.cpp @@ -3,6 +3,7 @@ #include "Pgsql_oids.h" #include "ResultTableModelUtil.h" +#include "CustomDataRole.h" PgLabItemDelegate::PgLabItemDelegate(QObject *parent) : QStyledItemDelegate(parent) @@ -81,7 +82,7 @@ void PgLabItemDelegate::initStyleOption(QStyleOptionViewItem *option, // } Oid oid = InvalidOid; - value = index.data(Qt::UserRole); // get OID + value = index.data(CustomDataTypeRole); // get OID if (value.isValid()) oid = value.toUInt(); //getType(index.column()); From 1ae9a1151a6688d3a8dd049e90fe5dcbb040f793 Mon Sep 17 00:00:00 2001 From: eelke Date: Sun, 21 Oct 2018 13:47:38 +0200 Subject: [PATCH 5/8] All the detail tabs on the TablePage now update when the sort order of the table list changes. --- pglab/PropertiesPage.cpp | 12 +++++++- pglab/PropertiesPage.h | 8 ++++++ pglab/TablesPage.cpp | 62 ++++++++++++++++++++++++---------------- pglab/TablesPage.h | 4 +++ pglab/TriggerPage.cpp | 1 + 5 files changed, 61 insertions(+), 26 deletions(-) diff --git a/pglab/PropertiesPage.cpp b/pglab/PropertiesPage.cpp index 6ab4d06..e565e76 100644 --- a/pglab/PropertiesPage.cpp +++ b/pglab/PropertiesPage.cpp @@ -19,7 +19,17 @@ PropertiesPage::PropertiesPage(QWidget *parent) : QSplitter(parent) auto item_delegate = new PgLabItemDelegate(this); m_tableView->setItemDelegate(item_delegate); + m_tableView->setSelectionBehavior(QAbstractItemView::SelectRows); } -//property_model->setSourceModel(m_tablesModel); +void PropertiesPage::setSourceModel(QAbstractItemModel *model) +{ + m_propertyProxyModel->setSourceModel(model); +} + + +void PropertiesPage::setActiveRow(const QModelIndex &row) +{ + m_propertyProxyModel->setActiveRow(row); +} diff --git a/pglab/PropertiesPage.h b/pglab/PropertiesPage.h index e169eba..5686d31 100644 --- a/pglab/PropertiesPage.h +++ b/pglab/PropertiesPage.h @@ -7,6 +7,7 @@ class QTableView; class SqlCodePreview; class PgDatabaseCatalog; class PropertyProxyModel; +class QAbstractItemModel; class PropertiesPage : public QSplitter { @@ -14,9 +15,16 @@ class PropertiesPage : public QSplitter public: explicit PropertiesPage(QWidget *parent = nullptr); + void setSourceModel(QAbstractItemModel *model); signals: public slots: + /** Updates the model (and view) to show the values for row + * + * The column part of the index is not used QModelIndex is used to make is eacy to connect to + * QItemSelectionModel::currentRowChanged + */ + void setActiveRow(const QModelIndex &row); private: QTableView *m_tableView = nullptr; diff --git a/pglab/TablesPage.cpp b/pglab/TablesPage.cpp index a64fc33..81c7c41 100644 --- a/pglab/TablesPage.cpp +++ b/pglab/TablesPage.cpp @@ -26,6 +26,7 @@ TablesPage::TablesPage(MainWindow *parent) { ui->setupUi(this); + // WARNING delegates should NOT be shared!!! auto pglab_delegate = new PgLabItemDelegate(this); auto icon_delegate = new IconColumnDelegate(this); @@ -35,6 +36,7 @@ TablesPage::TablesPage(MainWindow *parent) ui->tableListTable->setItemDelegate(pglab_delegate); ui->tableListTable->setSortingEnabled(true); ui->tableListTable->sortByColumn(0, Qt::AscendingOrder); + ui->tableListTable->setSelectionBehavior(QAbstractItemView::SelectRows); // Columns SetTableViewDefault(ui->columnsTable); @@ -63,11 +65,14 @@ TablesPage::TablesPage(MainWindow *parent) // Connect signals // --------------- // Table selection -// connect(ui->tableListTable->selectionModel(), &QItemSelectionModel::currentRowChanged, -// property_model, &PropertyProxyModel::setActiveRow); connect(ui->tableListTable->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &TablesPage::tableListTable_currentRowChanged); + connect(m_tablesModel, &QAbstractItemModel::layoutChanged, + this, &TablesPage::tableListTable_layoutChanged); + + //layoutChanged(const QList &parents = ..., QAbstractItemModel::LayoutChangeHint hint = ...) + connect(ui->constraintsTable->selectionModel(), &QItemSelectionModel::selectionChanged, this, &TablesPage::constraintsTable_selectionChanged); connect(ui->constraintsTable->model(), &QAbstractItemModel::modelReset, this, @@ -81,9 +86,14 @@ TablesPage::TablesPage(MainWindow *parent) &TablesPage::indexesTable_modelReset); // Non designer based code + // - Properties page m_propertiesPage = new PropertiesPage(this); + m_propertiesPage->setSourceModel(m_tablesModel); m_propertiesTab = addDetailTab(m_propertiesPage); + connect(ui->tableListTable->selectionModel(), &QItemSelectionModel::currentRowChanged, + m_propertiesPage, &PropertiesPage::setActiveRow); + // - Trigger page m_triggerPage = new TriggerPage(this); m_triggerTab = addDetailTab(m_triggerPage); @@ -122,7 +132,6 @@ void TablesPage::setCatalog(std::shared_ptr cat) ui->tableListTable->resizeColumnsToContents(); m_triggerPage->setCatalog(cat); -// m_namespaceFilterWidget->init(cat->namespaces()); auto highlighter = new SqlSyntaxHighlighter(ui->constraintSqlEdit->document()); highlighter->setTypes(*cat->types()); @@ -130,36 +139,39 @@ void TablesPage::setCatalog(std::shared_ptr cat) highlighter->setTypes(*cat->types()); } + void TablesPage::tableListTable_currentRowChanged(const QModelIndex ¤t, const QModelIndex &previous) { if (current.row() != previous.row()) { PgClass table = m_tablesModel->getTable(current.row()); - m_columnsModel->setData(m_catalog, table); - ui->columnsTable->resizeColumnsToContents(); - - m_constraintModel->setData(m_catalog, table); - ui->constraintsTable->resizeColumnsToContents(); - ui->constraintsTable->selectionModel()->reset(); - - m_indexModel->setData(m_catalog, table); - ui->indexesTable->resizeColumnsToContents(); - - m_triggerPage->setFilter(table); + selectedTableChanged(table); } } -//void TablesPage::constraintsTable_currentRowChanged(const QModelIndex ¤t, const QModelIndex &previous) -//{ -// if (current.row() != previous.row()) { -//// QString drop_definition = m_constraintModel->dropDefinition(current.row()); -//// QString create_definition = m_constraintModel->createDefinition(current.row()); -// const PgConstraint& constraint = m_constraintModel->constraint(current.row()); -// QString drop = getDropConstraintDefinition(*m_catalog, constraint); -// QString add = getConstraintDefinition(*m_catalog, constraint); -// ui->constraintSqlEdit->setPlainText(drop % QString::fromUtf16(u"\n") % add); -// } -//} +void TablesPage::tableListTable_layoutChanged(const QList &, QAbstractItemModel::LayoutChangeHint ) +{ + auto&& index = ui->tableListTable->selectionModel()->currentIndex(); + PgClass table = m_tablesModel->getTable(index.row()); + selectedTableChanged(table); +} + + +void TablesPage::selectedTableChanged(const PgClass & table) +{ + m_columnsModel->setData(m_catalog, table); + ui->columnsTable->resizeColumnsToContents(); + + m_constraintModel->setData(m_catalog, table); + ui->constraintsTable->resizeColumnsToContents(); + ui->constraintsTable->selectionModel()->reset(); + + m_indexModel->setData(m_catalog, table); + ui->indexesTable->resizeColumnsToContents(); + + m_triggerPage->setFilter(table); +} + void TablesPage::constraintsTable_selectionChanged(const QItemSelection &/*selected*/, const QItemSelection &/*deselected*/) { diff --git a/pglab/TablesPage.h b/pglab/TablesPage.h index 23d2443..08284be 100644 --- a/pglab/TablesPage.h +++ b/pglab/TablesPage.h @@ -18,6 +18,7 @@ class IndexModel; class MainWindow; class PropertiesPage; class TriggerPage; +class PgClass; class TablesPage : public QWidget { @@ -44,9 +45,12 @@ private: void retranslateUi(bool all = true); QWidget* addDetailTab(QWidget *contents); + + void selectedTableChanged(const PgClass & table); private slots: void tableListTable_currentRowChanged(const QModelIndex ¤t, const QModelIndex &previous); + void tableListTable_layoutChanged(const QList &parents, QAbstractItemModel::LayoutChangeHint hint); // void constraintsTable_currentRowChanged(const QModelIndex ¤t, const QModelIndex &previous); void constraintsTable_selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); void constraintsTable_modelReset(); diff --git a/pglab/TriggerPage.cpp b/pglab/TriggerPage.cpp index add7b1f..d2705a3 100644 --- a/pglab/TriggerPage.cpp +++ b/pglab/TriggerPage.cpp @@ -26,6 +26,7 @@ TriggerPage::TriggerPage(QWidget *parent) m_sortFilterProxy->setSourceModel(m_model); m_tableView->setModel(m_sortFilterProxy); m_tableView->setSortingEnabled(true); + m_tableView->setSelectionBehavior(QAbstractItemView::SelectRows); auto item_delegate = new PgLabItemDelegate(this); m_tableView->setItemDelegate(item_delegate); From 6b9b602c6442716bb477d056e458dfe4fe2943d4 Mon Sep 17 00:00:00 2001 From: eelke Date: Sun, 4 Nov 2018 11:24:13 +0100 Subject: [PATCH 6/8] Replaced old PasswordManager code with code using Botan's new PSK_Database This greatly reduces the amount of encryption related code required. Thought we still have todo our own key strenthening but this also is easier with Botan::PasswordHash. --- core/PasswordManager.cpp | 369 ++++++++++------------- core/PasswordManager.h | 117 ++++--- tests/pglabtests/pglabtests.pro | 2 +- tests/pglabtests/tst_PasswordManager.cpp | 111 ++++--- 4 files changed, 294 insertions(+), 305 deletions(-) diff --git a/core/PasswordManager.cpp b/core/PasswordManager.cpp index cf60e9d..166cde5 100644 --- a/core/PasswordManager.cpp +++ b/core/PasswordManager.cpp @@ -1,245 +1,180 @@ #include "PasswordManager.h" -#include -#include -#include -#include -#include -#include -#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +#include #include -#include -#include - -#include - - -using namespace Botan; - -namespace { - - /* - First 24 bits of SHA-256("Botan Cryptobox"), followed by 8 0 bits - for later use as flags, etc if needed - */ - const uint8_t c_PasswordVersionCode = 0x10; - - const size_t c_VersionCodeLen = 1; - - const size_t CIPHER_KEY_LEN = 32; - const size_t CIPHER_IV_LEN = 16; - const size_t MAC_KEY_LEN = 32; - const size_t MAC_OUTPUT_LEN = 20; - const size_t PBKDF_SALT_LEN = 10; - //const size_t PBKDF_ITERATIONS = 8 * 1024; - - const size_t PBKDF_OUTPUT_LEN = CIPHER_KEY_LEN + CIPHER_IV_LEN + MAC_KEY_LEN; - - const char * const c_Cipher = "Serpent/CTR-BE"; - - const char * const c_IniGroupSecurity = "Security"; +#include +#include +#include +#include - StrengthenedKey generateKey(const std::string &passphrase, const uint8_t *salt, - int saltlength, int iterations) - { - PKCS5_PBKDF2 pbkdf(new HMAC(new SHA_512)); - OctetString master_key = pbkdf.derive_key( - PBKDF_OUTPUT_LEN, - passphrase, - salt, saltlength, - iterations); - - const uint8_t* mk = master_key.begin(); - - return StrengthenedKey( - SymmetricKey(mk, CIPHER_KEY_LEN), - SymmetricKey(mk + CIPHER_KEY_LEN, MAC_KEY_LEN), - InitializationVector(mk + CIPHER_KEY_LEN + MAC_KEY_LEN, CIPHER_IV_LEN)); - } - -// secure_vector pbkdf_salt(PBKDF_SALT_LEN); -// rng.randomize( pbkdf_salt.data(), pbkdf_salt.size()); -// StrengthenedKey strengthened_key = generateKey(passphrase, pbkdf_salt.data(), pbkdf_salt.size()); - - - std::string encrypt(const std::string &input, - const StrengthenedKey &strengthened_key) - { - - Pipe pipe(get_cipher(c_Cipher, strengthened_key.cipher_key, - strengthened_key.iv, ENCRYPTION), - new Fork( - nullptr, - new MAC_Filter(new HMAC(new SHA_512), - strengthened_key.mac_key, MAC_OUTPUT_LEN))); - - pipe.process_msg((const uint8_t*)input.data(), input.length()); - - /* - Output format is: - mac (20 bytes) - ciphertext - */ - const size_t ciphertext_len = pipe.remaining(0); - std::vector out_buf(MAC_OUTPUT_LEN + ciphertext_len); - - BOTAN_ASSERT_EQUAL( - pipe.read(&out_buf[0], MAC_OUTPUT_LEN, 1), - MAC_OUTPUT_LEN, "MAC output"); - BOTAN_ASSERT_EQUAL( - pipe.read(&out_buf[MAC_OUTPUT_LEN], ciphertext_len, 0), - ciphertext_len, "Ciphertext size"); - - return base64_encode(out_buf.data(), out_buf.size()); - } - - std::string decrypt(const std::string &input, const StrengthenedKey &strengthened_key) - { - secure_vector ciphertext = base64_decode(input); - - if(ciphertext.size() < (MAC_OUTPUT_LEN)) { - throw Decoding_Error("Invalid encrypted password input"); - } - - Pipe pipe(new Fork( - get_cipher(c_Cipher, strengthened_key.cipher_key, strengthened_key.iv, DECRYPTION), - new MAC_Filter(new HMAC(new SHA_512), strengthened_key.mac_key, MAC_OUTPUT_LEN) - )); - - const size_t ciphertext_offset = MAC_OUTPUT_LEN; - pipe.process_msg(&ciphertext[ciphertext_offset], ciphertext.size() - ciphertext_offset); - - uint8_t computed_mac[MAC_OUTPUT_LEN]; - BOTAN_ASSERT_EQUAL(MAC_OUTPUT_LEN, pipe.read(computed_mac, MAC_OUTPUT_LEN, 1), "MAC size"); - - if(!same_mem(computed_mac, &ciphertext[0], MAC_OUTPUT_LEN)) { - throw Decoding_Error("Encrypted password integrity failure"); - } - - return pipe.read_all_as_string(0); - } - - struct constants { - const int pbkdf_salt_len; - }; - - constants v1_consts = { - 10 - }; - -} // end of unnamed namespace - -/* - * File layout: - * - * Header - * version - * key_salt - * hash_salt - * master_hash - * - * - * Passwords - * key = pw - */ - - - -PasswordManager::PasswordManager(int iterations) - : m_iterations(iterations) +Botan::secure_vector PasswordManager::KeyStrengthener::derive(const std::string &passphrase) { + Botan::secure_vector master_key(m_keySize); + m_hasher->derive_key(master_key.data(), master_key.size(), passphrase.c_str(), passphrase.length(), m_salt.data(), m_salt.size()); + + return master_key; } -Expected PasswordManager::unlock(const std::string &master_password) +void PasswordManager::KeyStrengthener::saveParams(std::shared_ptr db, const std::string &table_name) { - try { - bool result = false; - if (m_masterHash.length() == 0 && master_password.empty()) { - result = true; - } else { - StrengthenedKey key = generateKey(master_password, m_keySalt.begin(), - m_keySalt.length(), m_iterations); - OctetString hash = hashStrengthenedKey(key, m_hashSalt); + auto sc = dynamic_cast(m_hasher.get()); + size_t i1 = sc->N(); + size_t i2 = sc->r(); + size_t i3 = sc->p(); - BOOST_ASSERT_MSG(hash.length() == m_masterHash.length(), "Both hashes should have the same length! Versioning error?"); + // SAVE parameters in database + auto stmt = db->new_statement("INSERT INTO " + table_name + "(id, algo, i1, i2, i3, ks, salt) VALUES(?1, ?2, ?3, ?4, ?5)"); + stmt->bind(1, 1); + stmt->bind(2, "Scrypt"); + stmt->bind(3, i1); + stmt->bind(4, i2); + stmt->bind(5, i3); + stmt->bind(6, m_keySize); + stmt->bind(7, Botan::base64_encode(m_salt)); + stmt->spin(); - if (same_mem(m_masterHash.begin(), hash.begin(), hash.length())) { - result = true; - m_masterKey = key; - } - } - return result; - } catch (...) { - return Expected::fromException(); +} + +// ------------------------- + +void PasswordManager::openDatabase(std::shared_ptr db, std::string passphrase) +{ +// std::string psk_db_file_name; +// auto db = std::make_shared(psk_db_file_name); + + KeyStrengthener ks; + // if (database exists) + if (isPskStoreInitialized(db)) { + ks = getKeyStrengthener(db); + } + else { + initializeNewPskStore(db); + ks = createKeyStrengthener(); + ks.saveParams(db, m_secretAlgoTableName); + } + + Botan::secure_vector master_key = ks.derive(passphrase); + m_pskDatabase = std::make_unique(master_key, db, m_passwordTableName); +} + + +void PasswordManager::closeDatabase() +{ + m_pskDatabase.reset(); +} + + +void PasswordManager::set(const std::string &id, const std::string &passwd) +{ + if (m_pskDatabase) { + + } + else { + throw PasswordManagerLockedException(); } } -Expected PasswordManager::changeMasterPassword(const std::string &old_master_password, - const std::string &new_master_password) +std::string PasswordManager::get(const std::string &id, const std::string &passwd) { - try { - bool result = false; - if (m_masterHash.length() == 0 && old_master_password.empty()) { - // Nothing set yet so we initialize for first use - m_keySalt = OctetString(m_rng, v1_consts.pbkdf_salt_len); - m_masterKey = generateKey(new_master_password, m_keySalt.begin(), m_keySalt.length(), m_iterations); + if (m_pskDatabase) { - m_hashSalt = OctetString(m_rng, v1_consts.pbkdf_salt_len); - m_masterHash = hashStrengthenedKey(m_masterKey, m_hashSalt); - result = true; - } - return result; - } catch (...) { - return Expected::fromException(); + } + else { + throw PasswordManagerLockedException(); } } -void PasswordManager::lock() +void PasswordManager::remove(const std::string &id) { - m_masterKey = StrengthenedKey(); -} + if (m_pskDatabase) { -bool PasswordManager::locked() const -{ - return m_masterKey.cipher_key.size() == 0; -} - -Expected PasswordManager::savePassword(const std::string &key, const std::string &password) -{ - if (locked()) { - return Expected::fromException(std::logic_error("Need to unlock the password manager first")); } - std::string epw = encrypt(password, m_masterKey); - m_store.emplace(key, epw); - - return Expected(); -} - -Expected PasswordManager::getPassword(const std::string &key, std::string &out) -{ - if (locked()) { - return Expected::fromException(std::logic_error("Need to unlock the password manager first")); + else { + throw PasswordManagerLockedException(); } - auto fi = m_store.find(key); - - bool result = false; - if (fi != m_store.end()) { - out = decrypt(fi->second, m_masterKey); - result = true; - } - - return result; } -Botan::OctetString PasswordManager::hashStrengthenedKey(const StrengthenedKey &key, const OctetString &salt) + +void PasswordManager::initializeNewPskStore(std::shared_ptr db) { - std::unique_ptr hash3(Botan::HashFunction::create("SHA-3")); - BOOST_ASSERT_MSG(hash3 != nullptr, "SHA-3 algorithm not available"); - hash3->update(salt.begin(), salt.length()); - hash3->update(key.cipher_key.begin(), key.cipher_key.length()); - hash3->update(key.mac_key.begin(), key.mac_key.length()); - hash3->update(key.iv.begin(), key.iv.length()); - return hash3->final(); + // Create tables + // - psk_masterkey_algo + // - psk_passwd + std::string create_statement = + "CREATE TABLE IF NOT EXISTS " + m_secretAlgoTableName + "( \n" + " id INTEGER PRIMARY KEY \n" + " algo TEXT \n" + " i1 INTEGER \n" + " i2 INTEGER \n" + " i3 INTEGER \n" + " ks INTEGER \n" + " salt TEXT \n" + ");"; + db->create_table(create_statement); + +} + +bool PasswordManager::isPskStoreInitialized(std::shared_ptr db) +{ + // Is the table with the secret data present and filled? + auto stmt = db->new_statement("SELECT name FROM sqlite_master WHERE type='table' AND name=?1"); + stmt->bind(1, m_secretAlgoTableName); + bool ok = stmt->step(); + if (ok) { + auto stmt = db->new_statement("SELECT algo FROM " + m_secretAlgoTableName + " WHERE id=1"); + return stmt->step(); + } + return false; +} + +PasswordManager::KeyStrengthener PasswordManager::getKeyStrengthener(std::shared_ptr db) +{ + auto stmt = db->new_statement("SELECT algo, i1, i2, i3, ks, salt FROM " + m_secretAlgoTableName + " WHERE id=1"); + if (stmt->step()) { + std::string algo = stmt->get_str(0); + size_t i1 = boost::lexical_cast(stmt->get_str(1)); + size_t i2 = boost::lexical_cast(stmt->get_str(2)); + size_t i3 = boost::lexical_cast(stmt->get_str(3)); + size_t ks = boost::lexical_cast(stmt->get_str(4)); + + auto pwh_fam = Botan::PasswordHashFamily::create(algo); + return KeyStrengthener( + pwh_fam->from_params(i1, i2, i3), + Botan::base64_decode(stmt->get_str(5)), + ks + ); + } + else { + + } +} + +PasswordManager::KeyStrengthener PasswordManager::createKeyStrengthener() +{ +// std::unique_ptr pwh; + + size_t key_size = 64; + Botan::secure_vector salt(key_size); + Botan::AutoSeeded_RNG rng; + rng.randomize(salt.data(), salt.size()); + + const std::string algo = "Scrypt"; + auto pwh_fam = Botan::PasswordHashFamily::create(algo); + return KeyStrengthener( + pwh_fam->tune(key_size, std::chrono::seconds(2), 130), + salt, + key_size + ); } diff --git a/core/PasswordManager.h b/core/PasswordManager.h index 5ddd021..e014ebc 100644 --- a/core/PasswordManager.h +++ b/core/PasswordManager.h @@ -2,65 +2,100 @@ #define PASSWORDMANAGER_H #include "Expected.h" +#include #include +#include -#include -#include +#include + + +//#include +//#include #include -struct StrengthenedKey { - Botan::SymmetricKey cipher_key; - Botan::SymmetricKey mac_key; - Botan::InitializationVector iv; +namespace Botan { - StrengthenedKey() {} - StrengthenedKey(const Botan::SymmetricKey &ck, const Botan::SymmetricKey &mk, - const Botan::InitializationVector &i) - : cipher_key(ck) - , mac_key(mk) - , iv(i) - {} + class Encrypted_PSK_Database_SQL; + class Sqlite3_Database; + class PasswordHash; + +} + +class PasswordManagerException: public std::exception { +public: + using std::exception::exception; //(char const* const _Message); }; +class PasswordManagerLockedException: public PasswordManagerException { +public: + using PasswordManagerException::PasswordManagerException; + +}; class PasswordManager { public: + enum Result { + Ok, + Locked, + Error + }; -// static PasswordManager create(const std::string &file_name); + PasswordManager() = default; - explicit PasswordManager(int iterations = 8192); - /** Unlocks the passwords of the connections. - * - * \return Normally it return a bool specifying if the password was accepted. - * on rare occasions it could return an error. - */ - Expected unlock(const std::string &master_password); - - Expected changeMasterPassword(const std::string &master_password, - const std::string &new_master_password); - - /** Forget master password - */ - void lock(); - bool locked() const; - - Expected savePassword(const std::string &key, const std::string &password); - Expected getPassword(const std::string &key, std::string &out); + void openDatabase(std::shared_ptr db, std::string passphrase); + void closeDatabase(); + void set(const std::string &id, const std::string &passwd); + std::string get(const std::string &id, const std::string &passwd); + void remove(const std::string &id); private: - int m_iterations; - Botan::AutoSeeded_RNG m_rng; - Botan::OctetString m_keySalt; // salt for generating crypto key - StrengthenedKey m_masterKey; // crypto key - Botan::OctetString m_hashSalt; // salt of the hash of the passphrase - Botan::OctetString m_masterHash; // hash for checking the passphrase + std::string m_passwordTableName = "psk_passwd"; + std::string m_secretAlgoTableName = "psk_masterkey_algo"; + std::unique_ptr m_pskDatabase; - using t_KeyPasswords = std::map; + bool isPskStoreInitialized(std::shared_ptr db); + void initializeNewPskStore(std::shared_ptr db); - t_KeyPasswords m_store; + class KeyStrengthener { + public: + KeyStrengthener() = default; + KeyStrengthener(std::unique_ptr hasher, Botan::secure_vector salt, size_t keysize) + : m_hasher (std::move(hasher)) + , m_salt (std::move(salt)) + , m_keySize(keysize) + {} + + KeyStrengthener(const KeyStrengthener&) = delete; + KeyStrengthener& operator=(const KeyStrengthener &) = delete; + + KeyStrengthener(KeyStrengthener &&rhs) + : m_hasher (std::move(rhs.m_hasher)) + , m_salt (std::move(rhs.m_salt)) + , m_keySize(rhs.m_keySize) + {} + KeyStrengthener& operator=(KeyStrengthener &&rhs) + { + if (&rhs != this) { + m_hasher = std::move(rhs.m_hasher); + m_salt = std::move(rhs.m_salt); + m_keySize = rhs.m_keySize; + } + return *this; + } + + Botan::secure_vector derive(const std::string &passphrase); + void saveParams(std::shared_ptr db, const std::string &table_name); + private: + std::unique_ptr m_hasher; + Botan::secure_vector m_salt; + size_t m_keySize; + }; + + /// Get PasswordHash from parameters in database + KeyStrengthener getKeyStrengthener(std::shared_ptr db); + KeyStrengthener createKeyStrengthener(); - static Botan::OctetString hashStrengthenedKey(const StrengthenedKey &key, const Botan::OctetString &salt); }; diff --git a/tests/pglabtests/pglabtests.pro b/tests/pglabtests/pglabtests.pro index eff3fce..3a9ad20 100644 --- a/tests/pglabtests/pglabtests.pro +++ b/tests/pglabtests/pglabtests.pro @@ -39,7 +39,7 @@ C:\VSproj\boost32\include INCLUDEPATH += $$PWD/../../core DEPENDPATH += $$PWD/../../core -LIBS += c:\prog\lib\botand_imp.lib +LIBS += c:\prog\lib\botan_imp.lib win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../core/release/libcore.a else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../core/debug/libcore.a diff --git a/tests/pglabtests/tst_PasswordManager.cpp b/tests/pglabtests/tst_PasswordManager.cpp index 566dbf0..ab3fa74 100644 --- a/tests/pglabtests/tst_PasswordManager.cpp +++ b/tests/pglabtests/tst_PasswordManager.cpp @@ -6,69 +6,88 @@ using namespace testing; -TEST(PasswordManager, initial_changeMasterPassword_returns_true) -{ - PasswordManager pwm(10); +#include +#include - auto res = pwm.changeMasterPassword("", "my test passphrase"); - ASSERT_NO_THROW( res.get() ); - ASSERT_THAT( res.get(), Eq(true) ); -} +//TEST(Botan, recreate) +//{ +// auto phf = Botan::PasswordHashFamily::create("Scrypt"); +// size_t N = 65536, r = 10, p = 2; +// auto ph = phf->from_params(N, r, p); +// auto sc = dynamic_cast(ph.get()); -TEST(PasswordManager, unlock_succeeds) -{ - PasswordManager pwm(10); +// ASSERT_EQ(N, sc->N()); +// ASSERT_EQ(r, sc->r()); +// ASSERT_EQ(p, sc->p()); - std::string passphrase = "my test passphrase"; +// auto phf2 = phf->create(ph->to_string()); +// phf2->default_params() - auto res = pwm.changeMasterPassword("", passphrase); - ASSERT_NO_THROW( res.get() ); - ASSERT_THAT( res.get(), Eq(true) ); +//} - auto res2 = pwm.unlock(passphrase); - ASSERT_NO_THROW( res2.get() ); - ASSERT_THAT( res2.get(), Eq(true) ); -} +//TEST(PasswordManager, initial_changeMasterPassword_returns_true) +//{ +// PasswordManager pwm(10); -TEST(PasswordManager, unlock_fails) -{ - PasswordManager pwm(10); +// auto res = pwm.changeMasterPassword("", "my test passphrase"); +// ASSERT_NO_THROW( res.get() ); +// ASSERT_THAT( res.get(), Eq(true) ); +//} - std::string passphrase = "my test passphrase"; +//TEST(PasswordManager, unlock_succeeds) +//{ +// PasswordManager pwm(10); - auto res = pwm.changeMasterPassword("", passphrase); - ASSERT_NO_THROW( res.get() ); - ASSERT_THAT( res.get(), Eq(true) ); +// std::string passphrase = "my test passphrase"; - auto res2 = pwm.unlock(passphrase + "2"); - ASSERT_NO_THROW( res2.get() ); - ASSERT_THAT( res2.get(), Eq(false) ); -} +// auto res = pwm.changeMasterPassword("", passphrase); +// ASSERT_NO_THROW( res.get() ); +// ASSERT_THAT( res.get(), Eq(true) ); -TEST(PasswordManager, test_save_get) -{ - PasswordManager pwm(10); +// auto res2 = pwm.unlock(passphrase); +// ASSERT_NO_THROW( res2.get() ); +// ASSERT_THAT( res2.get(), Eq(true) ); +//} - std::string passphrase = "my test passphrase"; +//TEST(PasswordManager, unlock_fails) +//{ +// PasswordManager pwm(10); - auto res = pwm.changeMasterPassword("", passphrase); - ASSERT_NO_THROW( res.get() ); - ASSERT_THAT( res.get(), Eq(true) ); +// std::string passphrase = "my test passphrase"; + +// auto res = pwm.changeMasterPassword("", passphrase); +// ASSERT_NO_THROW( res.get() ); +// ASSERT_THAT( res.get(), Eq(true) ); // auto res2 = pwm.unlock(passphrase + "2"); // ASSERT_NO_THROW( res2.get() ); // ASSERT_THAT( res2.get(), Eq(false) ); +//} - const std::string password = "password123"; - const std::string key = "abc"; +//TEST(PasswordManager, test_save_get) +//{ +// PasswordManager pwm(10); - auto res2 = pwm.savePassword(key, password); - ASSERT_THAT( res2.valid(), Eq(true) ); +// std::string passphrase = "my test passphrase"; - std::string result; - auto res3 = pwm.getPassword(key, result); - ASSERT_THAT( res3.valid(), Eq(true) ); - ASSERT_THAT( res3.get(), Eq(true) ); - ASSERT_THAT( result, Eq(password) ); +// auto res = pwm.changeMasterPassword("", passphrase); +// ASSERT_NO_THROW( res.get() ); +// ASSERT_THAT( res.get(), Eq(true) ); -} +//// auto res2 = pwm.unlock(passphrase + "2"); +//// ASSERT_NO_THROW( res2.get() ); +//// ASSERT_THAT( res2.get(), Eq(false) ); + +// const std::string password = "password123"; +// const std::string key = "abc"; + +// auto res2 = pwm.savePassword(key, password); +// ASSERT_THAT( res2.valid(), Eq(true) ); + +// std::string result; +// auto res3 = pwm.getPassword(key, result); +// ASSERT_THAT( res3.valid(), Eq(true) ); +// ASSERT_THAT( res3.get(), Eq(true) ); +// ASSERT_THAT( result, Eq(password) ); + +//} From 2230a4bd61768dc0c7e5a66f2ef076f2154e6c33 Mon Sep 17 00:00:00 2001 From: eelke Date: Sun, 4 Nov 2018 11:26:20 +0100 Subject: [PATCH 7/8] Lot of password related changes all over the place. Password is no longer saved with the connection list. Password is not entered along with other connection credentials. Password is now asked for when required. Still working on saving the password and auto retrieving it from the password manager. --- pglab/BackupDialog.h | 2 +- pglab/ConnectionList.cpp | 74 ++++++++++++----------- pglab/ConnectionList.h | 37 +++++------- pglab/ConnectionListModel.cpp | 30 +++++++++- pglab/ConnectionListModel.h | 5 +- pglab/ConnectionManagerWindow.cpp | 97 +++++++++---------------------- pglab/ConnectionManagerWindow.h | 5 +- pglab/ConnectionManagerWindow.ui | 50 ++++++---------- pglab/MasterController.cpp | 97 ++++++++++++++++++++++++++----- pglab/MasterController.h | 14 ++++- pglab/PassPhraseForm.cpp | 45 ++++++++++++++ pglab/PassPhraseForm.h | 22 +++++++ pglab/PassPhraseForm.ui | 75 ++++++++++++++++++++++++ pglab/PasswordPromptDialog.cpp | 70 ++++++++++++++++++++++ pglab/PasswordPromptDialog.h | 32 ++++++++++ pglab/TablesPage.cpp | 12 ++-- pglab/pglab.pro | 13 +++-- pglablib/ConnectionConfig.cpp | 16 +++++ pglablib/ConnectionConfig.h | 5 ++ pglablib/pglablib.pro | 2 +- readme.md | 0 21 files changed, 508 insertions(+), 195 deletions(-) create mode 100644 pglab/PassPhraseForm.cpp create mode 100644 pglab/PassPhraseForm.h create mode 100644 pglab/PassPhraseForm.ui create mode 100644 pglab/PasswordPromptDialog.cpp create mode 100644 pglab/PasswordPromptDialog.h create mode 100644 readme.md diff --git a/pglab/BackupDialog.h b/pglab/BackupDialog.h index 6ce44dd..d35e82a 100644 --- a/pglab/BackupDialog.h +++ b/pglab/BackupDialog.h @@ -16,7 +16,7 @@ class BackupDialog : public QDialog Q_OBJECT public: - explicit BackupDialog(QWidget *parent = 0); + explicit BackupDialog(QWidget *parent = nullptr); ~BackupDialog(); void ConnectTo(QProcess *process); diff --git a/pglab/ConnectionList.cpp b/pglab/ConnectionList.cpp index c373474..8476b6f 100644 --- a/pglab/ConnectionList.cpp +++ b/pglab/ConnectionList.cpp @@ -1,6 +1,7 @@ #include "ConnectionList.h" #include "ScopeGuard.h" #include "util.h" +#include "PasswordManager.h" #include #include #include @@ -19,31 +20,38 @@ namespace { 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("password", stdStrToQ(cc.password())); settings.setValue("dbname", stdStrToQ(cc.dbname())); - settings.setValue("sslmode", (int)cc.sslMode()); + settings.setValue("sslmode", static_cast(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())); } + template + bool in_range(T value) + { + return value >= std::numeric_limits::min() && value <= std::numeric_limits::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"))); - cc.setPort(settings.value("port", 5432).toInt()); + int p = settings.value("port", 5432).toInt(); + if (!in_range(p)) { + p = 0; // let the user re-enter a valid value + } + + cc.setPort(static_cast(p)); cc.setUser(qvarToStdStr(settings.value("user"))); - // std::string encpw = qvarToStdStr(settings.value("encryptedpw")); - // if (encpw.empty()) { - cc.setPassword(qvarToStdStr(settings.value("password"))); - // } - // else { - // cc.setEncryptedPassword(encpw); - // } + + //cc.setPassword(qvarToStdStr(settings.value("password"))); + cc.setDbname(qvarToStdStr(settings.value("dbname"))); - cc.setSslMode((SslMode)settings.value("sslmode").toInt()); + cc.setSslMode(static_cast(settings.value("sslmode").toInt())); cc.setSslCert(qvarToStdStr(settings.value("sslcert"))); cc.setSslKey(qvarToStdStr(settings.value("sslkey"))); cc.setSslRootCert(qvarToStdStr(settings.value("sslrootcert"))); @@ -69,19 +77,20 @@ QString ConnectionList::iniFileName() ConnectionList::ConnectionList() { - } -int ConnectionList::createNew() +size_t ConnectionList::createNew() { - m_connections.emplace_back(QUuid::createUuid(), ConnectionConfig()); + ConnectionConfig cc; + cc.setUuid(QUuid::createUuid()); + m_connections.push_back(cc); return m_connections.size()-1; } -void ConnectionList::remove(int idx, int count) +void ConnectionList::remove(size_t idx, size_t count) { - auto f = m_connections.begin() + idx; - auto l = f + count; + auto f = m_connections.begin() + static_cast(idx); + auto l = f + static_cast(count); deleteFromIni(f, l); m_connections.erase(f, l); } @@ -91,7 +100,7 @@ void ConnectionList::deleteFromIni(t_Connections::iterator begin, t_Connections: QString file_name = iniFileName(); QSettings settings(file_name, QSettings::IniFormat); for (auto i = begin; i != end; ++i) { - settings.remove(i->m_uuid.toString()); + settings.remove(i->uuid().toString()); } } @@ -111,8 +120,9 @@ void ConnectionList::load() SCOPE_EXIT { settings.endGroup(); }; ConnectionConfig cc; + cc.setUuid(uuid); LoadConnectionConfig(settings, cc); - m_connections.emplace_back(uuid, cc); + m_connections.push_back(cc); } } } @@ -123,26 +133,24 @@ void ConnectionList::save() QString file_name = iniFileName(); QSettings settings(file_name, QSettings::IniFormat); for (auto& e : m_connections) { - settings.beginGroup(e.m_uuid.toString()); + settings.beginGroup(e.uuid().toString()); SCOPE_EXIT { settings.endGroup(); }; - SaveConnectionConfig(settings, e.m_config); - e.m_config.clean(); + SaveConnectionConfig(settings, e); + e.clean(); } settings.sync(); } -void ConnectionList::save(int index) +void ConnectionList::save(size_t index) { - if (index >= 0 && index < (int)m_connections.size()) { - auto& e = m_connections[index]; - if (e.m_config.dirty()) { - QString file_name = iniFileName(); - QSettings settings(file_name, QSettings::IniFormat); - settings.beginGroup(e.m_uuid.toString()); - SaveConnectionConfig(settings, e.m_config); - e.m_config.clean(); - settings.sync(); - } + 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(); } } diff --git a/pglab/ConnectionList.h b/pglab/ConnectionList.h index f9ba131..5916fa9 100644 --- a/pglab/ConnectionList.h +++ b/pglab/ConnectionList.h @@ -8,47 +8,42 @@ #include #include "Expected.h" - class ConnectionList { -private: - static QString iniFileName(); - public: ConnectionList(); - int size() const { return m_connections.size(); } + size_t size() const { return m_connections.size(); } - ConnectionConfig& getConfigByIdx(int idx) + ConnectionConfig& getConfigByIdx(size_t idx) { - return m_connections.at(idx).m_config; + return m_connections.at(idx); } - int createNew(); + size_t createNew(); - void remove(int idx, int count); + void remove(size_t idx, size_t count); void load(); void save(); - void save(int index); - + void save(size_t index); private: - class LijstElem { - public: - QUuid m_uuid; - ConnectionConfig m_config; +// 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) - {} - }; +// LijstElem(const QUuid id, const ConnectionConfig &cfg) +// : m_uuid(id), m_config(cfg) +// {} +// }; - using t_Connections = std::vector; + using t_Connections = std::vector; t_Connections m_connections; void deleteFromIni(t_Connections::iterator begin, t_Connections::iterator end); - + static QString iniFileName(); }; #endif // CONNECTIONLIST_H diff --git a/pglab/ConnectionListModel.cpp b/pglab/ConnectionListModel.cpp index dfb87c2..f1f3649 100644 --- a/pglab/ConnectionListModel.cpp +++ b/pglab/ConnectionListModel.cpp @@ -150,6 +150,27 @@ Expected ConnectionListModel::get(int row) } } + +#include + +template +size_t as_size_t(T t); + +template <> +size_t as_size_t(int t) +{ + BOOST_ASSERT(t >= 0); + return static_cast(t); +} + +template <> +size_t as_size_t(long t) +{ + BOOST_ASSERT(t >= 0); + return static_cast(t); +} + + //void ConnectionListModel::del(const int idx) bool ConnectionListModel::removeRows(int row, int count, const QModelIndex &parent) { @@ -159,7 +180,7 @@ bool ConnectionListModel::removeRows(int row, int count, const QModelIndex &pare beginRemoveRows(parent, row, row + count -1); SCOPE_EXIT { endRemoveRows(); }; - m_connections->remove(row, count); + m_connections->remove(as_size_t(row), as_size_t(count)); result = true; } return result; @@ -176,7 +197,12 @@ void ConnectionListModel::save() m_connections->save(); } -void ConnectionListModel::save(int index) +void ConnectionListModel::save(size_t index) { m_connections->save(index); } + +void ConnectionListModel::save(size_t index, const ConnectionConfig &cc) +{ + +} diff --git a/pglab/ConnectionListModel.h b/pglab/ConnectionListModel.h index 78ce96a..e9cde8d 100644 --- a/pglab/ConnectionListModel.h +++ b/pglab/ConnectionListModel.h @@ -34,13 +34,14 @@ public: virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; void save(); - void save(int index); + void save(size_t index); + void save(size_t index, const ConnectionConfig &cc); + static QString makeLongDescription(const ConnectionConfig &cfg); private: ConnectionList *m_connections; - static QString makeLongDescription(const ConnectionConfig &cfg); }; #endif // CONNECTIONLISTMODEL_H diff --git a/pglab/ConnectionManagerWindow.cpp b/pglab/ConnectionManagerWindow.cpp index 2883c40..42c2e03 100644 --- a/pglab/ConnectionManagerWindow.cpp +++ b/pglab/ConnectionManagerWindow.cpp @@ -7,6 +7,21 @@ #include #include "ConnectionListModel.h" + +#include +#include + +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) @@ -53,9 +68,13 @@ void ConnectionManagerWindow::on_currentChanged(const QModelIndex ¤t, { int currow = current.row(); auto clm = m_masterController->getConnectionListModel(); - clm->save(prevSelection); + if (prevSelection) + clm->save(*prevSelection); m_mapper->setCurrentIndex(currow); - prevSelection = currow; + if (currow >= 0) + prevSelection = static_cast(currow); + else + prevSelection.reset(); } void ConnectionManagerWindow::on_actionDelete_connection_triggered() @@ -82,7 +101,6 @@ void ConnectionManagerWindow::setupWidgetMappings() m_mapper->addMapping(ui->edtHost, 2); m_mapper->addMapping(ui->spinPort, 3); m_mapper->addMapping(ui->edtUser, 4); - m_mapper->addMapping(ui->edtPassword, 5); m_mapper->addMapping(ui->edtDbname, 6); m_mapper->toFirst(); } @@ -90,7 +108,13 @@ void ConnectionManagerWindow::setupWidgetMappings() void ConnectionManagerWindow::on_actionConnect_triggered() { auto ci = ui->listView->selectionModel()->currentIndex(); - m_masterController->openSqlWindowForConnection(ci.row()); + if (ci.isValid()) { + auto r = static_cast(ci.row()); + m_masterController->openSqlWindowForConnection(r); + } + else { + // TODO can we give unobtrusive message why it didn't work? + } } void ConnectionManagerWindow::on_actionQuit_application_triggered() @@ -117,68 +141,3 @@ void ConnectionManagerWindow::on_actionManage_server_triggered() } - -#include -//#include -//#include -//#include -//#include -#include - - -void ConnectionManagerWindow::on_testButton_clicked() -{ - std::string error = Botan::runtime_version_check(BOTAN_VERSION_MAJOR, - BOTAN_VERSION_MINOR, - BOTAN_VERSION_PATCH); - if (error.empty()) { -// Botan::AutoSeeded_RNG rng; -// Botan::secure_vector salt = -// //{ 0x3f, 0x0a, 0xb0, 0x11, 0x44, 0xfe, 0x9d, 0xf7, 0x85, 0xd3, 0x11, 0x38, 0xe2, 0xdf, 0x31, 0x42 }; -// rng.random_vec(16); -// // salt should be random and saved with encrypted data so it can be used when we decrypt - -// std::string password = "Hello kitty"; -// std::unique_ptr pbkdf(Botan::get_pbkdf("PBKDF2(SHA-256)")); -// Botan::OctetString aes256_key = pbkdf->derive_key(32, password, salt.data(), salt.size(), 10000); - -// std::string plaintext("Your great-grandfather gave this watch to your granddad for good luck. Unfortunately, Dane's luck wasn't as good as his old man's."); -// Botan::secure_vector pt(plaintext.data(),plaintext.data()+plaintext.length()); - -// std::unique_ptr enc(Botan::get_cipher_mode("AES-256/CBC/PKCS7", Botan::ENCRYPTION)); -// enc->set_key(aes256_key); - -// //generate fresh nonce (IV) -// //std::unique_ptr rng(new Botan::AutoSeeded_RNG); -// std::vector iv(enc->default_nonce_length()); -// rng.randomize(iv.data(), iv.size()); -// enc->start(iv); -// enc->finish(pt); -// //std::cout << std::endl << enc->name() << " with iv " << Botan::hex_encode(iv) << std::endl << Botan::hex_encode(pt); - - - //std::string s = aes256_key.as_string();// + "\n" + t.format_string(); - - std::string passphrase = "my passphrase"; - std::string plaintext("password1234"); - try { - Botan::AutoSeeded_RNG rng; - std::string encrypted = Botan::CryptoBox::encrypt((const uint8_t*)plaintext.data(), plaintext.length(), passphrase, rng); - - std::string decrypted = Botan::CryptoBox::decrypt(encrypted, passphrase); - - std::string s = encrypted + "\n" + decrypted; - QMessageBox::information(this, "pglab", - QString::fromUtf8(s.c_str()), QMessageBox::Yes); - } - catch (Botan::Decoding_Error &/*e*/) { - QMessageBox::information(this, "pglab", - tr("Failure to decrypt"), QMessageBox::Yes); - - } - } - else { - QMessageBox ::information(this, "pglab", - QString::fromUtf8(error.c_str()), QMessageBox::Yes); - } -} diff --git a/pglab/ConnectionManagerWindow.h b/pglab/ConnectionManagerWindow.h index 7bad733..0821f8e 100644 --- a/pglab/ConnectionManagerWindow.h +++ b/pglab/ConnectionManagerWindow.h @@ -2,6 +2,7 @@ #define CONNECTIONMANAGERWINDOW_H #include +#include namespace Ui { class ConnectionManagerWindow; @@ -30,14 +31,12 @@ private slots: void on_actionBackup_database_triggered(); void on_actionManage_server_triggered(); - void on_testButton_clicked(); - private: Ui::ConnectionManagerWindow *ui; QDataWidgetMapper *m_mapper = nullptr; MasterController *m_masterController; - int prevSelection = -1; + boost::optional prevSelection; void setupWidgetMappings(); }; diff --git a/pglab/ConnectionManagerWindow.ui b/pglab/ConnectionManagerWindow.ui index ea3fd3d..839e9db 100644 --- a/pglab/ConnectionManagerWindow.ui +++ b/pglab/ConnectionManagerWindow.ui @@ -75,30 +75,23 @@ - - - Password - - - - - - - QLineEdit::Password - - - - Database - + - + + + + SSL + + + + 2 @@ -136,53 +129,46 @@ - - - SSL - - - - Certificate - + - + Key - + - + Root cert. - + - + Revocation list - + - + PushButton @@ -201,7 +187,7 @@ 0 0 800 - 25 + 20 diff --git a/pglab/MasterController.cpp b/pglab/MasterController.cpp index 05b1319..79ab6dd 100644 --- a/pglab/MasterController.cpp +++ b/pglab/MasterController.cpp @@ -5,6 +5,7 @@ #include "MainWindow.h" #include "ServerWindow.h" #include "BackupDialog.h" +#include "PasswordPromptDialog.h" MasterController::MasterController(QObject *parent) : QObject(parent) @@ -33,39 +34,103 @@ void MasterController::showConnectionManager() m_connectionManagerWindow->show(); } -void MasterController::openSqlWindowForConnection(int connection_index) +void MasterController::openSqlWindowForConnection(size_t connection_index) { - auto cc = m_connectionListModel->get(connection_index); - m_connectionListModel->save(connection_index); - if (cc.valid()) { - auto w = new MainWindow(this, nullptr); - w->setAttribute( Qt::WA_DeleteOnClose ); - w->setConfig(cc.get()); - w->show(); + + auto res = m_connectionListModel->get(connection_index); + if (res.valid()) { + auto cc = res.get(); + + m_connectionListModel->save(connection_index, cc); + if (retrieveConnectionPassword(cc)) { + // TODO instead of directly openening the mainwindow + // do async connect and only open window when we have + // working connection + auto w = new MainWindow(this, nullptr); + w->setAttribute( Qt::WA_DeleteOnClose ); + w->setConfig(cc); + w->show(); + } } } void MasterController::openBackupDlgForConnection(int connection_index) { - auto cc = m_connectionListModel->get(connection_index); - m_connectionListModel->save(connection_index); - if (cc.valid()) { + auto res = m_connectionListModel->get(connection_index); + if (res.valid()) { + auto cc = res.get(); + 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.get()); + w->setConfig(cc); w->show(); } } void MasterController::openServerWindowForConnection(int connection_index) { - auto cc = m_connectionListModel->get(connection_index); - m_connectionListModel->save(connection_index); - if (cc.valid()) { + auto res = m_connectionListModel->get(connection_index); + if (res.valid()) { + auto cc = res.get(); + retrieveConnectionPassword(cc); + m_connectionListModel->save(connection_index, cc); + auto w = new ServerWindow(this, nullptr); w->setAttribute( Qt::WA_DeleteOnClose ); - w->setConfig(cc.get()); + w->setConfig(cc); w->show(); } } + + +bool MasterController::retrieveConnectionPassword(ConnectionConfig &cc) +{ + // Look at config + // - is password required, how do we know? + // - IF is password stored in pskdb + // - ask pskdb for password + // - ELSE + // - ask user for password + QString str = ConnectionListModel::makeLongDescription(cc); + auto dlg = std::make_unique(nullptr); + dlg->setConnectionDescription(str); + int exec_result = dlg->exec(); + + if (exec_result == QDialog::Accepted) { + cc.setPassword(dlg->password().toUtf8().data()); + // - IF user checked remember password + // - ask pskdb to store password + + return true; + } + return false; +} + + +bool MasterController::getPasswordFromPskdb(const std::string &password_id, std::string &password) +{ +// func: getPasswordFromPskdb +// IF pskdb locked +// prompt user for pskdb passphrase +// unlock pskdb +// get pwd + return false; +} + + +bool MasterController::storePasswordInPskdb(const std::string &password_id, const std::string password) +{ +// func: storePasswordInPskdb +// IF pskdb not setup +// notify user and ask for passphrase +// init pskdb +// ELSE +// IF pskdb locked +// ask for passphrase +// unlock +// store pwd + return false; +} diff --git a/pglab/MasterController.h b/pglab/MasterController.h index 562e9ee..4a10c9a 100644 --- a/pglab/MasterController.h +++ b/pglab/MasterController.h @@ -16,7 +16,7 @@ class ConnectionManagerWindow; class MasterController : public QObject { Q_OBJECT public: - explicit MasterController(QObject *parent = 0); + explicit MasterController(QObject *parent = nullptr); MasterController(const MasterController&) = delete; MasterController &operator=(const MasterController&) = delete; ~MasterController(); @@ -29,7 +29,7 @@ public: } void showConnectionManager(); - void openSqlWindowForConnection(int connection_index); + void openSqlWindowForConnection(size_t connection_index); void openServerWindowForConnection(int connection_index); void openBackupDlgForConnection(int connection_index); @@ -41,7 +41,15 @@ private: ConnectionList *m_connectionList = nullptr; ConnectionListModel *m_connectionListModel = nullptr; ConnectionManagerWindow *m_connectionManagerWindow = nullptr; - + + /** 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); }; #endif // MASTERCONTROLLER_H diff --git a/pglab/PassPhraseForm.cpp b/pglab/PassPhraseForm.cpp new file mode 100644 index 0000000..36ff2fa --- /dev/null +++ b/pglab/PassPhraseForm.cpp @@ -0,0 +1,45 @@ +#include "PassPhraseForm.h" +#include "ui_PassPhraseForm.h" + +PassPhraseForm::PassPhraseForm(QWidget *parent) : + QWidget(parent), + ui(new Ui::PassPhraseForm) +{ + ui->setupUi(this); +} + +PassPhraseForm::~PassPhraseForm() +{ + delete ui; +} + +/* + +Password strength calculation: +seperate characters in couple of groups + +For the use of characters from each group a certain value is added to the base value +which is meant to signify the size of the set of characters the password is based on. + +Some analysis of relative positions might be required! Predictable placement of special charachters and uppercase/lowercase or numbers +should be penalized. + +These calculations should result in a search space size per character + +the base to the power of the length of the password gives the resulting strength +from this result we take the 10 log to get the magnitude of the value. + +a-z 1:3 2:7 3:13 4:26 +A-Z 1:3 2:7 3:13 4:26 +0-9 1:4 2:10 +`~!@#$%^&*()_-=+[{]};:'",<.>/?\| 1:4 2:8 3:16 4:32 +space +1 + +Straf punten +alleen speciaal karakter aan eind van string -8 +alleen hoofdletter aan begin van wachtwoord -6 + +la-~ZDv4E-O*y]C +bYGWlDyeKKbcZBjoWX + +*/ diff --git a/pglab/PassPhraseForm.h b/pglab/PassPhraseForm.h new file mode 100644 index 0000000..c0c0f8d --- /dev/null +++ b/pglab/PassPhraseForm.h @@ -0,0 +1,22 @@ +#ifndef PASSPHRASEFORM_H +#define PASSPHRASEFORM_H + +#include + +namespace Ui { +class PassPhraseForm; +} + +class PassPhraseForm : public QWidget +{ + Q_OBJECT + +public: + explicit PassPhraseForm(QWidget *parent = nullptr); + ~PassPhraseForm(); + +private: + Ui::PassPhraseForm *ui; +}; + +#endif // PASSPHRASEFORM_H diff --git a/pglab/PassPhraseForm.ui b/pglab/PassPhraseForm.ui new file mode 100644 index 0000000..89bd22f --- /dev/null +++ b/pglab/PassPhraseForm.ui @@ -0,0 +1,75 @@ + + + PassPhraseForm + + + + 0 + 0 + 397 + 228 + + + + Form + + + false + + + + + + 20 + + + 20 + + + + + Enter passphrase: + + + + + + + 32767 + + + QLineEdit::NoEcho + + + + + + + Repeat passphrase: + + + + + + + QLineEdit::Password + + + + + + + 67 + + + + + + + + + + + + + diff --git a/pglab/PasswordPromptDialog.cpp b/pglab/PasswordPromptDialog.cpp new file mode 100644 index 0000000..0b11759 --- /dev/null +++ b/pglab/PasswordPromptDialog.cpp @@ -0,0 +1,70 @@ +#include "PasswordPromptDialog.h" +#include +#include +#include +#include +#include +#include + +PasswordPromptDialog::PasswordPromptDialog(QWidget *parent) + : QDialog(parent) +{ + m_connectionLabel = new QLabel(this); + auto dialog_buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this); + + m_passwordLabel = new QLabel(this); + m_passwordInput = new QLineEdit(this); + m_passwordInput->setEchoMode(QLineEdit::Password); + + m_saveCheck = new QCheckBox(this); + + + auto mainLayout = new QGridLayout; + int row = 0; + mainLayout->addWidget(m_connectionLabel, row, 0, 1, 2); + ++row; + mainLayout->addWidget(m_passwordLabel, row, 0); + mainLayout->addWidget(m_passwordInput, row, 1); + ++row; + mainLayout->addWidget(m_saveCheck, row, 1); + ++row; + mainLayout->addWidget(dialog_buttons, row, 0, 1 ,2); + setLayout(mainLayout); + + m_passwordInput->setFocus(); + retranslateUi(); + + // QMetaObject::connectSlotsByName(BackupDialog); + connect(dialog_buttons, &QDialogButtonBox::accepted, this, &PasswordPromptDialog::accept); + connect(dialog_buttons, &QDialogButtonBox::rejected, this, &PasswordPromptDialog::reject); +} + +void PasswordPromptDialog::retranslateUi() +{ + const char * context = "PasswordPromptDialog"; + setWindowTitle(QApplication::translate(context, "Connection password", nullptr)); + m_passwordLabel->setText(QApplication::translate(context, "Password", nullptr)); + m_passwordInput->setPlaceholderText(QApplication::translate(context, "Enter password", nullptr)); + m_saveCheck->setText(QApplication::translate(context, "Save password", nullptr)); +} + +void PasswordPromptDialog::setConnectionDescription(const QString &description) +{ + m_connectionLabel->setText(QString(tr("Please provide password for connection %1")).arg(description)); + +} + +QString PasswordPromptDialog::password() const +{ + return m_passwordInput->text(); +} + +//void PasswordPromptDialog::accept() +//{ + +//} + +//void PasswordPromptDialog::reject() +//{ + +//} diff --git a/pglab/PasswordPromptDialog.h b/pglab/PasswordPromptDialog.h new file mode 100644 index 0000000..f394a9e --- /dev/null +++ b/pglab/PasswordPromptDialog.h @@ -0,0 +1,32 @@ +#ifndef PASSWORDPROMPTDIALOG_H +#define PASSWORDPROMPTDIALOG_H + +#include + +class QCheckBox; +class QLabel; +class QLineEdit; + +class PasswordPromptDialog : public QDialog +{ + Q_OBJECT +public: + explicit PasswordPromptDialog(QWidget *parent = nullptr); + + void setConnectionDescription(const QString &description); + + QString password() const; +private: + QLabel *m_connectionLabel = nullptr; + QLabel *m_passwordLabel = nullptr; + QLineEdit *m_passwordInput = nullptr; + QCheckBox *m_saveCheck = nullptr; + + void retranslateUi(); + +private slots: +// void accept(); +// void reject(); +}; + +#endif // PASSWORDPROMPTDIALOG_H diff --git a/pglab/TablesPage.cpp b/pglab/TablesPage.cpp index 81c7c41..a622e07 100644 --- a/pglab/TablesPage.cpp +++ b/pglab/TablesPage.cpp @@ -26,14 +26,10 @@ TablesPage::TablesPage(MainWindow *parent) { ui->setupUi(this); - // WARNING delegates should NOT be shared!!! - auto pglab_delegate = new PgLabItemDelegate(this); - auto icon_delegate = new IconColumnDelegate(this); - SetTableViewDefault(ui->tableListTable); m_tablesModel = new TablesTableModel(this); ui->tableListTable->setModel(m_tablesModel); - ui->tableListTable->setItemDelegate(pglab_delegate); + ui->tableListTable->setItemDelegate(new PgLabItemDelegate(this)); ui->tableListTable->setSortingEnabled(true); ui->tableListTable->sortByColumn(0, Qt::AscendingOrder); ui->tableListTable->setSelectionBehavior(QAbstractItemView::SelectRows); @@ -47,14 +43,14 @@ TablesPage::TablesPage(MainWindow *parent) SetTableViewDefault(ui->constraintsTable); m_constraintModel = new ConstraintModel(this); ui->constraintsTable->setModel(m_constraintModel); - ui->constraintsTable->setItemDelegateForColumn(0, icon_delegate); + ui->constraintsTable->setItemDelegateForColumn(0, new IconColumnDelegate(this)); // Indexes SetTableViewDefault(ui->indexesTable); m_indexModel = new IndexModel(this); ui->indexesTable->setModel(m_indexModel); - ui->indexesTable->setItemDelegate(pglab_delegate); - ui->indexesTable->setItemDelegateForColumn(0, icon_delegate); + ui->indexesTable->setItemDelegate(new PgLabItemDelegate(this)); + ui->indexesTable->setItemDelegateForColumn(0, new IconColumnDelegate(this)); // Set code editor fonts QFont code_font = UserConfiguration::instance()->codeFont(); diff --git a/pglab/pglab.pro b/pglab/pglab.pro index 7313d8a..605fece 100644 --- a/pglab/pglab.pro +++ b/pglab/pglab.pro @@ -23,7 +23,7 @@ DEFINES += _WIN32_WINNT=0x0501 #LIBS += -LC:\VSproj\boost32\lib -LC:/PROG/LIB -lws2_32 -llibpq #debug { -LIBS += c:/prog/lib/botand_imp.lib +LIBS += c:/prog/lib/botan_imp.lib #} #release { @@ -83,7 +83,9 @@ PropertyProxyModel.cpp \ TriggerPage.cpp \ SqlCodePreview.cpp \ CustomFilterSortModel.cpp \ - PropertiesPage.cpp + PropertiesPage.cpp \ + PassPhraseForm.cpp \ + PasswordPromptDialog.cpp HEADERS += \ QueryResultModel.h \ @@ -137,7 +139,9 @@ CustomDataRole.h \ TriggerPage.h \ SqlCodePreview.h \ CustomFilterSortModel.h \ - PropertiesPage.h + PropertiesPage.h \ + PassPhraseForm.h \ + PasswordPromptDialog.h FORMS += mainwindow.ui \ ConnectionManagerWindow.ui \ @@ -151,7 +155,8 @@ FORMS += mainwindow.ui \ NamespaceFilterWidget.ui \ ApplicationWindow.ui \ CrudTab.ui \ - CodeGenerator.ui + CodeGenerator.ui \ + PassPhraseForm.ui RESOURCES += \ resources.qrc diff --git a/pglablib/ConnectionConfig.cpp b/pglablib/ConnectionConfig.cpp index d6fcb21..f6c21e8 100644 --- a/pglablib/ConnectionConfig.cpp +++ b/pglablib/ConnectionConfig.cpp @@ -57,6 +57,22 @@ ConnectionConfig::ConnectionConfig() : m_applicationName(QCoreApplication::applicationName().toUtf8().data()) {} + +void ConnectionConfig::setUuid(const QUuid &uuid) +{ + if (uuid != m_uuid) { + m_dirty = true; + m_uuid = uuid; + } +} + + +const QUuid &ConnectionConfig::uuid() const +{ + return m_uuid; +} + + void ConnectionConfig::setName(std::string desc) { if (m_name != desc) { diff --git a/pglablib/ConnectionConfig.h b/pglablib/ConnectionConfig.h index b451a6a..ce9191e 100644 --- a/pglablib/ConnectionConfig.h +++ b/pglablib/ConnectionConfig.h @@ -1,6 +1,7 @@ #ifndef CONNECTION_H #define CONNECTION_H +#include #include #include @@ -27,6 +28,9 @@ class ConnectionConfig { public: ConnectionConfig(); + void setUuid(const QUuid &uuid); + const QUuid &uuid() const; + void setName(std::string desc); const std::string& name() const; @@ -73,6 +77,7 @@ public: bool dirty() const; void clean(); private: + QUuid m_uuid; std::string m_name; std::string m_host; std::string m_hostaddr; diff --git a/pglablib/pglablib.pro b/pglablib/pglablib.pro index f678cd8..34c3dba 100644 --- a/pglablib/pglablib.pro +++ b/pglablib/pglablib.pro @@ -4,7 +4,7 @@ # #------------------------------------------------- -QT += widgets +QT += widgets core TARGET = pglablib TEMPLATE = lib diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..e69de29 From e36924c0873c7f9541b67be483b30eae2124effb Mon Sep 17 00:00:00 2001 From: eelke Date: Thu, 8 Nov 2018 21:50:49 +0100 Subject: [PATCH 8/8] Passwords are now saved in a password manager. The password manager uses strong encryption using a key derived from the passphrase using scrypt key strengthening algorithm. This ensures encryption is performed using a strong key and that brute forcing the passphrase is time consuming. If the user loses his passphrase no recovery is possible. --- core/PasswordManager.cpp | 357 ++++++++++++++++++----- core/PasswordManager.h | 36 ++- core/core.pro | 1 + pglab/ConnectionList.cpp | 7 + pglab/ConnectionList.h | 5 + pglab/ConnectionListModel.cpp | 7 +- pglab/ConnectionListModel.h | 4 +- pglab/ConnectionManagerWindow.h | 6 +- pglab/CrudModel.cpp | 2 +- pglab/CrudModel.h | 10 +- pglab/MasterController.cpp | 178 ++++++++--- pglab/MasterController.h | 23 +- pglab/PassPhraseForm.cpp | 45 --- pglab/PassPhraseForm.h | 22 -- pglab/PassPhraseForm.ui | 75 ----- pglab/PasswordPromptDialog.cpp | 92 ++++-- pglab/PasswordPromptDialog.h | 24 +- pglab/pglab.pro | 5 +- pglablib/ConnectionConfig.cpp | 13 + pglablib/ConnectionConfig.h | 15 +- pglablib/PgConstraintContainer.cpp | 4 +- pglablib/PgConstraintContainer.h | 4 +- pglablib/codebuilder/CodeBuilder.cpp | 2 +- pglablib/codebuilder/StringEscapeRule.h | 2 +- pgsql/Pgsql_Params.h | 4 +- pgsql/Pgsql_Value.h | 6 +- tests/pglabtests/tst_PasswordManager.cpp | 2 +- 27 files changed, 605 insertions(+), 346 deletions(-) delete mode 100644 pglab/PassPhraseForm.cpp delete mode 100644 pglab/PassPhraseForm.h delete mode 100644 pglab/PassPhraseForm.ui diff --git a/core/PasswordManager.cpp b/core/PasswordManager.cpp index 166cde5..b57b972 100644 --- a/core/PasswordManager.cpp +++ b/core/PasswordManager.cpp @@ -1,22 +1,128 @@ #include "PasswordManager.h" -//#include -//#include -//#include -//#include -//#include -//#include -//#include -//#include -//#include -//#include +#include +#include +#include +#include +#include #include #include -#include -#include +//#include +#include + +//#include #include #include +namespace { + + class SqlException : public std::runtime_error { + public: + QSqlError error; + SqlException(const QSqlError &err) + : std::runtime_error(err.text().toUtf8().data()) + , error(err) + {} + + }; + + class QPSK_Database : public Botan::Encrypted_PSK_Database + { + public: + /** + * @param master_key specifies the master key used to encrypt all + * keys and value. It can be of any length, but should be at least 256 bits. + * + * Subkeys for the cryptographic algorithms used are derived from this + * master key. No key stretching is performed; if encrypting a PSK database + * using a password, it is recommended to use PBKDF2 to derive the database + * master key. + */ + QPSK_Database(const Botan::secure_vector& master_key, QSqlDatabase &db, const QString &table_name) + : Encrypted_PSK_Database(master_key) + , m_db(db) + , m_tableName(table_name) + { + QSqlQuery q_create_table(m_db); + q_create_table.prepare("CREATE TABLE IF NOT EXISTS " + table_name + + "(psk_name TEXT PRIMARY KEY, psk_value TEXT)"); + if (!q_create_table.exec()) { + auto err = q_create_table.lastError(); + throw SqlException(err); + } + } + + protected: + /// Save a encrypted (name.value) pair to the database. Both will be base64 encoded strings. + virtual void kv_set(const std::string& index, const std::string& value) override + { + QSqlQuery q(m_db); + q.prepare("insert or replace into " + m_tableName + " values(:name, :value)"); + q.bindValue(":name", QString::fromUtf8(index.c_str())); + q.bindValue(":value", QString::fromUtf8(value.c_str())); + if (!q.exec()) { + auto err = q.lastError(); + throw SqlException(err); + } + } + + /// Get a value previously saved with set_raw_value. Should return an empty + /// string if index is not found. + virtual std::string kv_get(const std::string& index) const override + { + QSqlQuery q(m_db); + q.prepare("SELECT psk_value FROM " + m_tableName + + " WHERE psk_name = :name"); + q.bindValue(":name", QString::fromUtf8(index.c_str())); + if (q.exec()) { + if (q.next()) { + return q.value(0).toString().toUtf8().data(); + } + } + else { + auto err = q.lastError(); + throw SqlException(err); + } + return std::string(); + } + + /// Remove an index + virtual void kv_del(const std::string& index) override + { + QSqlQuery q(m_db); + q.prepare("DELETE FROM " + m_tableName + " WHERE psk_name=:name"); + q.bindValue(":name", QString::fromUtf8(index.c_str())); + if (!q.exec()) { + auto err = q.lastError(); + throw SqlException(err); + } + } + + /// Return all indexes in the table. + virtual std::set kv_get_all() const override + { + QSqlQuery q(m_db); + q.prepare("SELECT psk_name FROM " + m_tableName); + + std::set result; + if (q.exec()) { + while (q.next()) { + result.insert(q.value(0).toString().toUtf8().data()); + } + } + else { + auto err = q.lastError(); + throw SqlException(err); + } + return result; + } + private: + QSqlDatabase &m_db; + QString m_tableName; + }; + + +} Botan::secure_vector PasswordManager::KeyStrengthener::derive(const std::string &passphrase) @@ -27,69 +133,129 @@ Botan::secure_vector PasswordManager::KeyStrengthener::derive(const std return master_key; } -void PasswordManager::KeyStrengthener::saveParams(std::shared_ptr db, const std::string &table_name) +void PasswordManager::KeyStrengthener::saveParams(QSqlDatabase &db, const QString &table_name) { auto sc = dynamic_cast(m_hasher.get()); size_t i1 = sc->N(); size_t i2 = sc->r(); size_t i3 = sc->p(); + + auto salt_str = QString::fromUtf8(Botan::base64_encode(m_salt).c_str()); // SAVE parameters in database - auto stmt = db->new_statement("INSERT INTO " + table_name + "(id, algo, i1, i2, i3, ks, salt) VALUES(?1, ?2, ?3, ?4, ?5)"); - stmt->bind(1, 1); - stmt->bind(2, "Scrypt"); - stmt->bind(3, i1); - stmt->bind(4, i2); - stmt->bind(5, i3); - stmt->bind(6, m_keySize); - stmt->bind(7, Botan::base64_encode(m_salt)); - stmt->spin(); + QSqlQuery insert_statement(db); + insert_statement.prepare("INSERT OR REPLACE INTO " + table_name + "(id, algo, i1, i2, i3, ks, salt) " + + "VALUES(:id, :algo, :i1, :i2, :i3, :ks, :salt)"); + insert_statement.bindValue(":id", 1); + insert_statement.bindValue(":algo", "Scrypt"); + insert_statement.bindValue(":i1", i1); + insert_statement.bindValue(":i2", i2); + insert_statement.bindValue(":i3", i3); + insert_statement.bindValue(":ks", m_keySize); + insert_statement.bindValue(":salt", salt_str); + if (!insert_statement.exec()) { + //throw std::runtime_error("PasswordManager::KeyStrengthener::saveParams failed"); + auto err = insert_statement.lastError(); + throw SqlException(err); + + } } // ------------------------- -void PasswordManager::openDatabase(std::shared_ptr db, std::string passphrase) +PasswordManager::PasswordManager() = default; +PasswordManager::~PasswordManager() = default; + + +bool PasswordManager::initialized(QSqlDatabase& db) { -// std::string psk_db_file_name; -// auto db = std::make_shared(psk_db_file_name); - - KeyStrengthener ks; - // if (database exists) - if (isPskStoreInitialized(db)) { - ks = getKeyStrengthener(db); - } - else { - initializeNewPskStore(db); - ks = createKeyStrengthener(); - ks.saveParams(db, m_secretAlgoTableName); - } - - Botan::secure_vector master_key = ks.derive(passphrase); - m_pskDatabase = std::make_unique(master_key, db, m_passwordTableName); + return isPskStoreInitialized(db); } +bool PasswordManager::createDatabase(QSqlDatabase &db, QString passphrase) +{ + if (!isPskStoreInitialized(db)) { + initializeNewPskStore(db); + auto ks = createKeyStrengthener(); + ks.saveParams(db, m_secretAlgoTableName); + + auto master_key = ks.derive(passphrase.toUtf8().data()); + + std::unique_ptr hash3(Botan::HashFunction::create("SHA-3")); + hash3->update(master_key); + auto mkh = QString::fromUtf8(Botan::base64_encode(hash3->final()).c_str()); + + QSqlQuery q_ins_hash(db); + q_ins_hash.prepare("INSERT INTO " + m_secretHashTableName + "(id, hash) VALUES(:id, :hash)"); + q_ins_hash.bindValue(":id", 1); + q_ins_hash.bindValue(":hash", mkh); + if (!q_ins_hash.exec()) { + auto err = q_ins_hash.lastError(); + qDebug() << err.text(); + throw SqlException(err); + } + + m_pskDatabase = std::make_unique(master_key, db, m_passwordTableName); + return true; + } + return false; +} + +bool PasswordManager::openDatabase(QSqlDatabase &db, QString passphrase) +{ + if (isPskStoreInitialized(db)) { + auto ks = getKeyStrengthener(db); + + auto master_key = ks.derive(passphrase.toUtf8().data()); + std::unique_ptr hash3(Botan::HashFunction::create("SHA-3")); + hash3->update(master_key); + auto mkh_bin = hash3->final(); + + QSqlQuery q("SELECT hash FROM " + m_secretHashTableName + " WHERE id=1", db); + if (q.next()) { + auto hash_b64 = q.value(0).toString().toUtf8(); + auto hash_bin = Botan::base64_decode(hash_b64.data(), static_cast(hash_b64.size())); + if (hash_bin == mkh_bin) { + m_pskDatabase = std::make_unique(master_key, db, m_passwordTableName); + return true; + } + } + } + return false; +} void PasswordManager::closeDatabase() { m_pskDatabase.reset(); } +bool PasswordManager::locked() const +{ + return m_pskDatabase == nullptr; +} void PasswordManager::set(const std::string &id, const std::string &passwd) { if (m_pskDatabase) { - + m_pskDatabase->set_str(id, passwd); } else { throw PasswordManagerLockedException(); } } -std::string PasswordManager::get(const std::string &id, const std::string &passwd) +bool PasswordManager::get(const std::string &id, std::string &password) { if (m_pskDatabase) { - + try { + password = m_pskDatabase->get_str(id); + return true; + } + catch (const Botan::Invalid_Argument &) { + // not present + return false; + } } else { throw PasswordManagerLockedException(); @@ -99,7 +265,7 @@ std::string PasswordManager::get(const std::string &id, const std::string &passw void PasswordManager::remove(const std::string &id) { if (m_pskDatabase) { - + m_pskDatabase->remove(id); } else { throw PasswordManagerLockedException(); @@ -107,57 +273,100 @@ void PasswordManager::remove(const std::string &id) } -void PasswordManager::initializeNewPskStore(std::shared_ptr db) +void PasswordManager::initializeNewPskStore(QSqlDatabase &db) { - // Create tables - // - psk_masterkey_algo - // - psk_passwd - std::string create_statement = +// // Create tables +// // - psk_masterkey_algo +// // - psk_passwd + { + QSqlQuery create_tbl(db); + create_tbl.prepare( "CREATE TABLE IF NOT EXISTS " + m_secretAlgoTableName + "( \n" - " id INTEGER PRIMARY KEY \n" - " algo TEXT \n" - " i1 INTEGER \n" - " i2 INTEGER \n" - " i3 INTEGER \n" - " ks INTEGER \n" + " id INTEGER PRIMARY KEY, \n" + " algo TEXT, \n" + " i1 INTEGER, \n" + " i2 INTEGER, \n" + " i3 INTEGER, \n" + " ks INTEGER, \n" " salt TEXT \n" - ");"; - db->create_table(create_statement); + ");"); + if (!create_tbl.exec()) { +// auto sql_error = create_tbl.lastError(); +// throw std::runtime_error("create table failed"); + auto err = create_tbl.lastError(); + throw SqlException(err); + } + } +// db->create_table( + QSqlQuery create_tbl(db); + create_tbl.prepare( + "CREATE TABLE IF NOT EXISTS " + m_secretHashTableName + "( \n" + " id INTEGER PRIMARY KEY, \n" + " hash TEXT \n" + ");"); + if (!create_tbl.exec()) { + auto err = create_tbl.lastError(); + throw SqlException(err); + } } -bool PasswordManager::isPskStoreInitialized(std::shared_ptr db) +bool PasswordManager::isPskStoreInitialized(QSqlDatabase& db) { // Is the table with the secret data present and filled? - auto stmt = db->new_statement("SELECT name FROM sqlite_master WHERE type='table' AND name=?1"); - stmt->bind(1, m_secretAlgoTableName); - bool ok = stmt->step(); - if (ok) { - auto stmt = db->new_statement("SELECT algo FROM " + m_secretAlgoTableName + " WHERE id=1"); - return stmt->step(); + QSqlQuery query(db); + query.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=:name"); + query.bindValue(":name", m_secretAlgoTableName); + if (!query.exec()) { + auto err = query.lastError(); + throw SqlException(err); } - return false; + if (!query.next()) { + return false; + } + + query.bindValue(":name", m_secretHashTableName); + if (!query.exec()) { + auto err = query.lastError(); + throw SqlException(err); + } + if (!query.next()) { + return false; + } + + QSqlQuery sel_algo("SELECT algo FROM " + m_secretAlgoTableName + " WHERE id=1", db); + if (!sel_algo.next()) { + return false; + } + + QSqlQuery sel_hash("SELECT hash FROM " + m_secretHashTableName + " WHERE id=1", db); + if (!sel_hash.next()) { + return false; + } + + return true; } -PasswordManager::KeyStrengthener PasswordManager::getKeyStrengthener(std::shared_ptr db) +PasswordManager::KeyStrengthener PasswordManager::getKeyStrengthener(QSqlDatabase &db) { - auto stmt = db->new_statement("SELECT algo, i1, i2, i3, ks, salt FROM " + m_secretAlgoTableName + " WHERE id=1"); - if (stmt->step()) { - std::string algo = stmt->get_str(0); - size_t i1 = boost::lexical_cast(stmt->get_str(1)); - size_t i2 = boost::lexical_cast(stmt->get_str(2)); - size_t i3 = boost::lexical_cast(stmt->get_str(3)); - size_t ks = boost::lexical_cast(stmt->get_str(4)); + QSqlQuery query("SELECT algo, i1, i2, i3, ks, salt FROM " + m_secretAlgoTableName + " WHERE id=1", db); + if (query.next()) { + std::string algo = query.value(0).toString().toUtf8().data(); + size_t i1 = query.value(1).toUInt(); + size_t i2 = query.value(2).toUInt(); + size_t i3 = query.value(3).toUInt(); + size_t ks = query.value(4).toUInt(); + auto salt = query.value(5).toString().toUtf8(); auto pwh_fam = Botan::PasswordHashFamily::create(algo); return KeyStrengthener( pwh_fam->from_params(i1, i2, i3), - Botan::base64_decode(stmt->get_str(5)), + Botan::base64_decode(salt.data(), static_cast(salt.size())), ks ); } else { - + throw std::runtime_error("fail"); } } diff --git a/core/PasswordManager.h b/core/PasswordManager.h index e014ebc..fc918e6 100644 --- a/core/PasswordManager.h +++ b/core/PasswordManager.h @@ -2,6 +2,7 @@ #define PASSWORDMANAGER_H #include "Expected.h" +#include #include #include #include @@ -16,8 +17,8 @@ namespace Botan { - class Encrypted_PSK_Database_SQL; - class Sqlite3_Database; + class Encrypted_PSK_Database; + //class Sqlite3_Database; class PasswordHash; } @@ -41,21 +42,31 @@ public: Error }; - PasswordManager() = default; + PasswordManager(); + ~PasswordManager(); - void openDatabase(std::shared_ptr db, std::string passphrase); + /** Check if it has been initialized before. + * + * If returns false then use createDatabase to set it up + * else use openDatabase to get access. + */ + bool initialized(QSqlDatabase &db); + bool createDatabase(QSqlDatabase &db, QString passphrase); + bool openDatabase(QSqlDatabase &db, QString passphrase); void closeDatabase(); + bool locked() const; void set(const std::string &id, const std::string &passwd); - std::string get(const std::string &id, const std::string &passwd); + bool get(const std::string &id, std::string &password); void remove(const std::string &id); private: - std::string m_passwordTableName = "psk_passwd"; - std::string m_secretAlgoTableName = "psk_masterkey_algo"; - std::unique_ptr m_pskDatabase; + QString m_passwordTableName = "psk_passwd"; + QString m_secretAlgoTableName = "psk_masterkey_algo"; + QString m_secretHashTableName = "psk_masterkey_hash"; + std::unique_ptr m_pskDatabase; - bool isPskStoreInitialized(std::shared_ptr db); - void initializeNewPskStore(std::shared_ptr db); + bool isPskStoreInitialized(QSqlDatabase& db); + void initializeNewPskStore(QSqlDatabase &db); class KeyStrengthener { public: @@ -74,6 +85,7 @@ private: , m_salt (std::move(rhs.m_salt)) , m_keySize(rhs.m_keySize) {} + KeyStrengthener& operator=(KeyStrengthener &&rhs) { if (&rhs != this) { @@ -85,7 +97,7 @@ private: } Botan::secure_vector derive(const std::string &passphrase); - void saveParams(std::shared_ptr db, const std::string &table_name); + void saveParams(QSqlDatabase &db, const QString &table_name); private: std::unique_ptr m_hasher; Botan::secure_vector m_salt; @@ -93,7 +105,7 @@ private: }; /// Get PasswordHash from parameters in database - KeyStrengthener getKeyStrengthener(std::shared_ptr db); + KeyStrengthener getKeyStrengthener(QSqlDatabase &db); KeyStrengthener createKeyStrengthener(); }; diff --git a/core/core.pro b/core/core.pro index f603227..c1ea4ac 100644 --- a/core/core.pro +++ b/core/core.pro @@ -5,6 +5,7 @@ #------------------------------------------------- QT -= gui +QT += sql TARGET = core TEMPLATE = lib diff --git a/pglab/ConnectionList.cpp b/pglab/ConnectionList.cpp index 8476b6f..9187697 100644 --- a/pglab/ConnectionList.cpp +++ b/pglab/ConnectionList.cpp @@ -27,6 +27,7 @@ namespace { settings.setValue("sslkey", stdStrToQ(cc.sslKey())); settings.setValue("sslrootcert", stdStrToQ(cc.sslRootCert())); settings.setValue("sslcrl", stdStrToQ(cc.sslCrl())); + settings.setValue("passwordState", static_cast(cc.passwordState())); } template @@ -56,6 +57,12 @@ namespace { 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(v.toInt()); + cc.setPasswordState(pwstate); } diff --git a/pglab/ConnectionList.h b/pglab/ConnectionList.h index 5916fa9..38e974e 100644 --- a/pglab/ConnectionList.h +++ b/pglab/ConnectionList.h @@ -19,6 +19,11 @@ public: 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); diff --git a/pglab/ConnectionListModel.cpp b/pglab/ConnectionListModel.cpp index f1f3649..4449609 100644 --- a/pglab/ConnectionListModel.cpp +++ b/pglab/ConnectionListModel.cpp @@ -140,9 +140,9 @@ void ConnectionListModel::newItem() emit dataChanged(idx, idx); } -Expected ConnectionListModel::get(int row) +Expected ConnectionListModel::get(size_t row) { - if (row >= 0 && row < (int)m_connections->size()) { + if (row < m_connections->size()) { return m_connections->getConfigByIdx(row); } else { @@ -204,5 +204,6 @@ void ConnectionListModel::save(size_t index) void ConnectionListModel::save(size_t index, const ConnectionConfig &cc) { - + m_connections->setConfigByIdx(index, cc); + m_connections->save(index); } diff --git a/pglab/ConnectionListModel.h b/pglab/ConnectionListModel.h index e9cde8d..fccce79 100644 --- a/pglab/ConnectionListModel.h +++ b/pglab/ConnectionListModel.h @@ -20,7 +20,7 @@ class ConnectionListModel : public QAbstractListModel { public: ConnectionListModel(ConnectionList *conns, QObject *parent); ConnectionListModel(const ConnectionListModel&) = delete; - ~ConnectionListModel(); + ~ConnectionListModel() override; virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; virtual int columnCount(const QModelIndex &/*parent*/) const override; @@ -30,7 +30,7 @@ public: virtual Qt::ItemFlags flags(const QModelIndex &index) const override; void newItem(); - Expected get(int row); + Expected get(size_t row); virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; void save(); diff --git a/pglab/ConnectionManagerWindow.h b/pglab/ConnectionManagerWindow.h index 0821f8e..0bdf9ad 100644 --- a/pglab/ConnectionManagerWindow.h +++ b/pglab/ConnectionManagerWindow.h @@ -2,7 +2,7 @@ #define CONNECTIONMANAGERWINDOW_H #include -#include +#include namespace Ui { class ConnectionManagerWindow; @@ -19,7 +19,7 @@ class QStandardItemModel; class ConnectionManagerWindow : public QMainWindow { Q_OBJECT public: - explicit ConnectionManagerWindow(MasterController *master, QWidget *parent = 0); + explicit ConnectionManagerWindow(MasterController *master, QWidget *parent = nullptr); ~ConnectionManagerWindow(); private slots: @@ -36,7 +36,7 @@ private: QDataWidgetMapper *m_mapper = nullptr; MasterController *m_masterController; - boost::optional prevSelection; + std::optional prevSelection; void setupWidgetMappings(); }; diff --git a/pglab/CrudModel.cpp b/pglab/CrudModel.cpp index 091bf9a..9d4499c 100644 --- a/pglab/CrudModel.cpp +++ b/pglab/CrudModel.cpp @@ -98,7 +98,7 @@ CrudModel::Value CrudModel::getData(const QModelIndex &index) const //Oid o = m_roData->type(col); // First see if we have buffered editted values that still need saving - boost::optional val; + std::optional val; if (row_mapping.pending) { val = m_pendingRowList.getValue(col, row_mapping.rowKey); } diff --git a/pglab/CrudModel.h b/pglab/CrudModel.h index 277b49d..8a3f466 100644 --- a/pglab/CrudModel.h +++ b/pglab/CrudModel.h @@ -12,7 +12,7 @@ #include #include #include -#include +#include class PgConstraint; class OpenDatabase; @@ -121,7 +121,7 @@ private: // std::shared_ptr resultToRowList(std::shared_ptr result); - using Value = boost::optional; + using Value = std::optional; /** Used to remember the changes that have been made to rows. * @@ -194,7 +194,7 @@ private: iter->second.setValue(col, value); } - boost::optional getValue(int col, int row) const + std::optional getValue(int col, int row) const { auto iter = m_rows.find(row); if (iter != m_rows.end()) { @@ -204,7 +204,7 @@ private: return cell->second; } - return boost::none; + return std::nullopt; } auto begin() { return m_rows.begin(); } @@ -233,7 +233,7 @@ private: ASyncWindow * m_asyncWindow; std::shared_ptr m_database; PgClass m_table; - boost::optional m_primaryKey; + std::optional m_primaryKey; ASyncDBConnection m_dbConn; bool callLoadData = false; diff --git a/pglab/MasterController.cpp b/pglab/MasterController.cpp index 79ab6dd..4dc03a2 100644 --- a/pglab/MasterController.cpp +++ b/pglab/MasterController.cpp @@ -2,11 +2,30 @@ #include "ConnectionManagerWindow.h" #include "ConnectionList.h" #include "ConnectionListModel.h" +#include "PasswordManager.h" #include "MainWindow.h" #include "ServerWindow.h" #include "BackupDialog.h" #include "PasswordPromptDialog.h" +#include +#include +#include +#include +namespace { + + QString GetUserConfigDatabaseName() + { + QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + QDir dir(path); + if (!dir.exists()) { + dir.mkpath("."); + } + path += "/pglabuser.db"; + return path; + } + +} MasterController::MasterController(QObject *parent) : QObject(parent) {} @@ -20,6 +39,21 @@ MasterController::~MasterController() void MasterController::init() { + //std::string dbfilename = QDir::toNativeSeparators(GetUserConfigDatabaseName()).toUtf8().data(); + //m_userConfigDatabase = std::make_shared(dbfilename); + + m_userConfigDatabase = QSqlDatabase::addDatabase("QSQLITE"); + m_userConfigDatabase.setDatabaseName(GetUserConfigDatabaseName()); + + if (!m_userConfigDatabase.open()) { + qDebug() << "Error: connection with database fail"; + } + else { + qDebug() << "Database: connection ok"; + } + + m_passwordManager = std::make_shared(); + m_connectionList = new ConnectionList; m_connectionList->load(); m_connectionListModel = new ConnectionListModel(m_connectionList, this); @@ -41,8 +75,8 @@ void MasterController::openSqlWindowForConnection(size_t connection_index) if (res.valid()) { auto cc = res.get(); - m_connectionListModel->save(connection_index, cc); 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 @@ -55,55 +89,65 @@ void MasterController::openSqlWindowForConnection(size_t connection_index) } -void MasterController::openBackupDlgForConnection(int connection_index) +void MasterController::openBackupDlgForConnection(size_t connection_index) { auto res = m_connectionListModel->get(connection_index); if (res.valid()) { auto cc = res.get(); - 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(); + 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 MasterController::openServerWindowForConnection(int connection_index) +void MasterController::openServerWindowForConnection(size_t connection_index) { auto res = m_connectionListModel->get(connection_index); if (res.valid()) { auto cc = res.get(); - retrieveConnectionPassword(cc); - m_connectionListModel->save(connection_index, cc); - - auto w = new ServerWindow(this, nullptr); - w->setAttribute( Qt::WA_DeleteOnClose ); - w->setConfig(cc); - w->show(); + if (retrieveConnectionPassword(cc)) { + m_connectionListModel->save(connection_index, cc); + auto w = new ServerWindow(this, nullptr); + w->setAttribute( Qt::WA_DeleteOnClose ); + w->setConfig(cc); + w->show(); + } } } bool MasterController::retrieveConnectionPassword(ConnectionConfig &cc) { - // Look at config - // - is password required, how do we know? - // - IF is password stored in pskdb - // - ask pskdb for password - // - ELSE - // - ask user for password + 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(nullptr); - dlg->setConnectionDescription(str); + auto dlg = std::make_unique(PasswordPromptDialog::SaveOption, nullptr); + dlg->setDescription(QString(tr("Please provide password for connection %1")).arg(str)); int exec_result = dlg->exec(); if (exec_result == QDialog::Accepted) { - cc.setPassword(dlg->password().toUtf8().data()); - // - IF user checked remember password - // - ask pskdb to store password - + 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; @@ -112,25 +156,71 @@ bool MasterController::retrieveConnectionPassword(ConnectionConfig &cc) bool MasterController::getPasswordFromPskdb(const std::string &password_id, std::string &password) { -// func: getPasswordFromPskdb -// IF pskdb locked -// prompt user for pskdb passphrase -// unlock pskdb -// get pwd - return false; + if (!UnlockPasswordManagerIfNeeded()) + return false; + + return m_passwordManager->get(password_id, password); } bool MasterController::storePasswordInPskdb(const std::string &password_id, const std::string password) { -// func: storePasswordInPskdb -// IF pskdb not setup -// notify user and ask for passphrase -// init pskdb -// ELSE -// IF pskdb locked -// ask for passphrase -// unlock -// store pwd + if (!UnlockPasswordManagerIfNeeded()) + return false; + + m_passwordManager->set(password_id, password); + return true; +} + +bool MasterController::UnlockPasswordManagerIfNeeded() +{ + if (m_passwordManager->initialized(m_userConfigDatabase)) { + if (!m_passwordManager->locked()) + return true; + + while (true) { + // ask user for passphrase + auto dlg = std::make_unique(nullptr, nullptr); + dlg->setDescription(tr("Enter passphrase for password manager")); + int exec_result = dlg->exec(); + bool ok = (exec_result == QDialog::Accepted); + + // IF user gave OK + if (ok) { + if (m_passwordManager->openDatabase(m_userConfigDatabase, dlg->password())) { + return true; + } + } + else { + return false; + } + } + } + else { + // Ask user for passphrase + confirmation, clearly instruct this is first setup + // create + auto dlg = std::make_unique(PasswordPromptDialog::ConfirmPassword, nullptr); + dlg->setDescription(tr("Enter passphrase for password manager initialization")); + int exec_result = dlg->exec(); + if (exec_result == QDialog::Accepted) { + QString passphrase = dlg->password(); + if (m_passwordManager->createDatabase(m_userConfigDatabase, passphrase)) { + return true; + } + } + } return false; } + +std::string MasterController::getPskId(const ConnectionConfig &cc) +{ + std::string id = "dbpw/"; + id += cc.uuid().toString().toUtf8().data(); + return id; +} + +//std::shared_ptr MasterController::getUserConfigDatabase() +//{ +// return m_userConfigDatabase; +//} + diff --git a/pglab/MasterController.h b/pglab/MasterController.h index 4a10c9a..3034203 100644 --- a/pglab/MasterController.h +++ b/pglab/MasterController.h @@ -2,14 +2,21 @@ #define MASTERCONTROLLER_H #include +#include +#include #include #include +#include +//namespace Botan { +// class Sqlite3_Database; +//} class ConnectionConfig; class ConnectionList; class ConnectionListModel; class ConnectionManagerWindow; +class PasswordManager; /** \brief Controller class responsible for all things global. */ @@ -30,8 +37,10 @@ public: void showConnectionManager(); void openSqlWindowForConnection(size_t connection_index); - void openServerWindowForConnection(int connection_index); - void openBackupDlgForConnection(int connection_index); + void openServerWindowForConnection(size_t connection_index); + void openBackupDlgForConnection(size_t connection_index); + +// std::shared_ptr getUserConfigDatabase(); signals: @@ -41,6 +50,12 @@ private: ConnectionList *m_connectionList = nullptr; ConnectionListModel *m_connectionListModel = nullptr; ConnectionManagerWindow *m_connectionManagerWindow = nullptr; + //std::shared_ptr m_userConfigDatabase; + QSqlDatabase m_userConfigDatabase; + /** Using long lived object so it can remember its master password for sometime + * if the user wishes it. + */ + std::shared_ptr m_passwordManager; /** Retrieves the connection password from the user (directly or through the psk db) * @@ -50,6 +65,10 @@ private: 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 diff --git a/pglab/PassPhraseForm.cpp b/pglab/PassPhraseForm.cpp deleted file mode 100644 index 36ff2fa..0000000 --- a/pglab/PassPhraseForm.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "PassPhraseForm.h" -#include "ui_PassPhraseForm.h" - -PassPhraseForm::PassPhraseForm(QWidget *parent) : - QWidget(parent), - ui(new Ui::PassPhraseForm) -{ - ui->setupUi(this); -} - -PassPhraseForm::~PassPhraseForm() -{ - delete ui; -} - -/* - -Password strength calculation: -seperate characters in couple of groups - -For the use of characters from each group a certain value is added to the base value -which is meant to signify the size of the set of characters the password is based on. - -Some analysis of relative positions might be required! Predictable placement of special charachters and uppercase/lowercase or numbers -should be penalized. - -These calculations should result in a search space size per character - -the base to the power of the length of the password gives the resulting strength -from this result we take the 10 log to get the magnitude of the value. - -a-z 1:3 2:7 3:13 4:26 -A-Z 1:3 2:7 3:13 4:26 -0-9 1:4 2:10 -`~!@#$%^&*()_-=+[{]};:'",<.>/?\| 1:4 2:8 3:16 4:32 -space +1 - -Straf punten -alleen speciaal karakter aan eind van string -8 -alleen hoofdletter aan begin van wachtwoord -6 - -la-~ZDv4E-O*y]C -bYGWlDyeKKbcZBjoWX - -*/ diff --git a/pglab/PassPhraseForm.h b/pglab/PassPhraseForm.h deleted file mode 100644 index c0c0f8d..0000000 --- a/pglab/PassPhraseForm.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef PASSPHRASEFORM_H -#define PASSPHRASEFORM_H - -#include - -namespace Ui { -class PassPhraseForm; -} - -class PassPhraseForm : public QWidget -{ - Q_OBJECT - -public: - explicit PassPhraseForm(QWidget *parent = nullptr); - ~PassPhraseForm(); - -private: - Ui::PassPhraseForm *ui; -}; - -#endif // PASSPHRASEFORM_H diff --git a/pglab/PassPhraseForm.ui b/pglab/PassPhraseForm.ui deleted file mode 100644 index 89bd22f..0000000 --- a/pglab/PassPhraseForm.ui +++ /dev/null @@ -1,75 +0,0 @@ - - - PassPhraseForm - - - - 0 - 0 - 397 - 228 - - - - Form - - - false - - - - - - 20 - - - 20 - - - - - Enter passphrase: - - - - - - - 32767 - - - QLineEdit::NoEcho - - - - - - - Repeat passphrase: - - - - - - - QLineEdit::Password - - - - - - - 67 - - - - - - - - - - - - - diff --git a/pglab/PasswordPromptDialog.cpp b/pglab/PasswordPromptDialog.cpp index 0b11759..e089a6c 100644 --- a/pglab/PasswordPromptDialog.cpp +++ b/pglab/PasswordPromptDialog.cpp @@ -5,66 +5,96 @@ #include #include #include +#include -PasswordPromptDialog::PasswordPromptDialog(QWidget *parent) +Q_DECLARE_OPERATORS_FOR_FLAGS(PasswordPromptDialog::Flags) + +PasswordPromptDialog::PasswordPromptDialog(Flags flags, QWidget *parent) : QDialog(parent) + , m_Flags(flags) { m_connectionLabel = new QLabel(this); - auto dialog_buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this); - - m_passwordLabel = new QLabel(this); - m_passwordInput = new QLineEdit(this); - m_passwordInput->setEchoMode(QLineEdit::Password); - - m_saveCheck = new QCheckBox(this); + m_DialogButtons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this); + const size_t inputFieldCount = flags.testFlag(ConfirmPassword) ? 2 : 1; auto mainLayout = new QGridLayout; int row = 0; mainLayout->addWidget(m_connectionLabel, row, 0, 1, 2); ++row; - mainLayout->addWidget(m_passwordLabel, row, 0); - mainLayout->addWidget(m_passwordInput, row, 1); - ++row; - mainLayout->addWidget(m_saveCheck, row, 1); - ++row; - mainLayout->addWidget(dialog_buttons, row, 0, 1 ,2); + for (size_t idx = 0; idx < inputFieldCount; ++idx) { + auto lbl = new QLabel(this); + auto input = new QLineEdit(this); + input->setEchoMode(QLineEdit::Password); + mainLayout->addWidget(lbl, row, 0); + mainLayout->addWidget(input, row, 1); + m_passwordLabel[idx] = lbl; + m_passwordInput[idx] = input; + ++row; + } + if (m_Flags.testFlag(SaveOption)) { + m_saveCheck = new QCheckBox(this); + mainLayout->addWidget(m_saveCheck, row, 1); + ++row; + } + mainLayout->addWidget(m_DialogButtons, row, 0, 1 ,2); setLayout(mainLayout); - m_passwordInput->setFocus(); + m_passwordInput[0]->setFocus(); retranslateUi(); // QMetaObject::connectSlotsByName(BackupDialog); - connect(dialog_buttons, &QDialogButtonBox::accepted, this, &PasswordPromptDialog::accept); - connect(dialog_buttons, &QDialogButtonBox::rejected, this, &PasswordPromptDialog::reject); + connect(m_DialogButtons, &QDialogButtonBox::accepted, this, &PasswordPromptDialog::accept); + connect(m_DialogButtons, &QDialogButtonBox::rejected, this, &PasswordPromptDialog::reject); + connect(m_passwordInput[0], &QLineEdit::textChanged, this, &PasswordPromptDialog::passwordChanged); + if (m_passwordInput[1]) + connect(m_passwordInput[1], &QLineEdit::textChanged, this, &PasswordPromptDialog::passwordChanged); } void PasswordPromptDialog::retranslateUi() { const char * context = "PasswordPromptDialog"; setWindowTitle(QApplication::translate(context, "Connection password", nullptr)); - m_passwordLabel->setText(QApplication::translate(context, "Password", nullptr)); - m_passwordInput->setPlaceholderText(QApplication::translate(context, "Enter password", nullptr)); - m_saveCheck->setText(QApplication::translate(context, "Save password", nullptr)); + m_passwordLabel[0]->setText(QApplication::translate(context, "Password", nullptr)); + m_passwordInput[0]->setPlaceholderText(QApplication::translate(context, "Enter password", nullptr)); + if (m_passwordLabel[1]) + m_passwordLabel[1]->setText(QApplication::translate(context, "Confirm password", nullptr)); + if (m_passwordInput[1]) + m_passwordInput[1]->setPlaceholderText(QApplication::translate(context, "Reenter same password for confirmation", nullptr)); + if (m_saveCheck) + m_saveCheck->setText(QApplication::translate(context, "Save password", nullptr)); } -void PasswordPromptDialog::setConnectionDescription(const QString &description) +void PasswordPromptDialog::updateOkEnabled() { - m_connectionLabel->setText(QString(tr("Please provide password for connection %1")).arg(description)); + bool enabled = true; + if (m_passwordInput[1]) + enabled = m_passwordInput[0]->text() == m_passwordInput[1]->text(); + + auto btn = m_DialogButtons->button(QDialogButtonBox::Ok); + btn->setEnabled(enabled); +} + +void PasswordPromptDialog::passwordChanged(const QString &) +{ + updateOkEnabled(); +} + +void PasswordPromptDialog::setDescription(const QString &description) +{ + m_connectionLabel->setText(QString("%1").arg(description)); } QString PasswordPromptDialog::password() const { - return m_passwordInput->text(); + return m_passwordInput[0]->text(); } -//void PasswordPromptDialog::accept() -//{ +bool PasswordPromptDialog::saveChecked() const +{ + if (m_saveCheck) + return m_saveCheck->checkState() == Qt::Checked; -//} - -//void PasswordPromptDialog::reject() -//{ - -//} + return false; +} diff --git a/pglab/PasswordPromptDialog.h b/pglab/PasswordPromptDialog.h index f394a9e..a1c31f5 100644 --- a/pglab/PasswordPromptDialog.h +++ b/pglab/PasswordPromptDialog.h @@ -4,6 +4,7 @@ #include class QCheckBox; +class QDialogButtonBox; class QLabel; class QLineEdit; @@ -11,22 +12,33 @@ class PasswordPromptDialog : public QDialog { Q_OBJECT public: - explicit PasswordPromptDialog(QWidget *parent = nullptr); + enum Flag { + ConfirmPassword = 1, + SaveOption = 2 + }; - void setConnectionDescription(const QString &description); + Q_DECLARE_FLAGS(Flags, Flag) + //Q_FLAG(Flags) + + explicit PasswordPromptDialog(Flags flags, QWidget *parent = nullptr); + + void setDescription(const QString &description); QString password() const; + bool saveChecked() const; private: + Flags m_Flags; QLabel *m_connectionLabel = nullptr; - QLabel *m_passwordLabel = nullptr; - QLineEdit *m_passwordInput = nullptr; + QLabel *m_passwordLabel[2] = { nullptr, nullptr }; + QLineEdit *m_passwordInput[2] = { nullptr, nullptr }; QCheckBox *m_saveCheck = nullptr; + QDialogButtonBox *m_DialogButtons = nullptr; void retranslateUi(); + void updateOkEnabled(); private slots: -// void accept(); -// void reject(); + void passwordChanged(const QString &text); }; #endif // PASSWORDPROMPTDIALOG_H diff --git a/pglab/pglab.pro b/pglab/pglab.pro index 605fece..08070e0 100644 --- a/pglab/pglab.pro +++ b/pglab/pglab.pro @@ -84,7 +84,6 @@ PropertyProxyModel.cpp \ SqlCodePreview.cpp \ CustomFilterSortModel.cpp \ PropertiesPage.cpp \ - PassPhraseForm.cpp \ PasswordPromptDialog.cpp HEADERS += \ @@ -140,7 +139,6 @@ CustomDataRole.h \ SqlCodePreview.h \ CustomFilterSortModel.h \ PropertiesPage.h \ - PassPhraseForm.h \ PasswordPromptDialog.h FORMS += mainwindow.ui \ @@ -155,8 +153,7 @@ FORMS += mainwindow.ui \ NamespaceFilterWidget.ui \ ApplicationWindow.ui \ CrudTab.ui \ - CodeGenerator.ui \ - PassPhraseForm.ui + CodeGenerator.ui RESOURCES += \ resources.qrc diff --git a/pglablib/ConnectionConfig.cpp b/pglablib/ConnectionConfig.cpp index f6c21e8..1dd47b7 100644 --- a/pglablib/ConnectionConfig.cpp +++ b/pglablib/ConnectionConfig.cpp @@ -258,6 +258,19 @@ const char * const * ConnectionConfig::getValues() const return m_values.data(); } +PasswordState ConnectionConfig::passwordState() const +{ + return m_passwordState; +} + +void ConnectionConfig::setPasswordState(PasswordState password_state) +{ + if (m_passwordState != password_state) { + m_dirty = true; + m_passwordState = password_state; + } +} + bool ConnectionConfig::isSameDatabase(const ConnectionConfig &rhs) const { return m_host == rhs.m_host diff --git a/pglablib/ConnectionConfig.h b/pglablib/ConnectionConfig.h index ce9191e..1652bcf 100644 --- a/pglablib/ConnectionConfig.h +++ b/pglablib/ConnectionConfig.h @@ -15,10 +15,10 @@ enum class SslMode { verify_full=5 }; -enum class PasswordMode { - Unsave, - Encrypted, - DontSave +enum class PasswordState { + NotNeeded, ///< the Connection doesn't require a password + NotStored, ///< password needed but we do not know it + SavedPasswordManager, ///< Saved in the password manager }; class QProcessEnvironment; @@ -70,6 +70,9 @@ public: const char * const * getKeywords() const; const char * const * getValues() const; + PasswordState passwordState() const; + void setPasswordState(PasswordState password_state); + bool isSameDatabase(const ConnectionConfig &rhs) const; void writeToEnvironment(QProcessEnvironment &env) const; @@ -84,7 +87,7 @@ private: std::string m_port = "5432"; std::string m_user; - std::string m_password; + std::string m_password; ///< TODO do we want to keep this here or should we remember it seperatly? std::string m_dbname; std::string m_sslMode; @@ -94,9 +97,11 @@ private: std::string m_sslCrl; std::string m_applicationName; + PasswordState m_passwordState = PasswordState::NotStored; bool m_dirty = false; + static void strToEnv(QProcessEnvironment &env, const QString &var, const std::string &val); static std::vector s_keywords; diff --git a/pglablib/PgConstraintContainer.cpp b/pglablib/PgConstraintContainer.cpp index 0f2d448..ae59f4a 100644 --- a/pglablib/PgConstraintContainer.cpp +++ b/pglablib/PgConstraintContainer.cpp @@ -74,9 +74,9 @@ std::vector PgConstraintContainer::getConstraintsForRelation(Oid r return result; } -boost::optional PgConstraintContainer::getPrimaryForRelation(Oid relid) const +std::optional PgConstraintContainer::getPrimaryForRelation(Oid relid) const { - boost::optional result; + std::optional result; for (const auto &e : m_container) { if (e.relid == relid && e.type == ConstraintType::PrimaryKey) { result = e; diff --git a/pglablib/PgConstraintContainer.h b/pglablib/PgConstraintContainer.h index 5910942..2b87350 100644 --- a/pglablib/PgConstraintContainer.h +++ b/pglablib/PgConstraintContainer.h @@ -5,7 +5,7 @@ #include "PgConstraint.h" #include "Pgsql_declare.h" #include -#include +#include class PgConstraintContainer : public PgContainer { public: @@ -16,7 +16,7 @@ public: const PgConstraint* getFKeyForTableColumn(Oid relid, int16_t attnum) const; std::vector getConstraintsForRelation(Oid relid) const; - boost::optional getPrimaryForRelation(Oid relid) const; + std::optional getPrimaryForRelation(Oid relid) const; protected: virtual PgConstraint loadElem(const Pgsql::Row &row) override; }; diff --git a/pglablib/codebuilder/CodeBuilder.cpp b/pglablib/codebuilder/CodeBuilder.cpp index 763c567..ac0527f 100644 --- a/pglablib/codebuilder/CodeBuilder.cpp +++ b/pglablib/codebuilder/CodeBuilder.cpp @@ -69,7 +69,7 @@ void CodeBuilder::genStructFields(QTextStream &q, const ColumnDataList &columns) // Any way at generation time we might want to be able to specify the null handle // - exception/error return // - magic value - // - boost::optional + // - std::optional // - boolean flags // - null pointer (useful for languages where this has no cost, other cases boolean flags will be more performant) } diff --git a/pglablib/codebuilder/StringEscapeRule.h b/pglablib/codebuilder/StringEscapeRule.h index a0ea1f5..a8c9822 100644 --- a/pglablib/codebuilder/StringEscapeRule.h +++ b/pglablib/codebuilder/StringEscapeRule.h @@ -41,7 +41,7 @@ public: * field often provides enough flexibility. */ QString m_prefixWith; -// boost::optional m_numericConversion; +// std::optional m_numericConversion; }; #endif // STRINGESCAPERULE_H diff --git a/pgsql/Pgsql_Params.h b/pgsql/Pgsql_Params.h index 0bb5a69..7ecbe60 100644 --- a/pgsql/Pgsql_Params.h +++ b/pgsql/Pgsql_Params.h @@ -6,7 +6,7 @@ #include #include "Pgsql_declare.h" #include "Pgsql_oids.h" -#include +#include namespace Pgsql { @@ -37,7 +37,7 @@ namespace Pgsql { Param add(const QString &s, Oid oid=varchar_oid); Param add(const char *data, Oid oid=varchar_oid); - Param add(boost::optional s, Oid oid=varchar_oid) + Param add(std::optional s, Oid oid=varchar_oid) { return add(s ? s->c_str() : nullptr, oid); } diff --git a/pgsql/Pgsql_Value.h b/pgsql/Pgsql_Value.h index 21e6211..c2ad771 100644 --- a/pgsql/Pgsql_Value.h +++ b/pgsql/Pgsql_Value.h @@ -8,7 +8,7 @@ #include #include #include -#include +#include namespace Pgsql { @@ -154,10 +154,10 @@ namespace Pgsql { }; template - void operator<<(boost::optional &s, const Value &v) + void operator<<(std::optional &s, const Value &v) { if (v.null()) - s = boost::optional(); + s = std::optional(); else *s << v; } diff --git a/tests/pglabtests/tst_PasswordManager.cpp b/tests/pglabtests/tst_PasswordManager.cpp index ab3fa74..677c8bd 100644 --- a/tests/pglabtests/tst_PasswordManager.cpp +++ b/tests/pglabtests/tst_PasswordManager.cpp @@ -1,6 +1,6 @@ #include #include -#include "PasswordManager.h" +//#include "PasswordManager.h" #include "PrintTo_Qt.h" using namespace testing;