From 6164d5d67ac52243290ad53e3d09e603ae5f15b1 Mon Sep 17 00:00:00 2001 From: beryxz Date: Sat, 6 Apr 2024 00:09:28 +0200 Subject: [PATCH] feat(logger): add results count Add a label to show total count of results and also the count of the filtered items. --- .../LogEntriesListener.java | 5 ++ .../LogEntriesManager.java | 59 +++++++++++++++++++ .../model/LogsTableModel.java | 6 +- .../sensitivediscoverer/ui/LogsTable.java | 6 +- .../ui/LogsTableContextMenu.java | 4 +- .../sensitivediscoverer/ui/tab/LoggerTab.java | 57 ++++++++++++------ .../utils/LoggerUtils.java | 4 +- src/main/resources/TextUI_en_US.properties | 1 + .../sensitivediscoverer/RegexScannerTest.java | 20 +++---- 9 files changed, 125 insertions(+), 37 deletions(-) create mode 100644 src/main/java/com/cys4/sensitivediscoverer/LogEntriesListener.java create mode 100644 src/main/java/com/cys4/sensitivediscoverer/LogEntriesManager.java diff --git a/src/main/java/com/cys4/sensitivediscoverer/LogEntriesListener.java b/src/main/java/com/cys4/sensitivediscoverer/LogEntriesListener.java new file mode 100644 index 0000000..5eb9eb9 --- /dev/null +++ b/src/main/java/com/cys4/sensitivediscoverer/LogEntriesListener.java @@ -0,0 +1,5 @@ +package com.cys4.sensitivediscoverer; + +public interface LogEntriesListener { + void onSizeChange(int entriesCount); +} diff --git a/src/main/java/com/cys4/sensitivediscoverer/LogEntriesManager.java b/src/main/java/com/cys4/sensitivediscoverer/LogEntriesManager.java new file mode 100644 index 0000000..7299d6c --- /dev/null +++ b/src/main/java/com/cys4/sensitivediscoverer/LogEntriesManager.java @@ -0,0 +1,59 @@ +package com.cys4.sensitivediscoverer; + +import com.cys4.sensitivediscoverer.model.LogEntity; + +import java.util.ArrayList; +import java.util.List; + +public class LogEntriesManager { + private final List logEntries; + private final List listeners; + + public LogEntriesManager() { + this.logEntries = new ArrayList<>(); + this.listeners = new ArrayList<>(); + } + + public void add(LogEntity entry) { + logEntries.add(entry); + listeners.forEach(listener -> listener.onSizeChange(logEntries.size())); + } + + public void remove(LogEntity entry) { + logEntries.remove(entry); + listeners.forEach(listener -> listener.onSizeChange(logEntries.size())); + } + + public void clear() { + logEntries.clear(); + listeners.forEach(listener -> listener.onSizeChange(logEntries.size())); + } + + public int size() { + return logEntries.size(); + } + + public LogEntity get(int index) { + return logEntries.get(index); + } + + /** + * Returns an unmodifiable List containing all the log entries. + * @return a List containing the log entries. + */ + public List getAll() { + return List.copyOf(logEntries); + } + + public boolean contains(LogEntity entry) { + return logEntries.contains(entry); + } + + public void subscribeChangeListener(LogEntriesListener listener) { + listeners.add(listener); + } + + public void unsubscribeChangeListener(LogEntriesListener listener) { + listeners.remove(listener); + } +} diff --git a/src/main/java/com/cys4/sensitivediscoverer/model/LogsTableModel.java b/src/main/java/com/cys4/sensitivediscoverer/model/LogsTableModel.java index 4314406..88c522f 100644 --- a/src/main/java/com/cys4/sensitivediscoverer/model/LogsTableModel.java +++ b/src/main/java/com/cys4/sensitivediscoverer/model/LogsTableModel.java @@ -1,5 +1,7 @@ package com.cys4.sensitivediscoverer.model; +import com.cys4.sensitivediscoverer.LogEntriesManager; + import javax.swing.table.AbstractTableModel; import java.util.List; @@ -8,9 +10,9 @@ public class LogsTableModel extends AbstractTableModel { // get the reference of the array of entries - private final List logEntries; + private final LogEntriesManager logEntries; - public LogsTableModel(List logEntries) { + public LogsTableModel(LogEntriesManager logEntries) { this.logEntries = logEntries; } diff --git a/src/main/java/com/cys4/sensitivediscoverer/ui/LogsTable.java b/src/main/java/com/cys4/sensitivediscoverer/ui/LogsTable.java index cc466c8..8b92ae5 100644 --- a/src/main/java/com/cys4/sensitivediscoverer/ui/LogsTable.java +++ b/src/main/java/com/cys4/sensitivediscoverer/ui/LogsTable.java @@ -4,22 +4,22 @@ import burp.api.montoya.http.message.responses.HttpResponse; import burp.api.montoya.ui.editor.HttpRequestEditor; import burp.api.montoya.ui.editor.HttpResponseEditor; +import com.cys4.sensitivediscoverer.LogEntriesManager; import com.cys4.sensitivediscoverer.model.LogEntity; import com.cys4.sensitivediscoverer.model.LogsTableModel; import javax.swing.*; -import java.util.List; /** * JTable for Viewing Logs */ public class LogsTable extends JTable { - private final List logEntries; + private final LogEntriesManager logEntries; private final HttpRequestEditor requestViewer; private final HttpResponseEditor responseViewer; - public LogsTable(LogsTableModel logsTableModel, List logEntries, HttpRequestEditor requestViewer, HttpResponseEditor responseViewer) { + public LogsTable(LogsTableModel logsTableModel, LogEntriesManager logEntries, HttpRequestEditor requestViewer, HttpResponseEditor responseViewer) { super(logsTableModel); this.setAutoCreateRowSorter(false); diff --git a/src/main/java/com/cys4/sensitivediscoverer/ui/LogsTableContextMenu.java b/src/main/java/com/cys4/sensitivediscoverer/ui/LogsTableContextMenu.java index b3dec7a..9b32d2a 100644 --- a/src/main/java/com/cys4/sensitivediscoverer/ui/LogsTableContextMenu.java +++ b/src/main/java/com/cys4/sensitivediscoverer/ui/LogsTableContextMenu.java @@ -8,6 +8,7 @@ import burp.api.montoya.http.message.responses.HttpResponse; import burp.api.montoya.ui.editor.HttpRequestEditor; import burp.api.montoya.ui.editor.HttpResponseEditor; +import com.cys4.sensitivediscoverer.LogEntriesManager; import com.cys4.sensitivediscoverer.model.LogEntity; import com.cys4.sensitivediscoverer.model.LogsTableModel; import com.cys4.sensitivediscoverer.model.RegexEntity; @@ -17,14 +18,13 @@ import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; import java.awt.event.ActionEvent; -import java.util.List; import static com.cys4.sensitivediscoverer.Messages.getLocaleString; public class LogsTableContextMenu extends JPopupMenu { public LogsTableContextMenu(LogEntity logEntry, - List logEntries, + LogEntriesManager logEntries, HttpRequestEditor originalRequestViewer, HttpResponseEditor originalResponseViewer, LogsTableModel logsTableModel, diff --git a/src/main/java/com/cys4/sensitivediscoverer/ui/tab/LoggerTab.java b/src/main/java/com/cys4/sensitivediscoverer/ui/tab/LoggerTab.java index 0761ca6..381f7d8 100644 --- a/src/main/java/com/cys4/sensitivediscoverer/ui/tab/LoggerTab.java +++ b/src/main/java/com/cys4/sensitivediscoverer/ui/tab/LoggerTab.java @@ -4,6 +4,7 @@ import burp.api.montoya.http.message.responses.HttpResponse; import burp.api.montoya.ui.editor.HttpRequestEditor; import burp.api.montoya.ui.editor.HttpResponseEditor; +import com.cys4.sensitivediscoverer.LogEntriesManager; import com.cys4.sensitivediscoverer.MainUI; import com.cys4.sensitivediscoverer.RegexScanner; import com.cys4.sensitivediscoverer.model.LogEntity; @@ -44,13 +45,13 @@ public class LoggerTab implements ApplicationTab { private final MainUI mainUI; private final JPanel panel; /** - * List containing the findings history (log entries). + * Manager for the list containing the findings history (log entries). *

* When running multiple analysis on the same RegexScanner instance, * this list remains the same unless manually cleared. * This is required for not logging the same finding twice. */ - private final List logEntries; + private final LogEntriesManager logEntriesManager; private final Object analyzeLock = new Object(); private final Object loggerLock = new Object(); private final RegexScanner regexScanner; @@ -70,7 +71,7 @@ public LoggerTab(MainUI mainUI) { this.mainUI = mainUI; this.isAnalysisRunning = false; this.analyzeProxyHistoryThread = null; - this.logEntries = new ArrayList<>(); + this.logEntriesManager = new LogEntriesManager(); this.regexScanner = new RegexScanner( this.mainUI.getBurpApi(), this.mainUI.getScannerOptions(), @@ -167,16 +168,18 @@ private JPanel createCenterBox(JScrollPane logEntriesPane) { private JPanel createHeaderBox(JScrollPane logEntriesPane) { JPanel headerBox; + JPanel analysisBar; + JPanel resultsFilterBar; GridBagConstraints gbc; headerBox = new JPanel(); headerBox.setLayout(new GridBagLayout()); - JPanel analysisBar = createAnalysisBar(logEntriesPane); + analysisBar = createAnalysisBar(logEntriesPane); gbc = createGridConstraints(0, 0, 1.0, 0.0, GridBagConstraints.HORIZONTAL); headerBox.add(analysisBar, gbc); - JPanel resultsFilterBar = createResultsFilterBar(); + resultsFilterBar = createResultsFilterBar(); gbc = createGridConstraints(0, 1, 1.0, 0.0, GridBagConstraints.HORIZONTAL); gbc.insets = new Insets(2, 10, 5, 10); headerBox.add(resultsFilterBar, gbc); @@ -191,30 +194,50 @@ private JPanel createResultsFilterBar() { resultsFilterBar = new JPanel(); resultsFilterBar.setLayout(new GridBagLayout()); - JLabel searchLabel = new JLabel(getLocaleString("logger-searchBar-label")); + JLabel resultsCountLabel = new JLabel(getLocaleString("logger-resultsCount-label")); gbc = createGridConstraints(0, 0, 0, 0, GridBagConstraints.HORIZONTAL); gbc.insets = new Insets(0, 0, 0, 5); + resultsFilterBar.add(resultsCountLabel, gbc); + JLabel filteredCountValueLabel = new JLabel("0"); + gbc = createGridConstraints(1, 0, 0, 0, GridBagConstraints.HORIZONTAL); + logsTableRowSorter.addRowSorterListener(rowSorterEvent -> filteredCountValueLabel.setText(String.valueOf(logsTable.getRowSorter().getViewRowCount()))); + resultsFilterBar.add(filteredCountValueLabel, gbc); + JLabel totalCountValueLabel = new JLabel("/0"); + logEntriesManager.subscribeChangeListener(entriesCount -> { + filteredCountValueLabel.setText(String.valueOf(Math.min(entriesCount, logsTable.getRowSorter().getViewRowCount()))); + totalCountValueLabel.setText("/" + entriesCount); + }); + gbc = createGridConstraints(2, 0, 0, 0, GridBagConstraints.HORIZONTAL); + resultsFilterBar.add(totalCountValueLabel, gbc); + JLabel resultsCountSeparator = new JLabel("│"); + gbc = createGridConstraints(3, 0, 0, 0, GridBagConstraints.HORIZONTAL); + gbc.insets = new Insets(0, 10, 0, 10); + resultsFilterBar.add(resultsCountSeparator, gbc); + + JLabel searchLabel = new JLabel(getLocaleString("logger-searchBar-label")); + gbc = createGridConstraints(4, 0, 0, 0, GridBagConstraints.HORIZONTAL); + gbc.insets = new Insets(0, 0, 0, 5); resultsFilterBar.add(searchLabel, gbc); JTextField searchField = new JTextField(); - gbc = createGridConstraints(1, 0, 1, 0, GridBagConstraints.HORIZONTAL); + gbc = createGridConstraints(5, 0, 1, 0, GridBagConstraints.HORIZONTAL); resultsFilterBar.add(searchField, gbc); JCheckBox regexCheckbox = new JCheckBox(getLocaleString(LogsTableModel.Column.REGEX.getLocaleKey())); regexCheckbox.setSelected(true); - gbc = createGridConstraints(2, 0, 0, 0, GridBagConstraints.HORIZONTAL); + gbc = createGridConstraints(6, 0, 0, 0, GridBagConstraints.HORIZONTAL); gbc.insets = new Insets(0, 10, 0, 0); resultsFilterBar.add(regexCheckbox, gbc); JCheckBox matchCheckbox = new JCheckBox(getLocaleString(LogsTableModel.Column.MATCH.getLocaleKey())); matchCheckbox.setSelected(true); - gbc = createGridConstraints(3, 0, 0, 0, GridBagConstraints.HORIZONTAL); + gbc = createGridConstraints(7, 0, 0, 0, GridBagConstraints.HORIZONTAL); gbc.insets = new Insets(0, 10, 0, 0); resultsFilterBar.add(matchCheckbox, gbc); JCheckBox URLCheckbox = new JCheckBox(getLocaleString(LogsTableModel.Column.URL.getLocaleKey())); URLCheckbox.setSelected(true); - gbc = createGridConstraints(4, 0, 0, 0, GridBagConstraints.HORIZONTAL); + gbc = createGridConstraints(8, 0, 0, 0, GridBagConstraints.HORIZONTAL); gbc.insets = new Insets(0, 10, 0, 0); resultsFilterBar.add(URLCheckbox, gbc); @@ -371,19 +394,17 @@ private Runnable setupProgressBarCallback(int maxItems) { private void setupScan() { analysisButton.setText(getLocaleString("logger-analysis-stop")); -//todo logsTable.setAutoCreateRowSorter(false); LoggerTab.this.analyzedItems = 0; progressBar.setValue(LoggerTab.this.analyzedItems); } private void startScan() { - Consumer addLogEntryCallback = LoggerUtils.createAddLogEntryCallback(logEntries, loggerLock, Optional.of(logsTableModel)); + Consumer addLogEntryCallback = LoggerUtils.createAddLogEntryCallback(logEntriesManager, loggerLock, Optional.of(logsTableModel)); regexScanner.analyzeProxyHistory(this::setupProgressBarCallback, addLogEntryCallback); } private void finalizeScan() { analysisButton.setText((String) analysisButton.getClientProperty("initialText")); -//todo logsTable.setAutoCreateRowSorter(true); analyzeProxyHistoryThread = null; isAnalysisRunning = false; LoggerTab.this.postAnalysisOperations(); @@ -396,10 +417,10 @@ private void finalizeScan() { } private JScrollPane createLogEntriesTable() { - logsTableModel = new LogsTableModel(logEntries); + logsTableModel = new LogsTableModel(logEntriesManager); this.originalRequestViewer = this.mainUI.getBurpApi().userInterface().createHttpRequestEditor(); this.originalResponseViewer = this.mainUI.getBurpApi().userInterface().createHttpResponseEditor(); - this.logsTable = new LogsTable(logsTableModel, logEntries, this.originalRequestViewer, this.originalResponseViewer); + this.logsTable = new LogsTable(logsTableModel, logEntriesManager, this.originalRequestViewer, this.originalResponseViewer); // disable sorting on columns while scanning. This helps to prevent Swing exceptions. logsTable.getTableHeader().putClientProperty("analysisDependent", "1"); logsTable.setAutoCreateRowSorter(false); @@ -418,10 +439,10 @@ private void onMouseEvent(MouseEvent e) { int row = logsTable.getSelectedRow(); if (row == -1) return; int realRow = logsTable.convertRowIndexToModel(row); - LogEntity logEntry = logEntries.get(realRow); + LogEntity logEntry = logEntriesManager.get(realRow); if (e.getComponent() instanceof LogsTable) { - new LogsTableContextMenu(logEntry, logEntries, originalRequestViewer, originalResponseViewer, logsTableModel, logsTable, mainUI.getBurpApi(), isAnalysisRunning) + new LogsTableContextMenu(logEntry, logEntriesManager, originalRequestViewer, originalResponseViewer, logsTableModel, logsTable, mainUI.getBurpApi(), isAnalysisRunning) .show(e.getComponent(), e.getX(), e.getY()); } } @@ -496,7 +517,7 @@ private JButton createClearLogsButton(JScrollPane scrollPaneLogger) { btnClearLogs.addActionListener(e -> { int dialog = JOptionPane.showConfirmDialog(null, getLocaleString("logger-clearLogs-confirm")); if (dialog == JOptionPane.YES_OPTION) { - logEntries.clear(); + logEntriesManager.clear(); logsTableModel.clear(); originalResponseViewer.setResponse(HttpResponse.httpResponse("")); diff --git a/src/main/java/com/cys4/sensitivediscoverer/utils/LoggerUtils.java b/src/main/java/com/cys4/sensitivediscoverer/utils/LoggerUtils.java index 710bdda..79e96a7 100644 --- a/src/main/java/com/cys4/sensitivediscoverer/utils/LoggerUtils.java +++ b/src/main/java/com/cys4/sensitivediscoverer/utils/LoggerUtils.java @@ -1,14 +1,14 @@ package com.cys4.sensitivediscoverer.utils; +import com.cys4.sensitivediscoverer.LogEntriesManager; import com.cys4.sensitivediscoverer.model.LogEntity; import com.cys4.sensitivediscoverer.model.LogsTableModel; -import java.util.List; import java.util.Optional; import java.util.function.Consumer; public class LoggerUtils { - public static Consumer createAddLogEntryCallback(List logEntries, Object logEntriesLock, Optional logsTableModel) { + public static Consumer createAddLogEntryCallback(LogEntriesManager logEntries, Object logEntriesLock, Optional logsTableModel) { return (LogEntity logEntry) -> { synchronized (logEntriesLock) { int row = logEntries.size(); diff --git a/src/main/resources/TextUI_en_US.properties b/src/main/resources/TextUI_en_US.properties index 92a974e..156112b 100644 --- a/src/main/resources/TextUI_en_US.properties +++ b/src/main/resources/TextUI_en_US.properties @@ -19,6 +19,7 @@ logger-clearLogs-label=Clear all logs logger-clearLogs-confirm=Delete ALL the logs in the list? logger-exportLogs-label=Export list logs... logger-searchBar-label=Filter results: +logger-resultsCount-label=Results: logger-ctxMenu-sendToRepeater=Send to Repeater logger-ctxMenu-sendToIntruder=Send to Intruder logger-ctxMenu-sendToOrganizer=Send to Organizer diff --git a/src/test/java/com/cys4/sensitivediscoverer/RegexScannerTest.java b/src/test/java/com/cys4/sensitivediscoverer/RegexScannerTest.java index cfd39c8..43fada3 100644 --- a/src/test/java/com/cys4/sensitivediscoverer/RegexScannerTest.java +++ b/src/test/java/com/cys4/sensitivediscoverer/RegexScannerTest.java @@ -25,7 +25,7 @@ class RegexScannerTest { private RegexScanner regexScanner; private BurpMontoyaApiMock burpApi; private ScannerOptions scannerOptions; - private List logEntries; + private LogEntriesManager logEntriesManager; private Function progressBarCallbackSetupMock; private Consumer logEntityConsumer; @@ -45,12 +45,12 @@ void setUp() { @BeforeEach void setUpCallbacks() { - this.logEntries = new ArrayList<>(); + this.logEntriesManager = new LogEntriesManager(); final Object loggerLock = new Object(); progressBarCallbackSetupMock = (maxItems) -> () -> { }; - logEntityConsumer = LoggerUtils.createAddLogEntryCallback(logEntries, loggerLock, Optional.empty()); + logEntityConsumer = LoggerUtils.createAddLogEntryCallback(logEntriesManager, loggerLock, Optional.empty()); } @Test @@ -68,8 +68,8 @@ void testGeneralRegexesWithFindings() { extensionsRegexes); regexScanner.analyzeProxyHistory(progressBarCallbackSetupMock, logEntityConsumer); - assertThat(logEntries).as("Check count of entries found").hasSize(10); - assertThat(logEntries).containsExactly( + assertThat(logEntriesManager.size()).as("Check count of entries found").isEqualTo(10); + assertThat(logEntriesManager.getAll()).containsExactly( new LogEntity(request2.finalRequest(), request2.response(), generalRegexes.get(0), HttpSection.REQ_URL, "test.com"), new LogEntity(request2.finalRequest(), request2.response(), generalRegexes.get(0), HttpSection.REQ_HEADERS, "test.com"), new LogEntity(request2.finalRequest(), request2.response(), generalRegexes.get(0), HttpSection.REQ_BODY, "testing 2"), @@ -104,8 +104,8 @@ void testGeneralRegexesNoDuplicatesInFindings() { regexScanner.analyzeProxyHistory(progressBarCallbackSetupMock, logEntityConsumer); regexScanner.analyzeProxyHistory(progressBarCallbackSetupMock, logEntityConsumer); - assertThat(logEntries).as("Check duplicates aren't inserted more than once").hasSize(10); - assertThat(logEntries).containsExactly( + assertThat(logEntriesManager.size()).as("Check duplicates aren't inserted more than once").isEqualTo(10); + assertThat(logEntriesManager.getAll()).containsExactly( new LogEntity(request2.finalRequest(), request2.response(), generalRegexes.get(0), HttpSection.REQ_URL, "test.com"), new LogEntity(request2.finalRequest(), request2.response(), generalRegexes.get(0), HttpSection.REQ_HEADERS, "test.com"), new LogEntity(request2.finalRequest(), request2.response(), generalRegexes.get(0), HttpSection.REQ_BODY, "testing 2"), @@ -136,7 +136,7 @@ void testGeneralRegexesNoFindings() { extensionsRegexes); regexScanner.analyzeProxyHistory(progressBarCallbackSetupMock, logEntityConsumer); - assertThat(logEntries).as("Check count of entries found").hasSize(0); + assertThat(logEntriesManager.size()).as("Check count of entries found").isEqualTo(0); } private void setProxyHistory(ProxyHttpRequestResponseMock... proxyElements) { @@ -158,8 +158,8 @@ void testRefinerRegex() { extensionsRegexes); regexScanner.analyzeProxyHistory(progressBarCallbackSetupMock, logEntityConsumer); - assertThat(logEntries).as("Check count of entries found").hasSize(2); - assertThat(logEntries).containsExactly( + assertThat(logEntriesManager.size()).as("Check count of entries found").isEqualTo(2); + assertThat(logEntriesManager.getAll()).containsExactly( new LogEntity(request.finalRequest(), request.response(), generalRegexes.get(0), HttpSection.REQ_BODY, "randomstring.example.com"), new LogEntity(request.finalRequest(), request.response(), generalRegexes.get(0), HttpSection.RES_BODY, "bucket-name.test.example.com") );