#include #include #include #include #include #include #include #include #include REGISTER_DOCK_WIDGET(CredentialsWidget, "Credentials", true) CredentialsWidget::CredentialsWidget(AdaptixWidget* w) : DockTab("Credentials", w->GetProfile()->GetProject(), ":/icons/key"), adaptixWidget(w) { this->createUI(); connect(tableView, &QTableView::customContextMenuRequested, this, &CredentialsWidget::handleCredentialsMenu); connect(tableView, &QTableView::doubleClicked, this, &CredentialsWidget::onEditCreds); connect(tableView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this](const QItemSelection &selected, const QItemSelection &deselected){ Q_UNUSED(selected) Q_UNUSED(deselected) if (!inputFilter->hasFocus()) tableView->setFocus(); }); connect(hideButton, &ClickableLabel::clicked, this, &CredentialsWidget::toggleSearchPanel); connect(inputFilter, &QLineEdit::textChanged, this, &CredentialsWidget::onFilterUpdate); connect(inputFilter, &QLineEdit::returnPressed, this, [this]() { proxyModel->setTextFilter(inputFilter->text()); }); connect(typeComboBox, &QComboBox::currentTextChanged, this, &CredentialsWidget::onTypeFilterUpdate); connect(storageComboBox, &QComboBox::currentTextChanged, this, &CredentialsWidget::onStorageFilterUpdate); shortcutSearch = new QShortcut(QKeySequence("Ctrl+F"), this); shortcutSearch->setContext(Qt::WidgetWithChildrenShortcut); connect(shortcutSearch, &QShortcut::activated, this, &CredentialsWidget::toggleSearchPanel); auto shortcutEsc = new QShortcut(QKeySequence(Qt::Key_Escape), inputFilter); shortcutEsc->setContext(Qt::WidgetShortcut); connect(shortcutEsc, &QShortcut::activated, this, [this]() { searchWidget->setVisible(false); proxyModel->setSearchVisible(false); }); this->dockWidget->setWidget(this); } CredentialsWidget::~CredentialsWidget() = default; void CredentialsWidget::SetUpdatesEnabled(const bool enabled) { if (!enabled) { bufferingEnabled = true; } else { bufferingEnabled = false; flushPendingCreds(); } if (proxyModel) proxyModel->setDynamicSortFilter(enabled); if (tableView) tableView->setSortingEnabled(enabled); tableView->setUpdatesEnabled(enabled); } void CredentialsWidget::flushPendingCreds() { if (pendingCreds.isEmpty()) return; QList filtered; { QWriteLocker locker(&adaptixWidget->CredentialsLock); QSet existingIds; for (const auto& c : adaptixWidget->Credentials) existingIds.insert(c.CredId); for (const auto& cred : pendingCreds) { if (existingIds.contains(cred.CredId)) continue; existingIds.insert(cred.CredId); adaptixWidget->Credentials.push_back(cred); filtered.append(cred); } } if (!filtered.isEmpty()) credsModel->add(filtered); pendingCreds.clear(); } void CredentialsWidget::createUI() { auto horizontalSpacer2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); searchWidget = new QWidget(this); searchWidget->setVisible(false); inputFilter = new QLineEdit(searchWidget); inputFilter->setPlaceholderText("filter: (adm | user) & aes256"); inputFilter->setMaximumWidth(300); autoSearchCheck = new QCheckBox("auto", searchWidget); autoSearchCheck->setChecked(true); autoSearchCheck->setToolTip("Auto search on text change. If unchecked, press Enter to search."); typeComboBox = new QComboBox(searchWidget); typeComboBox->setMinimumWidth(150); typeComboBox->setEditable(true); typeComboBox->setInsertPolicy(QComboBox::NoInsert); typeComboBox->addItem("All Types"); storageComboBox = new QComboBox(searchWidget); storageComboBox->setMinimumWidth(150); storageComboBox->setEditable(true); storageComboBox->setInsertPolicy(QComboBox::NoInsert); storageComboBox->addItem("All Storages"); hideButton = new ClickableLabel(" x "); hideButton->setCursor(Qt::PointingHandCursor); searchLayout = new QHBoxLayout(searchWidget); searchLayout->setContentsMargins(0, 5, 0, 0); searchLayout->setSpacing(4); searchLayout->addWidget(inputFilter); searchLayout->addWidget(autoSearchCheck); searchLayout->addSpacing(8); searchLayout->addWidget(typeComboBox); searchLayout->addSpacing(8); searchLayout->addWidget(storageComboBox); searchLayout->addSpacing(8); searchLayout->addWidget(hideButton); searchLayout->addSpacerItem(horizontalSpacer2); credsModel = new CredsTableModel(this); proxyModel = new CredsFilterProxyModel(this); proxyModel->setSourceModel(credsModel); proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); tableView = new QTableView( this ); tableView->setModel(proxyModel); tableView->setHorizontalHeader(new BoldHeaderView(Qt::Horizontal, tableView)); tableView->setContextMenuPolicy( Qt::CustomContextMenu ); tableView->setAutoFillBackground( false ); tableView->setShowGrid( false ); tableView->setSortingEnabled( true ); tableView->setWordWrap( true ); tableView->setCornerButtonEnabled( false ); tableView->setSelectionBehavior( QAbstractItemView::SelectRows ); tableView->setFocusPolicy( Qt::NoFocus ); tableView->setAlternatingRowColors( true ); tableView->horizontalHeader()->setSectionResizeMode( QHeaderView::ResizeToContents ); tableView->horizontalHeader()->setCascadingSectionResizes( true ); tableView->horizontalHeader()->setHighlightSections( false ); tableView->verticalHeader()->setVisible( false ); tableView->sortByColumn(CC_Date, Qt::AscendingOrder); tableView->horizontalHeader()->setSectionResizeMode( CC_Password, QHeaderView::Stretch ); tableView->setItemDelegate(new PaddingDelegate(tableView)); tableView->setItemDelegateForColumn(CC_Password, new WrapAnywhereDelegate(tableView)); tableView->hideColumn(0); mainGridLayout = new QGridLayout( this ); mainGridLayout->setContentsMargins( 0, 0, 0, 0); mainGridLayout->addWidget( searchWidget, 0, 0, 1, 1); mainGridLayout->addWidget( tableView, 1, 0, 1, 1); } /// Main void CredentialsWidget::AddCredentialsItems(QList credsList) { if (credsList.isEmpty()) return; if (bufferingEnabled) { pendingCreds.append(credsList); return; } QList filtered; { QWriteLocker locker(&adaptixWidget->CredentialsLock); QSet existingIds; for (const auto& c : adaptixWidget->Credentials) existingIds.insert(c.CredId); for (const auto& cred : credsList) { if (existingIds.contains(cred.CredId)) continue; existingIds.insert(cred.CredId); adaptixWidget->Credentials.push_back(cred); filtered.append(cred); } } if (filtered.isEmpty()) return; credsModel->add(filtered); if (adaptixWidget->IsSynchronized()) { this->UpdateColumnsSize(); this->UpdateFilterComboBoxes(); } } void CredentialsWidget::EditCredentialsItem(const CredentialData &newCredentials) const { { QWriteLocker locker(&adaptixWidget->CredentialsLock); for ( int i = 0; i < adaptixWidget->Credentials.size(); i++ ) { if( adaptixWidget->Credentials[i].CredId == newCredentials.CredId ) { CredentialData* cd = &adaptixWidget->Credentials[i]; cd->Username = newCredentials.Username; cd->Password = newCredentials.Password; cd->Realm = newCredentials.Realm; cd->Type = newCredentials.Type; cd->Tag = newCredentials.Tag; cd->Storage = newCredentials.Storage; cd->Host = newCredentials.Host; break; } } } credsModel->update(newCredentials.CredId, newCredentials); this->UpdateFilterComboBoxes(); } void CredentialsWidget::RemoveCredentialsItem(const QStringList &credsId) const { QStringList filtered; { QWriteLocker locker(&adaptixWidget->CredentialsLock); for (auto credId : credsId) { for ( int i = 0; i < adaptixWidget->Credentials.size(); i++ ) { if( adaptixWidget->Credentials[i].CredId == credId ) { filtered.append(credId); adaptixWidget->Credentials.erase( adaptixWidget->Credentials.begin() + i ); break; } } } } credsModel->remove(filtered); this->UpdateFilterComboBoxes(); } void CredentialsWidget::CredsSetTag(const QStringList &credsIds, const QString &tag) const { { QWriteLocker locker(&adaptixWidget->CredentialsLock); QSet set1 = QSet(credsIds.begin(), credsIds.end()); for ( int i = 0; i < adaptixWidget->Credentials.size(); i++ ) { if( set1.contains(adaptixWidget->Credentials[i].CredId) ) { adaptixWidget->Credentials[i].Tag = tag; set1.remove(adaptixWidget->Credentials[i].CredId); if (set1.size() == 0) break; } } } credsModel->setTag(credsIds, tag); } void CredentialsWidget::UpdateColumnsSize() const { tableView->resizeColumnToContents(CC_Username); tableView->resizeColumnToContents(CC_Realm); tableView->resizeColumnToContents(CC_Type); tableView->resizeColumnToContents(CC_Date); tableView->resizeColumnToContents(CC_Tag); tableView->resizeColumnToContents(CC_Storage); tableView->resizeColumnToContents(CC_Agent); tableView->resizeColumnToContents(CC_Host); } void CredentialsWidget::UpdateFilterComboBoxes() const { QSet types; QSet storages; { QReadLocker locker(&adaptixWidget->CredentialsLock); for (const auto& cred : adaptixWidget->Credentials) { if (!cred.Type.isEmpty()) types.insert(cred.Type); if (!cred.Storage.isEmpty()) storages.insert(cred.Storage); } } QString currentType = typeComboBox->currentText(); QString currentStorage = storageComboBox->currentText(); typeComboBox->blockSignals(true); storageComboBox->blockSignals(true); typeComboBox->clear(); typeComboBox->addItem("All Types"); QStringList typeList = types.values(); typeList.sort(); typeComboBox->addItems(typeList); storageComboBox->clear(); storageComboBox->addItem("All Storages"); QStringList storageList = storages.values(); storageList.sort(); storageComboBox->addItems(storageList); int typeIdx = typeComboBox->findText(currentType); if (typeIdx >= 0) typeComboBox->setCurrentIndex(typeIdx); else typeComboBox->setCurrentText(currentType); int storageIdx = storageComboBox->findText(currentStorage); if (storageIdx >= 0) storageComboBox->setCurrentIndex(storageIdx); else storageComboBox->setCurrentText(currentStorage); typeComboBox->blockSignals(false); storageComboBox->blockSignals(false); } void CredentialsWidget::Clear() const { { QWriteLocker locker(&adaptixWidget->CredentialsLock); adaptixWidget->Credentials.clear(); } credsModel->clear(); inputFilter->clear(); } /// Sender void CredentialsWidget::CredentialsAdd(QList credsList) { QJsonArray jsonArray; for (const auto &cred : credsList) { QJsonObject obj; obj["username"] = cred.Username; obj["password"] = cred.Password; obj["realm"] = cred.Realm; obj["type"] = cred.Type; obj["tag"] = cred.Tag; obj["storage"] = cred.Storage; obj["host"] = cred.Host; jsonArray.append(obj); } QJsonObject dataJson; dataJson["creds"] = jsonArray; QByteArray jsonData = QJsonDocument(dataJson).toJson(); HttpReqCredentialsCreateAsync(jsonData, *(adaptixWidget->GetProfile()), [](bool success, const QString &message, const QJsonObject&) { if (!success) MessageError(message); }); } /// Slots void CredentialsWidget::toggleSearchPanel() const { if (this->searchWidget->isVisible()) { this->searchWidget->setVisible(false); proxyModel->setSearchVisible(false); } else { this->searchWidget->setVisible(true); proxyModel->setSearchVisible(true); inputFilter->setFocus(); } } void CredentialsWidget::onFilterUpdate() const { if (autoSearchCheck->isChecked()) { proxyModel->setTextFilter(inputFilter->text()); } inputFilter->setFocus(); } void CredentialsWidget::onTypeFilterUpdate(const QString &text) const { QString filterText = text; if (filterText == "All Types") filterText.clear(); proxyModel->setTypeFilter(filterText); } void CredentialsWidget::onStorageFilterUpdate(const QString &text) const { QString filterText = text; if (filterText == "All Storages") filterText.clear(); proxyModel->setStorageFilter(filterText); } void CredentialsWidget::handleCredentialsMenu(const QPoint &pos ) const { auto ctxMenu = QMenu(); ctxMenu.addAction("Create", this, &CredentialsWidget::onCreateCreds ); QModelIndex index = tableView->indexAt(pos); if (index.isValid()) { QStringList creds; QModelIndexList selectedRows = tableView->selectionModel()->selectedRows(); for (const QModelIndex &proxyIndex : selectedRows) { QModelIndex sourceIndex = proxyModel->mapToSource(proxyIndex); if (!sourceIndex.isValid()) continue; QString taskId = credsModel->data(credsModel->index(sourceIndex.row(), CC_Id), Qt::DisplayRole).toString(); creds.append(taskId); } ctxMenu.addAction("Edit", this, &CredentialsWidget::onEditCreds ); ctxMenu.addAction("Remove", this, &CredentialsWidget::onRemoveCreds ); ctxMenu.addSeparator(); int centerCount = adaptixWidget->ScriptManager->AddMenuCreds(&ctxMenu, "Creds", creds); if (centerCount > 0) ctxMenu.addSeparator(); ctxMenu.addAction("Set tag", this, &CredentialsWidget::onSetTag ); ctxMenu.addAction("Export to file", this, &CredentialsWidget::onExportCreds ); ctxMenu.addAction("Copy to clipboard", this, &CredentialsWidget::onCopyToClipboard ); } QPoint globalPos = tableView->mapToGlobal(pos); ctxMenu.exec(globalPos); } void CredentialsWidget::onCreateCreds() { DialogCredential* dialogCreds = new DialogCredential(); while (true) { dialogCreds->StartDialog(); if (dialogCreds->IsValid()) break; QString msg = dialogCreds->GetMessage(); if (msg.isEmpty()) { delete dialogCreds; return; } MessageError(msg); } CredentialData credData = dialogCreds->GetCredData(); delete dialogCreds; QList credList; credList.append(credData); this->CredentialsAdd(credList); } void CredentialsWidget::onEditCreds() const { auto idx = tableView->currentIndex(); if (!idx.isValid()) return; QString credId = proxyModel->index(idx.row(), CC_Id).data().toString(); bool found = false; CredentialData credentialData; for (auto creds : adaptixWidget->Credentials) { if (creds.CredId == credId) { credentialData = creds; found = true; break; } } if (!found) return; DialogCredential* dialogCreds = new DialogCredential(); dialogCreds->SetEditmode(credentialData); while (true) { dialogCreds->StartDialog(); if (dialogCreds->IsValid()) break; QString msg = dialogCreds->GetMessage(); if (msg.isEmpty()) { delete dialogCreds; return; } MessageError(msg); } CredentialData credData = dialogCreds->GetCredData(); QJsonObject dataJson; dataJson["cred_id"] = credData.CredId; dataJson["username"] = credData.Username; dataJson["password"] = credData.Password; dataJson["realm"] = credData.Realm; dataJson["type"] = credData.Type; dataJson["tag"] = credData.Tag; dataJson["storage"] = credData.Storage; dataJson["host"] = credData.Host; QByteArray jsonData = QJsonDocument(dataJson).toJson(); delete dialogCreds; HttpReqCredentialsEditAsync(jsonData, *(adaptixWidget->GetProfile()), [](bool success, const QString& message, const QJsonObject&) { if (!success) MessageError(message.isEmpty() ? "Server is not responding" : message); }); } void CredentialsWidget::onRemoveCreds() const { QStringList listId; QModelIndexList selectedRows = tableView->selectionModel()->selectedRows(); for (const QModelIndex &proxyIndex : selectedRows) { QModelIndex sourceIndex = proxyModel->mapToSource(proxyIndex); if (!sourceIndex.isValid()) continue; QString agentId = credsModel->data(credsModel->index(sourceIndex.row(), CC_Id), Qt::DisplayRole).toString(); listId.append(agentId); } if(listId.empty()) return; HttpReqCredentialsRemoveAsync(listId, *(adaptixWidget->GetProfile()), [](bool success, const QString& message, const QJsonObject&) { if (!success) MessageError(message.isEmpty() ? "Response timeout" : message); }); } void CredentialsWidget::onExportCreds() const { auto idx = tableView->currentIndex(); if (!idx.isValid()) return; QInputDialog dialog; dialog.setWindowTitle("Format for saving"); dialog.setLabelText("Format:"); dialog.setTextValue("%realm%\\%username%:%password%"); QLineEdit *lineEdit = dialog.findChild(); if (lineEdit) { lineEdit->setMinimumWidth(400); } bool inputOk = (dialog.exec() == QDialog::Accepted); if (!inputOk) return; QString format = dialog.textValue(); QString baseDir = QStringLiteral("creds.txt"); if (adaptixWidget && adaptixWidget->GetProfile()) baseDir = QDir(adaptixWidget->GetProfile()->GetProjectDir()).filePath(QStringLiteral("creds.txt")); NonBlockingDialogs::getSaveFileName(const_cast(this), "Save credentials", baseDir, "Text Files (*.txt);;All Files (*)", [this, format](const QString& fileName) { if (fileName.isEmpty()) return; QFile file(fileName); if (!file.open(QIODevice::WriteOnly)) { MessageError("Failed to open file for writing"); return; } QString content = ""; QModelIndexList selectedRows = tableView->selectionModel()->selectedRows(); for (const QModelIndex &proxyIndex : selectedRows) { QModelIndex sourceIndex = proxyModel->mapToSource(proxyIndex); if (!sourceIndex.isValid()) continue; QString realm = credsModel->data(credsModel->index(sourceIndex.row(), CC_Realm), Qt::DisplayRole).toString(); QString username = credsModel->data(credsModel->index(sourceIndex.row(), CC_Username), Qt::DisplayRole).toString(); QString password = credsModel->data(credsModel->index(sourceIndex.row(), CC_Password), Qt::DisplayRole).toString(); QString temp = format; content += temp .replace("%realm%", realm) .replace("%username%", username) .replace("%password%", password) + "\n"; } file.write(content.trimmed().toUtf8()); file.close(); }); } void CredentialsWidget::onSetTag() const { QString tag = ""; QStringList listId; QModelIndexList selectedRows = tableView->selectionModel()->selectedRows(); for (const QModelIndex &proxyIndex : selectedRows) { QModelIndex sourceIndex = proxyModel->mapToSource(proxyIndex); if (!sourceIndex.isValid()) continue; QString cTag = credsModel->data(credsModel->index(sourceIndex.row(), CC_Tag), Qt::DisplayRole).toString(); QString agentId = credsModel->data(credsModel->index(sourceIndex.row(), CC_Id), Qt::DisplayRole).toString(); listId.append(agentId); if (tag.isEmpty()) tag = cTag; } if(listId.empty()) return; bool inputOk; QString newTag = QInputDialog::getText(nullptr, "Set tags", "New tag", QLineEdit::Normal,tag, &inputOk); if ( inputOk ) { HttpReqCredentialsSetTagAsync(listId, newTag, *(adaptixWidget->GetProfile()), [](bool success, const QString& message, const QJsonObject&) { if (!success) MessageError(message.isEmpty() ? "Response timeout" : message); }); } } void CredentialsWidget::onCopyToClipboard() const { auto idx = tableView->currentIndex(); if (!idx.isValid()) return; QInputDialog dialog; dialog.setWindowTitle("Format for clipboard"); dialog.setComboBoxEditable(true); dialog.setTextValue("%realm%\\%username%:%password%"); dialog.setComboBoxItems(QStringList() << "%realm%\\%username%:%password%" << "%username%" << "%password%" << "'%realm%/%username%:%password%' (impacket)" << "-hashes :%password% '%realm%/%username%' (impacket)" << "-u '%username%' -p '%password%' (netexec)" << "-u '%username%' -H '%password%' (netexec)" << "-u '%username%@%realm%' -p '%password%' (certipy)" ); dialog.setLabelText("Format:"); QLineEdit *lineEdit = dialog.findChild(); if (lineEdit) lineEdit->setMinimumWidth(400); bool inputOk = (dialog.exec() == QDialog::Accepted); if (!inputOk) return; QString format = dialog.textValue(); QString content = ""; QModelIndexList selectedRows = tableView->selectionModel()->selectedRows(); for (const QModelIndex &proxyIndex : selectedRows) { QModelIndex sourceIndex = proxyModel->mapToSource(proxyIndex); if (!sourceIndex.isValid()) continue; QString realm = credsModel->data(credsModel->index(sourceIndex.row(), CC_Realm), Qt::DisplayRole).toString(); QString username = credsModel->data(credsModel->index(sourceIndex.row(), CC_Username), Qt::DisplayRole).toString(); QString password = credsModel->data(credsModel->index(sourceIndex.row(), CC_Password), Qt::DisplayRole).toString(); QString temp = format; content += temp .replace("%realm%", realm) .replace("%username%", username) .replace("%password%", password) .replace(" (impacket)", "") .replace(" (netexec)", "") .replace(" (certipy)", "") + "\n"; } QClipboard *clipboard = QApplication::clipboard(); clipboard->setText(content.trimmed()); }