Database round2 (#193)

* Dont throw as many errors

* hold a QSqlDatabase in the connection

* only Make Dir if it doesn't exist

* more database cleanup

* Replace remaining FileError use with other methods

* Try not do

* allow database to report errors

Co-authored-by: Chris Rizzitello <crizzitello@ics.com>
main
crizzitello 2022-07-18 18:16:42 -04:00 committed by GitHub
parent 45ee47b145
commit 5928942399
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 465 additions and 618 deletions

View File

@ -3,7 +3,6 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
add_subdirectory(components) add_subdirectory(components)
add_subdirectory(db) add_subdirectory(db)
add_subdirectory(dtos) add_subdirectory(dtos)
add_subdirectory(exceptions)
add_subdirectory(forms) add_subdirectory(forms)
add_subdirectory(helpers) add_subdirectory(helpers)
add_subdirectory(models) add_subdirectory(models)
@ -54,7 +53,6 @@ target_link_libraries ( ashirt
ASHIRT::COMPONENTS ASHIRT::COMPONENTS
ASHIRT::HELPERS ASHIRT::HELPERS
ASHIRT::DTOS ASHIRT::DTOS
ASHIRT::EXCEPTIONS
ASHIRT::FORMS ASHIRT::FORMS
ASHIRT::PORTING ASHIRT::PORTING
) )

View File

