Merge branch 'safely-store-passwords' into 'master'
Safely store passwords See merge request eelke/pgLab!4
This commit is contained in:
commit
b0718258c9
46 changed files with 1234 additions and 598 deletions
|
|
@ -1,7 +1,6 @@
|
|||
#include "CsvWriter.h"
|
||||
|
||||
CsvWriter::CsvWriter()
|
||||
{}
|
||||
CsvWriter::CsvWriter() = default;
|
||||
|
||||
CsvWriter::CsvWriter(QTextStream *output)
|
||||
: m_output(output)
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ ExplainRoot::SPtr ExplainRoot::createFromJson(Json::Value &json)
|
|||
auto res = std::make_shared<ExplainRoot>();
|
||||
// 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;
|
||||
|
|
|
|||
|
|
@ -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<ExplainTreeModelItem> m_parentItem;
|
||||
|
||||
QString nodeType;
|
||||
bool parallelAware; // 9.6
|
||||
bool parallelAware = false; // 9.6
|
||||
QString strategy;
|
||||
QString joinType;
|
||||
float startupCost = 0.f;
|
||||
|
|
|
|||
|
|
@ -1,245 +1,389 @@
|
|||
#include "PasswordManager.h"
|
||||
|
||||
#include <botan/filters.h>
|
||||
#include <botan/pipe.h>
|
||||
#include <botan/sha2_64.h>
|
||||
#include <QSqlQuery>
|
||||
#include <QSqlError>
|
||||
#include <QDebug>
|
||||
#include <QVariant>
|
||||
#include <botan/hash.h>
|
||||
#include <botan/hmac.h>
|
||||
#include <botan/pbkdf2.h>
|
||||
#include <botan/rng.h>
|
||||
#include <botan/auto_rng.h>
|
||||
#include <botan/base64.h>
|
||||
#include <botan/loadstor.h>
|
||||
#include <botan/mem_ops.h>
|
||||
//#include <botan/psk_db_sql.h>
|
||||
#include <botan/psk_db.h>
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
|
||||
|
||||
using namespace Botan;
|
||||
//#include <botan/sqlite3.h>
|
||||
#include <botan/scrypt.h>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
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;
|
||||
class SqlException : public std::runtime_error {
|
||||
public:
|
||||
QSqlError error;
|
||||
SqlException(const QSqlError &err)
|
||||
: std::runtime_error(err.text().toUtf8().data())
|
||||
, error(err)
|
||||
{}
|
||||
|
||||
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";
|
||||
|
||||
|
||||
|
||||
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<uint8_t> 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<uint8_t> 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<uint8_t> 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
|
||||
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<uint8_t>& 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<std::string> kv_get_all() const override
|
||||
{
|
||||
QSqlQuery q(m_db);
|
||||
q.prepare("SELECT psk_name FROM " + m_tableName);
|
||||
|
||||
std::set<std::string> 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;
|
||||
};
|
||||
|
||||
} // 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<uint8_t> PasswordManager::KeyStrengthener::derive(const std::string &passphrase)
|
||||
{
|
||||
Botan::secure_vector<uint8_t> 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<bool> PasswordManager::unlock(const std::string &master_password)
|
||||
void PasswordManager::KeyStrengthener::saveParams(QSqlDatabase &db, const QString &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<Botan::Scrypt*>(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?");
|
||||
|
||||
if (same_mem(m_masterHash.begin(), hash.begin(), hash.length())) {
|
||||
result = true;
|
||||
m_masterKey = key;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} catch (...) {
|
||||
return Expected<bool>::fromException();
|
||||
}
|
||||
auto salt_str = QString::fromUtf8(Botan::base64_encode(m_salt).c_str());
|
||||
// SAVE parameters in database
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
Expected<bool> PasswordManager::changeMasterPassword(const std::string &old_master_password,
|
||||
const std::string &new_master_password)
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
|
||||
PasswordManager::PasswordManager() = default;
|
||||
PasswordManager::~PasswordManager() = default;
|
||||
|
||||
|
||||
bool PasswordManager::initialized(QSqlDatabase& db)
|
||||
{
|
||||
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);
|
||||
|
||||
m_hashSalt = OctetString(m_rng, v1_consts.pbkdf_salt_len);
|
||||
m_masterHash = hashStrengthenedKey(m_masterKey, m_hashSalt);
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
} catch (...) {
|
||||
return Expected<bool>::fromException();
|
||||
}
|
||||
return isPskStoreInitialized(db);
|
||||
}
|
||||
|
||||
void PasswordManager::lock()
|
||||
bool PasswordManager::createDatabase(QSqlDatabase &db, QString passphrase)
|
||||
{
|
||||
m_masterKey = StrengthenedKey();
|
||||
if (!isPskStoreInitialized(db)) {
|
||||
initializeNewPskStore(db);
|
||||
auto ks = createKeyStrengthener();
|
||||
ks.saveParams(db, m_secretAlgoTableName);
|
||||
|
||||
auto master_key = ks.derive(passphrase.toUtf8().data());
|
||||
|
||||
std::unique_ptr<Botan::HashFunction> 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<QPSK_Database>(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<Botan::HashFunction> 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<size_t>(hash_b64.size()));
|
||||
if (hash_bin == mkh_bin) {
|
||||
m_pskDatabase = std::make_unique<QPSK_Database>(master_key, db, m_passwordTableName);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PasswordManager::closeDatabase()
|
||||
{
|
||||
m_pskDatabase.reset();
|
||||
}
|
||||
|
||||
bool PasswordManager::locked() const
|
||||
{
|
||||
return m_masterKey.cipher_key.size() == 0;
|
||||
return m_pskDatabase == nullptr;
|
||||
}
|
||||
|
||||
Expected<void> PasswordManager::savePassword(const std::string &key, const std::string &password)
|
||||
void PasswordManager::set(const std::string &id, const std::string &passwd)
|
||||
{
|
||||
if (locked()) {
|
||||
return Expected<void>::fromException(std::logic_error("Need to unlock the password manager first"));
|
||||
if (m_pskDatabase) {
|
||||
m_pskDatabase->set_str(id, passwd);
|
||||
}
|
||||
else {
|
||||
throw PasswordManagerLockedException();
|
||||
}
|
||||
std::string epw = encrypt(password, m_masterKey);
|
||||
m_store.emplace(key, epw);
|
||||
|
||||
return Expected<void>();
|
||||
}
|
||||
|
||||
Expected<bool> PasswordManager::getPassword(const std::string &key, std::string &out)
|
||||
bool PasswordManager::get(const std::string &id, std::string &password)
|
||||
{
|
||||
if (locked()) {
|
||||
return Expected<bool>::fromException(std::logic_error("Need to unlock the password manager first"));
|
||||
if (m_pskDatabase) {
|
||||
try {
|
||||
password = m_pskDatabase->get_str(id);
|
||||
return true;
|
||||
}
|
||||
catch (const Botan::Invalid_Argument &) {
|
||||
// not present
|
||||
return false;
|
||||
}
|
||||
}
|
||||
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::remove(const std::string &id)
|
||||
{
|
||||
std::unique_ptr<Botan::HashFunction> 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();
|
||||
if (m_pskDatabase) {
|
||||
m_pskDatabase->remove(id);
|
||||
}
|
||||
else {
|
||||
throw PasswordManagerLockedException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PasswordManager::initializeNewPskStore(QSqlDatabase &db)
|
||||
{
|
||||
// // 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"
|
||||
" salt TEXT \n"
|
||||
");");
|
||||
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(QSqlDatabase& db)
|
||||
{
|
||||
// Is the table with the secret data present and filled?
|
||||
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);
|
||||
}
|
||||
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(QSqlDatabase &db)
|
||||
{
|
||||
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(salt.data(), static_cast<size_t>(salt.size())),
|
||||
ks
|
||||
);
|
||||
}
|
||||
else {
|
||||
throw std::runtime_error("fail");
|
||||
}
|
||||
}
|
||||
|
||||
PasswordManager::KeyStrengthener PasswordManager::createKeyStrengthener()
|
||||
{
|
||||
// std::unique_ptr<Botan::PasswordHash> pwh;
|
||||
|
||||
size_t key_size = 64;
|
||||
Botan::secure_vector<uint8_t> 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
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,65 +2,112 @@
|
|||
#define PASSWORDMANAGER_H
|
||||
|
||||
#include "Expected.h"
|
||||
#include <QSqlDatabase>
|
||||
#include <botan/secmem.h>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
#include <botan/botan.h>
|
||||
#include <botan/symkey.h>
|
||||
#include <botan/pwdhash.h>
|
||||
|
||||
|
||||
//#include <botan/botan.h>
|
||||
//#include <botan/symkey.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
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;
|
||||
//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();
|
||||
~PasswordManager();
|
||||
|
||||
explicit PasswordManager(int iterations = 8192);
|
||||
/** Unlocks the passwords of the connections.
|
||||
/** Check if it has been initialized before.
|
||||
*
|
||||
* \return Normally it return a bool specifying if the password was accepted.
|
||||
* on rare occasions it could return an error.
|
||||
* If returns false then use createDatabase to set it up
|
||||
* else use openDatabase to get access.
|
||||
*/
|
||||
Expected<bool> unlock(const std::string &master_password);
|
||||
|
||||
Expected<bool> changeMasterPassword(const std::string &master_password,
|
||||
const std::string &new_master_password);
|
||||
|
||||
/** Forget master password
|
||||
*/
|
||||
void lock();
|
||||
bool initialized(QSqlDatabase &db);
|
||||
bool createDatabase(QSqlDatabase &db, QString passphrase);
|
||||
bool openDatabase(QSqlDatabase &db, QString passphrase);
|
||||
void closeDatabase();
|
||||
bool locked() const;
|
||||
|
||||
Expected<void> savePassword(const std::string &key, const std::string &password);
|
||||
Expected<bool> getPassword(const std::string &key, std::string &out);
|
||||
|
||||
void set(const std::string &id, const std::string &passwd);
|
||||
bool get(const std::string &id, std::string &password);
|
||||
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
|
||||
QString m_passwordTableName = "psk_passwd";
|
||||
QString m_secretAlgoTableName = "psk_masterkey_algo";
|
||||
QString m_secretHashTableName = "psk_masterkey_hash";
|
||||
std::unique_ptr<Botan::Encrypted_PSK_Database> m_pskDatabase;
|
||||
|
||||
using t_KeyPasswords = std::map<std::string, std::string>;
|
||||
bool isPskStoreInitialized(QSqlDatabase& db);
|
||||
void initializeNewPskStore(QSqlDatabase &db);
|
||||
|
||||
t_KeyPasswords m_store;
|
||||
class KeyStrengthener {
|
||||
public:
|
||||
KeyStrengthener() = default;
|
||||
KeyStrengthener(std::unique_ptr<Botan::PasswordHash> hasher, Botan::secure_vector<uint8_t> 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<uint8_t> derive(const std::string &passphrase);
|
||||
void saveParams(QSqlDatabase &db, const QString &table_name);
|
||||
private:
|
||||
std::unique_ptr<Botan::PasswordHash> m_hasher;
|
||||
Botan::secure_vector<uint8_t> m_salt;
|
||||
size_t m_keySize;
|
||||
};
|
||||
|
||||
/// Get PasswordHash from parameters in database
|
||||
KeyStrengthener getKeyStrengthener(QSqlDatabase &db);
|
||||
KeyStrengthener createKeyStrengthener();
|
||||
|
||||
static Botan::OctetString hashStrengthenedKey(const StrengthenedKey &key, const Botan::OctetString &salt);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,4 @@
|
|||
|
||||
using namespace SqlAst;
|
||||
|
||||
Node::Node()
|
||||
{
|
||||
|
||||
}
|
||||
Node::Node() = default;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#-------------------------------------------------
|
||||
|
||||
QT -= gui
|
||||
QT += sql
|
||||
|
||||
TARGET = core
|
||||
TEMPLATE = lib
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "ConnectionList.h"
|
||||
#include "ScopeGuard.h"
|
||||
#include "util.h"
|
||||
#include "PasswordManager.h"
|
||||
#include <QDir>
|
||||
#include <QStandardPaths>
|
||||
#include <QSettings>
|
||||
|
|
@ -19,13 +20,20 @@ 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<int>(cc.sslMode()));
|
||||
settings.setValue("sslcert", stdStrToQ(cc.sslCert()));
|
||||
settings.setValue("sslkey", stdStrToQ(cc.sslKey()));
|
||||
settings.setValue("sslrootcert", stdStrToQ(cc.sslRootCert()));
|
||||
settings.setValue("sslcrl", stdStrToQ(cc.sslCrl()));
|
||||
settings.setValue("passwordState", static_cast<int>(cc.passwordState()));
|
||||
}
|
||||
|
||||
template <typename S, typename T>
|
||||
bool in_range(T value)
|
||||
{
|
||||
return value >= std::numeric_limits<S>::min() && value <= std::numeric_limits<S>::max();
|
||||
}
|
||||
|
||||
void LoadConnectionConfig(QSettings &settings, ConnectionConfig &cc)
|
||||
|
|
@ -33,21 +41,28 @@ namespace {
|
|||
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<uint16_t>(p)) {
|
||||
p = 0; // let the user re-enter a valid value
|
||||
}
|
||||
|
||||
cc.setPort(static_cast<uint16_t>(p));
|
||||
cc.setUser(qvarToStdStr(settings.value("user")));
|
||||
// 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<SslMode>(settings.value("sslmode").toInt()));
|
||||
cc.setSslCert(qvarToStdStr(settings.value("sslcert")));
|
||||
cc.setSslKey(qvarToStdStr(settings.value("sslkey")));
|
||||
cc.setSslRootCert(qvarToStdStr(settings.value("sslrootcert")));
|
||||
cc.setSslCrl(qvarToStdStr(settings.value("sslcrl")));
|
||||
|
||||
PasswordState pwstate;
|
||||
QVariant v = settings.value("passwordState");
|
||||
if (v.isNull()) pwstate = PasswordState::NotStored;
|
||||
else pwstate = static_cast<PasswordState>(v.toInt());
|
||||
cc.setPasswordState(pwstate);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -69,19 +84,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<int>(idx);
|
||||
auto l = f + static_cast<int>(count);
|
||||
deleteFromIni(f, l);
|
||||
m_connections.erase(f, l);
|
||||
}
|
||||
|
|
@ -91,7 +107,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 +127,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 +140,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()) {
|
||||
auto& e = m_connections.at(index);
|
||||
if (e.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.beginGroup(e.uuid().toString());
|
||||
SaveConnectionConfig(settings, e);
|
||||
e.clean();
|
||||
settings.sync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,47 +8,47 @@
|
|||
#include <vector>
|
||||
#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();
|
||||
void setConfigByIdx(size_t idx, const ConnectionConfig &cc)
|
||||
{
|
||||
m_connections[idx] = cc;
|
||||
}
|
||||
|
||||
void remove(int idx, int count);
|
||||
size_t createNew();
|
||||
|
||||
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<LijstElem>;
|
||||
using t_Connections = std::vector<ConnectionConfig>;
|
||||
t_Connections m_connections;
|
||||
|
||||
void deleteFromIni(t_Connections::iterator begin, t_Connections::iterator end);
|
||||
|
||||
|
||||
static QString iniFileName();
|
||||
};
|
||||
|
||||
#endif // CONNECTIONLIST_H
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
@ -150,6 +150,27 @@ Expected<ConnectionConfig> ConnectionListModel::get(int row)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
|
||||
template <typename T>
|
||||
size_t as_size_t(T t);
|
||||
|
||||
template <>
|
||||
size_t as_size_t(int t)
|
||||
{
|
||||
BOOST_ASSERT(t >= 0);
|
||||
return static_cast<size_t>(t);
|
||||
}
|
||||
|
||||
template <>
|
||||
size_t as_size_t(long t)
|
||||
{
|
||||
BOOST_ASSERT(t >= 0);
|
||||
return static_cast<size_t>(t);
|
||||
}
|
||||
|
||||
|
||||
//void ConnectionListModel::del(const int idx)
|
||||
bool ConnectionListModel::removeRows(int row, int count, const QModelIndex &parent)
|
||||
{
|
||||
|
|
@ -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,13 @@ 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)
|
||||
{
|
||||
m_connections->setConfigByIdx(index, cc);
|
||||
m_connections->save(index);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,17 +30,18 @@ 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();
|
||||
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
|
||||
|
|
|
|||
|
|
@ -7,6 +7,21 @@
|
|||
#include <QStandardItemModel>
|
||||
#include "ConnectionListModel.h"
|
||||
|
||||
|
||||
#include <QDir>
|
||||
#include <QStandardPaths>
|
||||
|
||||
QString pskFileName()
|
||||
{
|
||||
QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||
QDir dir(path);
|
||||
if (!dir.exists()) {
|
||||
dir.mkpath(".");
|
||||
}
|
||||
path += "/psk.ini";
|
||||
return path;
|
||||
}
|
||||
|
||||
ConnectionManagerWindow::ConnectionManagerWindow(MasterController *master, QWidget *parent)
|
||||
: QMainWindow(parent)
|
||||
, ui(new Ui::ConnectionManagerWindow)
|
||||
|
|
@ -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<size_t>(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<size_t>(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 <botan/botan.h>
|
||||
//#include <botan/base64.h>
|
||||
//#include <botan/pbkdf.h>
|
||||
//#include <botan/block_cipher.h>
|
||||
//#include <botan/hex.h>
|
||||
#include <botan/cryptobox.h>
|
||||
|
||||
|
||||
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<Botan::byte> 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<Botan::PBKDF> 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<uint8_t> pt(plaintext.data(),plaintext.data()+plaintext.length());
|
||||
|
||||
// std::unique_ptr<Botan::Cipher_Mode> enc(Botan::get_cipher_mode("AES-256/CBC/PKCS7", Botan::ENCRYPTION));
|
||||
// enc->set_key(aes256_key);
|
||||
|
||||
// //generate fresh nonce (IV)
|
||||
// //std::unique_ptr<Botan::RandomNumberGenerator> rng(new Botan::AutoSeeded_RNG);
|
||||
// std::vector<uint8_t> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#define CONNECTIONMANAGERWINDOW_H
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <optional>
|
||||
|
||||
namespace Ui {
|
||||
class ConnectionManagerWindow;
|
||||
|
|
@ -18,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:
|
||||
|
|
@ -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;
|
||||
std::optional<size_t> prevSelection;
|
||||
|
||||
void setupWidgetMappings();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -75,30 +75,23 @@
|
|||
<widget class="QLineEdit" name="edtUser"/>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Password</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="edtPassword">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Database</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="edtDbname"/>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>SSL</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QComboBox" name="cmbbxSsl">
|
||||
<property name="currentIndex">
|
||||
<number>2</number>
|
||||
|
|
@ -136,53 +129,46 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>SSL</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Certificate</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<item row="6" column="1">
|
||||
<widget class="QLineEdit" name="edtCert"/>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>Key</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<item row="7" column="1">
|
||||
<widget class="QLineEdit" name="edtKey"/>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>Root cert.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<item row="8" column="1">
|
||||
<widget class="QLineEdit" name="edtRootCert"/>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>Revocation list</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<item row="9" column="1">
|
||||
<widget class="QLineEdit" name="edtCrl"/>
|
||||
</item>
|
||||
<item row="11" column="0">
|
||||
<item row="10" column="0">
|
||||
<widget class="QPushButton" name="testButton">
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
|
|
@ -201,7 +187,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>25</height>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuFile">
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +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)
|
||||
{}
|
||||
|
|
@ -19,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);
|
||||
|
|
@ -33,39 +68,159 @@ 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 res = m_connectionListModel->get(connection_index);
|
||||
if (res.valid()) {
|
||||
auto cc = res.get();
|
||||
|
||||
if (retrieveConnectionPassword(cc)) {
|
||||
m_connectionListModel->save(connection_index, cc);
|
||||
// TODO instead of directly openening the mainwindow
|
||||
// do async connect and only open window when we have
|
||||
// working connection
|
||||
auto w = new MainWindow(this, nullptr);
|
||||
w->setAttribute( Qt::WA_DeleteOnClose );
|
||||
w->setConfig(cc.get());
|
||||
w->setConfig(cc);
|
||||
w->show();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MasterController::openBackupDlgForConnection(int connection_index)
|
||||
void MasterController::openBackupDlgForConnection(size_t 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();
|
||||
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.get());
|
||||
w->setConfig(cc);
|
||||
w->show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MasterController::openServerWindowForConnection(int connection_index)
|
||||
void MasterController::openServerWindowForConnection(size_t 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();
|
||||
if (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)
|
||||
{
|
||||
auto pw_state = cc.passwordState();
|
||||
if (pw_state == PasswordState::NotNeeded) {
|
||||
return true;
|
||||
}
|
||||
else if (pw_state == PasswordState::SavedPasswordManager) {
|
||||
std::string pw;
|
||||
bool result = getPasswordFromPskdb(getPskId(cc), pw);
|
||||
if (result) {
|
||||
cc.setPassword(pw);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Geen else hier want als voorgaande blok niet geretourneerd heeft moeten we wachtwoord
|
||||
// ook aan de gebruiker vragen zoals hier gebeurd.
|
||||
QString str = ConnectionListModel::makeLongDescription(cc);
|
||||
auto dlg = std::make_unique<PasswordPromptDialog>(PasswordPromptDialog::SaveOption, nullptr);
|
||||
dlg->setDescription(QString(tr("Please provide password for connection %1")).arg(str));
|
||||
int exec_result = dlg->exec();
|
||||
|
||||
if (exec_result == QDialog::Accepted) {
|
||||
std::string password = dlg->password().toUtf8().data();
|
||||
cc.setPassword(password);
|
||||
if (dlg->saveChecked()) {
|
||||
storePasswordInPskdb(getPskId(cc), password);
|
||||
cc.setPasswordState(PasswordState::SavedPasswordManager);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool MasterController::getPasswordFromPskdb(const std::string &password_id, std::string &password)
|
||||
{
|
||||
if (!UnlockPasswordManagerIfNeeded())
|
||||
return false;
|
||||
|
||||
return m_passwordManager->get(password_id, password);
|
||||
}
|
||||
|
||||
|
||||
bool MasterController::storePasswordInPskdb(const std::string &password_id, const std::string password)
|
||||
{
|
||||
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;
|
||||
//}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,21 +2,28 @@
|
|||
#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.
|
||||
*/
|
||||
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,9 +36,11 @@ public:
|
|||
}
|
||||
|
||||
void showConnectionManager();
|
||||
void openSqlWindowForConnection(int connection_index);
|
||||
void openServerWindowForConnection(int connection_index);
|
||||
void openBackupDlgForConnection(int connection_index);
|
||||
void openSqlWindowForConnection(size_t connection_index);
|
||||
void openServerWindowForConnection(size_t connection_index);
|
||||
void openBackupDlgForConnection(size_t connection_index);
|
||||
|
||||
// std::shared_ptr<Botan::Sqlite3_Database> getUserConfigDatabase();
|
||||
|
||||
signals:
|
||||
|
||||
|
|
@ -41,7 +50,25 @@ 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)
|
||||
*
|
||||
*/
|
||||
bool retrieveConnectionPassword(ConnectionConfig &cc);
|
||||
|
||||
bool getPasswordFromPskdb(const std::string &password_id, std::string &password);
|
||||
|
||||
bool storePasswordInPskdb(const std::string &password_id, const std::string password);
|
||||
|
||||
bool UnlockPasswordManagerIfNeeded();
|
||||
|
||||
static std::string getPskId(const ConnectionConfig &cc);
|
||||
};
|
||||
|
||||
#endif // MASTERCONTROLLER_H
|
||||
|
|
|
|||
100
pglab/PasswordPromptDialog.cpp
Normal file
100
pglab/PasswordPromptDialog.cpp
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
#include "PasswordPromptDialog.h"
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QCheckBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
#include <QLayout>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(PasswordPromptDialog::Flags)
|
||||
|
||||
PasswordPromptDialog::PasswordPromptDialog(Flags flags, QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, m_Flags(flags)
|
||||
{
|
||||
m_connectionLabel = new QLabel(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;
|
||||
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[0]->setFocus();
|
||||
retranslateUi();
|
||||
|
||||
// QMetaObject::connectSlotsByName(BackupDialog);
|
||||
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[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::updateOkEnabled()
|
||||
{
|
||||
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[0]->text();
|
||||
}
|
||||
|
||||
bool PasswordPromptDialog::saveChecked() const
|
||||
{
|
||||
if (m_saveCheck)
|
||||
return m_saveCheck->checkState() == Qt::Checked;
|
||||
|
||||
return false;
|
||||
}
|
||||
44
pglab/PasswordPromptDialog.h
Normal file
44
pglab/PasswordPromptDialog.h
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#ifndef PASSWORDPROMPTDIALOG_H
|
||||
#define PASSWORDPROMPTDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class QCheckBox;
|
||||
class QDialogButtonBox;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
|
||||
class PasswordPromptDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Flag {
|
||||
ConfirmPassword = 1,
|
||||
SaveOption = 2
|
||||
};
|
||||
|
||||
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[2] = { nullptr, nullptr };
|
||||
QLineEdit *m_passwordInput[2] = { nullptr, nullptr };
|
||||
QCheckBox *m_saveCheck = nullptr;
|
||||
QDialogButtonBox *m_DialogButtons = nullptr;
|
||||
|
||||
void retranslateUi();
|
||||
void updateOkEnabled();
|
||||
|
||||
private slots:
|
||||
void passwordChanged(const QString &text);
|
||||
};
|
||||
|
||||
#endif // PASSWORDPROMPTDIALOG_H
|
||||
|
|
@ -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());
|
||||
|
||||
|
|
|
|||
35
pglab/PropertiesPage.cpp
Normal file
35
pglab/PropertiesPage.cpp
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#include "PropertiesPage.h"
|
||||
#include "PgLabItemDelegate.h"
|
||||
#include "PropertyProxyModel.h"
|
||||
#include "ResultTableModelUtil.h"
|
||||
#include "SqlCodePreview.h"
|
||||
#include <QTableView>
|
||||
|
||||
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);
|
||||
m_tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
}
|
||||
|
||||
|
||||
void PropertiesPage::setSourceModel(QAbstractItemModel *model)
|
||||
{
|
||||
m_propertyProxyModel->setSourceModel(model);
|
||||
}
|
||||
|
||||
|
||||
void PropertiesPage::setActiveRow(const QModelIndex &row)
|
||||
{
|
||||
m_propertyProxyModel->setActiveRow(row);
|
||||
}
|
||||
36
pglab/PropertiesPage.h
Normal file
36
pglab/PropertiesPage.h
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#ifndef PROPERTIESPAGE_H
|
||||
#define PROPERTIESPAGE_H
|
||||
|
||||
#include <QSplitter>
|
||||
|
||||
class QTableView;
|
||||
class SqlCodePreview;
|
||||
class PgDatabaseCatalog;
|
||||
class PropertyProxyModel;
|
||||
class QAbstractItemModel;
|
||||
|
||||
class PropertiesPage : public QSplitter
|
||||
{
|
||||
Q_OBJECT
|
||||
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;
|
||||
// SqlCodePreview *m_definitionView = nullptr;
|
||||
PropertyProxyModel *m_propertyProxyModel = nullptr;
|
||||
|
||||
};
|
||||
|
||||
#endif // PROPERTIESPAGE_H
|
||||
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -26,15 +26,13 @@ TablesPage::TablesPage(MainWindow *parent)
|
|||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
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);
|
||||
|
||||
// Columns
|
||||
SetTableViewDefault(ui->columnsTable);
|
||||
|
|
@ -45,22 +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);
|
||||
|
||||
// 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);
|
||||
|
||||
ui->indexesTable->setItemDelegate(new PgLabItemDelegate(this));
|
||||
ui->indexesTable->setItemDelegateForColumn(0, new IconColumnDelegate(this));
|
||||
|
||||
// Set code editor fonts
|
||||
QFont code_font = UserConfiguration::instance()->codeFont();
|
||||
|
|
@ -71,13 +61,13 @@ 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(ui->constraintsTable->selectionModel(), &QItemSelectionModel::currentRowChanged, this,
|
||||
// &TablesPage::constraintsTable_currentRowChanged);
|
||||
connect(m_tablesModel, &QAbstractItemModel::layoutChanged,
|
||||
this, &TablesPage::tableListTable_layoutChanged);
|
||||
|
||||
//layoutChanged(const QList<QPersistentModelIndex> &parents = ..., QAbstractItemModel::LayoutChangeHint hint = ...)
|
||||
|
||||
connect(ui->constraintsTable->selectionModel(), &QItemSelectionModel::selectionChanged, this,
|
||||
&TablesPage::constraintsTable_selectionChanged);
|
||||
|
|
@ -92,8 +82,17 @@ 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, tr("Triggers"));
|
||||
m_triggerTab = addDetailTab(m_triggerPage);
|
||||
|
||||
retranslateUi(false);
|
||||
}
|
||||
|
||||
|
|
@ -103,16 +102,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;
|
||||
}
|
||||
|
||||
|
|
@ -128,7 +128,6 @@ void TablesPage::setCatalog(std::shared_ptr<PgDatabaseCatalog> cat)
|
|||
ui->tableListTable->resizeColumnsToContents();
|
||||
|
||||
m_triggerPage->setCatalog(cat);
|
||||
// m_namespaceFilterWidget->init(cat->namespaces());
|
||||
|
||||
auto highlighter = new SqlSyntaxHighlighter(ui->constraintSqlEdit->document());
|
||||
highlighter->setTypes(*cat->types());
|
||||
|
|
@ -136,10 +135,26 @@ void TablesPage::setCatalog(std::shared_ptr<PgDatabaseCatalog> 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());
|
||||
selectedTableChanged(table);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void TablesPage::tableListTable_layoutChanged(const QList<QPersistentModelIndex> &, 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();
|
||||
|
||||
|
|
@ -152,20 +167,7 @@ void TablesPage::tableListTable_currentRowChanged(const QModelIndex ¤t, co
|
|||
|
||||
m_triggerPage->setFilter(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::constraintsTable_selectionChanged(const QItemSelection &/*selected*/, const QItemSelection &/*deselected*/)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@ class PgDatabaseCatalog;
|
|||
class NamespaceFilterWidget;
|
||||
class IndexModel;
|
||||
class MainWindow;
|
||||
class PropertiesPage;
|
||||
class TriggerPage;
|
||||
class PgClass;
|
||||
|
||||
class TablesPage : public QWidget
|
||||
{
|
||||
|
|
@ -30,6 +32,8 @@ public:
|
|||
private:
|
||||
Ui::TablesPage *ui;
|
||||
MainWindow *m_window;
|
||||
QWidget *m_propertiesTab;
|
||||
PropertiesPage *m_propertiesPage;
|
||||
QWidget *m_triggerTab;
|
||||
TriggerPage *m_triggerPage;
|
||||
std::shared_ptr<PgDatabaseCatalog> m_catalog;
|
||||
|
|
@ -40,10 +44,13 @@ private:
|
|||
//NamespaceFilterWidget* m_namespaceFilterWidget;
|
||||
|
||||
void retranslateUi(bool all = true);
|
||||
QWidget* addDetailTab(QWidget *contents, QString caption);
|
||||
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<QPersistentModelIndex> &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();
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
</widget>
|
||||
<widget class="QTabWidget" name="twDetails">
|
||||
<property name="currentIndex">
|
||||
<number>3</number>
|
||||
<number>2</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="columnsTab">
|
||||
<attribute name="title">
|
||||
|
|
@ -86,16 +86,6 @@
|
|||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="propertiesTab">
|
||||
<attribute name="title">
|
||||
<string>Properties</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayoutProperties">
|
||||
<item>
|
||||
<widget class="QTableView" name="tablePropertiesTable"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
|||
|
|
@ -21,14 +21,12 @@ 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);
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
#ifndef TRIGGERPAGE_H
|
||||
#define TRIGGERPAGE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QSplitter>
|
||||
#include <memory>
|
||||
|
||||
class QSplitter;
|
||||
class QTableView;
|
||||
class SqlCodePreview;
|
||||
class PgDatabaseCatalog;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
@ -82,7 +82,9 @@ PropertyProxyModel.cpp \
|
|||
TriggerTableModel.cpp \
|
||||
TriggerPage.cpp \
|
||||
SqlCodePreview.cpp \
|
||||
CustomFilterSortModel.cpp
|
||||
CustomFilterSortModel.cpp \
|
||||
PropertiesPage.cpp \
|
||||
PasswordPromptDialog.cpp
|
||||
|
||||
HEADERS += \
|
||||
QueryResultModel.h \
|
||||
|
|
@ -135,7 +137,9 @@ CustomDataRole.h \
|
|||
TriggerTableModel.h \
|
||||
TriggerPage.h \
|
||||
SqlCodePreview.h \
|
||||
CustomFilterSortModel.h
|
||||
CustomFilterSortModel.h \
|
||||
PropertiesPage.h \
|
||||
PasswordPromptDialog.h
|
||||
|
||||
FORMS += mainwindow.ui \
|
||||
ConnectionManagerWindow.ui \
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
@ -242,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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef CONNECTION_H
|
||||
#define CONNECTION_H
|
||||
|
||||
#include <QUuid>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
|
|
@ -14,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;
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
@ -66,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;
|
||||
|
|
@ -73,13 +80,14 @@ public:
|
|||
bool dirty() const;
|
||||
void clean();
|
||||
private:
|
||||
QUuid m_uuid;
|
||||
std::string m_name;
|
||||
std::string m_host;
|
||||
std::string m_hostaddr;
|
||||
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;
|
||||
|
|
@ -89,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<const char*> s_keywords;
|
||||
|
|
|
|||
|
|
@ -74,9 +74,9 @@ std::vector<PgConstraint> PgConstraintContainer::getConstraintsForRelation(Oid r
|
|||
return result;
|
||||
}
|
||||
|
||||
boost::optional<PgConstraint> PgConstraintContainer::getPrimaryForRelation(Oid relid) const
|
||||
std::optional<PgConstraint> PgConstraintContainer::getPrimaryForRelation(Oid relid) const
|
||||
{
|
||||
boost::optional<PgConstraint> result;
|
||||
std::optional<PgConstraint> result;
|
||||
for (const auto &e : m_container) {
|
||||
if (e.relid == relid && e.type == ConstraintType::PrimaryKey) {
|
||||
result = e;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
#include "PgConstraint.h"
|
||||
#include "Pgsql_declare.h"
|
||||
#include <vector>
|
||||
#include <boost/optional.hpp>
|
||||
#include <optional>
|
||||
|
||||
class PgConstraintContainer : public PgContainer<PgConstraint> {
|
||||
public:
|
||||
|
|
@ -16,7 +16,7 @@ public:
|
|||
const PgConstraint* getFKeyForTableColumn(Oid relid, int16_t attnum) const;
|
||||
|
||||
std::vector<PgConstraint> getConstraintsForRelation(Oid relid) const;
|
||||
boost::optional<PgConstraint> getPrimaryForRelation(Oid relid) const;
|
||||
std::optional<PgConstraint> getPrimaryForRelation(Oid relid) const;
|
||||
protected:
|
||||
virtual PgConstraint loadElem(const Pgsql::Row &row) override;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -75,10 +75,14 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
QString getTypeDisplayString(const PgDatabaseCatalog &cat, Oid oid, int32_t typmod)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ public:
|
|||
* field often provides enough flexibility.
|
||||
*/
|
||||
QString m_prefixWith;
|
||||
// boost::optional<CharToNumericConversion> m_numericConversion;
|
||||
// std::optional<CharToNumericConversion> m_numericConversion;
|
||||
};
|
||||
|
||||
#endif // STRINGESCAPERULE_H
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
#
|
||||
#-------------------------------------------------
|
||||
|
||||
QT += widgets
|
||||
QT += widgets core
|
||||
|
||||
TARGET = pglablib
|
||||
TEMPLATE = lib
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
#include <libpq-fe.h>
|
||||
#include "Pgsql_declare.h"
|
||||
#include "Pgsql_oids.h"
|
||||
#include <boost/optional.hpp>
|
||||
#include <optional>
|
||||
|
||||
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<std::string> s, Oid oid=varchar_oid)
|
||||
Param add(std::optional<std::string> s, Oid oid=varchar_oid)
|
||||
{
|
||||
return add(s ? s->c_str() : nullptr, oid);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
#include <vector>
|
||||
#include <QString>
|
||||
#include <QDateTime>
|
||||
#include <boost/optional.hpp>
|
||||
#include <optional>
|
||||
|
||||
namespace Pgsql {
|
||||
|
||||
|
|
@ -154,10 +154,10 @@ namespace Pgsql {
|
|||
};
|
||||
|
||||
template <typename T>
|
||||
void operator<<(boost::optional<T> &s, const Value &v)
|
||||
void operator<<(std::optional<T> &s, const Value &v)
|
||||
{
|
||||
if (v.null())
|
||||
s = boost::optional<T>();
|
||||
s = std::optional<T>();
|
||||
else
|
||||
*s << v;
|
||||
}
|
||||
|
|
|
|||
0
readme.md
Normal file
0
readme.md
Normal file
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,74 +1,93 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock-matchers.h>
|
||||
#include "PasswordManager.h"
|
||||
//#include "PasswordManager.h"
|
||||
#include "PrintTo_Qt.h"
|
||||
|
||||
using namespace testing;
|
||||
|
||||
|
||||
TEST(PasswordManager, initial_changeMasterPassword_returns_true)
|
||||
{
|
||||
PasswordManager pwm(10);
|
||||
#include <botan/pwdhash.h>
|
||||
#include <botan/scrypt.h>
|
||||
|
||||
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<Botan::Scrypt*>(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) );
|
||||
|
||||
//}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue