Netman Clean up (#189)

Co-authored-by: Chris Rizzitello <crizzitello@ics.com>
main
crizzitello 2022-07-11 13:50:58 -04:00 committed by GitHub
parent 240ecdc3d5
commit 4727200bc9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 156 additions and 188 deletions

View File

@ -31,7 +31,7 @@ void TagCache::requestTags(QString operationSlug) {
return; return;
} }
auto reply = NetMan::getInstance().getOperationTags(operationSlug); auto reply = NetMan::getOperationTags(operationSlug);
tagRequests.insert(operationSlug, reply); tagRequests.insert(operationSlug, reply);
connect(reply, &QNetworkReply::finished, this, [this, reply, operationSlug]() { connect(reply, &QNetworkReply::finished, this, [this, reply, operationSlug]() {
onGetTagsComplete(reply, operationSlug); onGetTagsComplete(reply, operationSlug);

View File

@ -179,7 +179,7 @@ void TagEditor::createTag(QString tagName) {
tagCompleteTextBox->setEnabled(false); tagCompleteTextBox->setEnabled(false);
dto::Tag newTag(newText, TagWidget::randomColor()); dto::Tag newTag(newText, TagWidget::randomColor());
createTagReply = NetMan::getInstance().createTag(newTag, operationSlug); createTagReply = NetMan::createTag(newTag, operationSlug);
connect(createTagReply, &QNetworkReply::finished, this, &TagEditor::onCreateTagComplete); connect(createTagReply, &QNetworkReply::finished, this, &TagEditor::onCreateTagComplete);
} }

View File

@ -63,7 +63,7 @@ void CreateOperation::submitButtonClicked() {
submitButton->startAnimation(); submitButton->startAnimation();
submitButton->setEnabled(false); submitButton->setEnabled(false);
createOpReply = NetMan::getInstance().createOperation(name, slug); createOpReply = NetMan::createOperation(name, slug);
connect(createOpReply, &QNetworkReply::finished, this, &CreateOperation::onRequestComplete); connect(createOpReply, &QNetworkReply::finished, this, &CreateOperation::onRequestComplete);
} }
@ -81,7 +81,7 @@ void CreateOperation::onRequestComplete() {
dto::Operation op = dto::Operation::parseData(data); dto::Operation op = dto::Operation::parseData(data);
AppSettings::getInstance().setOperationDetails(op.slug, op.name); AppSettings::getInstance().setOperationDetails(op.slug, op.name);
operationNameTextBox->clear(); operationNameTextBox->clear();
NetMan::getInstance().refreshOperationsList(); NetMan::refreshOperationsList();
close(); close();
} }
else { else {

View File

@ -17,7 +17,7 @@ Credits::Credits(QWidget* parent)
, updateLabel(new QLabel(this)) , updateLabel(new QLabel(this))
{ {
setWindowTitle("About"); setWindowTitle("About");
connect(&NetMan::getInstance(), &NetMan::releasesChecked, this, &Credits::onReleasesUpdate); connect(NetMan::get(), &NetMan::releasesChecked, this, &Credits::onReleasesUpdate);
updateLabel->setVisible(false); updateLabel->setVisible(false);
updateLabel->setOpenExternalLinks(true); updateLabel->setOpenExternalLinks(true);

View File

@ -201,7 +201,7 @@ void EvidenceManager::submitEvidenceTriggered() {
evidenceIDForRequest = selectedRowEvidenceID(); evidenceIDForRequest = selectedRowEvidenceID();
try { try {
model::Evidence evi = db->getEvidenceDetails(evidenceIDForRequest); model::Evidence evi = db->getEvidenceDetails(evidenceIDForRequest);
uploadAssetReply = NetMan::getInstance().uploadAsset(evi); uploadAssetReply = NetMan::uploadAsset(evi);
connect(uploadAssetReply, &QNetworkReply::finished, this, &EvidenceManager::onUploadComplete); connect(uploadAssetReply, &QNetworkReply::finished, this, &EvidenceManager::onUploadComplete);
} }
catch (QSqlError& e) { catch (QSqlError& e) {

View File

@ -123,8 +123,7 @@ void EvidenceFilterForm::wireUi() {
submittedComboBox->installEventFilter(this); submittedComboBox->installEventFilter(this);
contentTypeComboBox->installEventFilter(this); contentTypeComboBox->installEventFilter(this);
connect(&NetMan::getInstance(), &NetMan::operationListUpdated, this, connect(NetMan::get(), &NetMan::operationListUpdated, this, &EvidenceFilterForm::onOperationListUpdated);
&EvidenceFilterForm::onOperationListUpdated);
connect(buttonBox, &QDialogButtonBox::accepted, this, &EvidenceFilterForm::writeAndClose); connect(buttonBox, &QDialogButtonBox::accepted, this, &EvidenceFilterForm::writeAndClose);
connect(includeStartDateCheckBox, &QCheckBox::stateChanged, fromDateEdit, &QDateEdit::setEnabled); connect(includeStartDateCheckBox, &QCheckBox::stateChanged, fromDateEdit, &QDateEdit::setEnabled);

View File

@ -91,7 +91,7 @@ void GetInfo::submitButtonClicked() {
if (saveData()) { if (saveData()) {
try { try {
model::Evidence evi = db->getEvidenceDetails(evidenceID); model::Evidence evi = db->getEvidenceDetails(evidenceID);
uploadAssetReply = NetMan::getInstance().uploadAsset(evi); uploadAssetReply = NetMan::uploadAsset(evi);
connect(uploadAssetReply, &QNetworkReply::finished, this, &GetInfo::onUploadComplete); connect(uploadAssetReply, &QNetworkReply::finished, this, &GetInfo::onUploadComplete);
} }
catch (QSqlError& e) { catch (QSqlError& e) {

View File

@ -220,7 +220,7 @@ void Settings::onSaveClicked() {
QString originalApiUrl = inst.apiURL; QString originalApiUrl = inst.apiURL;
inst.apiURL = hostPathTextBox->text(); inst.apiURL = hostPathTextBox->text();
if (originalApiUrl != hostPathTextBox->text()) { if (originalApiUrl != hostPathTextBox->text()) {
NetMan::getInstance().refreshOperationsList(); NetMan::refreshOperationsList();
} }
inst.screenshotExec = captureAreaCmdTextBox->text(); inst.screenshotExec = captureAreaCmdTextBox->text();
@ -261,7 +261,7 @@ void Settings::onTestConnectionClicked() {
} }
testConnectionButton->startAnimation(); testConnectionButton->startAnimation();
testConnectionButton->setEnabled(false); testConnectionButton->setEnabled(false);
currentTestReply = NetMan::getInstance().testConnection( currentTestReply = NetMan::testConnection(
hostPathTextBox->text(), accessKeyTextBox->text(), secretKeyTextBox->text()); hostPathTextBox->text(), accessKeyTextBox->text(), secretKeyTextBox->text());
connect(currentTestReply, &QNetworkReply::finished, this, &Settings::onTestRequestComplete); connect(currentTestReply, &QNetworkReply::finished, this, &Settings::onTestRequestComplete);
} }

View File

@ -5,7 +5,6 @@
class Constants { class Constants {
public: public:
inline static const auto unknownValue = QStringLiteral("???");
inline static const auto dbLocation = QStringLiteral("%1/evidence.sqlite").arg(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)); inline static const auto dbLocation = QStringLiteral("%1/evidence.sqlite").arg(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation));
inline static const auto defaultEvidenceRepo = QStringLiteral("%1/evidence").arg(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)); inline static const auto defaultEvidenceRepo = QStringLiteral("%1/evidence").arg(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation));
inline static const auto commitHash = QStringLiteral("%1").arg(COMMIT_HASH); inline static const auto commitHash = QStringLiteral("%1").arg(COMMIT_HASH);
@ -57,8 +56,8 @@ class Constants {
QRegularExpressionMatch match = ownerRegex.match(rawRepo); QRegularExpressionMatch match = ownerRegex.match(rawRepo);
// Note that the specific values for the error cases below don't matter // Note that the specific values for the error cases below don't matter
// They are set to avoid rerunning the parsing (since these values won't change mid-run) // They are set to avoid rerunning the parsing (since these values won't change mid-run)
parsedOwner = match.hasMatch() ? match.captured(1) : unknownValue; parsedOwner = match.hasMatch() ? match.captured(1) : QString();
parsedRepo = match.hasMatch() ? match.captured(2) : unknownValue; parsedRepo = match.hasMatch() ? match.captured(2) : QString();
} }
return field == RepoField::owner ? parsedOwner : parsedRepo; return field == RepoField::owner ? parsedOwner : parsedRepo;
} }

View File

@ -13,228 +13,98 @@
#include "dtos/operation.h" #include "dtos/operation.h"
#include "dtos/tag.h" #include "dtos/tag.h"
#include "dtos/github_release.h" #include "dtos/github_release.h"
#include "helpers/multipartparser.h" #include "helpers/constants.h"
#include <helpers/multipartparser.h>
#include "helpers/stopreply.h" #include "helpers/stopreply.h"
#include "models/evidence.h" #include "models/evidence.h"
class NetMan : public QObject { class NetMan : public QObject {
Q_OBJECT Q_OBJECT
public:
public:
static NetMan &getInstance() {
static NetMan instance;
return instance;
}
NetMan(NetMan const &) = delete;
void operator=(NetMan const &) = delete;
// type alias QList<dto::Operation> to provide shorter lines // type alias QList<dto::Operation> to provide shorter lines
using OperationVector = QList<dto::Operation>; using OperationVector = QList<dto::Operation>;
static NetMan* get() {
signals: static NetMan i;
void operationListUpdated(bool success, NetMan::OperationVector operations = NetMan::OperationVector()); return &i;
void releasesChecked(bool success, QList<dto::GithubRelease> releases = QList<dto::GithubRelease>());
void testConnectionComplete(bool connected, int statusCode);
private:
QNetworkAccessManager *nam;
NetMan(QObject * parent = nullptr)
: QObject(parent)
, nam(new QNetworkAccessManager(this))
{
} }
~NetMan() = default;
/// ashirtGet generates a basic GET request to the ashirt API server. No authentication is
/// provided (use addASHIRTAuth to do this)
/// Allows for an optional altHost parameter, in order to check for ashirt servers.
/// Normal usage should provide no value for this parameter.
RequestBuilder* ashirtGet(QString endpoint, const QString & altHost= QString()) {
QString base = (altHost.isEmpty()) ? AppConfig::getInstance().apiURL : altHost;
return RequestBuilder::newGet()
->setHost(base)
->setEndpoint(endpoint);
}
/// ashirtJSONPost generates a basic POST request with content type application/json. No
/// authentication is provided (use addASHIRTAuth to do this)
RequestBuilder* ashirtJSONPost(QString endpoint, QByteArray body) {
return RequestBuilder::newJSONPost()
->setHost(AppConfig::getInstance().apiURL)
->setEndpoint(endpoint)
->setBody(body);
}
/// ashirtFormPost generates a basic POST request with content type multipart/form-data.
/// No authentication is provided (use addASHIRTAuth to do this)
RequestBuilder* ashirtFormPost(QString endpoint, QByteArray body, QString boundary) {
return RequestBuilder::newFormPost(boundary)
->setHost(AppConfig::getInstance().apiURL)
->setEndpoint(endpoint)
->setBody(body);
}
/// addASHIRTAuth takes the provided RequestBuilder and adds on Authorization and Date headers
/// in order to properly authenticate with ASHIRT servers. Note that this should not be used for
/// non-ashirt requests
void addASHIRTAuth(RequestBuilder* reqBuilder, const QString& altApiKey = QString(),
const QString& altSecretKey = QString()) {
auto now = QDateTime::currentDateTimeUtc().toString(QStringLiteral("ddd, dd MMM yyyy hh:mm:ss 'GMT'"));
reqBuilder->addRawHeader(QStringLiteral("Date"), now);
// load default key if not present
QString apiKeyCopy = QString(altApiKey);
if (apiKeyCopy.isEmpty()) {
apiKeyCopy = AppConfig::getInstance().accessKey;
}
auto code = generateHash(RequestMethodToString(reqBuilder->getMethod()),
reqBuilder->getEndpoint(), now, reqBuilder->getBody(), altSecretKey);
auto authValue = QStringLiteral("%1:%2").arg(apiKeyCopy, code);
reqBuilder->addRawHeader(QStringLiteral("Authorization"), authValue);
}
/// generateHash provides a cryptographic hash for ASHIRT api server communication
QString generateHash(QString method, QString path, QString date, QByteArray body = NO_BODY,
const QString &secretKey = QString()) {
auto hashedBody = QCryptographicHash::hash(body, QCryptographicHash::Sha256);
std::string msg = (method + "\n" + path + "\n" + date + "\n").toStdString();
msg += hashedBody.toStdString();
QString secretKeyCopy = QString(secretKey);
if (secretKeyCopy.isEmpty()) {
secretKeyCopy = AppConfig::getInstance().secretKey;
}
QMessageAuthenticationCode code(QCryptographicHash::Sha256);
QByteArray key = QByteArray::fromBase64(secretKeyCopy.toUtf8());
code.setKey(key);
code.addData(QByteArray::fromStdString(msg));
return code.result().toBase64();
}
/// onGetOpsComplete is called when the network request associated with the method refreshOperationsList
/// completes. This will emit an operationListUpdated signal.
void onGetOpsComplete() {
bool isValid;
auto data = extractResponse(allOpsReply, isValid);
if (isValid) {
OperationVector ops = dto::Operation::parseDataAsList(data);
std::sort(ops.begin(), ops.end(),
[](dto::Operation i, dto::Operation j) { return i.name < j.name; });
Q_EMIT operationListUpdated(true, ops);
}
else {
Q_EMIT operationListUpdated(false);
}
tidyReply(&allOpsReply);
}
/// onGithubReleasesComplete is called when the network request associated with the method checkForNewRelease
/// completes. This will emit a releasesChecked signal
void onGithubReleasesComplete() {
bool isValid;
auto data = extractResponse(githubReleaseReply, isValid);
if (isValid) {
auto releases = dto::GithubRelease::parseDataAsList(data);
Q_EMIT releasesChecked(true, releases);
}
else {
Q_EMIT releasesChecked(false);
}
tidyReply(&githubReleaseReply);
}
public:
/// uploadAsset takes the given Evidence model, encodes it (and the file), and uploads this /// uploadAsset takes the given Evidence model, encodes it (and the file), and uploads this
/// to the configured ASHIRT API server. Returns a QNetworkReply to track the request /// to the configured ASHIRT API server. Returns a QNetworkReply to track the request
/// Note: does not specify the occurred_at field, so occurred_at will reflect the time of upload, /// Note: does not specify the occurred_at field, so occurred_at will reflect the time of upload,
/// rather than the time of capture. /// rather than the time of capture.
QNetworkReply *uploadAsset(model::Evidence evidence) { static QNetworkReply* uploadAsset(model::Evidence evidence) {
MultipartParser parser; MultipartParser parser;
parser.addParameter(QStringLiteral("notes"), evidence.description); parser.addParameter(QStringLiteral("notes"), evidence.description);
parser.addParameter(QStringLiteral("contentType"), evidence.contentType); parser.addParameter(QStringLiteral("contentType"), evidence.contentType);
// TODO: convert this time below into a proper unix timestamp (mSecSinceEpoch and secsSinceEpoch // TODO: convert this time below into a proper unix timestamp (mSecSinceEpoch and secsSinceEpoch
// produce invalid times) // produce invalid times)
// parser.AddParameter("occurred_at", std::to_string(evidence.recordedDate); // parser.AddParameter("occurred_at", std::to_string(evidence.recordedDate);
QStringList list; QStringList list;
for (const auto& tag : evidence.tags) { for (const auto& tag : evidence.tags)
list << QString::number(tag.serverTagId); list.append(QString::number(tag.serverTagId));
}
parser.addParameter(QStringLiteral("tagIds"), QStringLiteral("[%1]").arg(list.join(QStringLiteral(",")))); parser.addParameter(QStringLiteral("tagIds"), QStringLiteral("[%1]").arg(list.join(QStringLiteral(","))));
parser.addFile(QStringLiteral("file"), evidence.path); parser.addFile(QStringLiteral("file"), evidence.path);
auto builder = ashirtFormPost(QStringLiteral("/api/operations/%1/evidence").arg(evidence.operationSlug), parser.generateBody(), parser.boundary());
auto body = parser.generateBody();
auto builder = ashirtFormPost(QStringLiteral("/api/operations/%1/evidence").arg(evidence.operationSlug), body, parser.boundary());
addASHIRTAuth(builder); addASHIRTAuth(builder);
return builder->execute(nam); return builder->execute(get()->nam);
} }
/// testConnection provides a mechanism to validate a given host, apikey and secret key, to test /// testConnection provides a mechanism to validate a given host, apikey and secret key, to test
/// a connection to the ASHIRT API server /// a connection to the ASHIRT API server
QNetworkReply *testConnection(QString host, QString apiKey, QString secretKey) { static QNetworkReply *testConnection(QString host, QString apiKey, QString secretKey) {
auto builder = ashirtGet(QStringLiteral("/api/checkconnection"), host); auto builder = ashirtGet(QStringLiteral("/api/checkconnection"), host);
addASHIRTAuth(builder, apiKey, secretKey); addASHIRTAuth(builder, apiKey, secretKey);
return builder->execute(nam); return builder->execute(get()->nam);
} }
/// getAllOperations retrieves all (user-visble) operations from the configured ASHIRT API server. /// getAllOperations retrieves all (user-visble) operations from the configured ASHIRT API server.
/// Note: normally you should opt to use refreshOperationsList and retrieve the results by listening /// Note: normally you should opt to use refreshOperationsList and retrieve the results by listening
/// for the operationListUpdated signal. /// for the operationListUpdated signal.
QNetworkReply *getAllOperations() { static QNetworkReply *getAllOperations() {
auto builder = ashirtGet(QStringLiteral("/api/operations")); auto builder = ashirtGet(QStringLiteral("/api/operations"));
addASHIRTAuth(builder); addASHIRTAuth(builder);
return builder->execute(nam); return builder->execute(get()->nam);
} }
/// getGithubReleases retrieves the recent releases from github for the provided owner and repo. /// getGithubReleases retrieves the recent releases from github for the provided owner and repo.
/// Note that normally you should call checkForNewRelease /// Note that normally you should call checkForNewRelease
QNetworkReply *getGithubReleases(QString owner, QString repo) { static QNetworkReply *getGithubReleases(QString owner, QString repo) {
return RequestBuilder::newGet() return RequestBuilder::newGet()
->setHost(QStringLiteral("https://api.github.com")) ->setHost(QStringLiteral("https://api.github.com"))
->setEndpoint(QStringLiteral("/repos/%1/%2/releases").arg(owner, repo)) ->setEndpoint(QStringLiteral("/repos/%1/%2/releases").arg(owner, repo))
->execute(nam); ->execute(get()->nam);
} }
/// refreshOperationsList retrieves the operations currently visible to the user. Results should be /// refreshOperationsList retrieves the operations currently visible to the user. Results should be
/// retrieved by listening for the operationListUpdated signal /// retrieved by listening for the operationListUpdated signal
void refreshOperationsList() { static void refreshOperationsList() {
if (allOpsReply == nullptr) { if (get()->allOpsReply)
allOpsReply = getAllOperations(); return;
connect(allOpsReply, &QNetworkReply::finished, this, &NetMan::onGetOpsComplete); get()->allOpsReply = get()->getAllOperations();
} connect(get()->allOpsReply, &QNetworkReply::finished, get(), &NetMan::onGetOpsComplete);
} }
/// getOperationTags retrieves the tags for specified operation from the ASHIRT API server /// getOperationTags retrieves the tags for specified operation from the ASHIRT API server
QNetworkReply *getOperationTags(QString operationSlug) { static QNetworkReply *getOperationTags(QString operationSlug) {
auto builder = ashirtGet(QStringLiteral("/api/operations/%1/tags").arg(operationSlug)); auto builder = ashirtGet(QStringLiteral("/api/operations/%1/tags").arg(operationSlug));
addASHIRTAuth(builder); addASHIRTAuth(builder);
return builder->execute(nam); return builder->execute(get()->nam);
} }
/// createTag attempts to create a new tag for specified operation from the ASHIRT API server. /// createTag attempts to create a new tag for specified operation from the ASHIRT API server.
QNetworkReply *createTag(dto::Tag tag, QString operationSlug) { static QNetworkReply *createTag(dto::Tag tag, QString operationSlug) {
auto builder = ashirtJSONPost(QStringLiteral("/api/operations/%1/tags").arg(operationSlug), dto::Tag::toJson(tag)); auto builder = ashirtJSONPost(QStringLiteral("/api/operations/%1/tags").arg(operationSlug), dto::Tag::toJson(tag));
addASHIRTAuth(builder); addASHIRTAuth(builder);
return builder->execute(nam); return builder->execute(get()->nam);
} }
/// createOperation attempts to create a new operation with the given name and slug /// createOperation attempts to create a new operation with the given name and slug
QNetworkReply *createOperation(QString name, QString slug) { static QNetworkReply *createOperation(QString name, QString slug) {
auto builder = ashirtJSONPost(QStringLiteral("/api/operations"), dto::Operation::createOperationJson(name, slug)); auto builder = ashirtJSONPost(QStringLiteral("/api/operations"), dto::Operation::createOperationJson(name, slug));
addASHIRTAuth(builder); addASHIRTAuth(builder);
return builder->execute(nam); return builder->execute(get()->nam);
} }
/// extractResponse inspects the provided QNetworkReply and returns back the contents of the reply. /// extractResponse inspects the provided QNetworkReply and returns back the contents of the reply.
@ -251,16 +121,118 @@ class NetMan : public QObject {
/// checkForNewRelease retrieves the recent releases from github for the provided owner/repo project. /// checkForNewRelease retrieves the recent releases from github for the provided owner/repo project.
/// Callers should retrieve the result by listening for the releasesChecked signal /// Callers should retrieve the result by listening for the releasesChecked signal
void checkForNewRelease(QString owner, QString repo) { static void checkForNewRelease(QString owner, QString repo) {
if (owner == Constants::unknownValue || repo == Constants::unknownValue) { if (owner.isEmpty() || repo.isEmpty()) {
QTextStream(stderr) << "Skipping release check: no owner or repo set." << Qt::endl; QTextStream(stderr) << "Skipping release check: no owner or repo set." << Qt::endl;
return; return;
} }
githubReleaseReply = getGithubReleases(owner, repo); get()->githubReleaseReply = get()->getGithubReleases(owner, repo);
connect(githubReleaseReply, &QNetworkReply::finished, this, &NetMan::onGithubReleasesComplete); connect(get()->githubReleaseReply, &QNetworkReply::finished, get(), &NetMan::onGithubReleasesComplete);
} }
private: signals:
QNetworkReply *allOpsReply = nullptr; void operationListUpdated(bool success, NetMan::OperationVector operations = NetMan::OperationVector());
QNetworkReply *githubReleaseReply = nullptr; void releasesChecked(bool success, QList<dto::GithubRelease> releases = QList<dto::GithubRelease>());
private:
NetMan(QObject * parent = nullptr) : QObject(parent), nam(new QNetworkAccessManager(this)) { }
NetMan(NetMan const &) = delete;
void operator=(NetMan const &) = delete;
~NetMan() = default;
/// ashirtGet generates a basic GET request to the ashirt API server. No authentication is
/// provided (use addASHIRTAuth to do this)
/// Allows for an optional altHost parameter, in order to check for ashirt servers.
/// Normal usage should provide no value for this parameter.
static RequestBuilder* ashirtGet(QString endpoint, const QString & altHost= QString()) {
QString base = (altHost.isEmpty()) ? AppConfig::getInstance().apiURL : altHost;
return RequestBuilder::newGet()
->setHost(base)
->setEndpoint(endpoint);
}
/// ashirtJSONPost generates a basic POST request with content type application/json. No
/// authentication is provided (use addASHIRTAuth to do this)
static RequestBuilder* ashirtJSONPost(QString endpoint, QByteArray body) {
return RequestBuilder::newJSONPost()
->setHost(AppConfig::getInstance().apiURL)
->setEndpoint(endpoint)
->setBody(body);
}
/// ashirtFormPost generates a basic POST request with content type multipart/form-data.
/// No authentication is provided (use addASHIRTAuth to do this)
static RequestBuilder* ashirtFormPost(QString endpoint, QByteArray body, QString boundry) {
return RequestBuilder::newFormPost(boundry)
->setHost(AppConfig::getInstance().apiURL)
->setEndpoint(endpoint)
->setBody(body);
}
/// addASHIRTAuth takes the provided RequestBuilder and adds on Authorization and Date headers
/// in order to properly authenticate with ASHIRT servers. Note that this should not be used for
/// non-ashirt requests
static void addASHIRTAuth(RequestBuilder* reqBuilder, const QString& altApiKey = QString(),
const QString& altSecretKey = QString()) {
auto now = QDateTime::currentDateTimeUtc().toString(QStringLiteral("ddd, dd MMM yyyy hh:mm:ss 'GMT'"));
reqBuilder->addRawHeader(QStringLiteral("Date"), now);
// load default key if not present
QString apiKeyCopy = altApiKey.isEmpty() ? AppConfig::getInstance().accessKey : QString(altApiKey);
auto code = generateHash(RequestMethodToString(reqBuilder->getMethod()),
reqBuilder->getEndpoint(), now, reqBuilder->getBody(), altSecretKey);
auto authValue = QStringLiteral("%1:%2").arg(apiKeyCopy, code);
reqBuilder->addRawHeader(QStringLiteral("Authorization"), authValue);
}
/// generateHash provides a cryptographic hash for ASHIRT api server communication
static QString generateHash(QString method, QString path, QString date, QByteArray body = NO_BODY,
const QString &secretKey = QString()) {
QString msg = QStringLiteral("%1\n%2\n%3\n").arg(method, path, date);
QString secretKeyCopy = secretKey.isEmpty() ? AppConfig::getInstance().secretKey : QString(secretKey);
QMessageAuthenticationCode code(QCryptographicHash::Sha256);
code.setKey(QByteArray::fromBase64(secretKeyCopy.toUtf8()));
code.addData(msg.toLatin1());
code.addData(QCryptographicHash::hash(body, QCryptographicHash::Sha256));
return code.result().toBase64();
}
/// onGetOpsComplete is called when the network request associated with the method refreshOperationsList
/// completes. This will emit an operationListUpdated signal.
static void onGetOpsComplete() {
bool isValid;
auto data = get()->extractResponse(get()->allOpsReply, isValid);
if (isValid) {
OperationVector ops = dto::Operation::parseDataAsList(data);
std::sort(ops.begin(), ops.end(),
[](dto::Operation i, dto::Operation j) { return i.name < j.name; });
Q_EMIT get()->operationListUpdated(true, ops);
} else {
Q_EMIT get()->operationListUpdated(false);
}
tidyReply(&get()->allOpsReply);
}
/// onGithubReleasesComplete is called when the network request associated with the method checkForNewRelease
/// completes. This will emit a releasesChecked signal
static void onGithubReleasesComplete() {
bool isValid;
auto data = get()->extractResponse(get()->githubReleaseReply, isValid);
if (isValid) {
auto releases = dto::GithubRelease::parseDataAsList(data);
Q_EMIT get()->releasesChecked(true, releases);
} else {
Q_EMIT get()->releasesChecked(false);
}
tidyReply(&get()->githubReleaseReply);
}
QNetworkReply *allOpsReply = nullptr;
QNetworkReply *githubReleaseReply = nullptr;
QNetworkAccessManager *nam = nullptr;
}; };

View File

@ -56,7 +56,7 @@ TrayManager::TrayManager(QWidget * parent, DatabaseConnection* db)
wireUi(); wireUi();
// delayed so that windows can listen for get all ops signal // delayed so that windows can listen for get all ops signal
NetMan::getInstance().refreshOperationsList(); NetMan::refreshOperationsList();
QTimer::singleShot(5000, this, &TrayManager::checkForUpdate); QTimer::singleShot(5000, this, &TrayManager::checkForUpdate);
} }
@ -120,16 +120,14 @@ void TrayManager::wireUi() {
&TrayManager::captureWindowActionTriggered); &TrayManager::captureWindowActionTriggered);
// connect to network signals // connect to network signals
connect(&NetMan::getInstance(), &NetMan::operationListUpdated, this, connect(NetMan::get(), &NetMan::operationListUpdated, this, &TrayManager::onOperationListUpdated);
&TrayManager::onOperationListUpdated); connect(NetMan::get(), &NetMan::releasesChecked, this, &TrayManager::onReleaseCheck);
connect(&NetMan::getInstance(), &NetMan::releasesChecked, this, &TrayManager::onReleaseCheck); connect(&AppSettings::getInstance(), &AppSettings::onOperationUpdated, this, &TrayManager::setActiveOperationLabel);
connect(&AppSettings::getInstance(), &AppSettings::onOperationUpdated, this,
&TrayManager::setActiveOperationLabel);
connect(trayIcon, &QSystemTrayIcon::messageClicked, this, &TrayManager::onTrayMessageClicked); connect(trayIcon, &QSystemTrayIcon::messageClicked, this, &TrayManager::onTrayMessageClicked);
connect(trayIcon, &QSystemTrayIcon::activated, this, [this] { connect(trayIcon, &QSystemTrayIcon::activated, this, [this] {
newOperationAction->setEnabled(false); newOperationAction->setEnabled(false);
NetMan::getInstance().refreshOperationsList(); NetMan::refreshOperationsList();
}); });
connect(updateCheckTimer, &QTimer::timeout, this, &TrayManager::checkForUpdate); connect(updateCheckTimer, &QTimer::timeout, this, &TrayManager::checkForUpdate);
@ -297,7 +295,7 @@ void TrayManager::onOperationListUpdated(bool success,
} }
void TrayManager::checkForUpdate() { void TrayManager::checkForUpdate() {
NetMan::getInstance().checkForNewRelease(Constants::releaseOwner(), Constants::releaseRepo()); NetMan::checkForNewRelease(Constants::releaseOwner(), Constants::releaseRepo());
} }
void TrayManager::onReleaseCheck(bool success, const QList<dto::GithubRelease>& releases) { void TrayManager::onReleaseCheck(bool success, const QList<dto::GithubRelease>& releases) {