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.
This commit is contained in:
eelke 2018-11-08 21:50:49 +01:00
parent 2230a4bd61
commit e36924c087
27 changed files with 605 additions and 346 deletions

View file

@ -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<int>(cc.passwordState()));
}
template <typename S, typename T>
@ -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<PasswordState>(v.toInt());
cc.setPasswordState(pwstate);
}

View file

@ -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);

View file

@ -140,9 +140,9 @@ void ConnectionListModel::newItem()
emit dataChanged(idx, idx);
}
Expected<ConnectionConfig> ConnectionListModel::get(int row)
Expected<ConnectionConfig> 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);
}

View file

@ -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<ConnectionConfig> get(int row);
Expected<ConnectionConfig> get(size_t row);
virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
void save();

View file

@ -2,7 +2,7 @@
#define CONNECTIONMANAGERWINDOW_H
#include <QMainWindow>
#include <boost/optional.hpp>
#include <optional>
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<size_t> prevSelection;
std::optional<size_t> prevSelection;
void setupWidgetMappings();
};

View file

@ -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<Value> val;
std::optional<Value> val;
if (row_mapping.pending) {
val = m_pendingRowList.getValue(col, row_mapping.rowKey);
}

View file

@ -12,7 +12,7 @@
#include <memory>
#include <tuple>
#include <vector>
#include <boost/optional.hpp>
#include <optional>
class PgConstraint;
class OpenDatabase;
@ -121,7 +121,7 @@ private:
// std::shared_ptr<RowList> resultToRowList(std::shared_ptr<Pgsql::Result> result);
using Value = boost::optional<std::string>;
using Value = std::optional<std::string>;
/** Used to remember the changes that have been made to rows.
*
@ -194,7 +194,7 @@ private:
iter->second.setValue(col, value);
}
boost::optional<Value> getValue(int col, int row) const
std::optional<Value> 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<OpenDatabase> m_database;
PgClass m_table;
boost::optional<PgConstraint> m_primaryKey;
std::optional<PgConstraint> m_primaryKey;
ASyncDBConnection m_dbConn;
bool callLoadData = false;

View file

@ -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 <QDebug>
#include <QDir>
#include <QStandardPaths>
#include <botan/sqlite3.h>
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<Botan::Sqlite3_Database>(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<PasswordManager>();
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<PasswordPromptDialog>(nullptr);
dlg->setConnectionDescription(str);
auto dlg = std::make_unique<PasswordPromptDialog>(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<PasswordPromptDialog>(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>(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<Botan::Sqlite3_Database> MasterController::getUserConfigDatabase()
//{
// return m_userConfigDatabase;
//}

View file

@ -2,14 +2,21 @@
#define MASTERCONTROLLER_H
#include <QObject>
#include <QSqlDatabase>
#include <atomic>
#include <future>
#include <map>
#include <memory>
//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<Botan::Sqlite3_Database> getUserConfigDatabase();
signals:
@ -41,6 +50,12 @@ private:
ConnectionList *m_connectionList = nullptr;
ConnectionListModel *m_connectionListModel = nullptr;
ConnectionManagerWindow *m_connectionManagerWindow = nullptr;
//std::shared_ptr<Botan::Sqlite3_Database> 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<PasswordManager> 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

View file

@ -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
*/

View file

@ -1,22 +0,0 @@
#ifndef PASSPHRASEFORM_H
#define PASSPHRASEFORM_H
#include <QWidget>
namespace Ui {
class PassPhraseForm;
}
class PassPhraseForm : public QWidget
{
Q_OBJECT
public:
explicit PassPhraseForm(QWidget *parent = nullptr);
~PassPhraseForm();
private:
Ui::PassPhraseForm *ui;
};
#endif // PASSPHRASEFORM_H

View file

@ -1,75 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PassPhraseForm</class>
<widget class="QWidget" name="PassPhraseForm">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>397</width>
<height>228</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<property name="horizontalSpacing">
<number>20</number>
</property>
<property name="verticalSpacing">
<number>20</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Enter passphrase:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit">
<property name="maxLength">
<number>32767</number>
</property>
<property name="echoMode">
<enum>QLineEdit::NoEcho</enum>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Repeat passphrase:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lineEdit_2">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QProgressBar" name="progressBar">
<property name="value">
<number>67</number>
</property>
<property name="format">
<string/>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -5,66 +5,96 @@
#include <QLabel>
#include <QLayout>
#include <QLineEdit>
#include <QPushButton>
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;
}

View file

@ -4,6 +4,7 @@
#include <QDialog>
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

View file

@ -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