Remove database error thows (#191)

Co-authored-by: Chris Rizzitello <crizzitello@ics.com>
main
crizzitello 2022-07-12 15:31:28 -04:00 committed by GitHub
parent 1f2e1f32de
commit 9344c69b8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 106 additions and 158 deletions

View File

@ -36,6 +36,7 @@ Updating the database schema is slightly complicated, and the following rules mu
* This will create file in the `migrations` folder with the indicated name and a timestamp * This will create file in the `migrations` folder with the indicated name and a timestamp
* This should also add this migration to the qrc file. However, if this is not done, you can do this manually by editing the `rs_migrations.qrc` file. * This should also add this migration to the qrc file. However, if this is not done, you can do this manually by editing the `rs_migrations.qrc` file.
2. Inside the new migration file, add the necessary sql to apply the db change under `-- +migrate Up` 2. Inside the new migration file, add the necessary sql to apply the db change under `-- +migrate Up`
* This section must come first.
3. Inside the new migration file, add the necessary sql to _undo_ the db change under `-- +migrate Down` 3. Inside the new migration file, add the necessary sql to _undo_ the db change under `-- +migrate Down`
4. Only one statement is allowed under each heading. If multiple statements need to be applied, they should done as multiple migration files 4. Only one statement is allowed under each heading. If multiple statements need to be applied, they should done as multiple migration files
* This is a sqlite3/Qt limitation. * This is a sqlite3/Qt limitation.

View File

@ -6,7 +6,6 @@
#include <QLineEdit> #include <QLineEdit>
#include "codeeditor.h" #include "codeeditor.h"
#include "exceptions/fileerror.h"
#include "helpers/ui_helpers.h" #include "helpers/ui_helpers.h"
CodeBlockView::CodeBlockView(QWidget* parent) CodeBlockView::CodeBlockView(QWidget* parent)

View File

@ -179,32 +179,28 @@ SaveEvidenceResponse EvidenceEditor::saveEvidence() {
return resp; return resp;
} }
QList<DeleteEvidenceResponse> EvidenceEditor::deleteEvidence(QList<qint64> evidenceIDs) { QList<DeleteEvidenceResponse> EvidenceEditor::deleteEvidence(QList<qint64> evidenceIDs)
{
QList<DeleteEvidenceResponse> responses; QList<DeleteEvidenceResponse> responses;
for (qint64 id : evidenceIDs) { for (qint64 id : evidenceIDs) {
model::Evidence evi = db->getEvidenceDetails(id); model::Evidence evi = db->getEvidenceDetails(id);
DeleteEvidenceResponse resp(evi); DeleteEvidenceResponse resp(evi);
try{ try{
db->deleteEvidence(evi.id); resp.dbDeleteSuccess = db->deleteEvidence(evi.id);
resp.dbDeleteSuccess = true; } catch(QSqlError &e) {
}
catch(QSqlError &e) {
resp.dbDeleteSuccess = false;
resp.errorText = e.text(); resp.errorText = e.text();
} }
auto localFile = new QFile(evi.path);
if (!localFile->remove()) { auto localFile = QFile(evi.path);
if (!localFile.remove()) {
resp.fileDeleteSuccess = false; resp.fileDeleteSuccess = false;
resp.errorText += "\n" + localFile->errorString(); resp.errorText.append(QStringLiteral("\n%1").arg(localFile.errorString()));
} } else {
else {
resp.fileDeleteSuccess = true; resp.fileDeleteSuccess = true;
} }
localFile->deleteLater(); // deletes the pointer, not the file
resp.errorText = resp.errorText.trimmed(); resp.errorText = resp.errorText.trimmed();
responses.append(resp); responses.append(resp);
} }
return responses; return responses;
} }

View File

