diff --git a/CMakeLists.txt b/CMakeLists.txt index 1742e22a..973869c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,7 @@ find_package(Qt6 6.5.0 REQUIRED COMPONENTS Network Sql Core + Svg ) add_subdirectory(deploy) diff --git a/src/appconfig.cpp b/src/appconfig.cpp index 4d5fcaa2..8f238a5d 100644 --- a/src/appconfig.cpp +++ b/src/appconfig.cpp @@ -104,6 +104,9 @@ QString AppConfig::defaultValue(const QString &key) if (key == CONFIG::APIURL) return QStringLiteral("http://localhost:8080"); + if (key == CONFIG::SHOW_WELCOME_SCREEN) + return QStringLiteral("true"); + if (key == CONFIG::SHORTCUT_CAPTURECLIPBOARD) { if(!get()->appSettings->value(key).isValid()) return QStringLiteral("Meta+Alt+v"); diff --git a/src/appconfig.h b/src/appconfig.h index aab7bdcb..a60661b9 100644 --- a/src/appconfig.h +++ b/src/appconfig.h @@ -21,6 +21,7 @@ struct CONFIG { inline static const auto COMMAND_CAPTUREWINDOW = QStringLiteral("captureWindowExec"); inline static const auto SHORTCUT_CAPTUREWINDOW = QStringLiteral("captureWindowShortcut"); inline static const auto SHORTCUT_CAPTURECLIPBOARD = QStringLiteral("captureClipboardShortcut"); + inline static const auto SHOW_WELCOME_SCREEN = QStringLiteral("showWelcomeScreen"); }; /// AppConfig is a singleton for accessing the application's configuration. @@ -102,5 +103,6 @@ class AppConfig : public QObject CONFIG::COMMAND_CAPTUREWINDOW, CONFIG::SHORTCUT_CAPTUREWINDOW, CONFIG::SHORTCUT_CAPTURECLIPBOARD, + CONFIG::SHOW_WELCOME_SCREEN, }; }; diff --git a/src/forms/CMakeLists.txt b/src/forms/CMakeLists.txt index bd1481b2..00fc095a 100644 --- a/src/forms/CMakeLists.txt +++ b/src/forms/CMakeLists.txt @@ -9,6 +9,19 @@ add_library (FORMS STATIC getinfo/getinfo.cpp getinfo/getinfo.h porting/porting_dialog.cpp porting/porting_dialog.h settings/settings.cpp settings/settings.h + firstRunWizard/firstTimeWizard.h firstRunWizard/firstTimeWizard.cpp + firstRunWizard/welcomepage.h firstRunWizard/welcomepage.cpp + firstRunWizard/wizardpage.h firstRunWizard/wizardpage.cpp + firstRunWizard/requirementspage.h firstRunWizard/requirementspage.cpp + firstRunWizard/evidencepage.h firstRunWizard/evidencepage.cpp + firstRunWizard/apikeyspage.h firstRunWizard/apikeyspage.cpp + firstRunWizard/hostpathpage.h firstRunWizard/hostpathpage.cpp + firstRunWizard/hosttestpage.h firstRunWizard/hosttestpage.cpp + firstRunWizard/captureareapage.h firstRunWizard/captureareapage.cpp + firstRunWizard/capturewindowpage.h firstRunWizard/capturewindowpage.cpp + firstRunWizard/captureclipboardpage.h firstRunWizard/captureclipboardpage.cpp + firstRunWizard/opspage.h firstRunWizard/opspage.cpp + firstRunWizard/finishedpage.h firstRunWizard/finishedpage.cpp ) add_library(ASHIRT::FORMS ALIAS FORMS) @@ -26,6 +39,7 @@ target_link_libraries ( FORMS Qt::Gui Qt::Widgets Qt::Sql + Qt::Svg ASHIRT::HELPERS ASHIRT::MODELS ASHIRT::COMPONENTS diff --git a/src/forms/firstRunWizard/apikeyspage.cpp b/src/forms/firstRunWizard/apikeyspage.cpp new file mode 100644 index 00000000..fc12d31f --- /dev/null +++ b/src/forms/firstRunWizard/apikeyspage.cpp @@ -0,0 +1,114 @@ +#include "apikeyspage.h" + +#include +#include +#include +#include +#include +#include + +bool ApiKeysPage::validatePage() +{ + if (!field("host.accessKey").isValid()) + return false; + + if (!field("host.secretKey").isValid()) + return false; + + return true; +} + +void ApiKeysPage::initializePage() +{ + QString accessKey = AppConfig::value(CONFIG::ACCESSKEY); + setField("host.accessKey", accessKey); + accessKeyLine->setText(accessKey); + + QString secretKey = AppConfig::value(CONFIG::SECRETKEY); + setField("host.secretKey", secretKey); + secretKeyLine->setText(secretKey); +} + +ApiKeysPage::ApiKeysPage(QWidget *parent) + : WizardPage{Page_Api, parent} +{ + + auto f = font(); + auto _lblTitleLabel = new QLabel(this); + f.setPointSize(titleFont.first); + f.setWeight(titleFont.second); + _lblTitleLabel->setFont(f); + _lblTitleLabel->setText(tr("API Keys")); + + auto _lblSubtitle = new QLabel(this); + f.setPointSize(subTitleFont.first); + f.setWeight(subTitleFont.second); + _lblSubtitle->setFont(f); + _lblSubtitle->setWordWrap(true); + _lblSubtitle->setText(tr("Input Api keys for the server")); + + auto _lblBody = new QLabel(this); + f.setPointSize(bodyFont.first); + f.setWeight(bodyFont.second); + _lblBody->setFont(f); + _lblBody->setWordWrap(true); + _lblBody->setText(tr("• Login to the Ashirt server click the profile icon in the top right then select Account Settings
")); + + auto _lblBody2 = new QLabel(this); + _lblBody2->setFont(f); + _lblBody2->setWordWrap(true); + _lblBody2->setText(tr("• Select API Keys on the left, select click Generate New API Key
")); + + auto _lblBody3 = new QLabel(this); + _lblBody3->setFont(f); + _lblBody3->setWordWrap(true); + _lblBody3->setText(tr("• Enter your keys below")); + + auto _lblAKey = new QLabel(this); + f.setPointSize(smallFont.first); + f.setWeight(smallFont.second); + _lblAKey->setFont(f); + _lblAKey->setText(tr("Access Key")); + + accessKeyLine = new QLineEdit(this); + accessKeyLine->setMaxLength(24); + accessKeyLine->setTextMargins(3,0,3,0); + accessKeyLine->setMaximumWidth(fontMetrics().averageCharWidth() * 27); + registerField("host.accessKey*", accessKeyLine); + + auto t1 = new QVBoxLayout(); + t1->setSpacing(1); + t1->addWidget(_lblAKey); + t1->addWidget(accessKeyLine); + + auto _lblBKey = new QLabel(this); + f.setPointSize(smallFont.first); + f.setWeight(smallFont.second); + _lblBKey->setFont(f); + _lblBKey->setText(tr("Secret Key")); + + secretKeyLine = new QLineEdit(this); + secretKeyLine->setTextMargins(3,0,3,0); + registerField("host.secretKey*", secretKeyLine); + + auto t2 = new QVBoxLayout(); + t2->setSpacing(1); + t2->addWidget(_lblBKey); + t2->addWidget(secretKeyLine); + + auto tLayout = new QHBoxLayout(); + tLayout->addLayout(t1); + tLayout->addLayout(t2); + + auto layout = new QVBoxLayout(); + layout->addWidget(_lblTitleLabel); + layout->addWidget(_lblSubtitle); + layout->addSpacerItem(new QSpacerItem(0,30,QSizePolicy::Minimum, QSizePolicy::Fixed)); + layout->addWidget(_lblBody); + layout->addWidget(_lblBody2); + layout->addWidget(_lblBody3); + layout->addSpacerItem(new QSpacerItem(0,30,QSizePolicy::Minimum, QSizePolicy::Fixed)); + layout->addLayout(tLayout); + layout->addSpacerItem(new QSpacerItem(0,0,QSizePolicy::Minimum, QSizePolicy::Expanding)); + setLayout(layout); +} diff --git a/src/forms/firstRunWizard/apikeyspage.h b/src/forms/firstRunWizard/apikeyspage.h new file mode 100644 index 00000000..2fd8baf3 --- /dev/null +++ b/src/forms/firstRunWizard/apikeyspage.h @@ -0,0 +1,17 @@ +#pragma once + +#include "wizardpage.h" +#include + +class QLineEdit; +class ApiKeysPage : public WizardPage +{ + Q_OBJECT + public: + bool validatePage() override; + void initializePage() override; + ApiKeysPage(QWidget *parent = nullptr); + private: + QLineEdit * accessKeyLine = nullptr; + QLineEdit * secretKeyLine = nullptr; +}; diff --git a/src/forms/firstRunWizard/captureareapage.cpp b/src/forms/firstRunWizard/captureareapage.cpp new file mode 100644 index 00000000..de70f99c --- /dev/null +++ b/src/forms/firstRunWizard/captureareapage.cpp @@ -0,0 +1,115 @@ +#include "captureareapage.h" + +#include +#include +#include +#include +#include +#include + +#include "hotkeymanager.h" +#include "components/custom_keyseq_edit/singlestrokekeysequenceedit.h" + +bool CaptureAreaPage::validatePage() +{ + if(!field("command.area").isValid()) + return false; + AppConfig::setValue(CONFIG::COMMAND_SCREENSHOT, field("command.area").toString()); + + if(!field("keySequence.area").isValid()) + return false; + auto keyCombo = QKeySequence::fromString(field("keySequence.area").toString(), QKeySequence::NativeText); + AppConfig::setValue(CONFIG::SHORTCUT_SCREENSHOT, keyCombo.toString(QKeySequence::PortableText)); + HotkeyManager::updateHotkeys(); + return true; +} + +void CaptureAreaPage::initializePage() +{ + HotkeyManager::unregisterKey(HotkeyManager::ACTION_CAPTURE_AREA); + QString captureAreaCommand = AppConfig::value(CONFIG::COMMAND_SCREENSHOT); + if(!captureAreaCommand.isEmpty()) { + setField("command.area", captureAreaCommand); + captureAreaLine->setText(captureAreaCommand); + } + + QString sequence = AppConfig::value(CONFIG::SHORTCUT_SCREENSHOT); + if(!sequence.isEmpty()) { + setField("keySequence.area", sequence); + captureAreaKeySequenceEdit->setKeySequence(QKeySequence::fromString(sequence)); + } +} + +void CaptureAreaPage::cleanupPage() +{ + HotkeyManager::updateHotkeys(); +} + +CaptureAreaPage::CaptureAreaPage(QWidget *parent) + : WizardPage{Page_CaptureArea, parent} +{ + auto f = font(); + auto _lblTitleLabel = new QLabel(this); + f.setPointSize(titleFont.first); + f.setWeight(titleFont.second); + _lblTitleLabel->setFont(f); + _lblTitleLabel->setText(tr("Capture Area")); + + auto _lblSubtitle = new QLabel(this); + f.setPointSize(subTitleFont.first); + f.setWeight(subTitleFont.second); + _lblSubtitle->setFont(f); + _lblSubtitle->setWordWrap(true); + _lblSubtitle->setText(tr("Set the capture area command and shortcut.")); + + auto _lblB1 = new QLabel(this); + f.setPointSize(bodyFont.first); + f.setWeight(bodyFont.second); + _lblB1->setFont(f); + _lblB1->setText(tr("

• Enter the command ashirt will use to capture area screenshots

• Enter a key combination that will trigger the capture area command")); + + auto hLayout = new QHBoxLayout(); + hLayout->addWidget(_lblB1, 0, Qt::AlignHCenter); + + auto _lblAKey = new QLabel(this); + f.setPointSize(smallFont.first); + f.setWeight(smallFont.second); + _lblAKey->setFont(f); + _lblAKey->setText(tr("Capture Area Command")); + + captureAreaLine = new QLineEdit(this); + captureAreaLine->setTextMargins(3,0,3,0); + registerField("command.area*", captureAreaLine); + + auto t1 = new QVBoxLayout(); + t1->setSpacing(1); + t1->addWidget(_lblAKey); + t1->addWidget(captureAreaLine); + + auto _lblBKey = new QLabel(this); + f.setPointSize(smallFont.first); + f.setWeight(smallFont.second); + _lblBKey->setFont(f); + _lblBKey->setText(tr("Shortcut")); + + captureAreaKeySequenceEdit = new SingleStrokeKeySequenceEdit(this); + registerField("keySequence.area*", captureAreaKeySequenceEdit, "keySequence", SIGNAL(keySequenceChanged(const QKeySequence &))); + + auto t2 = new QVBoxLayout(); + t2->setSpacing(1); + t2->addWidget(_lblBKey); + t2->addWidget(captureAreaKeySequenceEdit); + + auto tLayout = new QHBoxLayout(); + tLayout->addLayout(t1, 8); + tLayout->addLayout(t2, 2); + + auto layout = new QVBoxLayout(); + layout->addWidget(_lblTitleLabel); + layout->addWidget(_lblSubtitle); + layout->addLayout(hLayout); + layout->addSpacerItem(new QSpacerItem(0,60,QSizePolicy::Minimum, QSizePolicy::Fixed)); + layout->addLayout(tLayout); + layout->addSpacerItem(new QSpacerItem(0,0,QSizePolicy::Minimum, QSizePolicy::Expanding)); + setLayout(layout); +} diff --git a/src/forms/firstRunWizard/captureareapage.h b/src/forms/firstRunWizard/captureareapage.h new file mode 100644 index 00000000..70165e8e --- /dev/null +++ b/src/forms/firstRunWizard/captureareapage.h @@ -0,0 +1,19 @@ +#pragma once + +#include "wizardpage.h" +#include + +class QLineEdit; +class SingleStrokeKeySequenceEdit; +class CaptureAreaPage : public WizardPage +{ + Q_OBJECT + public: + bool validatePage() override; + void initializePage() override; + void cleanupPage() override; + CaptureAreaPage(QWidget *parent = nullptr); + private: + QLineEdit *captureAreaLine = nullptr; + SingleStrokeKeySequenceEdit *captureAreaKeySequenceEdit = nullptr; +}; diff --git a/src/forms/firstRunWizard/captureclipboardpage.cpp b/src/forms/firstRunWizard/captureclipboardpage.cpp new file mode 100644 index 00000000..8f5528d7 --- /dev/null +++ b/src/forms/firstRunWizard/captureclipboardpage.cpp @@ -0,0 +1,84 @@ +#include "captureclipboardpage.h" + +#include +#include +#include +#include +#include +#include + +#include "hotkeymanager.h" +#include "components/custom_keyseq_edit/singlestrokekeysequenceedit.h" + +bool CaptureClipboardPage::validatePage() +{ + if(!field("keySequence.clipboard").isValid()) + return false; + + auto keyCombo = QKeySequence::fromString(field("keySequence.clipboard").toString(), QKeySequence::NativeText); + AppConfig::setValue(CONFIG::SHORTCUT_CAPTURECLIPBOARD, keyCombo.toString(QKeySequence::PortableText)); + HotkeyManager::updateHotkeys(); + return true; +} + +void CaptureClipboardPage::initializePage() +{ + HotkeyManager::unregisterKey(HotkeyManager::ACTION_CAPTURE_CLIPBOARD); + QString sequence = AppConfig::value(CONFIG::SHORTCUT_CAPTURECLIPBOARD); + setField("keySequence.clipboard", sequence); + captureClipboardKeySequence->setKeySequence(QKeySequence::fromString(sequence)); +} + +void CaptureClipboardPage::cleanupPage() +{ + HotkeyManager::updateHotkeys(); + HotkeyManager::unregisterKey(HotkeyManager::ACTION_CAPTURE_WINDOW); +} + +CaptureClipboardPage::CaptureClipboardPage(QWidget *parent) + : WizardPage{WizardPage::Page_CaptureClipboard, parent} +{ + auto f = font(); + auto _lblTitleLabel = new QLabel(this); + f.setPointSize(titleFont.first); + f.setWeight(titleFont.second); + _lblTitleLabel->setFont(f); + _lblTitleLabel->setText(tr("Capture Cliboard")); + + auto _lblSubtitle = new QLabel(this); + f.setPointSize(subTitleFont.first); + f.setWeight(subTitleFont.second); + _lblSubtitle->setFont(f); + _lblSubtitle->setWordWrap(true); + _lblSubtitle->setText(tr("Uploads clipboard images or text to ashirt.\nText is uploaded as a code block")); + + auto _lblB1 = new QLabel(this); + f.setPointSize(bodyFont.first); + f.setWeight(bodyFont.second); + _lblB1->setFont(f); + _lblB1->setText(tr("\n\n• Enter the shortcut code below")); + + auto _lblBKey = new QLabel(this); + f.setPointSize(smallFont.first); + f.setWeight(smallFont.second); + _lblBKey->setFont(f); + _lblBKey->setText(tr("Capture Clipboard Shortcut")); + + captureClipboardKeySequence = new SingleStrokeKeySequenceEdit(this); + captureClipboardKeySequence->setFixedWidth(120); + registerField("keySequence.clipboard*", captureClipboardKeySequence, "keySequence", SIGNAL(keySequenceChanged(const QKeySequence &))); + + auto t2 = new QVBoxLayout(); + t2->setSpacing(1); + t2->addWidget(_lblBKey); + t2->addWidget(captureClipboardKeySequence); + + auto layout = new QVBoxLayout(); + layout->addWidget(_lblTitleLabel); + layout->addWidget(_lblSubtitle); + layout->addWidget(_lblB1); + layout->addSpacerItem(new QSpacerItem(0,60,QSizePolicy::Minimum, QSizePolicy::Fixed)); + layout->addLayout(t2); + layout->addSpacerItem(new QSpacerItem(0,0,QSizePolicy::Minimum, QSizePolicy::Expanding)); + setLayout(layout); +} diff --git a/src/forms/firstRunWizard/captureclipboardpage.h b/src/forms/firstRunWizard/captureclipboardpage.h new file mode 100644 index 00000000..69387fc4 --- /dev/null +++ b/src/forms/firstRunWizard/captureclipboardpage.h @@ -0,0 +1,17 @@ +#pragma once + +#include "wizardpage.h" +#include + +class SingleStrokeKeySequenceEdit; +class CaptureClipboardPage : public WizardPage +{ + Q_OBJECT + public: + bool validatePage() override; + void initializePage() override; + void cleanupPage() override; + CaptureClipboardPage(QWidget *parent = nullptr); + private: + SingleStrokeKeySequenceEdit *captureClipboardKeySequence = nullptr; +}; diff --git a/src/forms/firstRunWizard/capturewindowpage.cpp b/src/forms/firstRunWizard/capturewindowpage.cpp new file mode 100644 index 00000000..ecbd2cf7 --- /dev/null +++ b/src/forms/firstRunWizard/capturewindowpage.cpp @@ -0,0 +1,113 @@ +#include "capturewindowpage.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "hotkeymanager.h" +#include "components/custom_keyseq_edit/singlestrokekeysequenceedit.h" + +bool CaptureWindowPage::validatePage() +{ + if(!field("command.window").isValid()) + return false; + AppConfig::setValue(CONFIG::COMMAND_CAPTUREWINDOW, field("command.window").toString()); + + if(!field("keySequence.window").isValid()) + return false; + auto keyCombo = QKeySequence::fromString(field("keySequence.window").toString(), QKeySequence::NativeText); + AppConfig::setValue(CONFIG::SHORTCUT_CAPTUREWINDOW, keyCombo.toString(QKeySequence::PortableText)); + HotkeyManager::updateHotkeys(); + return true; +} + +void CaptureWindowPage::initializePage() +{ + HotkeyManager::unregisterKey(HotkeyManager::ACTION_CAPTURE_WINDOW); + QString captureWindowCommand = AppConfig::value(CONFIG::COMMAND_CAPTUREWINDOW); + setField("command.window", captureWindowCommand); + captureWindowLine->setText(captureWindowCommand); + + QString sequence = AppConfig::value(CONFIG::SHORTCUT_CAPTUREWINDOW); + setField("keySequence.window", sequence); + captureWindowKeySequence->setKeySequence(QKeySequence::fromString(sequence)); +} + +void CaptureWindowPage::cleanupPage() +{ + HotkeyManager::updateHotkeys(); + HotkeyManager::unregisterKey(HotkeyManager::ACTION_CAPTURE_AREA); +} + +CaptureWindowPage::CaptureWindowPage(QWidget *parent) + : WizardPage{Page_CaptureWindow, parent} +{ + auto f = font(); + auto _lblTitleLabel = new QLabel(this); + f.setPointSize(titleFont.first); + f.setWeight(titleFont.second); + _lblTitleLabel->setFont(f); + _lblTitleLabel->setText(tr("Capture Window")); + + auto _lblSubtitle = new QLabel(this); + f.setPointSize(subTitleFont.first); + f.setWeight(subTitleFont.second); + _lblSubtitle->setFont(f); + _lblSubtitle->setWordWrap(true); + _lblSubtitle->setText(tr("Set the capture window command and shortcut.")); + + auto _lblB1 = new QLabel(this); + f.setPointSize(bodyFont.first); + f.setWeight(bodyFont.second); + _lblB1->setFont(f); + _lblB1->setText(tr("

• Enter the command ashirt will use to capture window screenshots

• Enter a key combination that will trigger the capture window command")); + + auto hLayout = new QHBoxLayout(); + hLayout->addWidget(_lblB1, 0, Qt::AlignHCenter); + + auto _lblAKey = new QLabel(this); + f.setPointSize(smallFont.first); + f.setWeight(smallFont.second); + _lblAKey->setFont(f); + _lblAKey->setText(tr("Capture Window Command")); + + captureWindowLine = new QLineEdit(this); + captureWindowLine->setTextMargins(3,0,3,0); + registerField("command.window*", captureWindowLine); + + auto t1 = new QVBoxLayout(); + t1->setSpacing(1); + t1->addWidget(_lblAKey); + t1->addWidget(captureWindowLine); + + auto _lblBKey = new QLabel(this); + f.setPointSize(smallFont.first); + f.setWeight(smallFont.second); + _lblBKey->setFont(f); + _lblBKey->setText(tr("Shortcut")); + + captureWindowKeySequence = new SingleStrokeKeySequenceEdit(this); + registerField("keySequence.window*", captureWindowKeySequence, "keySequence", SIGNAL(keySequenceChanged(const QKeySequence &))); + + auto t2 = new QVBoxLayout(); + t2->setSpacing(1); + t2->addWidget(_lblBKey); + t2->addWidget(captureWindowKeySequence); + + auto tLayout = new QHBoxLayout(); + tLayout->addLayout(t1, 8); + tLayout->addLayout(t2, 2); + + auto layout = new QVBoxLayout(); + layout->addWidget(_lblTitleLabel); + layout->addWidget(_lblSubtitle); + layout->addLayout(hLayout); + layout->addSpacerItem(new QSpacerItem(0,60,QSizePolicy::Minimum, QSizePolicy::Fixed)); + layout->addLayout(tLayout); + layout->addSpacerItem(new QSpacerItem(0,0,QSizePolicy::Minimum, QSizePolicy::Expanding)); + setLayout(layout); +} diff --git a/src/forms/firstRunWizard/capturewindowpage.h b/src/forms/firstRunWizard/capturewindowpage.h new file mode 100644 index 00000000..baffba4e --- /dev/null +++ b/src/forms/firstRunWizard/capturewindowpage.h @@ -0,0 +1,19 @@ +#pragma once + +#include "wizardpage.h" +#include + +class QLineEdit; +class SingleStrokeKeySequenceEdit; +class CaptureWindowPage : public WizardPage +{ + Q_OBJECT + public: + bool validatePage() override; + void initializePage() override; + void cleanupPage() override; + CaptureWindowPage(QWidget *parent = nullptr); + private: + QLineEdit *captureWindowLine = nullptr; + SingleStrokeKeySequenceEdit *captureWindowKeySequence = nullptr; +}; diff --git a/src/forms/firstRunWizard/evidencepage.cpp b/src/forms/firstRunWizard/evidencepage.cpp new file mode 100644 index 00000000..efd5b26c --- /dev/null +++ b/src/forms/firstRunWizard/evidencepage.cpp @@ -0,0 +1,121 @@ +#include "evidencepage.h" + +#include +#include +#include +#include +#include +#include + +bool EvidencePage::validatePage() +{ + QDir path = field("eRepo").toString(); + + if (!path.exists()) + path.mkpath(path.absolutePath()); + + if(!path.exists()) { + errorLabel->setText(tr("can not use selected path")); + return false; + } + AppConfig::setValue(CONFIG::EVIDENCEREPO, path.absolutePath()); + errorLabel->setText(QString()); + return true; +} + +void EvidencePage::initializePage() +{ + QString repoPath = AppConfig::value(CONFIG::EVIDENCEREPO); + setField("eRepo", repoPath); + evidenceLine->setText(repoPath); + + if(repoPath.isEmpty()) + errorLabel->setText(tr("Enter a Path")); + QDir repo(repoPath); + + if (!repo.exists()) { + repo.mkpath(repo.absolutePath()); + } + + if(!repo.exists() || !repo.isReadable()) + errorLabel->setText(tr("Invalid Path")); +} + +EvidencePage::EvidencePage(QWidget *parent) + : WizardPage{Page_Evidence, parent} +{ + auto f = font(); + + auto _lblTitleLabel = new QLabel(this); + f.setPointSize(titleFont.first); + f.setWeight(titleFont.second); + _lblTitleLabel->setFont(f); + _lblTitleLabel->setText(tr("Evidence Repository")); + + auto _lblSubtitle = new QLabel(this); + f.setPointSize(subTitleFont.first); + f.setWeight(subTitleFont.second); + _lblSubtitle->setFont(f); + _lblSubtitle->setWordWrap(true); + _lblSubtitle->setText(tr("This is where evidence is stored and your jumping off point.\n Files will be stored in a subdirectory using the operation name.")); + + auto _lblEvidence = new QLabel(this); + f.setPointSize(smallFont.first); + f.setWeight(smallFont.second); + _lblEvidence->setFont(f); + _lblEvidence->setText(tr("Evidence Repository:")); + + evidenceLine = new QLineEdit(this); + evidenceLine->setTextMargins(3,0,3,0); + registerField("eRepo*", evidenceLine); + + auto _btnRepoBrowse = new QPushButton(this); + _btnRepoBrowse->setText(tr("Browse")); + connect(_btnRepoBrowse, &QPushButton::clicked, this, [this] { + QString txt = QFileDialog::getExistingDirectory(this, tr("Select Path to Store Evidence"), evidenceLine->text()); + if(txt.isEmpty()) + return; + evidenceLine->setText(txt); + setField("eRepo", txt); + }); + + auto _btnReset = new QPushButton(this); + _btnReset->setText(tr("Restore Default")); + connect(_btnReset, &QPushButton::clicked, this, [this] { + evidenceLine->setText(AppConfig::defaultValue(CONFIG::EVIDENCEREPO)); + setField("eRepo", evidenceLine->text()); + }); + + errorLabel = new QLabel(this); + f.setPointSize(smallFont.first); + f.setWeight(smallFont.second); + errorLabel->setFont(f); + + auto btnLayout = new QHBoxLayout(); + btnLayout->addWidget(evidenceLine); + btnLayout->addWidget(_btnRepoBrowse); + btnLayout->addWidget(_btnReset); + + auto t1 = new QVBoxLayout(); + t1->setSpacing(1); + t1->addWidget(_lblEvidence); + t1->addLayout(btnLayout); + t1->addWidget(errorLabel); + + auto _lblBody = new QLabel(this); + f.setPointSize(bodyFont.first); + f.setWeight(bodyFont.second); + _lblBody->setFont(f); + _lblBody->setText(tr("• Browse and input operation filename")); + + auto layout = new QVBoxLayout(); + layout->addWidget(_lblTitleLabel); + layout->addWidget(_lblSubtitle); + layout->addSpacerItem(new QSpacerItem(0,30,QSizePolicy::Minimum, QSizePolicy::Fixed)); + layout->addWidget(_lblBody); + layout->addSpacerItem(new QSpacerItem(0,40,QSizePolicy::Minimum, QSizePolicy::Fixed)); + layout->addLayout(t1); + layout->addSpacerItem(new QSpacerItem(0,0,QSizePolicy::Minimum, QSizePolicy::Expanding)); + setLayout(layout); + +} diff --git a/src/forms/firstRunWizard/evidencepage.h b/src/forms/firstRunWizard/evidencepage.h new file mode 100644 index 00000000..53ed47de --- /dev/null +++ b/src/forms/firstRunWizard/evidencepage.h @@ -0,0 +1,18 @@ +#pragma once + +#include "wizardpage.h" +#include + +class QLineEdit; +class QLabel; +class EvidencePage : public WizardPage +{ + Q_OBJECT + public: + bool validatePage() override; + void initializePage() override; + EvidencePage(QWidget *parent = nullptr); + private: + QLineEdit * evidenceLine = nullptr; + QLabel *errorLabel = nullptr; +}; diff --git a/src/forms/firstRunWizard/finishedpage.cpp b/src/forms/firstRunWizard/finishedpage.cpp new file mode 100644 index 00000000..ea26b1e5 --- /dev/null +++ b/src/forms/firstRunWizard/finishedpage.cpp @@ -0,0 +1,70 @@ +#include "finishedpage.h" + +#include +#include +#include +#include +#include + +void FinishedPage::initializePage() +{ + auto shortCut = QKeySequence(AppConfig::value(CONFIG::SHORTCUT_CAPTUREWINDOW)).toString(QKeySequence::NativeText); + m_lblBody->setText(tr("\n\nYou can now submit evidence Try it now,\n Use %1 to capture the window").arg(shortCut)); +} + +bool FinishedPage::validatePage() +{ + AppConfig::setValue(CONFIG::SHOW_WELCOME_SCREEN, QStringLiteral("false")); + return true; +} + +FinishedPage::FinishedPage(QWidget *parent) + : WizardPage{Page_Finished, parent} + , m_lblBody{new QLabel(this)} +{ + auto f = font(); + auto _lblTitleLabel = new QLabel(this); + f.setPointSize(titleFont.first); + f.setWeight(titleFont.second); + _lblTitleLabel->setFont(f); + _lblTitleLabel->setText(tr("Congratulations!")); + + auto _lblSubtitle = new QLabel(this); + f.setPointSize(subTitleFont.first); + f.setWeight(subTitleFont.second); + _lblSubtitle->setFont(f); + _lblSubtitle->setWordWrap(true); + _lblSubtitle->setText(tr("You have successfully completed the configuration setup and can continue working within the ashirt application as needed.")); + + f.setPointSize(bodyFont.first); + f.setWeight(bodyFont.second); + f.setItalic(true); + m_lblBody->setFont(f); + + auto _lblBody2 = new QLabel(this); + f.setPointSize(bodyFont.first); + f.setWeight(bodyFont.second); + f.setItalic(true); + _lblBody2->setFont(f); + _lblBody2->setText(tr("\n\nFor more information or support")); + + QString buttonStyle = QStringLiteral("QPushButton:enabled{background-color: rgba(93, 172, 232, 255);font:;color:\"black\";}"); + auto _faqButton = new QPushButton(this); + _faqButton->setText(tr("Click here")); + _faqButton->setStyleSheet(buttonStyle); + connect(_faqButton, &QPushButton::clicked, this, [this]{ + QDesktopServices::openUrl(QUrl(QStringLiteral("https://ashirt.io"), QUrl::TolerantMode)); + }); + + auto hLayout = new QHBoxLayout(); + hLayout->addWidget(_lblBody2, 8, Qt::AlignLeft | Qt::AlignBottom); + hLayout->addWidget(_faqButton, 2, Qt::AlignLeft | Qt::AlignBottom); + + auto layout = new QVBoxLayout(); + layout->addWidget(_lblTitleLabel); + layout->addWidget(_lblSubtitle); + layout->addWidget(m_lblBody); + layout->addLayout(hLayout); + layout->addSpacerItem(new QSpacerItem(0,0,QSizePolicy::Minimum, QSizePolicy::Expanding)); + setLayout(layout); +} diff --git a/src/forms/firstRunWizard/finishedpage.h b/src/forms/firstRunWizard/finishedpage.h new file mode 100644 index 00000000..fe7d294d --- /dev/null +++ b/src/forms/firstRunWizard/finishedpage.h @@ -0,0 +1,16 @@ +#pragma once + +#include "wizardpage.h" +#include + +class QLabel; +class FinishedPage : public WizardPage +{ + Q_OBJECT + public: + void initializePage() override; + bool validatePage() override; + FinishedPage(QWidget *parent = nullptr); + private: + QLabel * m_lblBody = nullptr; +}; diff --git a/src/forms/firstRunWizard/firstTimeWizard.cpp b/src/forms/firstRunWizard/firstTimeWizard.cpp new file mode 100644 index 00000000..ed30521b --- /dev/null +++ b/src/forms/firstRunWizard/firstTimeWizard.cpp @@ -0,0 +1,44 @@ +#include "firstTimeWizard.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "requirementspage.h" +#include "evidencepage.h" +#include "apikeyspage.h" +#include "hostpathpage.h" +#include "hosttestpage.h" +#include "captureareapage.h" + +#include "capturewindowpage.h" +#include "captureclipboardpage.h" +#include "opspage.h" +#include "finishedpage.h" + +FirstTimeWizard::FirstTimeWizard() +{ + setFixedSize(780, 480); + setWizardStyle(ModernStyle); + + setOption(QWizard::NoCancelButton, true); + setOption(QWizard::NoDefaultButton, true); + setPage(WizardPage::Page_Requiments, new RequirementsPage(this)); + setPage(WizardPage::Page_Evidence, new EvidencePage(this)); + setPage(WizardPage::Page_HostPath, new HostPathPage(this)); + setPage(WizardPage::Page_Api, new ApiKeysPage(this)); + setPage(WizardPage::Page_HostTest, new HostTestPage(this)); + setPage(WizardPage::Page_CaptureArea, new CaptureAreaPage(this)); + setPage(WizardPage::Page_CaptureWindow, new CaptureWindowPage(this)); + setPage(WizardPage::Page_CaptureClipboard, new CaptureClipboardPage(this)); + setPage(WizardPage::Page_Ops, new OpsPage(this)); + setPage(WizardPage::Page_Finished, new FinishedPage(this)); + setStartId(WizardPage::Page_Requiments); +} diff --git a/src/forms/firstRunWizard/firstTimeWizard.h b/src/forms/firstRunWizard/firstTimeWizard.h new file mode 100644 index 00000000..5e673f2f --- /dev/null +++ b/src/forms/firstRunWizard/firstTimeWizard.h @@ -0,0 +1,11 @@ +//Settings Wizard Chris Rizzitello 2023 +#pragma once +#include + +class FirstTimeWizard : public QWizard +{ + Q_OBJECT + public: + FirstTimeWizard(); +}; + diff --git a/src/forms/firstRunWizard/hostpathpage.cpp b/src/forms/firstRunWizard/hostpathpage.cpp new file mode 100644 index 00000000..0e158223 --- /dev/null +++ b/src/forms/firstRunWizard/hostpathpage.cpp @@ -0,0 +1,81 @@ +#include "hostpathpage.h" + +#include +#include +#include +#include +#include +#include + +bool HostPathPage::validatePage() +{ + return field("host.url").isValid(); +} + +void HostPathPage::initializePage() +{ + QString hostPath = AppConfig::value(CONFIG::APIURL); + setField("host.url", hostPath); + hostPathLine->setText(hostPath); +} + +HostPathPage::HostPathPage(QWidget *parent) + : WizardPage{Page_HostPath, parent} +{ + auto f = font(); + auto _lblTitleLabel = new QLabel(this); + f.setPointSize(titleFont.first); + f.setWeight(titleFont.second); + _lblTitleLabel->setFont(f); + _lblTitleLabel->setText(tr("Server URL")); + + auto _lblSubtitle = new QLabel(this); + f.setPointSize(subTitleFont.first); + f.setWeight(subTitleFont.second); + _lblSubtitle->setFont(f); + _lblSubtitle->setWordWrap(true); + _lblSubtitle->setText(tr("The URL of the ashirt server. Example below:")); + + auto _lblSubtitle2 = new QLabel(this); + f.setPointSize(subTitleFont.first); + f.setWeight(subTitleFont.second); + f.setItalic(true); + _lblSubtitle2->setFont(f); + _lblSubtitle2->setWordWrap(true); + _lblSubtitle2->setText(tr("http://localhost:8080")); + + auto _lblBody = new QLabel(this); + f.setPointSize(bodyFont.first); + f.setWeight(bodyFont.second); + f.setItalic(false); + _lblBody->setFont(f); + _lblBody->setWordWrap(true); + _lblBody->setText(tr("• Enter the URL that was provided by admin")); + + auto _lblPath = new QLabel(this); + f.setPointSize(smallFont.first); + f.setWeight(smallFont.second); + _lblPath->setFont(f); + _lblPath->setText(tr("Server URL:")); + + hostPathLine = new QLineEdit(this); + hostPathLine->setTextMargins(3,0,3,0); + registerField("host.url*", hostPathLine); + + auto t1 = new QVBoxLayout(); + t1->setSpacing(1); + t1->addWidget(_lblPath); + t1->addWidget(hostPathLine); + + auto layout = new QVBoxLayout(); + layout->addWidget(_lblTitleLabel); + layout->addWidget(_lblSubtitle); + layout->addWidget(_lblSubtitle2); + layout->addSpacerItem(new QSpacerItem(0,30,QSizePolicy::Minimum, QSizePolicy::Fixed)); + layout->addWidget(_lblBody); + layout->addSpacerItem(new QSpacerItem(0,60,QSizePolicy::Minimum, QSizePolicy::Fixed)); + layout->addLayout(t1); + layout->addSpacerItem(new QSpacerItem(0,0,QSizePolicy::Minimum, QSizePolicy::Expanding)); + + setLayout(layout); +} diff --git a/src/forms/firstRunWizard/hostpathpage.h b/src/forms/firstRunWizard/hostpathpage.h new file mode 100644 index 00000000..43c530d4 --- /dev/null +++ b/src/forms/firstRunWizard/hostpathpage.h @@ -0,0 +1,16 @@ +#pragma once + +#include "wizardpage.h" +#include + +class QLineEdit; +class HostPathPage : public WizardPage +{ + Q_OBJECT + public: + bool validatePage() override; + void initializePage() override; + HostPathPage(QWidget *parent = nullptr); + private: + QLineEdit * hostPathLine = nullptr; +}; diff --git a/src/forms/firstRunWizard/hosttestpage.cpp b/src/forms/firstRunWizard/hosttestpage.cpp new file mode 100644 index 00000000..c2fda2ff --- /dev/null +++ b/src/forms/firstRunWizard/hosttestpage.cpp @@ -0,0 +1,156 @@ +#include "hosttestpage.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "helpers/netman.h" + +HostTestPage::HostTestPage(QWidget *parent) + : WizardPage{Page_HostTest, parent} + , timer{new QTimer(this)} +{ + setCommitPage(true); + timer->setSingleShot(false); + + connect(NetMan::get(), &NetMan::testStatusChanged, this, &HostTestPage::testResultsChanged); + + connect(timer, &QTimer::timeout, this, [this] { + f = (f > 359) ? 0 : f+=1; + update(); + }); + auto f = font(); + auto _lblTitleLabel = new QLabel(this); + f.setPointSize(titleFont.first); + f.setWeight(titleFont.second); + _lblTitleLabel->setFont(f); + _lblTitleLabel->setText(tr("Testing Server Connection")); + + //Hidden Check box to allow us to have a manditory key for going foward + auto isReady = new QCheckBox(this); + isReady->setVisible(false); + registerField("host.accessable*", isReady); + + auto layout = new QVBoxLayout(); + layout->addWidget(_lblTitleLabel); + layout->addSpacerItem(new QSpacerItem(0,300,QSizePolicy::Minimum, QSizePolicy::Expanding)); + setLayout(layout); +} + +bool HostTestPage::validatePage() +{ + if(!field("host.accessable").toBool()) + return false; + + AppConfig::setValue(CONFIG::ACCESSKEY, field("host.accessKey").toString()); + AppConfig::setValue(CONFIG::SECRETKEY, field("host.secretKey").toString()); + AppConfig::setValue(CONFIG::APIURL, field("host.url").toString()); + return true; +} + +void HostTestPage::initializePage() +{ + f=0; + setField("host.accessable", false); + connect(wizard(), &QWizard::currentIdChanged, this, &HostTestPage::timerCheck, Qt::UniqueConnection); + NetMan::testConnection(field("host.url").toString(), field("host.accessKey").toString(), field("host.secretKey").toString()); +} + +void HostTestPage::paintEvent(QPaintEvent *event) +{ + WizardPage::paintEvent(event); + QPixmap tmp(250,250); + QPainter p(&tmp); + + p.fillRect(tmp.rect(), palette().base()); + p.setRenderHint(QPainter::Antialiasing, true); + p.setRenderHint(QPainter::TextAntialiasing, true); + p.setPen(QPen(palette().text().color(), 8)); + p.drawEllipse(25, 25, 200, 200); + + if ( currentState == NetMan::INPROGRESS) { + p.translate(125, 125); + p.rotate(f); + p.translate(-125, -125); + p.setBrush(QBrush( isDarkMode() ? QColor(0x6B,0x7F,0x8B) : QColor(0x5B,0x6C,0x76))); + p.drawEllipse(113, 13, 24,24); + } + p.end(); + + QRect drawArea (154,88,250,250); + QRect errorArea (0, 351, 555, 48); + + static QFont errorFont = QFont("Helvetica Neue", 16, QFont::Normal); + errorFont.setStyleStrategy(QFont::StyleStrategy(QFont::PreferQuality | QFont::PreferAntialias)); + + static QFont statusFont = QFont("Helvetica Neue", 24, QFont::Normal); + statusFont.setStyleStrategy(QFont::StyleStrategy(QFont::PreferQuality | QFont::PreferAntialias)); + + static QFont checkFont = QFont("Helvetica Neue", 42, QFont::ExtraBold); + checkFont.setStyleStrategy(QFont::StyleStrategy(QFont::PreferQuality | QFont::NoSubpixelAntialias)); + + QPainter t (this); + t.setRenderHint(QPainter::TextAntialiasing, true); + t.setRenderHint(QPainter::Antialiasing, true); + + t.drawPixmap(drawArea, tmp); + + QTextOption textOptions; + textOptions.setAlignment(Qt::AlignCenter); + + t.setFont(errorFont); + t.drawText(errorArea, NetMan::lastTestError(), textOptions); + + t.translate(154, 88); + QRect iconArea (91, 50, 72, 72); + QRect labelArea (55,130,140,48); + QRect testLabelArea(55,100,140,48); + + t.setPen(palette().text().color()); + switch (currentState) { + case NetMan::INPROGRESS: + t.setFont(statusFont); + t.drawText(testLabelArea, "Testing", textOptions); + break; + case NetMan::SUCCESS: + t.setFont(checkFont); + t.drawText(iconArea, QStringLiteral("✓"), textOptions); + t.setFont(statusFont); + t.drawText(labelArea, "Success!", textOptions) ; + break; + default: + t.setFont(checkFont); + t.drawText(iconArea, QStringLiteral("X"), textOptions); + t.setFont(statusFont); + t.drawText(labelArea, "Failed", textOptions); + break; + }; + t.end(); + +} + +void HostTestPage::testResultsChanged(int result) +{ + currentState = result; + timerCheck(); + setField("host.accessable", (currentState == NetMan::SUCCESS)); + update(); +} + +void HostTestPage::timerCheck() +{ + if(wizard()->currentId() == id()) { + f=0; + if (currentState == NetMan::INPROGRESS) + timer->start(33); + else + timer->stop(); + } + else + timer->stop(); +} diff --git a/src/forms/firstRunWizard/hosttestpage.h b/src/forms/firstRunWizard/hosttestpage.h new file mode 100644 index 00000000..ca8c189b --- /dev/null +++ b/src/forms/firstRunWizard/hosttestpage.h @@ -0,0 +1,24 @@ +#pragma once + +#include "wizardpage.h" +#include + +class QLabel; +class QNetworkReply; +class HostTestPage : public WizardPage +{ + Q_OBJECT + public: + HostTestPage(QWidget *parent = nullptr); + protected: + bool validatePage() override; + void initializePage() override; + void paintEvent(QPaintEvent*) override; + private: + void testResultsChanged(int result); + void timerCheck(); + int f=0; + QTimer *timer = nullptr; + QNetworkReply *currentTestReply = nullptr; + int currentState; +}; diff --git a/src/forms/firstRunWizard/opspage.cpp b/src/forms/firstRunWizard/opspage.cpp new file mode 100644 index 00000000..990f3d36 --- /dev/null +++ b/src/forms/firstRunWizard/opspage.cpp @@ -0,0 +1,148 @@ +#include "opspage.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "helpers/netman.h" + +#include + +bool OpsPage::validatePage() +{ + if (!field("ops").isValid()) + return false; + + QString slug = opsCombo->currentData().toString(); + QString name = opsCombo->currentText(); + + if(!_OpsList.contains(name)) { + slug = name.toLower().replace(invalidCharsRegex, QStringLiteral("-")).replace(startOrEndDash, QString()); + createOpReply = NetMan::createOperation(name, slug); + connect(createOpReply, &QNetworkReply::finished, this, &OpsPage::createOpComplete); + return successful; + } + AppConfig::setOperationDetails(slug, name); + return true; +} + +void OpsPage::initializePage() +{ + NetMan::refreshOperationsList(); + int v = opsCombo->findText(AppConfig::operationName()); + setField("ops", v); + opsCombo->setCurrentIndex(v); +} + +OpsPage::OpsPage(QWidget *parent) + : WizardPage{Page_Ops, parent} +{ + connect(NetMan::get(), &NetMan::operationListUpdated, this, &OpsPage::operationsUpdated); + auto f = font(); + + auto _lblTitleLabel = new QLabel(this); + f.setPointSize(titleFont.first); + f.setWeight(titleFont.second); + _lblTitleLabel->setFont(f); + _lblTitleLabel->setText(tr("Select or Create Operation")); + + auto _lblSubtitle = new QLabel(this); + f.setPointSize(subTitleFont.first); + f.setWeight(subTitleFont.second); + _lblSubtitle->setFont(f); + _lblSubtitle->setWordWrap(true); + _lblSubtitle->setText(tr("Select the desired operation below. If none exist you can create a new one.")); + + auto _lblBody = new QLabel(this); + f.setPointSize(bodyFont.first); + f.setWeight(bodyFont.second); + _lblBody->setFont(f); + _lblBody->setWordWrap(true); + _lblBody->setText(tr("• Select the desired operation below. If none exist you can create a new one.")); + + auto _lblBody2 = new QLabel(this); + f.setPointSize(bodyFont.first); + f.setWeight(bodyFont.second); + _lblBody2->setFont(f); + _lblBody2->setWordWrap(true); + _lblBody2->setText(tr("• The user that creates an operation automatically becomes the admin, and can assign access to additional users in the ashirt server.")); + + auto _lblEvidence = new QLabel(this); + f.setPointSize(smallFont.first); + f.setWeight(smallFont.second); + _lblEvidence->setFont(f); + _lblEvidence->setText(tr("Operation Name:")); + + opsCombo = new QComboBox(this); + registerField("ops", opsCombo); + + responseLabel = new QLabel(this); + f.setPointSize(smallFont.first); + f.setWeight(smallFont.second); + responseLabel->setFont(f); + + auto t1 = new QVBoxLayout(); + t1->setSpacing(1); + t1->addWidget(_lblEvidence); + t1->addWidget(opsCombo); + t1->addWidget(responseLabel); + + auto topLayout = new QVBoxLayout(); + topLayout->addWidget(_lblTitleLabel); + topLayout->addWidget(_lblSubtitle); + topLayout->addSpacerItem(new QSpacerItem(0,20,QSizePolicy::Minimum, QSizePolicy::Fixed)); + topLayout->addWidget(_lblBody); + topLayout->addSpacerItem(new QSpacerItem(0,10,QSizePolicy::Minimum, QSizePolicy::Fixed)); + topLayout->addWidget(_lblBody2); + topLayout->addSpacerItem(new QSpacerItem(0,50,QSizePolicy::Minimum, QSizePolicy::Fixed)); + topLayout->addLayout(t1); + topLayout->addSpacerItem(new QSpacerItem(0,0,QSizePolicy::Minimum, QSizePolicy::Expanding)); + setLayout(topLayout); +} + +void OpsPage::operationsUpdated(bool success, const QList & operations) +{ + if (!success) + return; + + opsCombo->clear(); + opsCombo->setEditable(true); + _OpsList.clear(); + for (const auto& op : operations) { + _OpsList.insert(op.name, op.slug); + opsCombo->addItem(op.name, op.slug); + } + int v = opsCombo->findText(AppConfig::operationName()); + setField("ops", v); + opsCombo->setCurrentIndex(v); +} + +void OpsPage::createOpComplete() +{ + bool isValid; + auto data = NetMan::extractResponse(createOpReply, isValid); + if (isValid) { + dto::Operation op = dto::Operation::parseData(data); + AppConfig::setOperationDetails(op.slug, op.name); + NetMan::refreshOperationsList(); + cleanUpReply(&createOpReply); + successful = true; + wizard()->next(); + } else { + setField("ops", -1); + successful = false; + cleanUpReply(&createOpReply); + dto::AShirtError err = dto::AShirtError::parseData(data); + if (err.error.contains(QStringLiteral("slug already exists"))) { + responseLabel->setText(tr("A similar operation name already exists. Please try a new name.")); + } + else { + responseLabel->setText(tr("Got an unexpected error: %1").arg(err.error)); + } + } +} diff --git a/src/forms/firstRunWizard/opspage.h b/src/forms/firstRunWizard/opspage.h new file mode 100644 index 00000000..ba348dba --- /dev/null +++ b/src/forms/firstRunWizard/opspage.h @@ -0,0 +1,28 @@ +#pragma once + +#include "wizardpage.h" +#include + +#include "dtos/operation.h" + +class QNetworkReply; +class QLabel; +class QComboBox; +class OpsPage : public WizardPage +{ + Q_OBJECT + public: + bool validatePage() override; + void initializePage() override; + OpsPage(QWidget *parent = nullptr); + private: + void operationsUpdated(bool success, const QList & operations); + void createOpComplete(); + QLabel *responseLabel = nullptr; + QComboBox *opsCombo = nullptr; + QNetworkReply* createOpReply = nullptr; + QMap _OpsList; + inline static const QRegularExpression invalidCharsRegex = QRegularExpression(QStringLiteral("[^A-Za-z0-9]+")); + inline static const QRegularExpression startOrEndDash = QRegularExpression(QStringLiteral("^-|-$")); + bool successful= false; +}; diff --git a/src/forms/firstRunWizard/requirementspage.cpp b/src/forms/firstRunWizard/requirementspage.cpp new file mode 100644 index 00000000..af70c37b --- /dev/null +++ b/src/forms/firstRunWizard/requirementspage.cpp @@ -0,0 +1,36 @@ +#include "requirementspage.h" + +#include +#include + +RequirementsPage::RequirementsPage(QWidget *parent) + : WizardPage{Page_Requiments, parent} +{ + auto f = font(); + + auto _lblTitleLabel = new QLabel(this); + f.setPointSize(titleFont.first); + f.setWeight(titleFont.second); + _lblTitleLabel->setFont(f); + _lblTitleLabel->setText(tr("Requirements")); + + auto _lblSubtitle = new QLabel(this); + f.setPointSize(subTitleFont.first); + f.setWeight(subTitleFont.second); + _lblSubtitle->setFont(f); + _lblSubtitle->setWordWrap(true); + _lblSubtitle->setText(tr("To get started, make sure you have acquired the following information from your admin:")); + + auto _lblBody = new QLabel(this); + f.setPointSize(bodyFont.first); + f.setWeight(bodyFont.second); + _lblBody->setFont(f); + _lblBody->setText(tr("\n\n\n\t• URL for the ashirt server\n\n\t• Login method")); + + auto topLayout = new QVBoxLayout(); + topLayout->addWidget(_lblTitleLabel); + topLayout->addWidget(_lblSubtitle); + topLayout->addWidget(_lblBody); + topLayout->addSpacerItem(new QSpacerItem(0,0,QSizePolicy::Minimum, QSizePolicy::Expanding)); + setLayout(topLayout); +} diff --git a/src/forms/firstRunWizard/requirementspage.h b/src/forms/firstRunWizard/requirementspage.h new file mode 100644 index 00000000..594f03c3 --- /dev/null +++ b/src/forms/firstRunWizard/requirementspage.h @@ -0,0 +1,11 @@ +#pragma once + +#include "wizardpage.h" +#include + +class RequirementsPage : public WizardPage +{ + Q_OBJECT + public: + RequirementsPage(QWidget *parent = nullptr); +}; diff --git a/src/forms/firstRunWizard/welcomepage.cpp b/src/forms/firstRunWizard/welcomepage.cpp new file mode 100644 index 00000000..5a95cefa --- /dev/null +++ b/src/forms/firstRunWizard/welcomepage.cpp @@ -0,0 +1,163 @@ +#include "welcomepage.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +WelcomePage::WelcomePage(QWidget *parent) + : AShirtDialog{parent} + , m_sideGrad{QLinearGradient(0,0, m_side_w, m_side_h)} + +{ + m_sideGrad.setColorAt(0, QColor(0x2E, 0x33, 0x37)); + m_sideGrad.setColorAt(1, QColor(0x1D, 0x20, 0x24)); + setFixedSize(780, 468); + QString buttonStyle = QStringLiteral("QPushButton:enabled{\ + background-color: rgba(93, 172, 232, 255);\ + font:;color:\"black\";}"); + + auto f = font(); + + auto _lblTitleLabel = new QLabel(this); + f.setPointSize(m_titleFontSize); + f.setBold(true); + _lblTitleLabel->setFont(f); + _lblTitleLabel->setText(tr("Welcome!")); + + auto _lblSubtitle = new QLabel(this); + f.setPointSize(m_subTitleFontSize); + f.setBold(false); + _lblSubtitle->setFont(f); + _lblSubtitle->setText(tr("Click on the Ashirt tray icon to begin")); + + auto topLayout = new QVBoxLayout(); + topLayout->addWidget(_lblTitleLabel); + topLayout->addWidget(_lblSubtitle); + auto tGrid = new QGridLayout(); + tGrid->setContentsMargins(215, 20, 20, 20); + tGrid->addLayout(topLayout, 0, 0, 6, 5); + tGrid->setVerticalSpacing(15); + + auto startSetup = new QPushButton(this); + startSetup->setStyleSheet(buttonStyle); + startSetup->setText(tr("Start Setup")); + connect(startSetup, &QPushButton::clicked, this, [this]{ + close(); + Q_EMIT requestSetupWizard(); + }); + + auto _lblTopLabel = new QLabel(this); + f.setPointSize(m_h3FontSize); + f.setBold(true); + _lblTopLabel->setFont(f); + _lblTopLabel->setText(tr("New Users")); + + auto _lblTopBody = new QLabel(this); + f.setPointSize(m_bodyFontSize); + f.setBold(false); + _lblTopBody->setFont(f); + _lblTopBody->setText(tr("On your first launch, you must setup an appropriate\nconfiguration with our setup wizard.")); + auto line = new QFrame(this); + line->setFrameStyle(QFrame::HLine); + + tGrid->addWidget(_lblTopLabel, 7, 0, 2, 5, Qt::AlignLeft | Qt::AlignTop); + tGrid->addWidget(_lblTopBody, 8, 0, 4, 4, Qt::AlignLeft | Qt::AlignBottom); + tGrid->addWidget(startSetup, 8, 5, 6, 1, Qt::AlignVCenter); + tGrid->addWidget(line ,12 , 0, 1, 6, Qt::AlignVCenter); + + auto _lblMidLabel = new QLabel(this); + f.setPointSize(m_h3FontSize); + f.setBold(true); + _lblMidLabel->setFont(f); + _lblMidLabel->setText(tr("Explore the Server")); + + auto _lblMidBody = new QLabel(this); + f.setPointSize(m_bodyFontSize); + f.setBold(false); + _lblMidBody->setFont(f); + _lblMidBody->setText(tr("This is our frontend web toolkit that allows ease of use with\ntesting operations and viewing evience.")); + auto viewServer = new QPushButton(this); + viewServer->setStyleSheet(buttonStyle); + connect(viewServer, &QPushButton::clicked, this, [this] { + QDesktopServices::openUrl(QUrl(QStringLiteral("https://github.com/ashirt-ops/ashirt-server"), QUrl::TolerantMode)); + }); + viewServer->setText(tr("Download")); + + auto line2 = new QFrame(this); + line2->setFrameStyle(QFrame::HLine); + + tGrid->addWidget(_lblMidLabel, 13, 0, 2, 5, Qt::AlignLeft | Qt::AlignTop); + tGrid->addWidget(_lblMidBody, 14, 0, 4, 4, Qt::AlignLeft | Qt::AlignBottom); + tGrid->addWidget(viewServer, 14, 5, 6, 1, Qt::AlignVCenter); + tGrid->addWidget(line2 ,18 , 0, 1, 6, Qt::AlignVCenter); + + auto _lblBottomLabel = new QLabel(this); + f.setPointSize(m_h3FontSize); + f.setBold(true); + _lblBottomLabel->setFont(f); + _lblBottomLabel->setText(tr("Quick Docs")); + + auto _lblBottomBody = new QLabel(this); + f.setPointSize(m_bodyFontSize); + f.setBold(false); + _lblBottomBody->setFont(f); + _lblBottomBody->setText(tr("For more information about ashirt, support and access to\ndocumentation or common user questions.")); + auto learnMore = new QPushButton(this); + learnMore->setStyleSheet(buttonStyle); + connect(learnMore, &QPushButton::clicked, this, [this] { + QDesktopServices::openUrl(QUrl(QStringLiteral("https://ashirt.io"), QUrl::TolerantMode)); + }); + learnMore->setText(tr("Learn More")); + + tGrid->addWidget(_lblBottomLabel, 19, 0, 2, 5, Qt::AlignLeft | Qt::AlignTop); + tGrid->addWidget(_lblBottomBody, 20, 0, 4, 4, Qt::AlignLeft | Qt::AlignBottom); + tGrid->addWidget(learnMore, 20, 5, 6, 1, Qt::AlignVCenter); + + auto hide = new QCheckBox("Do Not show when starting", this); + hide->setChecked(AppConfig::value(CONFIG::SHOW_WELCOME_SCREEN) != "true"); + connect(hide,&QCheckBox::stateChanged, this, [hide, this] (int newState) { + if(newState != Qt::Checked) + AppConfig::setValue(CONFIG::SHOW_WELCOME_SCREEN, "true"); + else + AppConfig::setValue(CONFIG::SHOW_WELCOME_SCREEN, "false"); + }); + tGrid->addWidget(hide, 25, 0, 2, 6); + + setLayout(tGrid); +} + +void WelcomePage::paintEvent(QPaintEvent *) +{ + static QFont titleFont = QFont("Helvetica Neue", m_sideLargeFont, QFont::ExtraBold, false); + titleFont.setStyleHint(QFont::Helvetica, QFont::StyleStrategy(QFont::PreferQuality | QFont::PreferAntialias)); + + static QFont subFont = QFont("Helvetica Neue", m_sideSmallFont, QFont::Light, true); + subFont.setStyleHint(QFont::Helvetica, QFont::StyleStrategy(QFont::PreferQuality | QFont::PreferAntialias)); + + static QFont versionFont = QFont("Helvetica Neue", m_sideSmallFont, QFont::ExtraLight, false); + versionFont.setStyleHint(QFont::Helvetica, QFont::StyleStrategy(QFont::PreferQuality | QFont::PreferAntialias)); + +#ifdef Q_OS_MAC + subFont.setWeight(QFont::Normal); + versionFont.setWeight(QFont::Light); +#endif + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setRenderHint(QPainter::TextAntialiasing, true); + painter.fillRect(0,0, 200, height(), m_sideGrad); + painter.drawPixmap(QRect(50, 135, 100, 100), QStringLiteral(":/icons/shirt-light.svg")); + painter.setPen(QColor(0xF5,0xFB,0xFF)); + painter.setFont(titleFont); + painter.drawText(QRect(0, 227, 200, 251), QStringLiteral("ASHIRT"), QTextOption(Qt::AlignHCenter)); + painter.setFont(subFont); + painter.drawText(QRect(25, 275, 175, 350), QStringLiteral("Adversary Simulators High-Fedelity Intelligence and Reporting Toolkit")); + painter.setFont(versionFont); + painter.drawText(QRect(0, 445, 200, 455), QStringLiteral("Version %1").arg(ReleaseInfo::version), QTextOption(Qt::AlignHCenter)); + painter.end(); +} diff --git a/src/forms/firstRunWizard/welcomepage.h b/src/forms/firstRunWizard/welcomepage.h new file mode 100644 index 00000000..0709d5ae --- /dev/null +++ b/src/forms/firstRunWizard/welcomepage.h @@ -0,0 +1,35 @@ +#pragma once +#include +#include "ashirtdialog/ashirtdialog.h" + +class WelcomePage : public AShirtDialog +{ + Q_OBJECT + public: + explicit WelcomePage(QWidget *parent = nullptr); + + signals: + void requestSetupWizard(); + protected: + void paintEvent(QPaintEvent *); + private: + const int m_side_w = 200; + const int m_side_h = 468; + QLinearGradient m_sideGrad; +#ifdef Q_OS_MAC + const int m_titleFontSize = 28; + const int m_subTitleFontSize = 20; + const int m_h3FontSize = 18; + const int m_bodyFontSize = 14; + const int m_sideLargeFont = 28; + const int m_sideSmallFont = 12; + +#else + const int m_titleFontSize = 22; + const int m_subTitleFontSize = 16; + const int m_h3FontSize = 14; + const int m_bodyFontSize = 10; + const int m_sideLargeFont = 26; + const int m_sideSmallFont = 10; +#endif +}; diff --git a/src/forms/firstRunWizard/wizardpage.cpp b/src/forms/firstRunWizard/wizardpage.cpp new file mode 100644 index 00000000..397b1970 --- /dev/null +++ b/src/forms/firstRunWizard/wizardpage.cpp @@ -0,0 +1,253 @@ +#include "wizardpage.h" +#include +#include +#include +#include "system_helpers.h" + +WizardPage::WizardPage(int pageId, QWidget *parent) + : QWizardPage{parent} + , m_id(pageId) + , m_sideGrad{QLinearGradient(0,0, m_side_w, m_side_h)} +{ + m_sideGrad.setColorAt(0, QColor(0x2E, 0x33, 0x37)); + m_sideGrad.setColorAt(1, QColor(0x1D, 0x20, 0x24)); + + setPixmap(QWizard::WatermarkPixmap, paintSideImage(m_id)); + setStyleSheet(isDarkMode() ? _darkStyle : _lightStyle); +} + +void WizardPage::changeEvent(QEvent *event) +{ + if (event->type() == QEvent::PaletteChange) { + setStyleSheet(isDarkMode() ? _darkStyle : _lightStyle); + setPixmap(QWizard::WatermarkPixmap, paintSideImage(m_id)); + event->accept(); + return; + } + event->ignore(); +} + +bool WizardPage::isDarkMode() +{ + return !SystemHelpers::isLightTheme(); +} + +QPixmap WizardPage::paintSideImage(int pageId) +{ + QPen inactiveBubbleOutline = QPen(QColor(0x2D, 0x31, 0x35), 1); + QColor highlightedStep = QColor(0x79, 0xC5, 0xFF); + QString checkMark = QStringLiteral("✓"); + + QPixmap sideImage(m_side_w + 20, m_side_h); + QPainter painter; + + painter.begin(&sideImage); + + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setRenderHint(QPainter::TextAntialiasing, true); + painter.setRenderHint(QPainter::SmoothPixmapTransform, true); + + QString fontFamily = "Helvetica Neue"; +#ifdef Q_OS_LINUX + int sideFontSize = 14; +#elif defined Q_OS_WIN + int sideFontSize = 16; +#elif defined Q_OS_MAC + int sideFontSize = 22; +#endif + + static QFont currentItemFont = QFont(fontFamily, sideFontSize, QFont::ExtraBold); + currentItemFont.setStyleHint(QFont::Helvetica, QFont::StyleStrategy(QFont::PreferQuality | QFont::PreferAntialias)); + + static QFont nonCurrentItemFont = QFont(fontFamily, sideFontSize, QFont::Light); + nonCurrentItemFont.setStyleHint(QFont::Helvetica, QFont::StyleStrategy(QFont::PreferQuality | QFont::PreferAntialias)); + + static QFont bubbleTextFont = QFont(fontFamily, sideFontSize, QFont::Normal); + bubbleTextFont.setStyleHint(QFont::Helvetica, QFont::StyleStrategy(QFont::PreferQuality | QFont::PreferAntialias)); + + static QFont bubbleCheckFont = QFont(fontFamily, sideFontSize, QFont::Normal); + bubbleCheckFont.setStyleHint(QFont::Helvetica, QFont::StyleStrategy(QFont::PreferQuality | QFont::NoSubpixelAntialias)); + + static QFont stepsFont = QFont(fontFamily, smallFont.first, smallFont.second); + stepsFont.setStyleHint(QFont::Helvetica, QFont::StyleStrategy(QFont::PreferQuality | QFont::PreferAntialias)); + + QColor inactiveBubbleColor = QColor(0x2D, 0x32, 0x36); + QString _textTemp; + //Background + painter.fillRect(0,0, sideImage.width() - 20, sideImage.height(), m_sideGrad); + painter.fillRect(m_side_w, 0 , 20, m_side_h, QBrush(palette().base().color())); + + if(pageId == Page_Requiments) { + painter.setFont(currentItemFont); + painter.setPen(highlightedStep); + painter.setBrush(highlightedStep); + } else { + painter.setFont(nonCurrentItemFont); + painter.setPen(QPen(Qt::white)); + painter.setBrush(inactiveBubbleColor); + } + + painter.drawText(QRect(6, 30, 165, 32), Qt::AlignRight | Qt::AlignVCenter, QStringLiteral("Requirements")); + + painter.setPen(inactiveBubbleOutline); + painter.drawEllipse(184, 30,32,32); + + painter.setPen(Qt::white); + if(pageId == Page_Requiments) { + painter.setFont(bubbleTextFont); + _textTemp = QStringLiteral("1"); + } else { + painter.setFont(bubbleCheckFont); + _textTemp = checkMark; + } + painter.drawText(QRect(184, 30,32,32), Qt::AlignCenter, _textTemp); + + if(pageId == Page_Evidence) { + painter.setFont(currentItemFont); + painter.setPen(highlightedStep); + painter.setBrush(highlightedStep); + } else { + painter.setFont(nonCurrentItemFont); + painter.setPen(Qt::white); + painter.setBrush(inactiveBubbleColor); + } + + painter.drawText(QRect(6,117,165,32), Qt::AlignRight | Qt::AlignVCenter, QStringLiteral("Evidence Path")); + painter.setPen(inactiveBubbleOutline); + painter.drawEllipse(184,117,32,32); + + painter.setPen(Qt::white); + if(pageId < Page_HostPath) { + painter.setFont(bubbleTextFont); + _textTemp = QStringLiteral("2"); + } else { + painter.setFont(bubbleCheckFont); + _textTemp = checkMark; + } + painter.drawText(QRect(184, 117, 32,32), Qt::AlignCenter, _textTemp); + + if(pageId >= Page_HostPath && pageId < Page_CaptureArea) { + painter.setFont(currentItemFont); + painter.setPen(highlightedStep); + painter.setBrush(highlightedStep); + } else { + painter.setFont(nonCurrentItemFont); + painter.setPen(Qt::white); + painter.setBrush(inactiveBubbleColor); + } + + painter.drawText(QRect(6, 204, 165, 32), Qt::AlignRight | Qt::AlignVCenter, QStringLiteral("Server Settings")); + painter.setPen(inactiveBubbleOutline); + + if(pageId == Page_HostPath) { + painter.setBrush(highlightedStep); + painter.drawPie(184,204,32,32, 90*16, -120*16); + painter.setBrush(inactiveBubbleColor); + painter.drawPie(184,204, 32,32, 90*16, 240*16); + painter.setPen(Qt::white); + painter.setFont(stepsFont); + painter.drawText(QRect(0,229,200,16), Qt::AlignHCenter | Qt::AlignVCenter, QStringLiteral("Step 1 of 3")); + } else if(pageId == Page_Api) { + painter.setBrush(highlightedStep); + painter.drawPie(184,204,32,32, 90*16, -240*16); + painter.setBrush(inactiveBubbleColor); + painter.drawPie(184,204,32,32, 90*16, 120*16); + painter.setPen(Qt::white); + painter.setFont(stepsFont); + painter.drawText(QRect(0,229,200,16), Qt::AlignHCenter | Qt::AlignVCenter, QStringLiteral("Step 2 of 3")); + } else if(pageId == Page_HostTest) { + painter.drawEllipse(184,204,32,32); + painter.setPen(Qt::white); + painter.setFont(stepsFont); + painter.drawText(QRect(0,229,200,16), Qt::AlignHCenter | Qt::AlignVCenter, QStringLiteral("Step 3 of 3")); + } else { + painter.drawEllipse(184,204,32,32); + } + + painter.setPen(Qt::white); + if(pageId < Page_CaptureArea) { + painter.setFont(bubbleTextFont); + _textTemp = QStringLiteral("3"); + } else { + painter.setFont(bubbleCheckFont); + _textTemp = checkMark; + } + painter.drawText(QRect(184,204,32,32), Qt::AlignCenter, _textTemp); + + if(pageId >= Page_CaptureArea && pageId < Page_Ops) { + painter.setFont(currentItemFont); + painter.setPen(highlightedStep); + painter.setBrush(highlightedStep); + } else { + painter.setFont(nonCurrentItemFont); + painter.setPen(Qt::white); + painter.setBrush(inactiveBubbleColor); + } + + painter.drawText(QRect(6, 291, 165, 32), Qt::AlignRight | Qt::AlignVCenter, QStringLiteral("Input Settings")); + painter.setPen(inactiveBubbleOutline); + painter.drawEllipse(184,291,32,32); + + if(pageId == Page_CaptureArea) { + painter.setBrush(highlightedStep); + painter.drawPie(184,291,32,32, 90*16, -120*16); + painter.setBrush(inactiveBubbleColor); + painter.drawPie(184,291, 32,32, 90*16, 240*16); + painter.setPen(Qt::white); + painter.setFont(stepsFont); + painter.drawText(QRect(0,316,200,16), Qt::AlignHCenter | Qt::AlignVCenter, QStringLiteral("Step 1 of 3")); + } else if(pageId == Page_CaptureWindow) { + painter.setBrush(highlightedStep); + painter.drawPie(184,291,32,32, 90*16, -240*16); + painter.setBrush(inactiveBubbleColor); + painter.drawPie(184,291,32,32, 90*16, 120*16); + painter.setPen(Qt::white); + painter.setFont(stepsFont); + painter.drawText(QRect(0,316,200,16), Qt::AlignHCenter | Qt::AlignVCenter, QStringLiteral("Step 2 of 3")); + } else if(pageId == Page_CaptureClipboard) { + painter.drawEllipse(184,291,32,32); + painter.setPen(Qt::white); + painter.setFont(stepsFont); + painter.drawText(QRect(0,316,200,16), Qt::AlignHCenter | Qt::AlignVCenter, QStringLiteral("Step 3 of 3")); + } else { + painter.drawEllipse(184,291,32,32); + } + + + painter.setPen(Qt::white); + if(pageId < Page_Ops) { + painter.setFont(bubbleTextFont); + _textTemp = QStringLiteral("4"); + } else { + painter.setFont(bubbleCheckFont); + _textTemp = checkMark; + } + painter.drawText(QRect(184,291,32,32), Qt::AlignCenter, _textTemp); + + if(pageId >= Page_Ops && pageId < Page_Finished) { + painter.setFont(currentItemFont); + painter.setPen(highlightedStep); + painter.setBrush(highlightedStep); + } else { + painter.setFont(nonCurrentItemFont); + painter.setPen(Qt::white); + painter.setBrush(inactiveBubbleColor); + } + + painter.drawText(QRect(6, 378, 165, 32),Qt::AlignRight | Qt::AlignVCenter, QStringLiteral("Operation Name")); + painter.setPen(inactiveBubbleOutline); + painter.drawEllipse(184,378,32,32); + + painter.setPen(Qt::white); + if(pageId != Page_Finished) { + painter.setFont(bubbleTextFont); + _textTemp = QStringLiteral("5"); + } else { + painter.setFont(bubbleCheckFont); + _textTemp = checkMark; + } + painter.drawText(QRect(184,378,32,32),Qt::AlignCenter, _textTemp); + painter.end(); + + return sideImage; +} diff --git a/src/forms/firstRunWizard/wizardpage.h b/src/forms/firstRunWizard/wizardpage.h new file mode 100644 index 00000000..225ca005 --- /dev/null +++ b/src/forms/firstRunWizard/wizardpage.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +class WizardPage : public QWizardPage +{ + Q_OBJECT + public: + enum PAGE { + Page_Requiments = 0, + Page_Evidence, + Page_HostPath, + Page_Api, + Page_HostTest, + Page_CaptureArea, + Page_CaptureWindow, + Page_CaptureClipboard, + Page_Ops, + Page_Finished + }; + WizardPage(int pageId = Page_Requiments, QWidget *parent = nullptr); + int id() {return m_id;} + protected: + void changeEvent(QEvent *event); + bool isDarkMode(); +#ifdef Q_OS_MAC + const QPair titleFont = QPair(28, QFont::Bold); + const QPair subTitleFont = QPair(20, QFont::Normal); + const QPair bodyFont = QPair(18, QFont::Normal); + const QPair smallFont = QPair(12, QFont::Light); +#else + const QPair titleFont = QPair(22, QFont::Bold); + const QPair subTitleFont = QPair(14, QFont::Normal); + const QPair bodyFont = QPair(12, QFont::Normal); + const QPair smallFont = QPair(9, QFont::Normal); +#endif + private: + const int m_side_w = 200; + const int m_side_h = 468; + const int m_id; + QLinearGradient m_sideGrad; + QString _styleTemplate = QStringLiteral("QComboBox:editable,QLineEdit{background:rgba(%1,%2,%3,255);}"); + QString _lightStyle = _styleTemplate.arg(QStringLiteral("235"), QStringLiteral("235"), QStringLiteral("235")); + QString _darkStyle = _styleTemplate.arg(QStringLiteral("21"), QStringLiteral("23"), QStringLiteral("26")); + + QPixmap paintSideImage(int pageId); +}; diff --git a/src/forms/settings/settings.cpp b/src/forms/settings/settings.cpp index e01a2696..d12ee15f 100644 --- a/src/forms/settings/settings.cpp +++ b/src/forms/settings/settings.cpp @@ -36,6 +36,7 @@ Settings::Settings(QWidget *parent) , captureClipboardShortcutTextBox(new SingleStrokeKeySequenceEdit(this)) , testConnectionButton(new LoadingButton(tr("Test Connection"), this)) , couldNotSaveSettingsMsg(new QErrorMessage(this)) + , showWelcomeScreen(new QCheckBox(tr("Show Welcome Screen"), this)) { buildUi(); wireUi(); @@ -65,11 +66,13 @@ void Settings::buildUi() { +---------------+-------------+------------+-------------+ 6 | CodeblkSh Lbl | [CodeblkSh TB] | +---------------+-------------+------------+-------------+ - 7 | Test Conn Btn | StatusLabel | + 7 | [] Show Welcome Screen | +---------------+-------------+------------+-------------+ - 8 | Vertical spacer | + 8 | Test Conn Btn | StatusLabel | +---------------+-------------+------------+-------------+ - 9 | Dialog button Box{save, cancel} | + 9 | Vertical spacer | + +---------------+-------------+------------+-------------+ + 10 | Dialog button Box{save, cancel} | +---------------+-------------+------------+-------------+ */ auto gridLayout = new QGridLayout(this); @@ -107,14 +110,17 @@ void Settings::buildUi() { gridLayout->addWidget(captureClipboardShortcutTextBox, 6, 1); // row 7 - gridLayout->addWidget(testConnectionButton, 7, 0); - gridLayout->addWidget(connStatusLabel, 7, 1, 1, 4); + gridLayout->addWidget(showWelcomeScreen, 7, 1, 1, 5); // row 8 - gridLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Expanding), 8, 0, 1, gridLayout->columnCount()); + gridLayout->addWidget(testConnectionButton, 8, 0); + gridLayout->addWidget(connStatusLabel, 8, 1, 1, 4); // row 9 - gridLayout->addWidget(buttonBox, 9, 0, 1, gridLayout->columnCount()); + gridLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Expanding), 9, 0, 1, gridLayout->columnCount()); + + // row 10 + gridLayout->addWidget(buttonBox, 10, 0, 1, gridLayout->columnCount()); setLayout(gridLayout); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); @@ -175,6 +181,7 @@ void Settings::showEvent(QShowEvent *evt) { captureWindowCmdTextBox->setText(AppConfig::value(CONFIG::COMMAND_CAPTUREWINDOW)); captureWindowShortcutTextBox->setKeySequence(QKeySequence::fromString(AppConfig::value(CONFIG::SHORTCUT_CAPTUREWINDOW))); captureClipboardShortcutTextBox->setKeySequence(QKeySequence::fromString(AppConfig::value(CONFIG::SHORTCUT_CAPTURECLIPBOARD))); + showWelcomeScreen->setChecked(AppConfig::value(CONFIG::SHOW_WELCOME_SCREEN) == "true"); // re-enable form connStatusLabel->clear(); @@ -209,6 +216,8 @@ void Settings::onSaveClicked() { AppConfig::setValue(CONFIG::COMMAND_CAPTUREWINDOW, captureWindowCmdTextBox->text()); AppConfig::setValue(CONFIG::SHORTCUT_CAPTUREWINDOW, captureWindowShortcutTextBox->keySequence().toString()); AppConfig::setValue(CONFIG::SHORTCUT_CAPTURECLIPBOARD, captureClipboardShortcutTextBox->keySequence().toString()); + QString showWelcome = showWelcomeScreen->isChecked() ? "true" : "false"; + AppConfig::setValue(CONFIG::SHOW_WELCOME_SCREEN, showWelcome); HotkeyManager::updateHotkeys(); close(); diff --git a/src/forms/settings/settings.h b/src/forms/settings/settings.h index 086c8ce1..643ac913 100644 --- a/src/forms/settings/settings.h +++ b/src/forms/settings/settings.h @@ -5,6 +5,7 @@ #include "ashirtdialog/ashirtdialog.h" +#include #include class HotkeyManager; @@ -75,4 +76,5 @@ class Settings : public AShirtDialog { LoadingButton* testConnectionButton = nullptr; QPushButton* eviRepoBrowseButton = nullptr; QErrorMessage* couldNotSaveSettingsMsg = nullptr; + QCheckBox *showWelcomeScreen = nullptr; }; diff --git a/src/traymanager.cpp b/src/traymanager.cpp index cfa00cd5..fdb37f30 100644 --- a/src/traymanager.cpp +++ b/src/traymanager.cpp @@ -25,6 +25,8 @@ #include "helpers/system_helpers.h" #include "hotkeymanager.h" #include "models/codeblock.h" +#include "firstRunWizard/firstTimeWizard.h" +#include "firstRunWizard/welcomepage.h" TrayManager::TrayManager(QWidget * parent, DatabaseConnection* db) : QDialog(parent) @@ -51,6 +53,9 @@ TrayManager::TrayManager(QWidget * parent, DatabaseConnection* db) // delayed so that windows can listen for get all ops signal NetMan::refreshOperationsList(); QTimer::singleShot(5000, this, &TrayManager::checkForUpdate); + + if(AppConfig::value(CONFIG::SHOW_WELCOME_SCREEN) != "false") + showWelcomeScreen(); } TrayManager::~TrayManager() { @@ -156,6 +161,19 @@ void TrayManager::changeEvent(QEvent *event) event->ignore(); } +void TrayManager::showWizard() +{ + auto gandalf = new FirstTimeWizard(); + gandalf->show(); +} + +void TrayManager::showWelcomeScreen() +{ + auto welcomePage = new WelcomePage(this); + connect(welcomePage, &WelcomePage::requestSetupWizard, this, &TrayManager::showWizard); + welcomePage->show(); +} + void TrayManager::spawnGetInfoWindow(qint64 evidenceID) { auto getInfoWindow = new GetInfo(db, evidenceID, this); connect(getInfoWindow, &GetInfo::evidenceSubmitted, [](const model::Evidence& evi) { @@ -252,6 +270,12 @@ void TrayManager::showNoOperationSetTrayMessage() { void TrayManager::setActiveOperationLabel() { const auto& opName = AppConfig::operationName(); chooseOpSubmenu->setTitle(tr("Operation: %1").arg(opName.isEmpty() ? tr("") : opName)); + for(const auto &a : allOperationActions.actions()) { + if(a->text() == opName) { + a->setChecked(true); + selectedAction = a; + } + } } void TrayManager::onOperationListUpdated(bool success, diff --git a/src/traymanager.h b/src/traymanager.h index 82506a4e..a42d0fbf 100644 --- a/src/traymanager.h +++ b/src/traymanager.h @@ -94,6 +94,12 @@ class TrayManager : public QDialog { QTimer *updateCheckTimer = nullptr; MessageType currentTrayMessage = NO_ACTION; + ///Show the Settings Wizard + void showWizard(); + + ///Show the Welcome Screen + void showWelcomeScreen(); + /// openServicesPath is a variable to store where, on click, to open a path the next time a tray message is displayed QString openServicesPath;