Add create operation tray menu option (#66)

Closes #66
main
Joel Smith 2020-11-04 10:58:21 -08:00 committed by GitHub
parent 7c921a00c7
commit 76a3f4790f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 232 additions and 0 deletions

View File

@ -61,6 +61,7 @@ SOURCES += \
src/components/tagging/tagview.cpp \ src/components/tagging/tagview.cpp \
src/components/tagging/tagwidget.cpp \ src/components/tagging/tagwidget.cpp \
src/db/databaseconnection.cpp \ src/db/databaseconnection.cpp \
src/forms/add_operation/createoperation.cpp \
src/forms/evidence_filter/evidencefilter.cpp \ src/forms/evidence_filter/evidencefilter.cpp \
src/forms/evidence_filter/evidencefilterform.cpp \ src/forms/evidence_filter/evidencefilterform.cpp \
src/forms/getinfo/getinfo.cpp \ src/forms/getinfo/getinfo.cpp \
@ -101,6 +102,7 @@ HEADERS += \
src/dtos/checkConnection.h \ src/dtos/checkConnection.h \
src/exceptions/databaseerr.h \ src/exceptions/databaseerr.h \
src/exceptions/fileerror.h \ src/exceptions/fileerror.h \
src/forms/add_operation/createoperation.h \
src/forms/evidence_filter/evidencefilter.h \ src/forms/evidence_filter/evidencefilter.h \
src/forms/evidence_filter/evidencefilterform.h \ src/forms/evidence_filter/evidencefilterform.h \
src/forms/getinfo/getinfo.h \ src/forms/getinfo/getinfo.h \

27
src/dtos/ashirt_error.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef DTO_ASHIRT_ERROR_H
#define DTO_ASHIRT_ERROR_H
#include <QString>
#include "helpers/jsonhelpers.h"
namespace dto {
class AShirtError {
public:
QString error = "";
static AShirtError parseData(QByteArray data) {
return parseJSONItem<AShirtError>(data, AShirtError::fromJson);
}
private:
static AShirtError fromJson(QJsonObject obj) {
AShirtError e;
e.error = obj["error"].toString();
return e;
}
};
}
#endif // DTO_ASHIRT_ERROR_H

View File

@ -13,6 +13,10 @@ namespace dto {
class Operation { class Operation {
public: public:
Operation() {} Operation() {}
Operation(QString name, QString slug) {
this->name = name;
this->slug = slug;
}
enum OperationStatus { enum OperationStatus {
OperationStatusPlanning = 0, OperationStatusPlanning = 0,
@ -34,6 +38,17 @@ class Operation {
return parseJSONList<Operation>(data, Operation::fromJson); return parseJSONList<Operation>(data, Operation::fromJson);
} }
static QByteArray createOperationJson(QString name, QString slug) {
return createOperationJson(Operation(name, slug));
}
static QByteArray createOperationJson(Operation o) {
QJsonObject obj;
obj.insert("slug", o.slug);
obj.insert("name", o.name);
return QJsonDocument(obj).toJson();
}
private: private:
// provides a Operation from a given QJsonObject // provides a Operation from a given QJsonObject
static Operation fromJson(QJsonObject obj) { static Operation fromJson(QJsonObject obj) {

View File

@ -0,0 +1,125 @@
#include "createoperation.h"
#include <QRegularExpression>
#include "helpers/netman.h"
#include "helpers/stopreply.h"
#include "dtos/ashirt_error.h"
CreateOperation::CreateOperation(QWidget* parent) : QDialog(parent) {
buildUi();
wireUi();
}
CreateOperation::~CreateOperation() {
delete closeWindowAction;
delete submitButton;
delete _operationLabel;
delete responseLabel;
delete operationNameTextBox;
delete gridLayout;
stopReply(&createOpReply);
}
void CreateOperation::buildUi() {
gridLayout = new QGridLayout(this);
submitButton = new LoadingButton("Submit", this);
submitButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
_operationLabel = new QLabel("Operation Name", this);
_operationLabel->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
responseLabel = new QLabel(this);
operationNameTextBox = new QLineEdit(this);
// Layout
/* 0 1 2
+---------------+-------------+------------+
0 | Op Lbl | [Operation TB] |
+---------------+-------------+------------+
1 | Error Lbl |
+---------------+-------------+------------+
2 | <None> | <None> | Submit Btn |
+---------------+-------------+------------+
*/
// row 0
gridLayout->addWidget(_operationLabel, 0, 0);
gridLayout->addWidget(operationNameTextBox, 0, 1, 1, 2);
// row 1
gridLayout->addWidget(responseLabel, 1, 0, 1, 3);
// row 2
gridLayout->addWidget(submitButton, 2, 2);
closeWindowAction = new QAction(this);
closeWindowAction->setShortcut(QKeySequence::Close);
this->addAction(closeWindowAction);
this->setLayout(gridLayout);
this->resize(400, 1);
this->setWindowTitle("Create Operation");
Qt::WindowFlags flags = this->windowFlags();
flags |= Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint | Qt::WindowMinMaxButtonsHint |
Qt::WindowCloseButtonHint;
this->setWindowFlags(flags);
}
void CreateOperation::wireUi() {
connect(submitButton, &QPushButton::clicked, this, &CreateOperation::submitButtonClicked);
}
void CreateOperation::submitButtonClicked() {
responseLabel->setText("");
auto name = operationNameTextBox->text().trimmed();
auto slug = makeSlugFromName(name);
if (slug == "") {
responseLabel->setText(
(name == "")
? "The Operation Name must not be empty"
: "The Operation Name must include letters or numbers"
);
return;
}
submitButton->startAnimation();
submitButton->setEnabled(false);
createOpReply = NetMan::getInstance().createOperation(name, slug);
connect(createOpReply, &QNetworkReply::finished, this, &CreateOperation::onRequestComplete);
}
QString CreateOperation::makeSlugFromName(QString name) {
static QRegularExpression invalidCharsRegex("[^A-Za-z0-9]+");
static QRegularExpression startOrEndDash("^-|-$");
return name.toLower().replace(invalidCharsRegex, "-").replace(startOrEndDash, "");
}
void CreateOperation::onRequestComplete() {
bool isValid;
auto data = NetMan::extractResponse(createOpReply, isValid);
if (isValid) {
dto::Operation op = dto::Operation::parseData(data);
AppSettings::getInstance().setOperationDetails(op.slug, op.name);
operationNameTextBox->clear();
this->close();
}
else {
dto::AShirtError err = dto::AShirtError::parseData(data);
if (err.error.contains("slug already exists")) {
responseLabel->setText("A similar operation name already exists. Please try a new name.");
}
else {
responseLabel->setText("Got an unexpected error: " + err.error);
}
}
submitButton->stopAnimation();
submitButton->setEnabled(true);
tidyReply(&createOpReply);
}

View File

@ -0,0 +1,46 @@
#ifndef FORM_CREATEOPERATION_H
#define FORM_CREATEOPERATION_H
#include <QAction>
#include <QDialog>
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include <QNetworkReply>
#include "components/loading_button/loadingbutton.h"
class CreateOperation : public QDialog {
Q_OBJECT
public:
explicit CreateOperation(QWidget *parent = nullptr);
~CreateOperation();
private:
void buildUi();
void wireUi();
void submitButtonClicked();
private slots:
void onRequestComplete();
QString makeSlugFromName(QString name);
// void showEvent(QShowEvent *evt) override;
private:
QNetworkReply* createOpReply = nullptr;
// ui elements
QGridLayout* gridLayout = nullptr;
QAction* closeWindowAction = nullptr;
LoadingButton* submitButton = nullptr;
QLabel* _operationLabel = nullptr;
QLabel* responseLabel = nullptr;
QLineEdit* operationNameTextBox = nullptr;
};
#endif // FORM_CREATEOPERATION_H

View File

@ -234,6 +234,13 @@ class NetMan : public QObject {
return builder->execute(nam); return builder->execute(nam);
} }
/// createOperation attempts to create a new operation with the given name and slug
QNetworkReply *createOperation(QString name, QString slug) {
auto builder = ashirtJSONPost("/api/operations", dto::Operation::createOperationJson(name, slug));
addASHIRTAuth(builder);
return builder->execute(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.
/// In addition, it will also indicated, via the provided valid flag, if the response was valid. /// In addition, it will also indicated, via the provided valid flag, if the response was valid.
/// A Valid response is one that has a 200 or 201 response AND had no errors flaged from Qt /// A Valid response is one that has a 200 or 201 response AND had no errors flaged from Qt

View File

@ -99,6 +99,7 @@ void TrayManager::buildUi() {
settingsWindow = new Settings(hotkeyManager, this); settingsWindow = new Settings(hotkeyManager, this);
evidenceManagerWindow = new EvidenceManager(db, this); evidenceManagerWindow = new EvidenceManager(db, this);
creditsWindow = new Credits(this); creditsWindow = new Credits(this);
createOperationWindow = new CreateOperation(this);
trayIconMenu = new QMenu(this); trayIconMenu = new QMenu(this);
chooseOpSubmenu = new QMenu(tr("Select Operation")); chooseOpSubmenu = new QMenu(tr("Select Operation"));
@ -126,7 +127,10 @@ void TrayManager::buildUi() {
currentOperationMenuAction->setEnabled(false); currentOperationMenuAction->setEnabled(false);
chooseOpStatusAction = new QAction("Loading operations...", chooseOpSubmenu); chooseOpStatusAction = new QAction("Loading operations...", chooseOpSubmenu);
chooseOpStatusAction->setEnabled(false); chooseOpStatusAction->setEnabled(false);
newOperationAction = new QAction("New Operation", chooseOpSubmenu);
newOperationAction->setEnabled(false); // only enable when we have an internet connection
chooseOpSubmenu->addAction(chooseOpStatusAction); chooseOpSubmenu->addAction(chooseOpStatusAction);
chooseOpSubmenu->addAction(newOperationAction);
chooseOpSubmenu->addSeparator(); chooseOpSubmenu->addSeparator();
setActiveOperationLabel(); setActiveOperationLabel();
@ -158,6 +162,7 @@ void TrayManager::wireUi() {
connect(showEvidenceManagerAction, actTriggered, [this, toTop](){toTop(evidenceManagerWindow);}); connect(showEvidenceManagerAction, actTriggered, [this, toTop](){toTop(evidenceManagerWindow);});
connect(showCreditsAction, actTriggered, [this, toTop](){toTop(creditsWindow);}); connect(showCreditsAction, actTriggered, [this, toTop](){toTop(creditsWindow);});
connect(addCodeblockAction, actTriggered, this, &TrayManager::captureCodeblockActionTriggered); connect(addCodeblockAction, actTriggered, this, &TrayManager::captureCodeblockActionTriggered);
connect(newOperationAction, actTriggered, [this, toTop](){toTop(createOperationWindow);});
connect(screenshotTool, &Screenshot::onScreenshotCaptured, this, connect(screenshotTool, &Screenshot::onScreenshotCaptured, this,
&TrayManager::onScreenshotCaptured); &TrayManager::onScreenshotCaptured);
@ -180,6 +185,7 @@ void TrayManager::wireUi() {
connect(trayIcon, &QSystemTrayIcon::messageClicked, [](){QDesktopServices::openUrl(Constants::releasePageUrl());}); connect(trayIcon, &QSystemTrayIcon::messageClicked, [](){QDesktopServices::openUrl(Constants::releasePageUrl());});
connect(trayIcon, &QSystemTrayIcon::activated, [this] { connect(trayIcon, &QSystemTrayIcon::activated, [this] {
chooseOpStatusAction->setText("Loading operations..."); chooseOpStatusAction->setText("Loading operations...");
newOperationAction->setEnabled(false);
NetMan::getInstance().refreshOperationsList(); NetMan::getInstance().refreshOperationsList();
}); });
@ -296,6 +302,7 @@ void TrayManager::onOperationListUpdated(bool success,
if (success) { if (success) {
chooseOpStatusAction->setText(tr("Operations loaded")); chooseOpStatusAction->setText(tr("Operations loaded"));
newOperationAction->setEnabled(true);
cleanChooseOpSubmenu(); cleanChooseOpSubmenu();
for (const auto& op : operations) { for (const auto& op : operations) {

View File

@ -16,6 +16,7 @@
#include "helpers/screenshot.h" #include "helpers/screenshot.h"
#include "hotkeymanager.h" #include "hotkeymanager.h"
#include "tools/UGlobalHotkey/uglobalhotkeys.h" #include "tools/UGlobalHotkey/uglobalhotkeys.h"
#include "forms/add_operation/createoperation.h"
#ifndef QT_NO_SYSTEMTRAYICON #ifndef QT_NO_SYSTEMTRAYICON
@ -75,6 +76,7 @@ class TrayManager : public QDialog {
Settings *settingsWindow = nullptr; Settings *settingsWindow = nullptr;
EvidenceManager *evidenceManagerWindow = nullptr; EvidenceManager *evidenceManagerWindow = nullptr;
Credits *creditsWindow = nullptr; Credits *creditsWindow = nullptr;
CreateOperation *createOperationWindow = nullptr;
// UI Elements // UI Elements
QSystemTrayIcon *trayIcon = nullptr; QSystemTrayIcon *trayIcon = nullptr;
@ -91,6 +93,7 @@ class TrayManager : public QDialog {
QMenu *chooseOpSubmenu = nullptr; QMenu *chooseOpSubmenu = nullptr;
QAction *chooseOpStatusAction = nullptr; QAction *chooseOpStatusAction = nullptr;
QAction *newOperationAction = nullptr;
QAction *selectedAction = nullptr; // note: do not delete; for reference only QAction *selectedAction = nullptr; // note: do not delete; for reference only
std::vector<QAction *> allOperationActions; std::vector<QAction *> allOperationActions;
}; };