@ -44,20 +44,14 @@ void CodeBlockView::buildUi() {
gridLayout->addWidget(codeEditor, 1, 0, 1, gridLayout->columnCount()); gridLayout->addWidget(codeEditor, 1, 0, 1, gridLayout->columnCount());
} }
void CodeBlockView::loadFromFile(QString filepath) { void CodeBlockView::loadFromFile(QString filepath)
try { {
codeEditor->setPlainText(tr("No Codeblock Loaded"));
loadedCodeblock = Codeblock::readCodeblock(filepath); loadedCodeblock = Codeblock::readCodeblock(filepath);
codeEditor->setPlainText(loadedCodeblock.content); codeEditor->setPlainText(loadedCodeblock.content);
sourceTextBox->setText(loadedCodeblock.source); sourceTextBox->setText(loadedCodeblock.source);
UIHelpers::setComboBoxValue(languageComboBox, loadedCodeblock.subtype); UIHelpers::setComboBoxValue(languageComboBox, loadedCodeblock.subtype);
} }
catch (std::exception& e) {
QString msg = tr("Unable to load codeblock. Error: %1").arg(e.what());
codeEditor->setPlainText(msg);
setReadonly(true);
}
}
bool CodeBlockView::saveEvidence() { bool CodeBlockView::saveEvidence() {
loadedCodeblock.source = sourceTextBox->text(); loadedCodeblock.source = sourceTextBox->text();

View File

@ -95,35 +95,31 @@ void EvidenceEditor::setEnabled(bool enable) {
} }
} }
void EvidenceEditor::loadData() { void EvidenceEditor::loadData()
{
// get local db evidence data // get local db evidence data
clearEditor(); clearEditor();
try {
originalEvidenceData = db->getEvidenceDetails(evidenceID); originalEvidenceData = db->getEvidenceDetails(evidenceID);
if(originalEvidenceData.id == -1) {
loadedPreview = new ErrorView(tr("Unable to load evidence: %1").arg(db->errorString()), this);
return;
}
descriptionTextBox->setText(originalEvidenceData.description); descriptionTextBox->setText(originalEvidenceData.description);
operationSlug = originalEvidenceData.operationSlug; operationSlug = originalEvidenceData.operationSlug;
if (originalEvidenceData.contentType == QStringLiteral("image")) { if (originalEvidenceData.contentType == QStringLiteral("image")) {
loadedPreview = new ImageView(this); loadedPreview = new ImageView(this);
loadedPreview->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); loadedPreview->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
} } else if (originalEvidenceData.contentType == QStringLiteral("codeblock")) {
else if (originalEvidenceData.contentType == QStringLiteral("codeblock")) {
loadedPreview = new CodeBlockView(this); loadedPreview = new CodeBlockView(this);
loadedPreview->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); loadedPreview->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
} } else {
else { loadedPreview = new ErrorView(tr("Unsupported evidence type: %1").arg(originalEvidenceData.contentType), this);
loadedPreview =
new ErrorView(tr("Unsupported evidence type: %1").arg(originalEvidenceData.contentType), this);
} }
loadedPreview->loadFromFile(originalEvidenceData.path); loadedPreview->loadFromFile(originalEvidenceData.path);
loadedPreview->setReadonly(readonly); loadedPreview->setReadonly(readonly);
// get all remote tags (for op) // get all remote tags (for op)
tagEditor->loadTags(operationSlug, originalEvidenceData.tags); tagEditor->loadTags(operationSlug, originalEvidenceData.tags);
}
catch (QSqlError &e) {
loadedPreview = new ErrorView(tr("Unable to load evidence: %1").arg(e.text()), this);
}
splitter->insertWidget(0, loadedPreview); splitter->insertWidget(0, loadedPreview);
} }
@ -161,21 +157,27 @@ void EvidenceEditor::onTagsLoaded(bool success) {
// saveEvidence is a helper method to save (to the database) the currently // saveEvidence is a helper method to save (to the database) the currently
// loaded evidence, using the editor changes. // loaded evidence, using the editor changes.
SaveEvidenceResponse EvidenceEditor::saveEvidence() { SaveEvidenceResponse EvidenceEditor::saveEvidence()
{
if (loadedPreview != nullptr) { if (loadedPreview != nullptr) {
loadedPreview->saveEvidence(); loadedPreview->saveEvidence();
} }
auto evi = encodeEvidence(); auto evi = encodeEvidence();
auto resp = SaveEvidenceResponse(evi); auto resp = SaveEvidenceResponse(evi);
try {
db->updateEvidenceDescription(evi.description, evi.id); db->updateEvidenceDescription(evi.description, evi.id);
db->setEvidenceTags(evi.tags, evi.id); if(!db->errorString().isEmpty()) {
resp.actionSucceeded = true;
}
catch (QSqlError &e) {
resp.actionSucceeded = false; resp.actionSucceeded = false;
resp.errorText = e.text(); resp.errorText = db->errorString();
return resp;
} }
db->setEvidenceTags(evi.tags, evi.id);
if(!db->errorString().isEmpty()) {
resp.actionSucceeded = false;
resp.errorText = db->errorString();
return resp;
}
resp.actionSucceeded = true;
return resp; return resp;
} }
@ -185,12 +187,9 @@ QList<DeleteEvidenceResponse> EvidenceEditor::deleteEvidence(QList<qint64> evide
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{
resp.dbDeleteSuccess = db->deleteEvidence(evi.id); resp.dbDeleteSuccess = db->deleteEvidence(evi.id);
} catch(QSqlError &e) { if(!resp.dbDeleteSuccess)
resp.errorText = e.text(); resp.errorText = db->errorString();
}
auto localFile = QFile(evi.path); auto localFile = QFile(evi.path);
if (!localFile.remove()) { if (!localFile.remove()) {

View File

@ -6,68 +6,61 @@
#include <QDir> #include <QDir>
#include <QVariant> #include <QVariant>
#include "exceptions/fileerror.h"
#include "helpers/file_helpers.h" #include "helpers/file_helpers.h"
DatabaseConnection::DatabaseConnection(const QString& dbPath, const QString& databaseName) DatabaseConnection::DatabaseConnection(const QString& dbPath, const QString& databaseName)
: _dbName(databaseName) : _dbName(databaseName)
, _dbPath(dbPath) , _dbPath(dbPath)
, _db(QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), databaseName))
{ {
auto db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), databaseName); const auto dbDir = FileHelpers::getDirname(_dbPath);
QDir().mkpath(FileHelpers::getDirname(dbPath)); if(!QDir().exists(dbDir))
db.setDatabaseName(dbPath); QDir().mkpath(dbDir);
_db.setDatabaseName(_dbPath);
} }
void DatabaseConnection::withConnection(const QString& dbPath, const QString &dbName, bool DatabaseConnection::withConnection(const QString& dbPath, const QString &dbName,
const std::function<void(DatabaseConnection)> &actions) { const std::function<void(DatabaseConnection)> &actions)
{
DatabaseConnection conn(dbPath, dbName); DatabaseConnection conn(dbPath, dbName);
conn.connect(); if(!conn.connect())
try { return false;
actions(conn); actions(conn);
} catch(const std::runtime_error& e) { bool rtn = true;
qWarning() << "Error running action: " << e.what(); if( conn._db.lastError().type() != QSqlError::NoError)
} rtn = false;
conn.close(); conn.close();
QSqlDatabase::removeDatabase(dbName); QSqlDatabase::removeDatabase(dbPath);
return rtn;
} }
bool DatabaseConnection::connect() bool DatabaseConnection::connect()
{ {
auto db = getDB(); if (!_db.open())
if (!db.open()) {
return false; return false;
throw db.lastError();
}
return migrateDB(); return migrateDB();
} }
void DatabaseConnection::close() noexcept { getDB().close(); } qint64 DatabaseConnection::createEvidence(const QString &filepath, const QString &operationSlug, const QString &contentType)
{
qint64 DatabaseConnection::createEvidence(const QString &filepath, const QString &operationSlug, auto qKeys = QStringLiteral("path, operation_slug, content_type, recorded_date");
const QString &contentType) { auto qValues = QStringLiteral("?, ?, ?, datetime('now')");
return doInsert(getDB(), auto qStr = _sqlBasicInsert.arg(_tblEvidence, qKeys, qValues);
"INSERT INTO evidence" return doInsert(_db, qStr, {filepath, operationSlug, contentType});
" (path, operation_slug, content_type, recorded_date)"
" VALUES"
" (?, ?, ?, datetime('now'))",
{filepath, operationSlug, contentType});
} }
qint64 DatabaseConnection::createFullEvidence(const model::Evidence &evidence) { qint64 DatabaseConnection::createFullEvidence(const model::Evidence &evidence) {
return doInsert(getDB(), auto qKeys = QStringLiteral("path, operation_slug, content_type, description, error, recorded_date, upload_date");
"INSERT INTO evidence" auto qValues = QStringLiteral("?, ?, ?, ?, ?, ?, ?");
" (path, operation_slug, content_type, description, error, recorded_date, upload_date)" auto qStr = _sqlBasicInsert.arg(_tblEvidence, qKeys, qValues);
" VALUES" return doInsert(_db, qStr,
" (?, ?, ?, ?, ?, ?, ?)",
{evidence.path, evidence.operationSlug, evidence.contentType, evidence.description, {evidence.path, evidence.operationSlug, evidence.contentType, evidence.description,
evidence.errorText, evidence.recordedDate, evidence.uploadDate}); evidence.errorText, evidence.recordedDate, evidence.uploadDate});
} }
void DatabaseConnection::batchCopyFullEvidence(const QList<model::Evidence> &evidence) { void DatabaseConnection::batchCopyFullEvidence(const QList<model::Evidence> &evidence) {
QString baseQuery = "INSERT INTO evidence" auto baseQuery = QStringLiteral("INSERT INTO evidence (%1) VALUES %2").arg(_evidenceAllKeys, QStringLiteral("%1"));
" (id, path, operation_slug, content_type, description, error, recorded_date, upload_date)"
" VALUES %1";
int varsPerRow = 8; // count number of "?" int varsPerRow = 8; // count number of "?"
std::function<QVariantList(int)> getItemValues = [evidence](int i){ std::function<QVariantList(int)> getItemValues = [evidence](int i){
auto item = evidence.at(i); auto item = evidence.at(i);
@ -79,28 +72,13 @@ void DatabaseConnection::batchCopyFullEvidence(const QList<model::Evidence> &evi
batchInsert(baseQuery, varsPerRow, evidence.size(), getItemValues); batchInsert(baseQuery, varsPerRow, evidence.size(), getItemValues);
} }
qint64 DatabaseConnection::copyFullEvidence(const model::Evidence &evidence) {
return doInsert(getDB(),
"INSERT INTO evidence"
" (id, path, operation_slug, content_type, description, error, recorded_date, upload_date)"
" VALUES"
" (?, ?, ?, ?, ?, ?, ?)",
{evidence.id, evidence.path, evidence.operationSlug, evidence.contentType,
evidence.description, evidence.errorText, evidence.recordedDate,
evidence.uploadDate});
}
model::Evidence DatabaseConnection::getEvidenceDetails(qint64 evidenceID)
model::Evidence DatabaseConnection::getEvidenceDetails(qint64 evidenceID) { {
model::Evidence rtn; model::Evidence rtn;
auto query = executeQuery(getDB(), auto qStr = QStringLiteral("%1 WHERE id=? LIMIT 1").arg(_sqlSelectTemplate.arg(_evidenceAllKeys, _tblEvidence));
"SELECT" auto query = executeQuery(_db, qStr, {evidenceID});
" id, path, operation_slug, content_type, description, error, recorded_date, upload_date" if (_db.lastError().type() == QSqlError::NoError && query.first()) {
" FROM evidence"
" WHERE id=? LIMIT 1",
{evidenceID});
if (query.first()) {
rtn.id = query.value(QStringLiteral("id")).toLongLong(); rtn.id = query.value(QStringLiteral("id")).toLongLong();
rtn.path = query.value(QStringLiteral("path")).toString(); rtn.path = query.value(QStringLiteral("path")).toString();
rtn.operationSlug = query.value(QStringLiteral("operation_slug")).toString(); rtn.operationSlug = query.value(QStringLiteral("operation_slug")).toString();
@ -109,43 +87,39 @@ model::Evidence DatabaseConnection::getEvidenceDetails(qint64 evidenceID) {
rtn.errorText = query.value(QStringLiteral("error")).toString(); rtn.errorText = query.value(QStringLiteral("error")).toString();
rtn.recordedDate = query.value(QStringLiteral("recorded_date")).toDateTime(); rtn.recordedDate = query.value(QStringLiteral("recorded_date")).toDateTime();
rtn.uploadDate = query.value(QStringLiteral("upload_date")).toDateTime(); rtn.uploadDate = query.value(QStringLiteral("upload_date")).toDateTime();
rtn.recordedDate.setTimeSpec(Qt::UTC); rtn.recordedDate.setTimeSpec(Qt::UTC);
rtn.uploadDate.setTimeSpec(Qt::UTC); rtn.uploadDate.setTimeSpec(Qt::UTC);
rtn.tags = getTagsForEvidenceID(evidenceID); rtn.tags = getTagsForEvidenceID(evidenceID);
} } else {
else { rtn.id = -1;
qWarning() << "Could not find evidence with id: " << evidenceID;
} }
return rtn; return rtn;
} }
void DatabaseConnection::updateEvidenceDescription(const QString &newDescription, bool DatabaseConnection::updateEvidenceDescription(const QString &newDescription, qint64 evidenceID)
qint64 evidenceID) { {
executeQuery(getDB(), "UPDATE evidence SET description=? WHERE id=?", {newDescription, evidenceID}); auto q = executeQuery(_db, QStringLiteral("UPDATE evidence SET description=? WHERE id=?"), {newDescription, evidenceID});
return (q.lastError().type() == QSqlError::NoError);
} }
bool DatabaseConnection::deleteEvidence(qint64 evidenceID) bool DatabaseConnection::deleteEvidence(qint64 evidenceID)
{ {
auto q = executeQuery(getDB(), "DELETE FROM evidence WHERE id=?", {evidenceID}); auto q = executeQuery(_db, QStringLiteral("DELETE FROM evidence WHERE id=?"), {evidenceID});
if (q.lastError().type() == QSqlError::NoError) return (q.lastError().type() == QSqlError::NoError);
return true;
qWarning() << "Unable to Delete " << evidenceID << " " << q.lastError().text();
return false;
} }
void DatabaseConnection::updateEvidenceError(const QString &errorText, qint64 evidenceID) { bool DatabaseConnection::updateEvidenceError(const QString &errorText, qint64 evidenceID) {
executeQuery(getDB(), "UPDATE evidence SET error=? WHERE id=?", {errorText, evidenceID}); auto q = executeQuery(_db, QStringLiteral("UPDATE evidence SET error=? WHERE id=?"), {errorText, evidenceID});
return (q.lastError().type() == QSqlError::NoError);
} }
void DatabaseConnection::updateEvidenceSubmitted(qint64 evidenceID) { void DatabaseConnection::updateEvidenceSubmitted(qint64 evidenceID) {
executeQuery(getDB(), "UPDATE evidence SET upload_date=datetime('now') WHERE id=?", {evidenceID}); executeQuery(_db, QStringLiteral("UPDATE evidence SET upload_date=datetime('now') WHERE id=?"), {evidenceID});
} }
QList<model::Tag> DatabaseConnection::getTagsForEvidenceID(qint64 evidenceID) { QList<model::Tag> DatabaseConnection::getTagsForEvidenceID(qint64 evidenceID) {
QList<model::Tag> tags; QList<model::Tag> tags;
auto getTagQuery = executeQuery(getDB(), "SELECT id, tag_id, name FROM tags WHERE evidence_id=?", auto getTagQuery = executeQuery(_db, QStringLiteral("SELECT id, tag_id, name FROM tags WHERE evidence_id=?"),
{evidenceID}); {evidenceID});
while (getTagQuery.next()) { while (getTagQuery.next()) {
auto tag = model::Tag(getTagQuery.value(QStringLiteral("id")).toLongLong(), auto tag = model::Tag(getTagQuery.value(QStringLiteral("id")).toLongLong(),
@ -160,7 +134,7 @@ QList<model::Tag> DatabaseConnection::getFullTagsForEvidenceIDs(
const QList<qint64>& evidenceIDs) { const QList<qint64>& evidenceIDs) {
QList<model::Tag> tags; QList<model::Tag> tags;
batchQuery("SELECT id, evidence_id, tag_id, name FROM tags WHERE evidence_id IN (%1)", 1, evidenceIDs.size(), batchQuery(QStringLiteral("SELECT id, evidence_id, tag_id, name FROM tags WHERE evidence_id IN (%1)"), 1, evidenceIDs.size(),
[evidenceIDs](unsigned int index){ [evidenceIDs](unsigned int index){
return QVariantList{evidenceIDs[index]}; return QVariantList{evidenceIDs[index]};
}, },
@ -175,33 +149,37 @@ QList<model::Tag> DatabaseConnection::getFullTagsForEvidenceIDs(
return tags; return tags;
} }
void DatabaseConnection::setEvidenceTags(const QList<model::Tag> &newTags, qint64 evidenceID) bool DatabaseConnection::setEvidenceTags(const QList<model::Tag> &newTags, qint64 evidenceID)
{ {
// todo: this this actually work?
if(newTags.isEmpty()) if(newTags.isEmpty())
return; return false;
auto db = getDB();
QVariantList newTagIds; QVariantList newTagIds;
for (const auto &tag : newTags) { for (const auto &tag : newTags)
newTagIds.append(tag.serverTagId); newTagIds.append(tag.serverTagId);
}
executeQuery(db, "DELETE FROM tags WHERE tag_id NOT IN (?) AND evidence_id = ?",
{newTagIds, evidenceID});
auto currentTagsResult = auto qDelStr = QStringLiteral("DELETE FROM tags WHERE tag_id NOT IN (?) AND evidence_id = ?");
executeQuery(db, "SELECT tag_id FROM tags WHERE evidence_id = ?", {evidenceID}); auto a = executeQuery(_db, qDelStr, {newTagIds, evidenceID});
if(a.lastError().type() != QSqlError::NoError)
return false;
auto qSelStr = QStringLiteral("SELECT tag_id FROM tags WHERE evidence_id = ?");
auto currentTagsResult = executeQuery(_db, qSelStr, {evidenceID});
if (currentTagsResult.lastError().type() != QSqlError::NoError)
return false;
QList<qint64> currentTags; QList<qint64> currentTags;
while (currentTagsResult.next()) { while (currentTagsResult.next())
currentTags.append(currentTagsResult.value(QStringLiteral("tag_id")).toLongLong()); currentTags.append(currentTagsResult.value(QStringLiteral("tag_id")).toLongLong());
}
struct dataset { struct dataset {
qint64 evidenceID = 0; qint64 evidenceID = 0;
qint64 tagID = 0; qint64 tagID = 0;
QString name; QString name;
}; };
QList<dataset> tagDataToInsert; QList<dataset> tagDataToInsert;
QString baseQuery = "INSERT INTO tags (evidence_id, tag_id, name) VALUES ";
QString baseQuery = QStringLiteral("INSERT INTO tags (evidence_id, tag_id, name) VALUES ");
for (const auto &newTag : newTags) { for (const auto &newTag : newTags) {
if (currentTags.count(newTag.serverTagId) == 0) { if (currentTags.count(newTag.serverTagId) == 0) {
dataset item; dataset item;
@ -216,19 +194,23 @@ void DatabaseConnection::setEvidenceTags(const QList<model::Tag> &newTags, qint6
// sqlite indicates it's default is 100 passed parameter, but it can "handle thousands" // sqlite indicates it's default is 100 passed parameter, but it can "handle thousands"
if (!tagDataToInsert.empty()) { if (!tagDataToInsert.empty()) {
QVariantList args; QVariantList args;
baseQuery += "(?,?,?)"; baseQuery.append(QStringLiteral("(?, ?, ?"));
baseQuery += QString(", (?,?,?)").repeated(int(tagDataToInsert.size() - 1)); baseQuery.append(QString(", (?,?,?)").repeated(int(tagDataToInsert.size() - 1)));
baseQuery.append(QStringLiteral(")"));
for (const auto &item : tagDataToInsert) { for (const auto &item : tagDataToInsert) {
args.append(item.evidenceID); args.append(item.evidenceID);
args.append(item.tagID); args.append(item.tagID);
args.append(item.name); args.append(item.name);
} }
executeQuery(db, baseQuery, args); auto q = executeQuery(_db, baseQuery, args);
if (q.lastError().type() != QSqlError::NoError)
return false;
} }
return true;
} }
void DatabaseConnection::batchCopyTags(const QList<model::Tag> &allTags) { void DatabaseConnection::batchCopyTags(const QList<model::Tag> &allTags) {
QString baseQuery = "INSERT INTO tags (id, evidence_id, tag_id, name) VALUES %1"; QString baseQuery = QStringLiteral("INSERT INTO tags (id, evidence_id, tag_id, name) VALUES %1");
int varsPerRow = 4; int varsPerRow = 4;
std::function<QVariantList(int)> getItemValues = [allTags](int i){ std::function<QVariantList(int)> getItemValues = [allTags](int i){
model::Tag item = allTags.at(i); model::Tag item = allTags.at(i);
@ -237,23 +219,26 @@ void DatabaseConnection::batchCopyTags(const QList<model::Tag> &allTags) {
batchInsert(baseQuery, varsPerRow, allTags.size(), getItemValues); batchInsert(baseQuery, varsPerRow, allTags.size(), getItemValues);
} }
DBQuery DatabaseConnection::buildGetEvidenceWithFiltersQuery(const EvidenceFilters &filters) { DBQuery DatabaseConnection::buildGetEvidenceWithFiltersQuery(const EvidenceFilters &filters)
QString query = {
"SELECT" QString query = _sqlSelectTemplate.arg(_evidenceAllKeys, _tblEvidence);
" id, path, operation_slug, content_type, description, error, recorded_date, upload_date"
" FROM evidence";
QVariantList values; QVariantList values;
QStringList parts; QStringList parts;
if (filters.hasError != Tri::Any) { if (filters.hasError != Tri::Any) {
parts.append(" error LIKE ? "); parts.append(QStringLiteral(" error LIKE ? "));
// _% will ensure at least one character exists in the error column, ensuring it's populated // _% will ensure at least one character exists in the error column, ensuring it's populated
values.append(filters.hasError == Tri::Yes ? "_%" : ""); values.append(filters.hasError == Tri::Yes ? QStringLiteral("_%") : QString());
} }
if (filters.submitted != Tri::Any) { if (filters.submitted != Tri::Any) {
parts.append((filters.submitted == Tri::Yes) ? " upload_date IS NOT NULL " auto sub = QStringLiteral(" upload_data IS%1NULL");
: " upload_date IS NULL "); if(filters.submitted == Tri::Yes)
parts.append(sub.arg(QStringLiteral(" NOT ")));
else
parts.append(sub.arg(QStringLiteral(" ")));
} }
if (!filters.operationSlug.isEmpty()) { if (!filters.operationSlug.isEmpty()) {
parts.append(" operation_slug = ? "); parts.append(" operation_slug = ? ");
values.append(filters.operationSlug); values.append(filters.operationSlug);
@ -273,24 +258,24 @@ DBQuery DatabaseConnection::buildGetEvidenceWithFiltersQuery(const EvidenceFilte
} }
if (!parts.empty()) { if (!parts.empty()) {
query += " WHERE " + parts.at(0); query.append(QStringLiteral(" WHERE %1").arg(parts.at(0)));
for (size_t i = 1; i < parts.size(); i++) { for (size_t i = 1; i < parts.size(); i++)
query += " AND " + parts.at(i); query.append(QStringLiteral(" AND %1").arg(parts.at(i)));
}
} }
return DBQuery(query, values); return DBQuery(query, values);
} }
void DatabaseConnection::updateEvidencePath(const QString& newPath, qint64 evidenceID) { void DatabaseConnection::updateEvidencePath(const QString& newPath, qint64 evidenceID)
executeQuery(getDB(), "UPDATE evidence SET path=? WHERE id=?", {newPath, evidenceID}); {
executeQuery(_db, QStringLiteral("UPDATE evidence SET path=? WHERE id=?"), {newPath, evidenceID});
} }
QList<model::Evidence> DatabaseConnection::getEvidenceWithFilters( QList<model::Evidence> DatabaseConnection::getEvidenceWithFilters(const EvidenceFilters &filters)
const EvidenceFilters &filters) { {
auto dbQuery = buildGetEvidenceWithFiltersQuery(filters); auto dbQuery = buildGetEvidenceWithFiltersQuery(filters);
auto resultSet = executeQuery(getDB(), dbQuery.query(), dbQuery.values()); auto resultSet = executeQuery(_db, dbQuery.query(), dbQuery.values());
QList<model::Evidence> allEvidence; QList<model::Evidence> allEvidence;
while (resultSet.next()) { while (resultSet.next()) {
model::Evidence evi; model::Evidence evi;
evi.id = resultSet.value(QStringLiteral("id")).toLongLong(); evi.id = resultSet.value(QStringLiteral("id")).toLongLong();
@ -301,10 +286,8 @@ QList<model::Evidence> DatabaseConnection::getEvidenceWithFilters(
evi.errorText = resultSet.value(QStringLiteral("error")).toString(); evi.errorText = resultSet.value(QStringLiteral("error")).toString();
evi.recordedDate = resultSet.value(QStringLiteral("recorded_date")).toDateTime(); evi.recordedDate = resultSet.value(QStringLiteral("recorded_date")).toDateTime();
evi.uploadDate = resultSet.value(QStringLiteral("upload_date")).toDateTime(); evi.uploadDate = resultSet.value(QStringLiteral("upload_date")).toDateTime();
evi.recordedDate.setTimeSpec(Qt::UTC); evi.recordedDate.setTimeSpec(Qt::UTC);
evi.uploadDate.setTimeSpec(Qt::UTC); evi.uploadDate.setTimeSpec(Qt::UTC);
allEvidence.append(evi); allEvidence.append(evi);
} }
@ -312,12 +295,11 @@ QList<model::Evidence> DatabaseConnection::getEvidenceWithFilters(
} }
QList<model::Evidence> DatabaseConnection::createEvidenceExportView( QList<model::Evidence> DatabaseConnection::createEvidenceExportView(
const QString& pathToExport, const EvidenceFilters& filters, DatabaseConnection *runningDB) { const QString& pathToExport, const EvidenceFilters& filters, DatabaseConnection *runningDB)
{
QList<model::Evidence> exportEvidence; QList<model::Evidence> exportEvidence;
auto exportViewAction = [runningDB, filters, &exportEvidence](DatabaseConnection exportDB) { auto exportViewAction = [runningDB, filters, &exportEvidence](DatabaseConnection exportDB) {
exportEvidence = runningDB->getEvidenceWithFilters(filters); exportEvidence = runningDB->getEvidenceWithFilters(filters);
exportDB.batchCopyFullEvidence(exportEvidence); exportDB.batchCopyFullEvidence(exportEvidence);
QList<qint64> evidenceIds; QList<qint64> evidenceIds;
evidenceIds.resize(exportEvidence.size()); evidenceIds.resize(exportEvidence.size());
@ -326,65 +308,46 @@ QList<model::Evidence> DatabaseConnection::createEvidenceExportView(
QList<model::Tag> tags = runningDB->getFullTagsForEvidenceIDs(evidenceIds); QList<model::Tag> tags = runningDB->getFullTagsForEvidenceIDs(evidenceIds);
exportDB.batchCopyTags(tags); exportDB.batchCopyTags(tags);
}; };
withConnection(pathToExport, QStringLiteral("exportDB"), exportViewAction); withConnection(pathToExport, QStringLiteral("exportDB"), exportViewAction);
return exportEvidence; return exportEvidence;
} }
// migrateDB checks the migration status and then performs the full migration for any bool DatabaseConnection::migrateDB()
// lacking update. {
//
// Throws exceptions/FileError if a migration file cannot be found.
bool DatabaseConnection::migrateDB() {
auto db = getDB();
qInfo() << "Checking database state"; qInfo() << "Checking database state";
auto migrationsToApply = DatabaseConnection::getUnappliedMigrations(db); auto migrationsToApply = DatabaseConnection::getUnappliedMigrations();
for (const QString &newMigration : migrationsToApply) { for (const auto &newMigration : migrationsToApply) {
QFile migrationFile(QStringLiteral(":/migrations/%1").arg(newMigration)); QFile migrationFile(QStringLiteral("%1/%2").arg(_migrationPath, newMigration));
auto ok = migrationFile.open(QFile::ReadOnly); if (!migrationFile.open(QFile::ReadOnly))
if (!ok)
return false; return false;
auto content = QString(migrationFile.readAll()); auto content = QString(migrationFile.readAll());
migrationFile.close(); migrationFile.close();
qInfo() << "Applying Migration: " << newMigration; qInfo() << "Applying Migration: " << newMigration;
auto upScript = extractMigrateUpContent(content); auto upScript = extractMigrateUpContent(content);
executeQuery(db, upScript); executeQuery(_db, upScript);
executeQuery(db, executeQuery(_db, _sqlAddAppliedMigration, {newMigration});
"INSERT INTO migrations (migration_name, applied_at) VALUES (?, datetime('now'))",
{newMigration});
} }
qInfo() << "All migrations applied"; qInfo() << "All migrations applied";
return true; return true;
} }
// getUnappliedMigrations retrieves a list of all of the migrations that have not been applied QStringList DatabaseConnection::getUnappliedMigrations()
// to the local database.
//
// Note: All sql files must end in ".sql" to be picked up
//
// Throws:
// * BadDatabaseStateError if some migrations have been applied that are not known
// * QSqlError if database queries fail
QStringList DatabaseConnection::getUnappliedMigrations(const QSqlDatabase &db)
{ {
QDir migrationsDir(QStringLiteral(":/migrations")); QDir migrationsDir(_migrationPath);
const auto allMigrations = migrationsDir.entryList(QDir::Files, QDir::Name); const auto allMigrations = migrationsDir.entryList(QDir::Files, QDir::Name);
QStringList appliedMigrations; QStringList appliedMigrations;
QStringList migrationsToApply; QStringList migrationsToApply;
auto queryResult = executeQueryNoThrow(db, "SELECT migration_name FROM migrations"); auto queryResult = executeQueryNoThrow(_db, _sqlSelectTemplate.arg(_migration_name, _tblMigrations));
QSqlQuery* dbMigrations = &queryResult.query; QSqlQuery* dbMigrations = &queryResult.query;
while (queryResult.success && queryResult.query.next()) { while (queryResult.success && queryResult.query.next())
appliedMigrations << dbMigrations->value(QStringLiteral("migration_name")).toString(); appliedMigrations << dbMigrations->value(_migration_name).toString();
}
// compare the two list to find gaps // compare the two list to find gaps
for (const QString &possibleMigration : allMigrations) { for (const auto &possibleMigration : allMigrations) {
if (!possibleMigration.endsWith(QStringLiteral(".sql"))) if (!possibleMigration.endsWith(QStringLiteral(".sql")))
continue; // assume non-sql files aren't actual migrations. continue;
auto foundIndex = appliedMigrations.indexOf(possibleMigration); auto foundIndex = appliedMigrations.indexOf(possibleMigration);
if (foundIndex == -1) if (foundIndex == -1)
migrationsToApply << possibleMigration; migrationsToApply << possibleMigration;
@ -399,7 +362,8 @@ 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
{
QString upContent; QString upContent;
const QStringList lines = allContent.split(_newLine); const QStringList lines = allContent.split(_newLine);
for (const QString &line : lines) { for (const QString &line : lines) {
@ -415,52 +379,35 @@ QString DatabaseConnection::extractMigrateUpContent(const QString &allContent) n
// executeQuery simply attempts to execute the given stmt with the passed args. The statement is // executeQuery simply attempts to execute the given stmt with the passed args. The statement is
// first prepared, and arg placements can be specified with "?" // first prepared, and arg placements can be specified with "?"
//
// Throws: QSqlError when a query error occurs
QSqlQuery DatabaseConnection::executeQuery(const QSqlDatabase& db, const QString &stmt, QSqlQuery DatabaseConnection::executeQuery(const QSqlDatabase& db, const QString &stmt,
const QVariantList &args) { const QVariantList &args) {
auto result = executeQueryNoThrow(db, stmt, args); auto result = executeQueryNoThrow(db, stmt, args);
if (!result.success) { if (!result.success)
throw result.err; qWarning() << "Error executing Query: " << result.err.text();
}
return std::move(result.query); return std::move(result.query);
} }
QueryResult DatabaseConnection::executeQueryNoThrow(const QSqlDatabase& db, const QString &stmt, QueryResult DatabaseConnection::executeQueryNoThrow(const QSqlDatabase& db, const QString &stmt,
const QVariantList &args) noexcept { const QVariantList &args) noexcept
{
QSqlQuery query(db); QSqlQuery query(db);
if (!query.prepare(stmt))
bool prepared = query.prepare(stmt);
if (!prepared) {
return QueryResult(std::move(query)); return QueryResult(std::move(query));
} for (const auto &arg : args)
for (const auto &arg : args) {
query.addBindValue(arg); query.addBindValue(arg);
}
query.exec(); query.exec();
return QueryResult(std::move(query)); return QueryResult(std::move(query));
} }
// doInsert is a version of executeQuery that returns the last inserted id, rather than the // doInsert is a version of executeQuery that returns the last inserted id, rather than the
// underlying query/response // underlying query/response
// // Logs then returns -1
// Throws: QSqlError when a query error occurs qint64 DatabaseConnection::doInsert(const QSqlDatabase& db, const QString &stmt, const QVariantList &args)
qint64 DatabaseConnection::doInsert(const QSqlDatabase& db, const QString &stmt, {
const QVariantList &args) {
auto query = executeQuery(db, stmt, args); auto query = executeQuery(db, stmt, args);
if(query.lastInsertId() != QVariant())
return query.lastInsertId().toLongLong(); return query.lastInsertId().toLongLong();
} return -1;
QSqlDatabase DatabaseConnection::getDB()
{
return QSqlDatabase::database(_dbName);
}
QString DatabaseConnection::getDatabasePath()
{
return _dbPath;
} }
void DatabaseConnection::batchInsert(const QString& baseQuery, unsigned int varsPerRow, unsigned int numRows, void DatabaseConnection::batchInsert(const QString& baseQuery, unsigned int varsPerRow, unsigned int numRows,
@ -484,7 +431,6 @@ void DatabaseConnection::batchQuery(const QString &baseQuery, unsigned int varsP
variableTemplate = QString("?,").repeated(int(varsPerRow)); variableTemplate = QString("?,").repeated(int(varsPerRow));
} }
int runningRowIndex = 0; // tracks what row is next to be encoded/"inserted" int runningRowIndex = 0; // tracks what row is next to be encoded/"inserted"
auto db = getDB();
/// prepArgString generates a string that looks like ?,?,?, with as many ? as rowInsertTemplate * numRows /// prepArgString generates a string that looks like ?,?,?, with as many ? as rowInsertTemplate * numRows
auto prepArgString = [variableTemplate](unsigned int numRows){ auto prepArgString = [variableTemplate](unsigned int numRows){
auto inst = variableTemplate.repeated(int(numRows)); auto inst = variableTemplate.repeated(int(numRows));
@ -501,8 +447,8 @@ void DatabaseConnection::batchQuery(const QString &baseQuery, unsigned int varsP
return values; return values;
}; };
/// runQuery executes the given query, and iterates over the result set /// runQuery executes the given query, and iterates over the result set
auto runQuery = [db, decodeRows](const QString &query, const QVariantList& values) { auto runQuery = [this, decodeRows](const QString &query, const QVariantList& values) {
auto completedQuery = executeQuery(db, query, values); auto completedQuery = executeQuery(_db, query, values);
while (completedQuery.next()) { while (completedQuery.next()) {
decodeRows(completedQuery); decodeRows(completedQuery);
} }

View File

@ -28,9 +28,15 @@ class DBQuery {
inline QString query() { return _query; } inline QString query() { return _query; }
inline QVariantList values() { return _values; } inline QVariantList values() { return _values; }
}; };
/**
* @brief The DatabaseConnection class Interface to the local database
* All Changes / reads to db should return true on success
* any failed actions can have erorrs checked with DatabaseConnection::errorString()
*/
class DatabaseConnection { class DatabaseConnection {
public: public:
const unsigned long SQLITE_MAX_VARS = 999;
QString getDatabasePath() { return _dbPath; }
/** /**
* @brief DatabaseConnection construct a connect to the database, * @brief DatabaseConnection construct a connect to the database,
* Uses QSQLite Driver if the driver is missing the app to exit with code 255. * Uses QSQLite Driver if the driver is missing the app to exit with code 255.
@ -48,29 +54,39 @@ class DatabaseConnection {
* Specifically, it should NOT be the value from Constants::databaseName) * Specifically, it should NOT be the value from Constants::databaseName)
* @param actions A function that will execute after a connection is established. This is where * @param actions A function that will execute after a connection is established. This is where
* all db interactions should occur. * all db interactions should occur.
* Returns True is successful
*/ */
static void withConnection(const QString& dbPath, const QString &dbName, static bool withConnection(const QString& dbPath, const QString &dbName,
const std::function<void(DatabaseConnection)> &actions); const std::function<void(DatabaseConnection)> &actions);
///Return the last Error
QString errorString() {return _db.lastError().text();}
bool connect(); bool connect();
void close() noexcept; void close() noexcept {_db.close();}
static DBQuery buildGetEvidenceWithFiltersQuery(const EvidenceFilters &filters); static DBQuery buildGetEvidenceWithFiltersQuery(const EvidenceFilters &filters);
model::Evidence getEvidenceDetails(qint64 evidenceID); model::Evidence getEvidenceDetails(qint64 evidenceID);
QList<model::Evidence> getEvidenceWithFilters(const EvidenceFilters &filters); QList<model::Evidence> getEvidenceWithFilters(const EvidenceFilters &filters);
/// Return -1 if Failed
qint64 createEvidence(const QString &filepath, const QString &operationSlug, qint64 createEvidence(const QString &filepath, const QString &operationSlug,
const QString &contentType); const QString &contentType);
qint64 createFullEvidence(const model::Evidence &evidence); qint64 createFullEvidence(const model::Evidence &evidence);
void batchCopyFullEvidence(const QList<model::Evidence> &evidence); void batchCopyFullEvidence(const QList<model::Evidence> &evidence);
qint64 copyFullEvidence(const model::Evidence &evidence); qint64 copyFullEvidence(const model::Evidence &evidence);
void updateEvidenceDescription(const QString &newDescription, qint64 evidenceID); /**
void updateEvidenceError(const QString &errorText, qint64 evidenceID); * @brief updateEvidenceDescription
* @param newDescription
* @param evidenceID
* @return True if successful
*/
bool updateEvidenceDescription(const QString &newDescription, qint64 evidenceID);
bool updateEvidenceError(const QString &errorText, qint64 evidenceID);
void updateEvidenceSubmitted(qint64 evidenceID); void updateEvidenceSubmitted(qint64 evidenceID);
void updateEvidencePath(const QString& newPath, qint64 evidenceID); void updateEvidencePath(const QString& newPath, qint64 evidenceID);
void setEvidenceTags(const QList<model::Tag> &newTags, qint64 evidenceID); bool setEvidenceTags(const QList<model::Tag> &newTags, qint64 evidenceID);
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);
@ -91,25 +107,38 @@ class DatabaseConnection {
DatabaseConnection *runningDB); DatabaseConnection *runningDB);
QList<model::Tag> getTagsForEvidenceID(qint64 evidenceID); QList<model::Tag> getTagsForEvidenceID(qint64 evidenceID);
/// getDatabasePath returns the filepath associated with the loaded database QSqlError lastError() {return _db.lastError();}
QString getDatabasePath();
public:
const unsigned long SQLITE_MAX_VARS = 999;
private: private:
QString _dbName; QString _dbName;
QString _dbPath; QString _dbPath;
QSqlDatabase _db = QSqlDatabase();
inline static const auto _migrateUp = QStringLiteral("-- +migrate up"); inline static const auto _migrateUp = QStringLiteral("-- +migrate up");
inline static const auto _migrateDown = QStringLiteral("-- +migrate down"); inline static const auto _migrateDown = QStringLiteral("-- +migrate down");
inline static const auto _newLine = QStringLiteral("\n"); inline static const auto _newLine = QStringLiteral("\n");
inline static const auto _lineTemplate = QStringLiteral("%1").append(_newLine); inline static const auto _lineTemplate = QStringLiteral("%1").append(_newLine);
inline static const auto _migrationPath = QStringLiteral(":/migrations");
inline static const auto _sqlSelectTemplate = QStringLiteral("SELECT %1 FROM %2");
inline static const auto _sqlBasicInsert = QStringLiteral("INSERT INTO %1 (%2) VALUES (%3)");
inline static const auto _sqlAddAppliedMigration = QStringLiteral("INSERT INTO migrations (migration_name, applied_at) VALUES (?, datetime('now'))");
inline static const auto _migration_name = QStringLiteral("migration_name");
inline static const auto _tblEvidence = QStringLiteral("evidence");
inline static const auto _tblMigrations = QStringLiteral("migrations");
inline static const auto _evidenceAllKeys = QStringLiteral("id, path, operation_slug, content_type, description, error, recorded_date, upload_date");
/**
* @brief migrateDB - Check migration status and apply any outstanding ones
* @return true if successful
*/
bool migrateDB(); bool migrateDB();
QSqlDatabase getDB();
static QStringList getUnappliedMigrations(const QSqlDatabase &db); /**
static QString extractMigrateUpContent(const QString &allContent) noexcept; * @brief getUnappliedMigrations retrieves a list of all of the migrations that have not been applied to the database db
* Note: only files ending in ".sql" are checked
* @return List of migrations that have not be applied
*/
QStringList getUnappliedMigrations();
QString extractMigrateUpContent(const QString &allContent) noexcept;
static QSqlQuery executeQuery(const QSqlDatabase& db, const QString &stmt, static QSqlQuery executeQuery(const QSqlDatabase& db, const QString &stmt,
const QVariantList &args = {}); const QVariantList &args = {});
@ -118,6 +147,14 @@ class DatabaseConnection {
/// QueryResult.sucess/QueryResult.err fields to determine the actual result. /// QueryResult.sucess/QueryResult.err fields to determine the actual result.
static QueryResult executeQueryNoThrow(const QSqlDatabase& db, const QString &stmt, static QueryResult executeQueryNoThrow(const QSqlDatabase& db, const QString &stmt,
const QVariantList &args = {}) noexcept; const QVariantList &args = {}) noexcept;
/**
* @brief doInsert is a version of executeQuery that returns the last inserted id, rather than the underlying query/response
* @param db database to act upon
* @param stmt sql to run
* @param args args
* @return Inserted ID or -1 if failed.
*/
static qint64 doInsert(const QSqlDatabase &db, const QString &stmt, const QVariantList &args); static qint64 doInsert(const QSqlDatabase &db, const QString &stmt, const QVariantList &args);
/** /**

View File

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

View File

@ -1,76 +0,0 @@
// Copyright 2020, Verizon Media
// Licensed under the terms of MIT. See LICENSE file in project root for terms.
#pragma once
#include <QFileDevice>
#include <stdexcept>
#include <string>
class FileError : public std::runtime_error {
public:
/// mkError constructs an std::runtime_error with the given details.
static FileError mkError(QString msg, QString path, QFileDevice::FileError err) {
return FileError::mkError(msg.toStdString(), path.toStdString(), err);
}
/// mkError constructs an std::runtime_error with the given details.
static FileError mkError(std::string msg, std::string path, QFileDevice::FileError err) {
std::string suberror;
switch (err) {
case QFileDevice::ReadError:
suberror = "Error reading file";
break;
case QFileDevice::WriteError:
suberror = "Error writing file";
break;
case QFileDevice::FatalError:
suberror = "Fatal error occurred";
break;
case QFileDevice::ResourceError:
suberror = "Insufficient resources available";
break;
case QFileDevice::OpenError:
suberror = "Could not open file";
break;
case QFileDevice::AbortError:
suberror = "Operation was aborted";
break;
case QFileDevice::TimeOutError:
suberror = "Operation timed out";
break;
case QFileDevice::UnspecifiedError:
suberror = "Unknown Error";
break;
case QFileDevice::RemoveError:
suberror = "Unable to remove file";
break;
case QFileDevice::RenameError:
suberror = "Unable to rename/move file";
break;
case QFileDevice::PositionError:
suberror = "Position error"; // I don't think we'll ever enounter this error
break;
case QFileDevice::ResizeError:
suberror = "Unable to resize file";
break;
case QFileDevice::PermissionsError:
suberror = "Unable to access file";
break;
case QFileDevice::CopyError:
suberror = "Unable to copy file";
break;
case QFileDevice::NoError:
suberror = "Actually, no error occurred -- just bad programming.";
break;
}
FileError wrappedErr(msg + " (path: " + path + "): " + suberror);
wrappedErr.fileDeviceError = err;
return wrappedErr;
}
public:
QFileDevice::FileError fileDeviceError;
private:
FileError(std::string msg) : std::runtime_error(msg) {}
};

View File

@ -15,7 +15,6 @@
#include "appconfig.h" #include "appconfig.h"
#include "dtos/tag.h" #include "dtos/tag.h"
#include "exceptions/fileerror.h"
#include "forms/evidence_filter/evidencefilter.h" #include "forms/evidence_filter/evidencefilter.h"
#include "forms/evidence_filter/evidencefilterform.h" #include "forms/evidence_filter/evidencefilterform.h"
#include "helpers/netman.h" #include "helpers/netman.h"
@ -194,23 +193,23 @@ void EvidenceManager::showEvent(QShowEvent* evt) {
resetFilterButtonClicked(); resetFilterButtonClicked();
} }
void EvidenceManager::submitEvidenceTriggered() { void EvidenceManager::submitEvidenceTriggered()
{
loadingAnimation->startAnimation(); loadingAnimation->startAnimation();
evidenceTable->setEnabled(false); // prevent switching evidence while one is being submitted. evidenceTable->setEnabled(false); // prevent switching evidence while one is being submitted.
if (saveData()) { if (!saveData())
return;
evidenceIDForRequest = selectedRowEvidenceID(); evidenceIDForRequest = selectedRowEvidenceID();
try {
model::Evidence evi = db->getEvidenceDetails(evidenceIDForRequest); model::Evidence evi = db->getEvidenceDetails(evidenceIDForRequest);
uploadAssetReply = NetMan::uploadAsset(evi); if(evi.id == -1) {
connect(uploadAssetReply, &QNetworkReply::finished, this, &EvidenceManager::onUploadComplete);
}
catch (QSqlError& e) {
evidenceTable->setEnabled(true); evidenceTable->setEnabled(true);
loadingAnimation->stopAnimation(); loadingAnimation->stopAnimation();
QMessageBox::warning(this, tr("Cannot submit evidence"), QMessageBox::warning(this, tr("Cannot submit evidence"),
tr("Could not retrieve data. Please try again.")); tr("Could not retrieve data. Please try again."));
return;
} }
} uploadAssetReply = NetMan::uploadAsset(evi);
connect(uploadAssetReply, &QNetworkReply::finished, this, &EvidenceManager::onUploadComplete);
} }
void EvidenceManager::deleteEvidenceTriggered() { void EvidenceManager::deleteEvidenceTriggered() {
@ -277,19 +276,15 @@ void EvidenceManager::deleteSet(QList<qint64> ids) {
auto errLogPath = QStringLiteral("%1/%2.log") auto errLogPath = QStringLiteral("%1/%2.log")
.arg(AppConfig::value(CONFIG::EVIDENCEREPO) .arg(AppConfig::value(CONFIG::EVIDENCEREPO)
, QDateTime::currentDateTime().toMSecsSinceEpoch()); , QDateTime::currentDateTime().toMSecsSinceEpoch());
try {
QByteArray dataToWrite = tr("Paths to files that could not be deleted: \n\n %1") QByteArray dataToWrite = tr("Paths to files that could not be deleted: \n\n %1")
.arg(undeletedFiles.join(QStringLiteral("\n"))).toUtf8(); .arg(undeletedFiles.join(QStringLiteral("\n"))).toUtf8();
FileHelpers::writeFile(errLogPath, dataToWrite); logWritten = FileHelpers::writeFile(errLogPath, dataToWrite);
}
catch(FileError &e) {
logWritten = false;
}
QString msg = tr("Some files could not be deleted.");
if (logWritten) { QString msg = tr("Some files could not be deleted.");
if (logWritten)
msg.append(tr(" A list of the excluded files can be found here: \n").arg(errLogPath)); msg.append(tr(" A list of the excluded files can be found here: \n").arg(errLogPath));
}
QMessageBox::warning(this, tr("Could not complete evidence deletion"), msg); QMessageBox::warning(this, tr("Could not complete evidence deletion"), msg);
} }
@ -332,7 +327,8 @@ void EvidenceManager::applyFilterForm(const EvidenceFilters& filter) {
loadEvidence(); loadEvidence();
} }
void EvidenceManager::loadEvidence() { void EvidenceManager::loadEvidence()
{
qint64 reselectId = -1; qint64 reselectId = -1;
if (evidenceTable->selectedItems().size() > 0) { if (evidenceTable->selectedItems().size() > 0) {
reselectId = selectedRowEvidenceID(); reselectId = selectedRowEvidenceID();
@ -340,9 +336,11 @@ void EvidenceManager::loadEvidence() {
evidenceTable->clearContents(); evidenceTable->clearContents();
try {
auto filter = EvidenceFilters::parseFilter(filterTextBox->text()); auto filter = EvidenceFilters::parseFilter(filterTextBox->text());
QList<model::Evidence> operationEvidence = db->getEvidenceWithFilters(filter); QList<model::Evidence> operationEvidence = db->getEvidenceWithFilters(filter);
if(db->lastError().type() != QSqlError::NoError){
qWarning() << "Could not retrieve evidence for operation. Error: " << db->lastError().text();
}
evidenceTable->setRowCount(operationEvidence.size()); evidenceTable->setRowCount(operationEvidence.size());
// removing sorting temporarily to solve a bug (per qt: not a bug) // removing sorting temporarily to solve a bug (per qt: not a bug)
@ -380,10 +378,6 @@ void EvidenceManager::loadEvidence() {
evidenceTable->setCurrentCell(selectRow, 0); evidenceTable->setCurrentCell(selectRow, 0);
} }
} }
catch (QSqlError& e) {
qWarning() << "Could not retrieve evidence for operation. Error: " << e.text();
}
}
// buildBaseEvidenceRow constructs a container for a row of data. // buildBaseEvidenceRow constructs a container for a row of data.
// Note: the row (container) is on the stack, but items in the container // Note: the row (container) is on the stack, but items in the container
@ -432,15 +426,15 @@ void EvidenceManager::setRowText(int row, const model::Evidence& model) {
setColText(COL_DATE_SUBMITTED, uploadDateText); setColText(COL_DATE_SUBMITTED, uploadDateText);
} }
void EvidenceManager::refreshRow(int row) { void EvidenceManager::refreshRow(int row)
{
auto evidenceID = selectedRowEvidenceID(); auto evidenceID = selectedRowEvidenceID();
try {
auto updatedData = db->getEvidenceDetails(evidenceID); auto updatedData = db->getEvidenceDetails(evidenceID);
if (updatedData.id != -1) {
setRowText(row, updatedData); setRowText(row, updatedData);
return;
} }
catch (QSqlError& e) { qWarning() << "Could not refresh table row: " << db->errorString();
qWarning() << "Could not refresh table row: " << e.text();
}
} }
bool EvidenceManager::saveData() { bool EvidenceManager::saveData() {
@ -500,25 +494,13 @@ void EvidenceManager::onUploadComplete() {
NetMan::extractResponse(uploadAssetReply, isValid); NetMan::extractResponse(uploadAssetReply, isValid);
if (!isValid) { if (!isValid) {
auto errMessage = auto errMessage = tr("Unable to upload evidence: Network error (%1)").arg(uploadAssetReply->errorString());
tr("Unable to upload evidence: Network error (%1)").arg(uploadAssetReply->errorString());
try {
db->updateEvidenceError(errMessage, evidenceIDForRequest); db->updateEvidenceError(errMessage, evidenceIDForRequest);
}
catch (QSqlError& e) {
qWarning() << "Upload failed. Could not update internal database. Error: " << e.text();
}
QMessageBox::warning(this, tr("Cannot Submit Evidence"), QMessageBox::warning(this, tr("Cannot Submit Evidence"),
tr("Upload failed: Network error. Check your connection and try again.\n" tr("Upload failed: Network error. Check your connection and try again.\n"
"(Error: %1)").arg(uploadAssetReply->errorString())); "(Error: %1)").arg(uploadAssetReply->errorString()));
} } else {
else {
try {
db->updateEvidenceSubmitted(evidenceIDForRequest); db->updateEvidenceSubmitted(evidenceIDForRequest);
}
catch (QSqlError& e) {
qWarning() << "Upload successful. Could not update internal database. Error: " << e.text();
}
Q_EMIT evidenceChanged(evidenceIDForRequest, true); // lock the editing form Q_EMIT evidenceChanged(evidenceIDForRequest, true); // lock the editing form
} }
refreshRow(evidenceTable->currentRow()); refreshRow(evidenceTable->currentRow());

View File

@ -84,20 +84,20 @@ bool GetInfo::saveData() {
return saveResponse.actionSucceeded; return saveResponse.actionSucceeded;
} }
void GetInfo::submitButtonClicked() { void GetInfo::submitButtonClicked()
{
submitButton->startAnimation(); submitButton->startAnimation();
Q_EMIT setActionButtonsEnabled(false); Q_EMIT setActionButtonsEnabled(false);
if (saveData()) { if (!saveData())
try { return;
model::Evidence evi = db->getEvidenceDetails(evidenceID); model::Evidence evi = db->getEvidenceDetails(evidenceID);
uploadAssetReply = NetMan::uploadAsset(evi); if(evi.id == -1) {
connect(uploadAssetReply, &QNetworkReply::finished, this, &GetInfo::onUploadComplete);
}
catch (QSqlError& e) {
QMessageBox::warning(this, tr("Cannot submit evidence"), QMessageBox::warning(this, tr("Cannot submit evidence"),
tr("Could not retrieve data. Please try again.")); tr("Could not retrieve data. Please try again."));
return;
} }
} uploadAssetReply = NetMan::uploadAsset(evi);
connect(uploadAssetReply, &QNetworkReply::finished, this, &GetInfo::onUploadComplete);
} }
void GetInfo::deleteButtonClicked() { void GetInfo::deleteButtonClicked() {
@ -127,32 +127,25 @@ void GetInfo::deleteButtonClicked() {
} }
} }
void GetInfo::onUploadComplete() { void GetInfo::onUploadComplete()
{
if (uploadAssetReply->error() != QNetworkReply::NoError) { if (uploadAssetReply->error() != QNetworkReply::NoError) {
auto errMessage = auto errMessage = tr("Unable to upload evidence: Network error (%1)").arg(uploadAssetReply->errorString());
tr("Unable to upload evidence: Network error (%1)").arg(uploadAssetReply->errorString());
try {
db->updateEvidenceError(errMessage, evidenceID); db->updateEvidenceError(errMessage, evidenceID);
} if(!db->errorString().isEmpty())
catch (QSqlError& e) { qWarning() << "Upload failed. Could not update internal database. Error: " << db->errorString();
qWarning() << "Upload failed. Could not update internal database. Error: " << e.text();
}
QMessageBox::warning(this, tr("Cannot submit evidence"), QMessageBox::warning(this, tr("Cannot submit evidence"),
tr("Upload failed: Network error. Check your connection and try again.\n" tr("Upload failed: Network error. Check your connection and try again.\n"
"Note: This evidence has been saved. You can close this window and " "Note: This evidence has been saved. You can close this window and "
"re-submit from the evidence manager." "re-submit from the evidence manager."
"\n(Error: %1)").arg(uploadAssetReply->errorString())); "\n(Error: %1)").arg(uploadAssetReply->errorString()));
} } else {
else {
try {
db->updateEvidenceSubmitted(evidenceID); db->updateEvidenceSubmitted(evidenceID);
if(!db->errorString().isEmpty())
qWarning() << "Upload successful. Could not update internal database. Error: " << db->errorString();
Q_EMIT evidenceSubmitted(db->getEvidenceDetails(evidenceID)); Q_EMIT evidenceSubmitted(db->getEvidenceDetails(evidenceID));
close(); close();
} }
catch (QSqlError& e) {
qWarning() << "Upload successful. Could not update internal database. Error: " << e.text();
}
}
// we don't actually need anything from the uploadAssets reply, so just clean it up. // we don't actually need anything from the uploadAssets reply, so just clean it up.
// one thing we might want to record: evidence uuid... not sure why we'd need it though. // one thing we might want to record: evidence uuid... not sure why we'd need it though.
submitButton->stopAnimation(); submitButton->stopAnimation();

View File

@ -12,7 +12,6 @@
#include <QtConcurrent/QtConcurrent> #include <QtConcurrent/QtConcurrent>
#include "db/databaseconnection.h" #include "db/databaseconnection.h"
#include "exceptions/fileerror.h"
PortingDialog::PortingDialog(PortType dialogType, DatabaseConnection* db, QWidget *parent) PortingDialog::PortingDialog(PortType dialogType, DatabaseConnection* db, QWidget *parent)
: AShirtDialog(parent) : AShirtDialog(parent)
@ -156,7 +155,8 @@ QString PortingDialog::getPortPath() {
return portPath; return portPath;
} }
void PortingDialog::doExport(porting::SystemManifest* manifest, const QString& exportPath) { void PortingDialog::doExport(porting::SystemManifest* manifest, const QString& exportPath)
{
porting::SystemManifestExportOptions options; porting::SystemManifestExportOptions options;
options.exportDb = portEvidenceCheckBox->isChecked(); options.exportDb = portEvidenceCheckBox->isChecked();
options.exportConfig = portConfigCheckBox->isChecked(); options.exportConfig = portConfigCheckBox->isChecked();
@ -165,54 +165,42 @@ void PortingDialog::doExport(porting::SystemManifest* manifest, const QString& e
// the withconnection here that connects to the same database. Note: we shouldn't write to the db // the withconnection here that connects to the same database. Note: we shouldn't write to the db
// in this thread, if possible. // in this thread, if possible.
QString threadedDbName = QStringLiteral("%1_mt_forExport").arg(Constants::defaultDbName); QString threadedDbName = QStringLiteral("%1_mt_forExport").arg(Constants::defaultDbName);
DatabaseConnection::withConnection( auto success = DatabaseConnection::withConnection(
db->getDatabasePath(), threadedDbName, [this, &manifest, exportPath, options](DatabaseConnection conn) { db->getDatabasePath(), threadedDbName, [this, &manifest, exportPath, options](DatabaseConnection conn) {
try {
manifest->exportManifest(&conn, exportPath, options); manifest->exportManifest(&conn, exportPath, options);
}
catch(const FileError &e) {
portStatusLabel->setText(tr("Error during export: %1").arg(e.what()));
Q_EMIT onWorkComplete(false);
}
catch(const QSqlError &e) {
portStatusLabel->setText(tr("Error during export: %1").arg(e.text()));
Q_EMIT onWorkComplete(false);
}
}); });
if(success) {
Q_EMIT onWorkComplete(true); Q_EMIT onWorkComplete(true);
return;
}
portStatusLabel->setText(tr("Error during export: %1").arg(db->errorString()));
Q_EMIT onWorkComplete(false);
} }
porting::SystemManifest* PortingDialog::doPreImport(const QString& pathToSystemManifest) { porting::SystemManifest* PortingDialog::doPreImport(const QString& pathToSystemManifest) {
porting::SystemManifest* manifest = nullptr; porting::SystemManifest* manifest = nullptr;
try {
manifest = porting::SystemManifest::readManifest(pathToSystemManifest); manifest = porting::SystemManifest::readManifest(pathToSystemManifest);
} if(!manifest) {
catch(const FileError& e) {
portStatusLabel->setText(tr("Unable to parse system file.")); portStatusLabel->setText(tr("Unable to parse system file."));
onPortComplete(false); onPortComplete(false);
} }
return manifest; return manifest;
} }
void PortingDialog::doImport(porting::SystemManifest* manifest) { void PortingDialog::doImport(porting::SystemManifest* manifest)
{
porting::SystemManifestImportOptions options; porting::SystemManifestImportOptions options;
options.importDb = portEvidenceCheckBox->isChecked() ? options.Merge : options.None; options.importDb = portEvidenceCheckBox->isChecked() ? options.Merge : options.None;
options.importConfig = portConfigCheckBox->isChecked(); options.importConfig = portConfigCheckBox->isChecked();
QString threadedDbName = QStringLiteral("%1_mt_forImport").arg(Constants::defaultDbName); QString threadedDbName = QStringLiteral("%1_mt_forImport").arg(Constants::defaultDbName);
DatabaseConnection::withConnection( auto success = DatabaseConnection::withConnection(
db->getDatabasePath(), threadedDbName, [this, &manifest, options](DatabaseConnection conn){ db->getDatabasePath(), threadedDbName, [this, &manifest, options](DatabaseConnection conn){
try {
manifest->applyManifest(options, &conn); manifest->applyManifest(options, &conn);
}
catch(const FileError &e) {
portStatusLabel->setText(tr("Error during import: %1").arg(e.what()));
Q_EMIT onWorkComplete(false);
}
catch(const QSqlError &e) {
portStatusLabel->setText(tr("Error during import: ").arg(e.text()));
Q_EMIT onWorkComplete(false);
}
}); });
if(success) {
Q_EMIT onWorkComplete(true); Q_EMIT onWorkComplete(true);
return;
}
portStatusLabel->setText(tr("Error during import: %1").arg(db->errorString()));
Q_EMIT onWorkComplete(false);
} }

View File

@ -122,7 +122,7 @@ public:
/// Callers should retrieve the result by listening for the releasesChecked signal /// Callers should retrieve the result by listening for the releasesChecked signal
static void checkForNewRelease(QString owner, QString repo) { static void checkForNewRelease(QString owner, QString repo) {
if (owner.isEmpty() || repo.isEmpty()) { if (owner.isEmpty() || repo.isEmpty()) {
qWarning() << "Skipping release check: no owner or repo set."; qInfo() << "Skipping release check: no owner or repo set.";
return; return;
} }
get()->githubReleaseReply = get()->getGithubReleases(owner, repo); get()->githubReleaseReply = get()->getGithubReleases(owner, repo);

View File

@ -35,7 +35,7 @@ int main(int argc, char* argv[])
auto conn = new DatabaseConnection(Constants::dbLocation, Constants::defaultDbName); auto conn = new DatabaseConnection(Constants::dbLocation, Constants::defaultDbName);
if(!conn->connect()) { if(!conn->connect()) {
showMsgBox(QT_TRANSLATE_NOOP("main", "Unable to connect to database")); showMsgBox(QString(QT_TRANSLATE_NOOP("main", "Database Error: %1")).arg(conn->errorString()));
return -1; return -1;
} }

View File

@ -121,8 +121,10 @@ void SystemManifest::exportManifest(DatabaseConnection* db, const QString& outpu
} }
m_pathToManifest = QStringLiteral("%1/system.json").arg(basePath); m_pathToManifest = QStringLiteral("%1/system.json").arg(basePath);
FileHelpers::writeFile(m_pathToManifest, QJsonDocument(serialize(*this)).toJson()); if(FileHelpers::writeFile(m_pathToManifest, QJsonDocument(serialize(*this)).toJson()))
Q_EMIT onComplete(); Q_EMIT onComplete();
else
Q_EMIT onExportError(QStringLiteral("Error On Exporting manifest"));
} }
porting::EvidenceManifest SystemManifest::copyEvidence(const QString& baseExportPath, porting::EvidenceManifest SystemManifest::copyEvidence(const QString& baseExportPath,

View File

@ -36,7 +36,6 @@ namespace porting {
* @brief applyManifest takes the given manifest object (and options), and begins merging that data with the running system * @brief applyManifest takes the given manifest object (and options), and begins merging that data with the running system
* @param options switches to control what gets imported * @param options switches to control what gets imported
* @param systemDb The currently running/system database * @param systemDb The currently running/system database
* @throws QSqlError If there is an issue ingesting database records from the exported database
*/ */
void applyManifest(SystemManifestImportOptions options, DatabaseConnection* systemDb); void applyManifest(SystemManifestImportOptions options, DatabaseConnection* systemDb);
@ -46,7 +45,6 @@ namespace porting {
* @param outputDirPath the path to the expected export directory. Files will be placed under this directory * @param outputDirPath the path to the expected export directory. Files will be placed under this directory
* (not wrapped in another directory) * (not wrapped in another directory)
* @param options exporting options (e.g. do you want to copy both evidence *and* config * @param options exporting options (e.g. do you want to copy both evidence *and* config
* @throws QSqlError if there is an issue accessing the system database, or the copied database
*/ */
void exportManifest(DatabaseConnection* db, const QString& outputDirPath, void exportManifest(DatabaseConnection* db, const QString& outputDirPath,
const SystemManifestExportOptions& options); const SystemManifestExportOptions& options);
@ -69,6 +67,8 @@ namespace porting {
void onFileProcessed(quint64 runningCount); void onFileProcessed(quint64 runningCount);
/// onComplete fires when the entire import/export is finished /// onComplete fires when the entire import/export is finished
void onComplete(); void onComplete();
/// onComplete fires when the export has an error
void onExportError(QString errorString);
/// onCopyFileError fires when a file cannot be copied during import or export /// onCopyFileError fires when a file cannot be copied during import or export
void onCopyFileError(QString srcPath, QString dstPath, const QString& errStr); void onCopyFileError(QString srcPath, QString dstPath, const QString& errStr);
/// onStatusUpdate fires when the system moves between import/export phases /// onStatusUpdate fires when the system moves between import/export phases
@ -89,7 +89,6 @@ namespace porting {
/** /**
* @brief migrateConfig imports the config file associated with the started import * @brief migrateConfig imports the config file associated with the started import
* @throws FileError if the config file cannot be copied
*/ */
void migrateConfig(); void migrateConfig();

View File

@ -18,7 +18,6 @@
#include <iostream> #include <iostream>
#include "appconfig.h" #include "appconfig.h"
#include "db/databaseconnection.h" #include "db/databaseconnection.h"
#include "exceptions/fileerror.h"
#include "forms/getinfo/getinfo.h" #include "forms/getinfo/getinfo.h"
#include "helpers/netman.h" #include "helpers/netman.h"
#include "helpers/screenshot.h" #include "helpers/screenshot.h"
@ -211,6 +210,7 @@ void TrayManager::onClipboardCapture()
QString clipboardContent = mimeData->text(); QString clipboardContent = mimeData->text();
if (clipboardContent.isEmpty()) if (clipboardContent.isEmpty())
return; return;
Codeblock evidence(clipboardContent); Codeblock evidence(clipboardContent);
if(!Codeblock::saveCodeblock(evidence)) { if(!Codeblock::saveCodeblock(evidence)) {
setTrayMessage(NO_ACTION, _recordErrorTitle, tr("Error Gathering Evidence from clipboard"), QSystemTrayIcon::Information); setTrayMessage(NO_ACTION, _recordErrorTitle, tr("Error Gathering Evidence from clipboard"), QSystemTrayIcon::Information);
@ -227,30 +227,27 @@ void TrayManager::onClipboardCapture()
return; return;
} }
int evidenceID = 0; int evidenceID = createNewEvidence(path, type);
try { if(evidenceID == -1) {
evidenceID = createNewEvidence(path, type); showDBWriteErrorTrayMessage();
}
catch (QSqlError& e) {
showDBWriteErrorTrayMessage(e.text());
return; return;
} }
spawnGetInfoWindow(evidenceID); spawnGetInfoWindow(evidenceID);
} }
void TrayManager::onScreenshotCaptured(const QString& path) { void TrayManager::onScreenshotCaptured(const QString& path)
try { {
auto evidenceID = createNewEvidence(path, QStringLiteral("image")); auto evidenceID = createNewEvidence(path, QStringLiteral("image"));
if(evidenceID == -1) {
showDBWriteErrorTrayMessage();
return;
}
spawnGetInfoWindow(evidenceID); spawnGetInfoWindow(evidenceID);
} }
catch (QSqlError& e) {
showDBWriteErrorTrayMessage(e.text());
}
}
void TrayManager::showDBWriteErrorTrayMessage(const QString &errorMessage) void TrayManager::showDBWriteErrorTrayMessage()
{ {
setTrayMessage(NO_ACTION, _recordErrorTitle, tr("Could not write to database: %1").arg(errorMessage), QSystemTrayIcon::Warning); setTrayMessage(NO_ACTION, _recordErrorTitle,tr("Could not write to database"), QSystemTrayIcon::Warning);
} }
void TrayManager::showNoOperationSetTrayMessage() { void TrayManager::showNoOperationSetTrayMessage() {

View File

@ -61,7 +61,7 @@ class TrayManager : public QDialog {
qint64 createNewEvidence(const QString& filepath, const QString& evidenceType); qint64 createNewEvidence(const QString& filepath, const QString& evidenceType);
void spawnGetInfoWindow(qint64 evidenceID); void spawnGetInfoWindow(qint64 evidenceID);
void showNoOperationSetTrayMessage(); void showNoOperationSetTrayMessage();
void showDBWriteErrorTrayMessage(const QString &errorMessage = QString()); void showDBWriteErrorTrayMessage();
void checkForUpdate(); void checkForUpdate();
void cleanChooseOpSubmenu(); void cleanChooseOpSubmenu();
/// setTrayMessage mostly mirrors QSystemTrayIcon::showMessage, but adds the ability to set a message type, /// setTrayMessage mostly mirrors QSystemTrayIcon::showMessage, but adds the ability to set a message type,