@ -6,28 +6,16 @@
#include <QDir> #include <QDir>
#include <QVariant> #include <QVariant>
#include "exceptions/databaseerr.h"
#include "exceptions/fileerror.h" #include "exceptions/fileerror.h"
#include "helpers/file_helpers.h" #include "helpers/file_helpers.h"
// DatabaseConnection constructs a connection to the database, unsurpringly. Note that the DatabaseConnection::DatabaseConnection(const QString& dbPath, const QString& databaseName)
// constructor can throw a error (see below). Additionally, many methods can throw a QSqlError, : _dbName(databaseName)
// though are not marked as such in their comments. Other errors are listed in throw comments, or , _dbPath(dbPath)
// are marked as noexcept if no error is possible. {
// auto db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), databaseName);
// Throws: DBDriverUnavailable if the required database driver does not exist
DatabaseConnection::DatabaseConnection(const QString& dbPath, QString databaseName) {
const static QString dbDriver = "QSQLITE";
if (QSqlDatabase::isDriverAvailable(dbDriver)) {
dbName = std::move(databaseName);
auto db = QSqlDatabase::addDatabase(dbDriver, dbName);
QDir().mkpath(FileHelpers::getDirname(dbPath)); QDir().mkpath(FileHelpers::getDirname(dbPath));
db.setDatabaseName(dbPath); db.setDatabaseName(dbPath);
this->_dbPath = dbPath;
}
else {
throw DBDriverUnavailableError("SQLite");
}
} }
void DatabaseConnection::withConnection(const QString& dbPath, const QString &dbName, void DatabaseConnection::withConnection(const QString& dbPath, const QString &dbName,
@ -45,12 +33,14 @@ void DatabaseConnection::withConnection(const QString& dbPath, const QString &db
QSqlDatabase::removeDatabase(dbName); QSqlDatabase::removeDatabase(dbName);
} }
void DatabaseConnection::connect() { bool DatabaseConnection::connect()
{
auto db = getDB(); auto db = getDB();
if (!db.open()) { if (!db.open()) {
return false;
throw db.lastError(); throw db.lastError();
} }
migrateDB(); return migrateDB();
} }
void DatabaseConnection::close() noexcept { getDB().close(); } void DatabaseConnection::close() noexcept { getDB().close(); }
@ -137,8 +127,13 @@ void DatabaseConnection::updateEvidenceDescription(const QString &newDescription
executeQuery(getDB(), "UPDATE evidence SET description=? WHERE id=?", {newDescription, evidenceID}); executeQuery(getDB(), "UPDATE evidence SET description=? WHERE id=?", {newDescription, evidenceID});
} }
void DatabaseConnection::deleteEvidence(qint64 evidenceID) { bool DatabaseConnection::deleteEvidence(qint64 evidenceID)
executeQuery(getDB(), "DELETE FROM evidence WHERE id=?", {evidenceID}); {
auto q = executeQuery(getDB(), "DELETE FROM evidence WHERE id=?", {evidenceID});
if (q.lastError().type() == QSqlError::NoError)
return true;
QTextStream(stderr) << "Unable to Delete " << evidenceID << " " << q.lastError().text();
return false;
} }
void DatabaseConnection::updateEvidenceError(const QString &errorText, qint64 evidenceID) { void DatabaseConnection::updateEvidenceError(const QString &errorText, qint64 evidenceID) {
@ -339,7 +334,7 @@ QList<model::Evidence> DatabaseConnection::createEvidenceExportView(
// lacking update. // lacking update.
// //
// Throws exceptions/FileError if a migration file cannot be found. // Throws exceptions/FileError if a migration file cannot be found.
void DatabaseConnection::migrateDB() { bool DatabaseConnection::migrateDB() {
auto db = getDB(); auto db = getDB();
QTextStream(stdout) << "Checking database state" << Qt::endl; QTextStream(stdout) << "Checking database state" << Qt::endl;
auto migrationsToApply = DatabaseConnection::getUnappliedMigrations(db); auto migrationsToApply = DatabaseConnection::getUnappliedMigrations(db);
@ -347,10 +342,8 @@ void DatabaseConnection::migrateDB() {
for (const QString &newMigration : migrationsToApply) { for (const QString &newMigration : migrationsToApply) {
QFile migrationFile(QStringLiteral(":/migrations/%1").arg(newMigration)); QFile migrationFile(QStringLiteral(":/migrations/%1").arg(newMigration));
auto ok = migrationFile.open(QFile::ReadOnly); auto ok = migrationFile.open(QFile::ReadOnly);
if (!ok) { if (!ok)
throw FileError::mkError("Error reading migration file", return false;
migrationFile.fileName().toStdString(), migrationFile.error());
}
auto content = QString(migrationFile.readAll()); auto content = QString(migrationFile.readAll());
migrationFile.close(); migrationFile.close();
@ -362,6 +355,7 @@ void DatabaseConnection::migrateDB() {
{newMigration}); {newMigration});
} }
QTextStream(stdout) << "All migrations applied" << Qt::endl; QTextStream(stdout) << "All migrations applied" << Qt::endl;
return true;
} }
// getUnappliedMigrations retrieves a list of all of the migrations that have not been applied // getUnappliedMigrations retrieves a list of all of the migrations that have not been applied
@ -372,10 +366,10 @@ void DatabaseConnection::migrateDB() {
// Throws: // Throws:
// * BadDatabaseStateError if some migrations have been applied that are not known // * BadDatabaseStateError if some migrations have been applied that are not known
// * QSqlError if database queries fail // * QSqlError if database queries fail
QStringList DatabaseConnection::getUnappliedMigrations(const QSqlDatabase &db) { QStringList DatabaseConnection::getUnappliedMigrations(const QSqlDatabase &db)
{
QDir migrationsDir(QStringLiteral(":/migrations")); QDir migrationsDir(QStringLiteral(":/migrations"));
const auto allMigrations = migrationsDir.entryList(QDir::Files, QDir::Name);
auto allMigrations = migrationsDir.entryList(QDir::Files, QDir::Name);
QStringList appliedMigrations; QStringList appliedMigrations;
QStringList migrationsToApply; QStringList migrationsToApply;
@ -386,19 +380,17 @@ QStringList DatabaseConnection::getUnappliedMigrations(const QSqlDatabase &db) {
} }
// compare the two list to find gaps // compare the two list to find gaps
for (const QString &possibleMigration : allMigrations) { for (const QString &possibleMigration : allMigrations) {
if (!possibleMigration.endsWith(QStringLiteral(".sql"))) { if (!possibleMigration.endsWith(QStringLiteral(".sql")))
continue; // assume non-sql files aren't actual migrations. continue; // assume non-sql files aren't actual migrations.
}
auto foundIndex = appliedMigrations.indexOf(possibleMigration); auto foundIndex = appliedMigrations.indexOf(possibleMigration);
if (foundIndex == -1) { if (foundIndex == -1)
migrationsToApply << possibleMigration; migrationsToApply << possibleMigration;
} else
else {
appliedMigrations.removeAt(foundIndex); appliedMigrations.removeAt(foundIndex);
} }
}
if (!appliedMigrations.empty()) { if (!appliedMigrations.empty()) {
throw BadDatabaseStateError(); QTextStream(stderr) << "Database is in an inconsistent state";
} }
return migrationsToApply; return migrationsToApply;
} }
@ -406,21 +398,15 @@ QStringList DatabaseConnection::getUnappliedMigrations(const QSqlDatabase &db) {
// extractMigrateUpContent parses the given migration content and retrieves only // extractMigrateUpContent parses the given migration content and retrieves only
// the portion that applies to the "up" / apply logic. The "down" section is ignored. // the portion that applies to the "up" / apply logic. The "down" section is ignored.
QString DatabaseConnection::extractMigrateUpContent(const QString &allContent) noexcept { QString DatabaseConnection::extractMigrateUpContent(const QString &allContent) noexcept {
auto copying = false;
QString upContent; QString upContent;
for (const QString &line : allContent.split("\n")) { const QStringList lines = allContent.split(_newLine);
if (line.trimmed().toLower() == "-- +migrate up") { for (const QString &line : lines) {
copying = true; auto lowerLine = line.trimmed().toLower();
} if (lowerLine == _migrateUp)
else if (line.trimmed().toLower() == "-- +migrate down") { continue;
if (copying) { else if (lowerLine == _migrateDown)
break; break;
} upContent.append(_lineTemplate.arg(line));
copying = false;
}
else if (copying) {
upContent.append(line + "\n");
}
} }
return upContent; return upContent;
} }
@ -465,14 +451,13 @@ qint64 DatabaseConnection::doInsert(const QSqlDatabase& db, const QString &stmt,
return query.lastInsertId().toLongLong(); return query.lastInsertId().toLongLong();
} }
QSqlDatabase DatabaseConnection::getDB() { QSqlDatabase DatabaseConnection::getDB()
if (dbName.isEmpty()) { {
return QSqlDatabase::database(); return QSqlDatabase::database(_dbName);
}
return QSqlDatabase::database(dbName);
} }
QString DatabaseConnection::getDatabasePath() { QString DatabaseConnection::getDatabasePath()
{
return _dbPath; return _dbPath;
} }

View File

@ -24,17 +24,20 @@ class DBQuery {
public: public:
DBQuery(QString query) : DBQuery(query, {}) {} DBQuery(QString query) : DBQuery(query, {}) {}
DBQuery(QString query, QVariantList values) { DBQuery(QString query, QVariantList values) :_query(query), _values(values) { }
this->_query = query;
this->_values = values;
}
inline QString query() { return _query; } inline QString query() { return _query; }
inline QVariantList values() { return _values; } inline QVariantList values() { return _values; }
}; };
class DatabaseConnection { class DatabaseConnection {
public: public:
DatabaseConnection(const QString& dbPath, QString databaseName); /**
* @brief DatabaseConnection construct a connect to the database,
* Uses QSQLite Driver if the driver is missing the app to exit with code 255.
* @param dbPath - Path to the database
* @param databaseName - Name of the databaseFile or defaultName if none provided
*/
DatabaseConnection(const QString& dbPath, const QString& databaseName = Constants::defaultDbName);
/** /**
* @brief withConnection acts as a context manager for a single database connection. The goal for * @brief withConnection acts as a context manager for a single database connection. The goal for
@ -49,7 +52,7 @@ class DatabaseConnection {
static void withConnection(const QString& dbPath, const QString &dbName, static void withConnection(const QString& dbPath, const QString &dbName,
const std::function<void(DatabaseConnection)> &actions); const std::function<void(DatabaseConnection)> &actions);
void connect(); bool connect();
void close() noexcept; void close() noexcept;
static DBQuery buildGetEvidenceWithFiltersQuery(const EvidenceFilters &filters); static DBQuery buildGetEvidenceWithFiltersQuery(const EvidenceFilters &filters);
@ -71,7 +74,12 @@ class DatabaseConnection {
void batchCopyTags(const QList<model::Tag> &allTags); void batchCopyTags(const QList<model::Tag> &allTags);
QList<model::Tag> getFullTagsForEvidenceIDs(const QList<qint64>& evidenceIDs); QList<model::Tag> getFullTagsForEvidenceIDs(const QList<qint64>& evidenceIDs);
void deleteEvidence(qint64 evidenceID); /**
* @brief deleteEvidence Delete Evidence from the database
* @param evidenceID - ID To Delete
* @return true if successful
*/
bool deleteEvidence(qint64 evidenceID);
/// createEvidenceExportView duplicates the normal database with only a subset of evidence /// createEvidenceExportView duplicates the normal database with only a subset of evidence
/// present, as well as related data (e.g. tags) /// present, as well as related data (e.g. tags)
@ -90,10 +98,14 @@ class DatabaseConnection {
const unsigned long SQLITE_MAX_VARS = 999; const unsigned long SQLITE_MAX_VARS = 999;
private: private:
QString dbName; QString _dbName;
QString _dbPath; QString _dbPath;
inline static const auto _migrateUp = QStringLiteral("-- +migrate up");
inline static const auto _migrateDown = QStringLiteral("-- +migrate down");
inline static const auto _newLine = QStringLiteral("\n");
inline static const auto _lineTemplate =QStringLiteral("%1").append(_newLine);
void migrateDB(); bool migrateDB();
QSqlDatabase getDB(); QSqlDatabase getDB();
static QStringList getUnappliedMigrations(const QSqlDatabase &db); static QStringList getUnappliedMigrations(const QSqlDatabase &db);

View File

@ -1,8 +1,5 @@
add_library (EXCEPTIONS STATIC add_library (EXCEPTIONS STATIC fileerror.h)
databaseerr.h
fileerror.h
)
add_library(ASHIRT::EXCEPTIONS ALIAS EXCEPTIONS) add_library(ASHIRT::EXCEPTIONS ALIAS EXCEPTIONS)
@ -12,8 +9,4 @@ target_include_directories (EXCEPTIONS
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
) )
target_link_libraries ( EXCEPTIONS PUBLIC target_link_libraries ( EXCEPTIONS PUBLIC Qt::Core)
Qt::Core
)

View File

@ -1,17 +0,0 @@
// Copyright 2020, Verizon Media
// Licensed under the terms of MIT. See LICENSE file in project root for terms.
#pragma once
#include <stdexcept>
class BadDatabaseStateError : public std::runtime_error {
public:
BadDatabaseStateError() : std::runtime_error("Database is in an inconsistent state") {}
};
class DBDriverUnavailableError : public std::runtime_error {
public:
DBDriverUnavailableError(std::string friendlyDriverName)
: std::runtime_error(friendlyDriverName + " driver is unavailable") {}
};

View File

@ -118,13 +118,8 @@ void GetInfo::deleteButtonClicked() {
.arg(evi.path)); .arg(evi.path));
shouldClose = false; shouldClose = false;
} }
try {
db->deleteEvidence(evidenceID); db->deleteEvidence(evidenceID);
}
catch (QSqlError& e) {
QTextStream(stderr) << "Could not delete evidence from internal database. Error: "
<< e.text() << Qt::endl;
}
Q_EMIT setActionButtonsEnabled(true); Q_EMIT setActionButtonsEnabled(true);
if (shouldClose) { if (shouldClose) {

View File

@ -16,7 +16,6 @@ void handleCLI(std::vector<std::string> args);
#include "appconfig.h" #include "appconfig.h"
#include "appsettings.h" #include "appsettings.h"
#include "db/databaseconnection.h" #include "db/databaseconnection.h"
#include "exceptions/databaseerr.h"
#include "exceptions/fileerror.h" #include "exceptions/fileerror.h"
#include "traymanager.h" #include "traymanager.h"
@ -32,25 +31,10 @@ int main(int argc, char* argv[]) {
QCoreApplication::setOrganizationName("ashirt"); QCoreApplication::setOrganizationName("ashirt");
#endif #endif
DatabaseConnection* conn;
try { DatabaseConnection* conn = new DatabaseConnection(Constants::dbLocation, Constants::defaultDbName);
conn = new DatabaseConnection(Constants::dbLocation, Constants::defaultDbName); if(!conn->connect()) {
conn->connect(); QTextStream(stderr) << "Unable to connect to Database" << Qt::endl;
}
catch (FileError& err) {
QTextStream(stderr) << err.what() << Qt::endl;
return -1;
}
catch (DBDriverUnavailableError& err) {
QTextStream(stderr) << err.what() << Qt::endl;
return -1;
}
catch (QSqlError& e) {
QTextStream(stderr) << e.text() << Qt::endl;
return -1;
}
catch (std::exception& e) {
QTextStream(stderr) << "Unexpected error: " << e.what() << Qt::endl;
return -1; return -1;
} }
@ -66,11 +50,17 @@ int main(int argc, char* argv[]) {
qRegisterMetaType<model::Tag>(); qRegisterMetaType<model::Tag>();
app.setWindowIcon(getWindowIcon()); app.setWindowIcon(getWindowIcon());
if (!QSystemTrayIcon::isSystemTrayAvailable()) { if (!QSystemTrayIcon::isSystemTrayAvailable()) {
handleCLI(std::vector<std::string>(argv, argv + argc)); handleCLI(std::vector<std::string>(argv, argv + argc));
} }
QApplication::setQuitOnLastWindowClosed(false); QApplication::setQuitOnLastWindowClosed(false);
QObject::connect(&app, &QApplication::aboutToQuit, [conn] {
conn->close();
delete conn;
});
auto window = new TrayManager(nullptr, conn); auto window = new TrayManager(nullptr, conn);
rtn = app.exec(); rtn = app.exec();
AppSettings::getInstance().sync(); AppSettings::getInstance().sync();
@ -82,9 +72,6 @@ int main(int argc, char* argv[]) {
catch (...) { catch (...) {
QTextStream(stderr) << "Unhandled exception while running" << Qt::endl; QTextStream(stderr) << "Unhandled exception while running" << Qt::endl;
} }
conn->close();
delete conn;
return rtn; return rtn;
} }

View File

@ -20,7 +20,6 @@ class Codeblock {
/** /**
* @brief readCodeblock parses a local codeblock file and returns back the data as a codeblock * @brief readCodeblock parses a local codeblock file and returns back the data as a codeblock
* @param filepath The path to the codeblock file * @param filepath The path to the codeblock file
* @throws a FileError if any issues occur while reading the file
* @return a parsed Codeblock object, ready for use. * @return a parsed Codeblock object, ready for use.
*/ */
static Codeblock readCodeblock(const QString& filepath); static Codeblock readCodeblock(const QString& filepath);
@ -53,9 +52,7 @@ class Codeblock {
QByteArray encode(); QByteArray encode();
public: public:
/** /**
* @brief saveCodeblock encodes the provided codeblock, then writes that codeblock to it's * @brief saveCodeblock encodes the provided codeblock, then writes that codeblock to it's filePath
* filePath
* @throws a FileError if any issues occur while writing the file
* @param codeblock The codeblock to save * @param codeblock The codeblock to save
*/ */
static bool saveCodeblock(Codeblock codeblock); static bool saveCodeblock(Codeblock codeblock);