diff --git a/addOns/authhelper/CHANGELOG.md b/addOns/authhelper/CHANGELOG.md index 513fc41abb..97cdfa93f1 100644 --- a/addOns/authhelper/CHANGELOG.md +++ b/addOns/authhelper/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Fail the Microsoft login if not able to perform all the expected steps. - Track GWT headers. - Handle additional exceptions when processing JSON authentication components. +- Improved performance of the Session Detection scan rule. ### Fixed - Do not include known authentication providers in context. diff --git a/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/AuthUtils.java b/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/AuthUtils.java index 09b70edb4d..04fa1cd4b4 100644 --- a/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/AuthUtils.java +++ b/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/AuthUtils.java @@ -199,7 +199,8 @@ public void notifyMessageReceived(HttpMessage message) { private static long timeToWaitMs = TimeUnit.SECONDS.toMillis(5); - @Setter private static HistoryProvider historyProvider = new HistoryProvider(); + @Setter + private static HistoryProvider historyProvider = ExtensionAuthhelper.getHistoryProvider(); /** * These are session tokens that have been seen in responses but not yet seen in use. When they diff --git a/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/ExtensionAuthhelper.java b/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/ExtensionAuthhelper.java index a324c3cb7d..62ea1ec216 100644 --- a/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/ExtensionAuthhelper.java +++ b/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/ExtensionAuthhelper.java @@ -33,6 +33,7 @@ import java.util.Set; import java.util.stream.Stream; import javax.swing.ImageIcon; +import lombok.Getter; import org.apache.commons.configuration.Configuration; import org.apache.commons.lang3.StringUtils; import org.apache.commons.text.StringEscapeUtils; @@ -108,6 +109,8 @@ public class ExtensionAuthhelper extends ExtensionAdaptor { public static final Set HISTORY_TYPES_SET = Set.of(HISTORY_TYPES); + @Getter private static HistoryProvider historyProvider = new HistoryProvider(); + private ZapMenuItem authTesterMenu; private AuthTestDialog authTestDialog; @@ -212,6 +215,7 @@ public void destroy() { @Override public void hook(ExtensionHook extensionHook) { extensionHook.addSessionListener(new AuthSessionChangedListener()); + extensionHook.addSessionListener(historyProvider); extensionHook.addHttpSenderListener(authHeaderTracker); extensionHook.addOptionsParamSet(getParam()); if (hasView()) { diff --git a/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/HistoryProvider.java b/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/HistoryProvider.java index 9787a6eb2d..09c63f89b6 100644 --- a/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/HistoryProvider.java +++ b/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/HistoryProvider.java @@ -19,26 +19,54 @@ */ package org.zaproxy.addon.authhelper; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import org.apache.commons.text.StringEscapeUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.control.Control.Mode; import org.parosproxy.paros.core.scanner.Alert; import org.parosproxy.paros.db.DatabaseException; +import org.parosproxy.paros.db.paros.ParosDatabaseServer; +import org.parosproxy.paros.extension.SessionChangedListener; import org.parosproxy.paros.extension.history.ExtensionHistory; import org.parosproxy.paros.model.HistoryReference; +import org.parosproxy.paros.model.Model; +import org.parosproxy.paros.model.Session; import org.parosproxy.paros.network.HttpMalformedHeaderException; import org.parosproxy.paros.network.HttpMessage; import org.zaproxy.zap.authentication.AuthenticationHelper; /** A very thin layer on top of the History functionality, to make testing easier. */ -public class HistoryProvider { +public class HistoryProvider implements SessionChangedListener { private static final int MAX_NUM_RECORDS_TO_CHECK = 200; private static final Logger LOGGER = LogManager.getLogger(HistoryProvider.class); + private static final String QUERY_SESS_MGMT_TOKEN_MSG_IDS = + """ + SELECT HISTORYID FROM HISTORY + WHERE HISTORYID BETWEEN ? AND ? + AND ( + POSITION(? IN RESHEADER) > 0 + OR POSITION(? IN RESBODY) > 0 + OR POSITION(? IN REQHEADER) > 0 + OR POSITION(? IN REQHEADER) > 0 -- URLEncoded + OR POSITION(? IN RESBODY) > 0 -- JSONEscaped + ) + ORDER BY HISTORYID DESC + """; + + private ParosDatabaseServer pds; + private boolean server; + private ExtensionHistory extHist; private ExtensionHistory getExtHistory() { @@ -61,6 +89,51 @@ public HttpMessage getHttpMessage(int historyId) return null; } + /** + * The query is ordered DESCending so the List and subsequent processing should be newest + * message first. + */ + List getMessageIds(int first, int last, String value) { + if (!server) { + server = true; + if (Model.getSingleton().getDb().getDatabaseServer() + instanceof ParosDatabaseServer pdbs) { + pds = pdbs; + } else { + LOGGER.warn("Unsupported Database Server."); + } + } + if (pds == null) { + return List.of(); + } + + try (PreparedStatement psGetHistory = + pds.getSingletonConnection().prepareStatement(QUERY_SESS_MGMT_TOKEN_MSG_IDS)) { + psGetHistory.setInt(1, first); + psGetHistory.setInt(2, last); + psGetHistory.setString(3, value); + psGetHistory.setBytes(4, value.getBytes(StandardCharsets.UTF_8)); + psGetHistory.setString(5, value); + psGetHistory.setString(6, URLEncoder.encode(value, StandardCharsets.UTF_8)); + psGetHistory.setBytes( + 7, StringEscapeUtils.escapeJson(value).getBytes(StandardCharsets.UTF_8)); + + List msgIds = new ArrayList<>(); + try (ResultSet rs = psGetHistory.executeQuery()) { + while (rs.next()) { + msgIds.add(rs.getInt("HISTORYID")); + } + } catch (SQLException e) { + LOGGER.warn("Failed to process result set. {}", e.getMessage()); + } + LOGGER.debug("Found: {} candidate messages for {}", msgIds.size(), value); + return msgIds; + } catch (SQLException e) { + LOGGER.warn("Failed to prepare query.", e); + return List.of(); + } + } + public int getLastHistoryId() { return getExtHistory().getLastHistoryId(); } @@ -68,14 +141,14 @@ public int getLastHistoryId() { public SessionManagementRequestDetails findSessionTokenSource(String token, int firstId) { int lastId = getLastHistoryId(); if (firstId == -1) { - firstId = Math.max(0, lastId - MAX_NUM_RECORDS_TO_CHECK); + firstId = Math.max(1, lastId - MAX_NUM_RECORDS_TO_CHECK); } LOGGER.debug("Searching for session token from {} down to {} ", lastId, firstId); - for (int i = lastId; i >= firstId; i--) { + for (int id : getMessageIds(firstId, lastId, token)) { try { - HttpMessage msg = getHttpMessage(i); + HttpMessage msg = getHttpMessage(id); if (msg == null) { continue; } @@ -99,4 +172,25 @@ public SessionManagementRequestDetails findSessionTokenSource(String token, int } return null; } + + @Override + public void sessionChanged(Session session) { + pds = null; + server = false; + } + + @Override + public void sessionAboutToChange(Session session) { + // Nothing to do + } + + @Override + public void sessionScopeChanged(Session session) { + // Nothing to do + } + + @Override + public void sessionModeChanged(Mode mode) { + // Nothing to do + } } diff --git a/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/SessionManagementRequestDetails.java b/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/SessionManagementRequestDetails.java index b00cea2b5c..977a1be1b9 100644 --- a/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/SessionManagementRequestDetails.java +++ b/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/SessionManagementRequestDetails.java @@ -46,4 +46,20 @@ public List getTokens() { public int getConfidence() { return confidence; } + + @Override + public String toString() { + String historyIdStr = + msg.getHistoryRef() != null + ? String.valueOf(msg.getHistoryRef().getHistoryId()) + : "N/A"; + return "MsgID: " + + historyIdStr + + " tokens (" + + tokens.size() + + "): " + + tokens + + " confidence: " + + confidence; + } } diff --git a/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/SessionToken.java b/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/SessionToken.java index 8166818896..996e975bdb 100644 --- a/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/SessionToken.java +++ b/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/SessionToken.java @@ -122,4 +122,9 @@ private static int compareStrings(String string, String otherString) { } return string.compareTo(otherString); } + + @Override + public String toString() { + return "Source: " + source + " key: " + key + " value: " + value; + } } diff --git a/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/internal/ClientSideHandler.java b/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/internal/ClientSideHandler.java index 88176e4710..3e6847ed59 100644 --- a/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/internal/ClientSideHandler.java +++ b/addOns/authhelper/src/main/java/org/zaproxy/addon/authhelper/internal/ClientSideHandler.java @@ -60,7 +60,7 @@ public final class ClientSideHandler implements HttpMessageHandler { private AuthRequestDetails authReq; private int firstHrefId; - @Setter private HistoryProvider historyProvider = new HistoryProvider(); + @Setter private HistoryProvider historyProvider = ExtensionAuthhelper.getHistoryProvider(); public ClientSideHandler(User user) { this.user = user; diff --git a/addOns/authhelper/src/test/java/org/zaproxy/addon/authhelper/AuthUtilsUnitTest.java b/addOns/authhelper/src/test/java/org/zaproxy/addon/authhelper/AuthUtilsUnitTest.java index 220807c739..146021ac29 100644 --- a/addOns/authhelper/src/test/java/org/zaproxy/addon/authhelper/AuthUtilsUnitTest.java +++ b/addOns/authhelper/src/test/java/org/zaproxy/addon/authhelper/AuthUtilsUnitTest.java @@ -106,6 +106,7 @@ void setUp() throws Exception { setUpZap(); mockMessages(new ExtensionAuthhelper()); + AuthUtils.setHistoryProvider(new TestHistoryProvider()); } @AfterEach diff --git a/addOns/authhelper/src/test/java/org/zaproxy/addon/authhelper/SessionDetectionScanRuleUnitTest.java b/addOns/authhelper/src/test/java/org/zaproxy/addon/authhelper/SessionDetectionScanRuleUnitTest.java index f81779ce3e..26a22c2e29 100644 --- a/addOns/authhelper/src/test/java/org/zaproxy/addon/authhelper/SessionDetectionScanRuleUnitTest.java +++ b/addOns/authhelper/src/test/java/org/zaproxy/addon/authhelper/SessionDetectionScanRuleUnitTest.java @@ -31,7 +31,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.withSettings; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.AfterEach; @@ -43,9 +42,7 @@ import org.parosproxy.paros.Constant; import org.parosproxy.paros.control.Control; import org.parosproxy.paros.core.scanner.Alert; -import org.parosproxy.paros.db.DatabaseException; import org.parosproxy.paros.extension.ExtensionLoader; -import org.parosproxy.paros.model.HistoryReference; import org.parosproxy.paros.model.Model; import org.parosproxy.paros.model.Session; import org.parosproxy.paros.network.HttpHeader; @@ -67,7 +64,6 @@ /** Unit test for {@link SessionDetectionScanRule}. */ class SessionDetectionScanRuleUnitTest extends PassiveScannerTest { - private List history; private HistoryProvider historyProvider; @Override @@ -107,7 +103,6 @@ void shouldSetHeaderBasedSessionManagment() throws Exception { given(session.getContextsForUrl(anyString())).willReturn(Arrays.asList(context)); given(model.getSession()).willReturn(session); - history = new ArrayList<>(); historyProvider = new TestHistoryProvider(); AuthUtils.setHistoryProvider(historyProvider); @@ -204,7 +199,6 @@ void shouldDetectBodgeitSession() throws Exception { DiagnosticDataLoader.loadTestData( this.getResourcePath("internal/bodgeit.diags").toFile()); - history = new ArrayList<>(); historyProvider = new TestHistoryProvider(); AuthUtils.setHistoryProvider(historyProvider); // When @@ -228,7 +222,6 @@ void shouldDetectBodgeitSession() throws Exception { @Test void shouldDetectCtflearnSession() throws Exception { // Given - history = new ArrayList<>(); historyProvider = new TestHistoryProvider(); AuthUtils.setHistoryProvider(historyProvider); @@ -267,7 +260,6 @@ void shouldDetectCtflearnSession() throws Exception { @Test void shouldDetectDefthewebSession() throws Exception { // Given - history = new ArrayList<>(); historyProvider = new TestHistoryProvider(); AuthUtils.setHistoryProvider(historyProvider); @@ -295,7 +287,6 @@ void shouldDetectDefthewebSession() throws Exception { @Test void shouldDetectGinnjuiceSession() throws Exception { // Given - history = new ArrayList<>(); historyProvider = new TestHistoryProvider(); AuthUtils.setHistoryProvider(historyProvider); @@ -325,7 +316,6 @@ void shouldDetectGinnjuiceSession() throws Exception { @Test void shouldDetectInfosecexSession() throws Exception { // Given - history = new ArrayList<>(); historyProvider = new TestHistoryProvider(); AuthUtils.setHistoryProvider(historyProvider); @@ -372,7 +362,6 @@ void shouldFindTokenWhenOneIsPreviouslyUnknown() throws Exception { extensionLoader = mock(ExtensionLoader.class, withSettings().strictness(Strictness.LENIENT)); - history = new ArrayList<>(); historyProvider = new TestHistoryProvider(); AuthUtils.setHistoryProvider(historyProvider); @@ -470,27 +459,4 @@ void shouldIgnoreUnknownOrUnwantedContentTypes(String contentType) // Then assertThat(alertsRaised, hasSize(0)); } - - class TestHistoryProvider extends HistoryProvider { - @Override - public void addAuthMessageToHistory(HttpMessage msg) { - history.add(msg); - int id = history.size() - 1; - HistoryReference href = - mock(HistoryReference.class, withSettings().strictness(Strictness.LENIENT)); - given(href.getHistoryId()).willReturn(id); - msg.setHistoryRef(href); - } - - @Override - public HttpMessage getHttpMessage(int historyId) - throws HttpMalformedHeaderException, DatabaseException { - return history.get(historyId); - } - - @Override - public int getLastHistoryId() { - return history.size() - 1; - } - } } diff --git a/addOns/authhelper/src/test/java/org/zaproxy/addon/authhelper/TestHistoryProvider.java b/addOns/authhelper/src/test/java/org/zaproxy/addon/authhelper/TestHistoryProvider.java new file mode 100644 index 0000000000..be621a1b1c --- /dev/null +++ b/addOns/authhelper/src/test/java/org/zaproxy/addon/authhelper/TestHistoryProvider.java @@ -0,0 +1,77 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.authhelper; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.withSettings; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.IntStream; +import lombok.Getter; +import org.mockito.quality.Strictness; +import org.parosproxy.paros.db.DatabaseException; +import org.parosproxy.paros.model.HistoryReference; +import org.parosproxy.paros.network.HttpMalformedHeaderException; +import org.parosproxy.paros.network.HttpMessage; + +public class TestHistoryProvider extends HistoryProvider { + @Getter protected List history; + + public TestHistoryProvider() { + history = new ArrayList<>(); + } + + public TestHistoryProvider(List msgs) { + this.history = msgs; + } + + @Override + public void addAuthMessageToHistory(HttpMessage msg) { + history.add(msg); + int id = history.size(); + HistoryReference href = + mock(HistoryReference.class, withSettings().strictness(Strictness.LENIENT)); + given(href.getHistoryId()).willReturn(id); + msg.setHistoryRef(href); + } + + @Override + public HttpMessage getHttpMessage(int historyId) + throws HttpMalformedHeaderException, DatabaseException { + return history.get(historyId - 1); + } + + @Override + public int getLastHistoryId() { + return history.size(); + } + + @Override + List getMessageIds(int first, int last, String value) { + // Ordered high to low to mimic the query being DESC + return IntStream.rangeClosed(1, history.size()) + .boxed() + .sorted(Collections.reverseOrder()) + .toList(); + } +} diff --git a/addOns/authhelper/src/test/java/org/zaproxy/addon/authhelper/internal/ClientSideHandlerUnitTest.java b/addOns/authhelper/src/test/java/org/zaproxy/addon/authhelper/internal/ClientSideHandlerUnitTest.java index 89333a50c5..5b9df9c401 100644 --- a/addOns/authhelper/src/test/java/org/zaproxy/addon/authhelper/internal/ClientSideHandlerUnitTest.java +++ b/addOns/authhelper/src/test/java/org/zaproxy/addon/authhelper/internal/ClientSideHandlerUnitTest.java @@ -26,7 +26,6 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; -import java.util.ArrayList; import java.util.List; import org.apache.commons.httpclient.URI; import org.junit.jupiter.api.AfterEach; @@ -36,12 +35,9 @@ import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; import org.parosproxy.paros.control.Control; -import org.parosproxy.paros.db.DatabaseException; -import org.parosproxy.paros.model.HistoryReference; import org.parosproxy.paros.model.Model; import org.parosproxy.paros.model.Session; import org.parosproxy.paros.network.HttpHeader; -import org.parosproxy.paros.network.HttpMalformedHeaderException; import org.parosproxy.paros.network.HttpMessage; import org.parosproxy.paros.network.HttpRequestHeader; import org.parosproxy.paros.network.HttpResponseHeader; @@ -50,6 +46,7 @@ import org.zaproxy.addon.authhelper.ExtensionAuthhelper; import org.zaproxy.addon.authhelper.HistoryProvider; import org.zaproxy.addon.authhelper.SessionManagementRequestDetails; +import org.zaproxy.addon.authhelper.TestHistoryProvider; import org.zaproxy.addon.authhelper.internal.ClientSideHandler.AuthRequestDetails; import org.zaproxy.addon.network.server.HttpMessageHandlerContext; import org.zaproxy.zap.authentication.UsernamePasswordAuthenticationCredentials; @@ -70,7 +67,6 @@ class ClientSideHandlerUnitTest extends TestUtils { private Context context; private ClientSideHandler csh; private HttpMessageHandlerContext ctx; - private List history; private HistoryProvider historyProvider; @Mock(strictness = org.mockito.Mock.Strictness.LENIENT) @@ -95,7 +91,6 @@ void setUp() { csh = new ClientSideHandler(user); ctx = new TestHttpMessageHandlerContext(); - history = new ArrayList<>(); historyProvider = new TestHistoryProvider(); csh.setHistoryProvider(historyProvider); AuthUtils.setHistoryProvider(historyProvider); @@ -114,7 +109,7 @@ void shouldAddMessageToHistory() throws Exception { // When csh.handleMessage(ctx, msg); // Then - assertThat(history.size(), is(equalTo(1))); + assertThat(historyProvider.getLastHistoryId(), is(equalTo(1))); } @Test @@ -366,8 +361,8 @@ void shouldDetectSimpleLogin() throws Exception { csh.handleMessage(ctx, getMsg); // Then - assertThat(history.size(), is(equalTo(2))); - assertThat(csh.getAuthMsg().getHistoryRef().getHistoryId(), is(equalTo(0))); + assertThat(historyProvider.getLastHistoryId(), is(equalTo(2))); + assertThat(csh.getAuthMsg().getHistoryRef().getHistoryId(), is(equalTo(1))); } @Test @@ -380,9 +375,9 @@ void shouldDetectBodgeitLogin() throws Exception { msgs.forEach(msg -> csh.handleMessage(ctx, msg)); // Then - assertThat(history.size(), is(equalTo(3))); + assertThat(historyProvider.getLastHistoryId(), is(equalTo(3))); assertThat(csh.getAuthMsg(), is(notNullValue())); - assertThat(csh.getAuthMsg().getHistoryRef().getHistoryId(), is(equalTo(1))); + assertThat(csh.getAuthMsg().getHistoryRef().getHistoryId(), is(equalTo(2))); assertThat( csh.getAuthMsg().getRequestHeader().getURI().toString(), is(equalTo("https://example0/login.jsp"))); @@ -402,9 +397,9 @@ void shouldDetectCtflearnLogin() throws Exception { msgs.forEach(msg -> csh.handleMessage(ctx, msg)); // Then - assertThat(history.size(), is(equalTo(21))); + assertThat(historyProvider.getLastHistoryId(), is(equalTo(21))); assertThat(csh.getAuthMsg(), is(notNullValue())); - assertThat(csh.getAuthMsg().getHistoryRef().getHistoryId(), is(equalTo(9))); + assertThat(csh.getAuthMsg().getHistoryRef().getHistoryId(), is(equalTo(10))); assertThat( csh.getAuthMsg().getRequestHeader().getURI().toString(), is(equalTo("https://example0/login"))); @@ -426,9 +421,9 @@ void shouldDetectDefthewebLogin() throws Exception { msgs.forEach(msg -> csh.handleMessage(ctx, msg)); // Then - assertThat(history.size(), is(equalTo(20))); + assertThat(historyProvider.getLastHistoryId(), is(equalTo(20))); assertThat(csh.getAuthMsg(), is(notNullValue())); - assertThat(csh.getAuthMsg().getHistoryRef().getHistoryId(), is(equalTo(6))); + assertThat(csh.getAuthMsg().getHistoryRef().getHistoryId(), is(equalTo(7))); assertThat( csh.getAuthMsg().getRequestHeader().getURI().toString(), is(equalTo("https://example0/auth"))); @@ -450,9 +445,9 @@ void shouldDetectGinnjuiceLogin() throws Exception { msgs.forEach(msg -> csh.handleMessage(ctx, msg)); // Then - assertThat(history.size(), is(equalTo(3))); + assertThat(historyProvider.getLastHistoryId(), is(equalTo(3))); assertThat(csh.getAuthMsg(), is(notNullValue())); - assertThat(csh.getAuthMsg().getHistoryRef().getHistoryId(), is(equalTo(1))); + assertThat(csh.getAuthMsg().getHistoryRef().getHistoryId(), is(equalTo(2))); assertThat( csh.getAuthMsg().getRequestHeader().getURI().toString(), is(equalTo("https://example0/login"))); @@ -472,9 +467,9 @@ void shouldDetectInfosecexLogin() throws Exception { msgs.forEach(msg -> csh.handleMessage(ctx, msg)); // Then - assertThat(history.size(), is(equalTo(16))); + assertThat(historyProvider.getLastHistoryId(), is(equalTo(16))); assertThat(csh.getAuthMsg(), is(notNullValue())); - assertThat(csh.getAuthMsg().getHistoryRef().getHistoryId(), is(equalTo(1))); + assertThat(csh.getAuthMsg().getHistoryRef().getHistoryId(), is(equalTo(2))); assertThat( csh.getAuthMsg().getRequestHeader().getURI().toString(), is(equalTo("https://example0/sign_in"))); @@ -486,28 +481,6 @@ void shouldDetectInfosecexLogin() throws Exception { "authenticity_token=sanitizedtoken1&button=sanitizedtoken2&user[email]=FakeUserName@example.com&user[password]=F4keP4ssw0rd&\n"))); } - class TestHistoryProvider extends HistoryProvider { - @Override - public void addAuthMessageToHistory(HttpMessage msg) { - history.add(msg); - int id = history.size() - 1; - HistoryReference href = mock(HistoryReference.class); - given(href.getHistoryId()).willReturn(id); - msg.setHistoryRef(href); - } - - @Override - public HttpMessage getHttpMessage(int historyId) - throws HttpMalformedHeaderException, DatabaseException { - return history.get(historyId); - } - - @Override - public int getLastHistoryId() { - return history.size() - 1; - } - } - class TestHttpMessageHandlerContext implements HttpMessageHandlerContext { @Override