diff --git a/zap/src/main/java/org/parosproxy/paros/db/RecordContext.java b/zap/src/main/java/org/parosproxy/paros/db/RecordContext.java index 828b2a80bca..145d332e7ce 100644 --- a/zap/src/main/java/org/parosproxy/paros/db/RecordContext.java +++ b/zap/src/main/java/org/parosproxy/paros/db/RecordContext.java @@ -50,6 +50,7 @@ public class RecordContext { public static final int TYPE_AUTH_POLL_FREQ = 211; public static final int TYPE_AUTH_POLL_FREQ_UNITS = 212; public static final int TYPE_AUTH_POLL_HEADERS = 213; + public static final int TYPE_AUTH_POLL_METHOD = 214; public static final int TYPE_SESSION_MANAGEMENT_TYPE = 220; public static final int TYPE_SESSION_MANAGEMENT_FIELD_1 = 221; diff --git a/zap/src/main/java/org/zaproxy/zap/authentication/AuthenticationMethod.java b/zap/src/main/java/org/zaproxy/zap/authentication/AuthenticationMethod.java index 2623ef0c4df..b4b166418a5 100644 --- a/zap/src/main/java/org/zaproxy/zap/authentication/AuthenticationMethod.java +++ b/zap/src/main/java/org/zaproxy/zap/authentication/AuthenticationMethod.java @@ -20,31 +20,20 @@ package org.zaproxy.zap.authentication; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.regex.Pattern; import net.sf.json.JSON; import net.sf.json.JSONObject; -import org.apache.commons.httpclient.URI; -import org.apache.commons.httpclient.URIException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.parosproxy.paros.Constant; import org.parosproxy.paros.network.HttpMessage; -import org.parosproxy.paros.network.HttpRequestHeader; -import org.parosproxy.paros.network.HttpSender; -import org.parosproxy.paros.view.View; import org.zaproxy.zap.extension.api.ApiResponse; import org.zaproxy.zap.extension.api.ApiResponseSet; import org.zaproxy.zap.model.Context; -import org.zaproxy.zap.model.SessionStructure; import org.zaproxy.zap.session.SessionManagementMethod; import org.zaproxy.zap.session.WebSession; import org.zaproxy.zap.users.AuthenticationState; import org.zaproxy.zap.users.User; -import org.zaproxy.zap.utils.Stats; /** * The {@code AuthenticationMethod} represents an authentication method that can be used to @@ -57,6 +46,8 @@ public abstract class AuthenticationMethod { public static final String CONTEXT_CONFIG_AUTH = Context.CONTEXT_CONFIG + ".authentication"; public static final String CONTEXT_CONFIG_AUTH_TYPE = CONTEXT_CONFIG_AUTH + ".type"; public static final String CONTEXT_CONFIG_AUTH_STRATEGY = CONTEXT_CONFIG_AUTH + ".strategy"; + public static final String CONTEXT_CONFIG_AUTH_POLL_METHOD = + CONTEXT_CONFIG_AUTH + ".pollmethod"; public static final String CONTEXT_CONFIG_AUTH_POLL_URL = CONTEXT_CONFIG_AUTH + ".pollurl"; public static final String CONTEXT_CONFIG_AUTH_POLL_DATA = CONTEXT_CONFIG_AUTH + ".polldata"; public static final String CONTEXT_CONFIG_AUTH_POLL_HEADERS = @@ -66,16 +57,49 @@ public abstract class AuthenticationMethod { public static final String CONTEXT_CONFIG_AUTH_LOGGEDIN = CONTEXT_CONFIG_AUTH + ".loggedin"; public static final String CONTEXT_CONFIG_AUTH_LOGGEDOUT = CONTEXT_CONFIG_AUTH + ".loggedout"; - public static final String AUTH_STATE_ASSUMED_IN_STATS = "stats.auth.state.assumedin"; - public static final String AUTH_STATE_LOGGED_IN_STATS = "stats.auth.state.loggedin"; - public static final String AUTH_STATE_LOGGED_OUT_STATS = "stats.auth.state.loggedout"; - public static final String AUTH_STATE_NO_INDICATOR_STATS = "stats.auth.state.noindicator"; - public static final String AUTH_STATE_UNKNOWN_STATS = "stats.auth.state.unknown"; + /** + * @deprecated Use {@link VerificationMethod#AUTH_STATE_ASSUMED_IN_STATS}. + */ + @Deprecated(since = "2.18.0", forRemoval = true) + public static final String AUTH_STATE_ASSUMED_IN_STATS = + VerificationMethod.AUTH_STATE_ASSUMED_IN_STATS; + + /** + * @deprecated Use {@link VerificationMethod#AUTH_STATE_LOGGED_IN_STATS}. + */ + @Deprecated(since = "2.18.0", forRemoval = true) + public static final String AUTH_STATE_LOGGED_IN_STATS = + VerificationMethod.AUTH_STATE_LOGGED_IN_STATS; + + /** + * @deprecated Use {@link VerificationMethod#AUTH_STATE_LOGGED_OUT_STATS}. + */ + @Deprecated(since = "2.18.0", forRemoval = true) + public static final String AUTH_STATE_LOGGED_OUT_STATS = + VerificationMethod.AUTH_STATE_LOGGED_OUT_STATS; + + /** + * @deprecated Use {@link VerificationMethod#AUTH_STATE_NO_INDICATOR_STATS}. + */ + @Deprecated(since = "2.18.0", forRemoval = true) + public static final String AUTH_STATE_NO_INDICATOR_STATS = + VerificationMethod.AUTH_STATE_NO_INDICATOR_STATS; + + /** + * @deprecated Use {@link VerificationMethod#AUTH_STATE_UNKNOWN_STATS}. + */ + @Deprecated(since = "2.18.0", forRemoval = true) + public static final String AUTH_STATE_UNKNOWN_STATS = + VerificationMethod.AUTH_STATE_UNKNOWN_STATS; public static final String TOKEN_PREFIX = "{%"; public static final String TOKEN_POSTFIX = "%}"; - public static final int DEFAULT_POLL_FREQUENCY = 60; + /** + * @deprecated Use {@link VerificationMethod#DEFAULT_POLL_FREQUENCY}. + */ + @Deprecated(since = "2.18.0", forRemoval = true) + public static final int DEFAULT_POLL_FREQUENCY = VerificationMethod.DEFAULT_POLL_FREQUENCY; public static enum AuthCheckingStrategy { EACH_RESP, @@ -90,17 +114,20 @@ public static enum AuthPollFrequencyUnits { SECONDS } - private AuthCheckingStrategy authCheckingStrategy = AuthCheckingStrategy.EACH_RESP; - - private String pollUrl; - - private String pollData; + private VerificationMethod verificationMethod; - private String pollHeaders; + // Kept for binary compatibility with subclasses. Use VerificationMethod for all access. + @Deprecated(since = "2.18.0", forRemoval = true) + protected Pattern loggedInIndicatorPattern; - private int pollFrequency = DEFAULT_POLL_FREQUENCY; + // Kept for binary compatibility with subclasses. Use VerificationMethod for all access. + @Deprecated(since = "2.18.0", forRemoval = true) + protected Pattern loggedOutIndicatorPattern; - private AuthPollFrequencyUnits pollFrequencyUnits = AuthPollFrequencyUnits.REQUESTS; + { + verificationMethod = new VerificationMethod(); + verificationMethod.setUserDataReplacer(this::replaceUserDataInPollRequest); + } /** * Checks if the authentication method is fully configured. @@ -117,14 +144,8 @@ public static enum AuthPollFrequencyUnits { @Override public AuthenticationMethod clone() { AuthenticationMethod method = duplicate(); - method.authCheckingStrategy = this.authCheckingStrategy; - method.pollUrl = this.pollUrl; - method.pollData = this.pollData; - method.pollHeaders = this.pollHeaders; - method.pollFrequency = this.pollFrequency; - method.pollFrequencyUnits = this.pollFrequencyUnits; - method.loggedInIndicatorPattern = this.loggedInIndicatorPattern; - method.loggedOutIndicatorPattern = this.loggedOutIndicatorPattern; + method.setVerificationMethod( + this.verificationMethod.copy(method::replaceUserDataInPollRequest)); return method; } @@ -210,341 +231,200 @@ public void onMethodPersisted() {} */ public void onMethodDiscarded() {} - /** The logged in indicator pattern. */ - protected Pattern loggedInIndicatorPattern = null; - - /** The logged out indicator pattern. */ - protected Pattern loggedOutIndicatorPattern = null; - - private HttpSender httpSender; + /** + * Gets the {@link VerificationMethod} that handles authentication state checking for this + * authentication method. + * + * @return the verification method + * @since 2.18.0 + */ + public VerificationMethod getVerificationMethod() { + return verificationMethod; + } - private HttpSender getHttpSender() { - if (this.httpSender == null) { - this.httpSender = new HttpSender(HttpSender.AUTHENTICATION_POLL_INITIATOR); - } - return httpSender; + /** + * Sets the {@link VerificationMethod} that handles authentication state checking for this + * authentication method. + * + * @param verificationMethod the verification method to use + * @since 2.18.0 + */ + public void setVerificationMethod(VerificationMethod verificationMethod) { + this.verificationMethod = verificationMethod; + verificationMethod.setUserDataReplacer(this::replaceUserDataInPollRequest); + this.loggedInIndicatorPattern = verificationMethod.getLoggedInIndicatorPattern(); + this.loggedOutIndicatorPattern = verificationMethod.getLoggedOutIndicatorPattern(); } - /** Deprecated 2.10.0. */ - @Deprecated + /** + * @deprecated Use {@link VerificationMethod#isAuthenticated(HttpMessage, User)}. + */ + @Deprecated(since = "2.18.0", forRemoval = true) public boolean isAuthenticated(HttpMessage msg) { - return this.isAuthenticated(msg, null, false); + return verificationMethod.isAuthenticated(msg, null, false); } /** - * Checks if the response received by the Http Message corresponds to an authenticated Web - * Session. - * - *

If none of the indicators are set up, the method defaults to returning true, so that no - * authentications are tried when there is no way to check authentication. A message is also - * shown on the output console in this case. - * - * @param msg the http message - * @return true, if is authenticated or no indicators have been set, and false otherwise + * @deprecated Use {@link VerificationMethod#isAuthenticated(HttpMessage, User)}. */ + @Deprecated(since = "2.18.0", forRemoval = true) public boolean isAuthenticated(HttpMessage msg, User user) { - return this.isAuthenticated(msg, user, false); + return verificationMethod.isAuthenticated(msg, user); } /** - * Checks if the response received by the Http Message corresponds to an authenticated Web - * Session. - * - *

If none of the indicators are set up, the method defaults to returning true, so that no - * authentications are tried when there is no way to check authentication. A message is also - * shown on the output console in this case. - * - * @param msg the http message - * @param force always check even if the polling strategy is being used - * @return true, if is authenticated or no indicators have been set, and false otherwise + * @deprecated Use {@link VerificationMethod#isAuthenticated(HttpMessage, User, boolean)}. */ + @Deprecated(since = "2.18.0", forRemoval = true) public boolean isAuthenticated(HttpMessage msg, User user, boolean force) { - - if (msg == null - || user == null - || AuthCheckingStrategy.AUTO_DETECT.equals(this.authCheckingStrategy)) { - return false; - } - AuthenticationState authState = user.getAuthenticationState(); - // Assume logged in if nothing was set up - if (loggedInIndicatorPattern == null && loggedOutIndicatorPattern == null) { - try { - Stats.incCounter(SessionStructure.getHostName(msg), AUTH_STATE_NO_INDICATOR_STATS); - } catch (URIException e) { - // Ignore - } - if (View.isInitialised()) { - // Let the user know this - View.getSingleton() - .getOutputPanel() - .append( - Constant.messages.getString( - "authentication.output.indicatorsNotSet", - msg.getRequestHeader().getURI()) - + "\n"); - } - return true; - } - - HttpMessage msgToTest; - - switch (this.authCheckingStrategy) { - case EACH_REQ: - case EACH_REQ_RESP: - case EACH_RESP: - msgToTest = msg; - break; - case POLL_URL: - if (!force - && authState.getLastPollResult() != null - && authState.getLastPollResult()) { - // Check if we really need to poll the relevant URL again - switch (pollFrequencyUnits) { - case SECONDS: - if ((System.currentTimeMillis() - authState.getLastPollTime()) / 1000 - < pollFrequency) { - try { - Stats.incCounter( - SessionStructure.getHostName(msg), - AUTH_STATE_ASSUMED_IN_STATS); - } catch (URIException e) { - // Ignore - } - return true; - } - break; - case REQUESTS: - default: - if (authState.getRequestsSincePoll() < pollFrequency) { - authState.incRequestsSincePoll(); - try { - Stats.incCounter( - SessionStructure.getHostName(msg), - AUTH_STATE_ASSUMED_IN_STATS); - } catch (URIException e) { - // Ignore - } - return true; - } - break; - } - } - // Make the poll request - try { - HttpMessage pollMsg = pollAsUser(user); - msgToTest = pollMsg; - } catch (Exception e1) { - LOGGER.warn("Failed sending poll request to {}", this.getPollUrl(), e1); - return false; - } - break; - default: - return false; - } - - return evaluateAuthRequest(msgToTest, authState); + return verificationMethod.isAuthenticated(msg, user, force); } + /** + * @deprecated Use {@link VerificationMethod#evaluateAuthRequest(HttpMessage, + * AuthenticationState)}. + */ + @Deprecated(since = "2.18.0", forRemoval = true) public boolean evaluateAuthRequest(HttpMessage msg, AuthenticationState authState) { - List contentToTest = new ArrayList<>(); - switch (authCheckingStrategy) { - case EACH_REQ: - contentToTest.add(msg.getRequestHeader().toString()); - contentToTest.add(msg.getRequestBody().toString()); - break; - case EACH_REQ_RESP: - contentToTest.add(msg.getRequestHeader().toString()); - contentToTest.add(msg.getRequestBody().toString()); - contentToTest.add(msg.getResponseHeader().toString()); - contentToTest.add(msg.getResponseBody().toString()); - break; - case EACH_RESP: - case POLL_URL: - contentToTest.add(msg.getResponseHeader().toString()); - contentToTest.add(msg.getResponseBody().toString()); - break; - case AUTO_DETECT: - return false; - } - if (patternMatchesAny(loggedInIndicatorPattern, contentToTest)) { - // Looks like we're authenticated - try { - Stats.incCounter(SessionStructure.getHostName(msg), AUTH_STATE_LOGGED_IN_STATS); - } catch (URIException e) { - // Ignore - } - if (authCheckingStrategy.equals(AuthCheckingStrategy.POLL_URL)) { - authState.setLastPollResult(true); - } - return true; - } - - if (loggedOutIndicatorPattern != null - && !patternMatchesAny(loggedOutIndicatorPattern, contentToTest)) { - // Cant find the unauthenticated indicator, assume we're authenticated but record as - // unknown - try { - Stats.incCounter(SessionStructure.getHostName(msg), AUTH_STATE_UNKNOWN_STATS); - } catch (URIException e) { - // Ignore - } - if (this.authCheckingStrategy.equals(AuthCheckingStrategy.POLL_URL)) { - authState.setLastPollResult(true); - } - return true; - } - // Not looking good... - try { - Stats.incCounter(SessionStructure.getHostName(msg), AUTH_STATE_LOGGED_OUT_STATS); - } catch (URIException e) { - // Ignore - } - if (this.authCheckingStrategy.equals(AuthCheckingStrategy.POLL_URL)) { - authState.setLastPollResult(false); - } - return false; + return verificationMethod.evaluateAuthRequest(msg, authState); } + /** + * @deprecated Use {@link VerificationMethod#pollAsUser(User)}. + */ + @Deprecated(since = "2.18.0", forRemoval = true) public HttpMessage pollAsUser(User user) throws IOException { - if (!this.authCheckingStrategy.equals(AuthCheckingStrategy.POLL_URL)) { - throw new IllegalArgumentException("Authentication checking strategy is not POLL_URL"); - } - HttpMessage pollMsg = new HttpMessage(new URI(this.getPollUrl(), true)); - if (this.getPollData() != null && this.getPollData().length() > 0) { - pollMsg.getRequestHeader().setMethod(HttpRequestHeader.POST); - pollMsg.getRequestBody().setBody(this.getPollData()); - pollMsg.getRequestHeader().setContentLength(pollMsg.getRequestBody().length()); - } - if (this.getPollHeaders() != null && this.getPollHeaders().length() > 0) { - for (String header : this.getPollHeaders().split("\n")) { - String[] headerValue = header.split(":", 2); - if (headerValue.length == 2) { - pollMsg.getRequestHeader() - .addHeader(headerValue[0].trim(), headerValue[1].trim()); - } else { - LOGGER.error( - "Invalid header '{}' for poll request to {}", - header, - this.getPollUrl()); - } - } - } - pollMsg.setRequestingUser(user); - replaceUserDataInPollRequest(pollMsg, user); - - getHttpSender().sendAndReceive(pollMsg); - AuthenticationHelper.addAuthMessageToHistory( - pollMsg, List.of(AuthenticationHelper.HISTORY_TAG_VERIFICATION)); - - AuthenticationState authState = user.getAuthenticationState(); - authState.setLastPollTime(System.currentTimeMillis()); - authState.setRequestsSincePoll(0); - - return pollMsg; - } - - private static boolean patternMatchesAny(Pattern pattern, List content) { - if (pattern != null) { - for (String str : content) { - if (pattern.matcher(str).find()) { - return true; - } - } - } - return false; + return verificationMethod.pollAsUser(user); } /** - * Gets the logged in indicator pattern. - * - * @return the logged in indicator pattern + * @deprecated Use {@link VerificationMethod#getLoggedInIndicatorPattern()}. */ + @Deprecated(since = "2.18.0", forRemoval = true) public Pattern getLoggedInIndicatorPattern() { - return loggedInIndicatorPattern; + return verificationMethod.getLoggedInIndicatorPattern(); } /** - * Sets the logged in indicator pattern. - * - * @param loggedInIndicatorPattern the new logged in indicator pattern + * @deprecated Use {@link VerificationMethod#setLoggedInIndicatorPattern(String)}. */ + @Deprecated(since = "2.18.0", forRemoval = true) public void setLoggedInIndicatorPattern(String loggedInIndicatorPattern) { - if (loggedInIndicatorPattern == null || loggedInIndicatorPattern.trim().length() == 0) { - this.loggedInIndicatorPattern = null; - } else { - this.loggedInIndicatorPattern = Pattern.compile(loggedInIndicatorPattern); - } + verificationMethod.setLoggedInIndicatorPattern(loggedInIndicatorPattern); + this.loggedInIndicatorPattern = verificationMethod.getLoggedInIndicatorPattern(); } /** - * Gets the logged out indicator pattern. - * - * @return the logged out indicator pattern + * @deprecated Use {@link VerificationMethod#getLoggedOutIndicatorPattern()}. */ + @Deprecated(since = "2.18.0", forRemoval = true) public Pattern getLoggedOutIndicatorPattern() { - return loggedOutIndicatorPattern; + return verificationMethod.getLoggedOutIndicatorPattern(); } /** - * Sets the logged out indicator pattern. - * - * @param loggedOutIndicatorPattern the new logged out indicator pattern + * @deprecated Use {@link VerificationMethod#setLoggedOutIndicatorPattern(String)}. */ + @Deprecated(since = "2.18.0", forRemoval = true) public void setLoggedOutIndicatorPattern(String loggedOutIndicatorPattern) { - if (loggedOutIndicatorPattern == null || loggedOutIndicatorPattern.trim().length() == 0) { - this.loggedOutIndicatorPattern = null; - } else { - this.loggedOutIndicatorPattern = Pattern.compile(loggedOutIndicatorPattern); - } + verificationMethod.setLoggedOutIndicatorPattern(loggedOutIndicatorPattern); + this.loggedOutIndicatorPattern = verificationMethod.getLoggedOutIndicatorPattern(); } + /** + * @deprecated Use {@link VerificationMethod#getAuthCheckingStrategy()}. + */ + @Deprecated(since = "2.18.0", forRemoval = true) public AuthCheckingStrategy getAuthCheckingStrategy() { - return authCheckingStrategy; + return verificationMethod.getAuthCheckingStrategy(); } + /** + * @deprecated Use {@link VerificationMethod#setAuthCheckingStrategy(AuthCheckingStrategy)}. + */ + @Deprecated(since = "2.18.0", forRemoval = true) public void setAuthCheckingStrategy(AuthCheckingStrategy authCheckingStrategy) { - Objects.requireNonNull(authCheckingStrategy); - this.authCheckingStrategy = authCheckingStrategy; + verificationMethod.setAuthCheckingStrategy(authCheckingStrategy); } + /** + * @deprecated Use {@link VerificationMethod#getPollUrl()}. + */ + @Deprecated(since = "2.18.0", forRemoval = true) public String getPollUrl() { - return pollUrl; + return verificationMethod.getPollUrl(); } + /** + * @deprecated Use {@link VerificationMethod#setPollUrl(String)}. + */ + @Deprecated(since = "2.18.0", forRemoval = true) public void setPollUrl(String pollUrl) { - this.pollUrl = pollUrl; + verificationMethod.setPollUrl(pollUrl); } + /** + * @deprecated Use {@link VerificationMethod#getPollData()}. + */ + @Deprecated(since = "2.18.0", forRemoval = true) public String getPollData() { - return pollData; + return verificationMethod.getPollData(); } + /** + * @deprecated Use {@link VerificationMethod#setPollData(String)}. + */ + @Deprecated(since = "2.18.0", forRemoval = true) public void setPollData(String pollData) { - this.pollData = pollData; + verificationMethod.setPollData(pollData); } + /** + * @deprecated Use {@link VerificationMethod#getPollHeaders()}. + */ + @Deprecated(since = "2.18.0", forRemoval = true) public String getPollHeaders() { - return pollHeaders; + return verificationMethod.getPollHeaders(); } + /** + * @deprecated Use {@link VerificationMethod#setPollHeaders(String)}. + */ + @Deprecated(since = "2.18.0", forRemoval = true) public void setPollHeaders(String pollHeaders) { - this.pollHeaders = pollHeaders; + verificationMethod.setPollHeaders(pollHeaders); } + /** + * @deprecated Use {@link VerificationMethod#getPollFrequency()}. + */ + @Deprecated(since = "2.18.0", forRemoval = true) public int getPollFrequency() { - return pollFrequency; + return verificationMethod.getPollFrequency(); } + /** + * @deprecated Use {@link VerificationMethod#setPollFrequency(int)}. + */ + @Deprecated(since = "2.18.0", forRemoval = true) public void setPollFrequency(int pollFrequency) { - this.pollFrequency = pollFrequency; + verificationMethod.setPollFrequency(pollFrequency); } + /** + * @deprecated Use {@link VerificationMethod#getPollFrequencyUnits()}. + */ + @Deprecated(since = "2.18.0", forRemoval = true) public AuthPollFrequencyUnits getPollFrequencyUnits() { - return pollFrequencyUnits; + return verificationMethod.getPollFrequencyUnits(); } + /** + * @deprecated Use {@link VerificationMethod#setPollFrequencyUnits(AuthPollFrequencyUnits)}. + */ + @Deprecated(since = "2.18.0", forRemoval = true) public void setPollFrequencyUnits(AuthPollFrequencyUnits pollFrequencyUnits) { - this.pollFrequencyUnits = pollFrequencyUnits; + verificationMethod.setPollFrequencyUnits(pollFrequencyUnits); } /** @@ -558,63 +438,6 @@ public boolean isSameType(AuthenticationMethod other) { return other.getClass().equals(this.getClass()); } - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = - prime * result - + ((loggedInIndicatorPattern == null) - ? 0 - : loggedInIndicatorPattern.pattern().hashCode()); - result = - prime * result - + ((loggedOutIndicatorPattern == null) - ? 0 - : loggedOutIndicatorPattern.pattern().hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - AuthenticationMethod other = (AuthenticationMethod) obj; - if (!isSamePattern(loggedInIndicatorPattern, other.loggedInIndicatorPattern)) { - return false; - } - if (!isSamePattern(loggedOutIndicatorPattern, other.loggedOutIndicatorPattern)) { - return false; - } - if (!this.authCheckingStrategy.equals(other.authCheckingStrategy)) { - return false; - } - if (!Objects.equals(this.pollUrl, other.pollUrl)) { - return false; - } - if (!Objects.equals(this.pollData, other.pollData)) { - return false; - } - if (!Objects.equals(this.pollHeaders, other.pollHeaders)) { - return false; - } - if (this.pollFrequency != other.pollFrequency) { - return false; - } - if (!this.pollFrequencyUnits.equals(other.pollFrequencyUnits)) { - return false; - } - return true; - } - - private static boolean isSamePattern(Pattern pattern, Pattern other) { - if (pattern == null) { - return other == null; - } - return other != null && pattern.pattern().equals(other.pattern()); - } - /** * Thrown when an unsupported type of credentials is used with a {@link AuthenticationMethod} . */ diff --git a/zap/src/main/java/org/zaproxy/zap/authentication/PostBasedAuthenticationMethodType.java b/zap/src/main/java/org/zaproxy/zap/authentication/PostBasedAuthenticationMethodType.java index cf5e6bbb9ed..fb360619bf3 100644 --- a/zap/src/main/java/org/zaproxy/zap/authentication/PostBasedAuthenticationMethodType.java +++ b/zap/src/main/java/org/zaproxy/zap/authentication/PostBasedAuthenticationMethodType.java @@ -392,7 +392,7 @@ public WebSession authenticate( WebSession session = sessionManagementMethod.extractWebSession(msg); user.setAuthenticatedSession(session); - if (this.isAuthenticated(msg, user, true)) { + if (this.getVerificationMethod().isAuthenticated(msg, user, true)) { // Let the user know it worked AuthenticationHelper.notifyOutputAuthSuccessful(msg); user.getAuthenticationState().setLastAuthFailure(""); diff --git a/zap/src/main/java/org/zaproxy/zap/authentication/ScriptBasedAuthenticationMethodType.java b/zap/src/main/java/org/zaproxy/zap/authentication/ScriptBasedAuthenticationMethodType.java index d4646893356..4e05577efc8 100644 --- a/zap/src/main/java/org/zaproxy/zap/authentication/ScriptBasedAuthenticationMethodType.java +++ b/zap/src/main/java/org/zaproxy/zap/authentication/ScriptBasedAuthenticationMethodType.java @@ -192,8 +192,10 @@ public void loadScript(ScriptWrapper scriptW) { try { if (script instanceof AuthenticationScriptV2) { AuthenticationScriptV2 scriptV2 = (AuthenticationScriptV2) script; - setLoggedInIndicatorPattern(scriptV2.getLoggedInIndicator()); - setLoggedOutIndicatorPattern(scriptV2.getLoggedOutIndicator()); + getVerificationMethod() + .setLoggedInIndicatorPattern(scriptV2.getLoggedInIndicator()); + getVerificationMethod() + .setLoggedOutIndicatorPattern(scriptV2.getLoggedOutIndicator()); } String[] requiredParams = script.getRequiredParamsNames(); String[] optionalParams = script.getOptionalParamsNames(); @@ -322,8 +324,10 @@ public WebSession authenticate( try { if (script instanceof AuthenticationScriptV2) { AuthenticationScriptV2 scriptV2 = (AuthenticationScriptV2) script; - setLoggedInIndicatorPattern(scriptV2.getLoggedInIndicator()); - setLoggedOutIndicatorPattern(scriptV2.getLoggedOutIndicator()); + getVerificationMethod() + .setLoggedInIndicatorPattern(scriptV2.getLoggedInIndicator()); + getVerificationMethod() + .setLoggedOutIndicatorPattern(scriptV2.getLoggedOutIndicator()); } msg = script.authenticate( @@ -362,7 +366,7 @@ public WebSession authenticate( return null; } - if (this.isAuthenticated(msg, user, true)) { + if (this.getVerificationMethod().isAuthenticated(msg, user, true)) { // Let the user know it worked user.getAuthenticationState().setLastAuthFailure(""); AuthenticationHelper.notifyOutputAuthSuccessful(msg); @@ -581,17 +585,25 @@ private void loadScript(ScriptWrapper scriptW, boolean adaptOldValues) { Constant.messages.getString( "authentication.method.script.dialog.loggedInOutIndicatorsInScript.toolTip"); String loggedInIndicator = scriptV2.getLoggedInIndicator(); - this.method.setLoggedInIndicatorPattern(loggedInIndicator); - this.indicatorsPanel.setLoggedInIndicatorPattern(loggedInIndicator); - this.indicatorsPanel.setLoggedInIndicatorEnabled(false); - this.indicatorsPanel.setLoggedInIndicatorToolTip(toolTip); + this.method + .getVerificationMethod() + .setLoggedInIndicatorPattern(loggedInIndicator); String loggedOutIndicator = scriptV2.getLoggedOutIndicator(); - this.method.setLoggedOutIndicatorPattern(loggedOutIndicator); - this.indicatorsPanel.setLoggedOutIndicatorPattern(loggedOutIndicator); - this.indicatorsPanel.setLoggedOutIndicatorEnabled(false); - this.indicatorsPanel.setLoggedOutIndicatorToolTip(toolTip); - } else { + this.method + .getVerificationMethod() + .setLoggedOutIndicatorPattern(loggedOutIndicator); + + if (this.indicatorsPanel != null) { + this.indicatorsPanel.setLoggedInIndicatorPattern(loggedInIndicator); + this.indicatorsPanel.setLoggedInIndicatorEnabled(false); + this.indicatorsPanel.setLoggedInIndicatorToolTip(toolTip); + + this.indicatorsPanel.setLoggedOutIndicatorPattern(loggedOutIndicator); + this.indicatorsPanel.setLoggedOutIndicatorEnabled(false); + this.indicatorsPanel.setLoggedOutIndicatorToolTip(toolTip); + } + } else if (this.indicatorsPanel != null) { this.indicatorsPanel.setLoggedInIndicatorEnabled(true); this.indicatorsPanel.setLoggedInIndicatorToolTip(null); this.indicatorsPanel.setLoggedOutIndicatorEnabled(true); @@ -764,8 +776,10 @@ public void loadMethod( try { if (s instanceof AuthenticationScriptV2) { AuthenticationScriptV2 sV2 = (AuthenticationScriptV2) s; - method.setLoggedInIndicatorPattern(sV2.getLoggedInIndicator()); - method.setLoggedOutIndicatorPattern(sV2.getLoggedOutIndicator()); + method.getVerificationMethod() + .setLoggedInIndicatorPattern(sV2.getLoggedInIndicator()); + method.getVerificationMethod() + .setLoggedOutIndicatorPattern(sV2.getLoggedOutIndicator()); } method.credentialsParamNames = s.getCredentialsParamsNames(); } catch (Exception e) { @@ -986,8 +1000,10 @@ public void handleAction(JSONObject params) throws ApiException { try { if (s instanceof AuthenticationScriptV2) { AuthenticationScriptV2 sV2 = (AuthenticationScriptV2) s; - method.setLoggedInIndicatorPattern(sV2.getLoggedInIndicator()); - method.setLoggedOutIndicatorPattern(sV2.getLoggedOutIndicator()); + method.getVerificationMethod() + .setLoggedInIndicatorPattern(sV2.getLoggedInIndicator()); + method.getVerificationMethod() + .setLoggedOutIndicatorPattern(sV2.getLoggedOutIndicator()); } method.credentialsParamNames = s.getCredentialsParamsNames(); diff --git a/zap/src/main/java/org/zaproxy/zap/authentication/VerificationMethod.java b/zap/src/main/java/org/zaproxy/zap/authentication/VerificationMethod.java new file mode 100644 index 00000000000..a9dc8edca9c --- /dev/null +++ b/zap/src/main/java/org/zaproxy/zap/authentication/VerificationMethod.java @@ -0,0 +1,532 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2026 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.zap.authentication; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.regex.Pattern; +import org.apache.commons.httpclient.URI; +import org.apache.commons.httpclient.URIException; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.Constant; +import org.parosproxy.paros.network.HttpHeader; +import org.parosproxy.paros.network.HttpMessage; +import org.parosproxy.paros.network.HttpRequestHeader; +import org.parosproxy.paros.network.HttpSender; +import org.parosproxy.paros.view.View; +import org.zaproxy.zap.authentication.AuthenticationMethod.AuthCheckingStrategy; +import org.zaproxy.zap.authentication.AuthenticationMethod.AuthPollFrequencyUnits; +import org.zaproxy.zap.model.SessionStructure; +import org.zaproxy.zap.users.AuthenticationState; +import org.zaproxy.zap.users.User; +import org.zaproxy.zap.utils.Stats; + +/** + * Handles verification of whether a user is authenticated. + * + *

Verification can be done by checking each request/response for indicator patterns, or by + * polling a dedicated URL at a configurable frequency. + * + * @since 2.18.0 + */ +public class VerificationMethod { + + private static final Logger LOGGER = LogManager.getLogger(VerificationMethod.class); + + public static final String AUTH_STATE_ASSUMED_IN_STATS = "stats.auth.state.assumedin"; + public static final String AUTH_STATE_LOGGED_IN_STATS = "stats.auth.state.loggedin"; + public static final String AUTH_STATE_LOGGED_OUT_STATS = "stats.auth.state.loggedout"; + public static final String AUTH_STATE_NO_INDICATOR_STATS = "stats.auth.state.noindicator"; + public static final String AUTH_STATE_UNKNOWN_STATS = "stats.auth.state.unknown"; + + public static final int DEFAULT_POLL_FREQUENCY = 60; + + private AuthCheckingStrategy authCheckingStrategy = AuthCheckingStrategy.POLL_URL; + + private String pollMethod; + private String pollUrl; + private String pollData; + private String pollHeaders; + private int pollFrequency = DEFAULT_POLL_FREQUENCY; + private AuthPollFrequencyUnits pollFrequencyUnits = AuthPollFrequencyUnits.SECONDS; + + private Pattern loggedInIndicatorPattern = null; + private Pattern loggedOutIndicatorPattern = null; + + private HttpSender httpSender; + + private BiConsumer userDataReplacer; + + private HttpSender getHttpSender() { + if (this.httpSender == null) { + this.httpSender = new HttpSender(HttpSender.AUTHENTICATION_POLL_INITIATOR); + } + return httpSender; + } + + /** + * Checks if the response received by the HTTP message corresponds to an authenticated web + * session. + * + *

If none of the indicators are set up, the method defaults to returning {@code true}, so + * that no authentications are attempted when there is no way to check authentication. + * + * @param msg the http message + * @param user the user being checked + * @return {@code true} if authenticated or no indicators have been set, {@code false} otherwise + */ + public boolean isAuthenticated(HttpMessage msg, User user) { + return this.isAuthenticated(msg, user, false); + } + + /** + * Checks if the response received by the HTTP message corresponds to an authenticated web + * session. + * + *

If none of the indicators are set up, the method defaults to returning {@code true}, so + * that no authentications are attempted when there is no way to check authentication. + * + * @param msg the http message + * @param user the user being checked + * @param force always check even when the polling strategy would otherwise skip this check + * @return {@code true} if authenticated or no indicators have been set, {@code false} otherwise + */ + public boolean isAuthenticated(HttpMessage msg, User user, boolean force) { + if (msg == null + || user == null + || AuthCheckingStrategy.AUTO_DETECT.equals(this.authCheckingStrategy)) { + return false; + } + AuthenticationState authState = user.getAuthenticationState(); + if (loggedInIndicatorPattern == null && loggedOutIndicatorPattern == null) { + try { + Stats.incCounter(SessionStructure.getHostName(msg), AUTH_STATE_NO_INDICATOR_STATS); + } catch (URIException e) { + // Ignore + } + if (View.isInitialised()) { + View.getSingleton() + .getOutputPanel() + .append( + Constant.messages.getString( + "authentication.output.indicatorsNotSet", + msg.getRequestHeader().getURI()) + + "\n"); + } + return true; + } + + HttpMessage msgToTest; + + switch (this.authCheckingStrategy) { + case EACH_REQ: + case EACH_REQ_RESP: + case EACH_RESP: + msgToTest = msg; + break; + case POLL_URL: + if (!force + && authState.getLastPollResult() != null + && authState.getLastPollResult()) { + switch (pollFrequencyUnits) { + case SECONDS: + if ((System.currentTimeMillis() - authState.getLastPollTime()) / 1000 + < pollFrequency) { + try { + Stats.incCounter( + SessionStructure.getHostName(msg), + AUTH_STATE_ASSUMED_IN_STATS); + } catch (URIException e) { + // Ignore + } + return true; + } + break; + case REQUESTS: + default: + if (authState.getRequestsSincePoll() < pollFrequency) { + authState.incRequestsSincePoll(); + try { + Stats.incCounter( + SessionStructure.getHostName(msg), + AUTH_STATE_ASSUMED_IN_STATS); + } catch (URIException e) { + // Ignore + } + return true; + } + break; + } + } + try { + msgToTest = pollAsUser(user); + } catch (Exception e) { + LOGGER.warn("Failed sending poll request to {}", this.getPollUrl(), e); + return false; + } + break; + default: + return false; + } + + return evaluateAuthRequest(msgToTest, authState); + } + + /** + * Evaluates whether the given message indicates an authenticated state, updating the + * authentication state accordingly. + * + * @param msg the http message to evaluate + * @param authState the current authentication state to update + * @return {@code true} if the message indicates an authenticated state + */ + public boolean evaluateAuthRequest(HttpMessage msg, AuthenticationState authState) { + List contentToTest = new ArrayList<>(); + switch (authCheckingStrategy) { + case EACH_REQ: + contentToTest.add(msg.getRequestHeader().toString()); + contentToTest.add(msg.getRequestBody().toString()); + break; + case EACH_REQ_RESP: + contentToTest.add(msg.getRequestHeader().toString()); + contentToTest.add(msg.getRequestBody().toString()); + contentToTest.add(msg.getResponseHeader().toString()); + contentToTest.add(msg.getResponseBody().toString()); + break; + case EACH_RESP: + case POLL_URL: + contentToTest.add(msg.getResponseHeader().toString()); + contentToTest.add(msg.getResponseBody().toString()); + break; + case AUTO_DETECT: + return false; + } + if (patternMatchesAny(loggedInIndicatorPattern, contentToTest)) { + try { + Stats.incCounter(SessionStructure.getHostName(msg), AUTH_STATE_LOGGED_IN_STATS); + } catch (URIException e) { + // Ignore + } + if (authCheckingStrategy.equals(AuthCheckingStrategy.POLL_URL)) { + authState.setLastPollResult(true); + } + return true; + } + + if (loggedOutIndicatorPattern != null + && !patternMatchesAny(loggedOutIndicatorPattern, contentToTest)) { + // Can't find the unauthenticated indicator — assume authenticated but record as unknown + try { + Stats.incCounter(SessionStructure.getHostName(msg), AUTH_STATE_UNKNOWN_STATS); + } catch (URIException e) { + // Ignore + } + if (this.authCheckingStrategy.equals(AuthCheckingStrategy.POLL_URL)) { + authState.setLastPollResult(true); + } + return true; + } + try { + Stats.incCounter(SessionStructure.getHostName(msg), AUTH_STATE_LOGGED_OUT_STATS); + } catch (URIException e) { + // Ignore + } + if (this.authCheckingStrategy.equals(AuthCheckingStrategy.POLL_URL)) { + authState.setLastPollResult(false); + } + return false; + } + + /** + * Sends a poll request as the given user and returns the response message. + * + * @param user the user on whose behalf to poll + * @return the poll response message + * @throws IOException if the poll request could not be sent + * @throws IllegalArgumentException if the checking strategy is not {@link + * AuthCheckingStrategy#POLL_URL}, or if the poll URL is not set + */ + public HttpMessage pollAsUser(User user) throws IOException { + if (!this.authCheckingStrategy.equals(AuthCheckingStrategy.POLL_URL)) { + throw new IllegalArgumentException("Authentication checking strategy is not POLL_URL"); + } + if (pollUrl == null || pollUrl.isBlank()) { + throw new IllegalArgumentException("Poll URL is not set"); + } + HttpMessage pollMsg = new HttpMessage(new URI(this.getPollUrl(), true)); + if (!StringUtils.isBlank(pollMethod)) { + setRequestBody(pollMsg, pollMethod, getPollData()); + } else if (this.getPollData() != null && this.getPollData().length() > 0) { + setRequestBody(pollMsg, HttpRequestHeader.POST, getPollData()); + } + if (this.getPollHeaders() != null && this.getPollHeaders().length() > 0) { + for (String header : this.getPollHeaders().split("\n")) { + String[] headerValue = header.split(":", 2); + if (headerValue.length == 2) { + pollMsg.getRequestHeader() + .addHeader(headerValue[0].trim(), headerValue[1].trim()); + } else { + LOGGER.error( + "Invalid header '{}' for poll request to {}", + header, + this.getPollUrl()); + } + } + } + pollMsg.setRequestingUser(user); + if (userDataReplacer != null) { + userDataReplacer.accept(pollMsg, user); + } + + getHttpSender().sendAndReceive(pollMsg); + AuthenticationHelper.addAuthMessageToHistory( + pollMsg, List.of(AuthenticationHelper.HISTORY_TAG_VERIFICATION)); + + AuthenticationState authState = user.getAuthenticationState(); + authState.setLastPollTime(System.currentTimeMillis()); + authState.setRequestsSincePoll(0); + + return pollMsg; + } + + private static void setRequestBody(HttpMessage msg, String method, String data) { + msg.getRequestHeader().setMethod(method); + msg.getRequestBody().setBody(data); + + int bodyLength = msg.getRequestBody().length(); + if (bodyLength == 0 + && (HttpRequestHeader.GET.equalsIgnoreCase(method) + || HttpRequestHeader.HEAD.equalsIgnoreCase(method))) { + msg.getRequestHeader().setHeader(HttpHeader.CONTENT_LENGTH, null); + } else { + msg.getRequestHeader().setContentLength(bodyLength); + } + } + + private static boolean patternMatchesAny(Pattern pattern, List content) { + if (pattern != null) { + for (String str : content) { + if (pattern.matcher(str).find()) { + return true; + } + } + } + return false; + } + + /** + * Gets the logged in indicator pattern. + * + * @return the logged in indicator pattern + */ + public Pattern getLoggedInIndicatorPattern() { + return loggedInIndicatorPattern; + } + + /** + * Sets the logged in indicator pattern. + * + * @param loggedInIndicatorPattern the new logged in indicator pattern + */ + public void setLoggedInIndicatorPattern(String loggedInIndicatorPattern) { + if (loggedInIndicatorPattern == null || loggedInIndicatorPattern.trim().length() == 0) { + this.loggedInIndicatorPattern = null; + } else { + this.loggedInIndicatorPattern = Pattern.compile(loggedInIndicatorPattern); + } + } + + /** + * Gets the logged out indicator pattern. + * + * @return the logged out indicator pattern + */ + public Pattern getLoggedOutIndicatorPattern() { + return loggedOutIndicatorPattern; + } + + /** + * Sets the logged out indicator pattern. + * + * @param loggedOutIndicatorPattern the new logged out indicator pattern + */ + public void setLoggedOutIndicatorPattern(String loggedOutIndicatorPattern) { + if (loggedOutIndicatorPattern == null || loggedOutIndicatorPattern.trim().length() == 0) { + this.loggedOutIndicatorPattern = null; + } else { + this.loggedOutIndicatorPattern = Pattern.compile(loggedOutIndicatorPattern); + } + } + + public AuthCheckingStrategy getAuthCheckingStrategy() { + return authCheckingStrategy; + } + + public void setAuthCheckingStrategy(AuthCheckingStrategy authCheckingStrategy) { + Objects.requireNonNull(authCheckingStrategy); + this.authCheckingStrategy = authCheckingStrategy; + } + + public String getPollMethod() { + return pollMethod; + } + + public void setPollMethod(String pollMethod) { + this.pollMethod = StringUtils.isBlank(pollMethod) ? null : pollMethod; + } + + public String getPollUrl() { + return pollUrl; + } + + public void setPollUrl(String pollUrl) { + this.pollUrl = pollUrl; + } + + public String getPollData() { + return pollData; + } + + public void setPollData(String pollData) { + this.pollData = pollData; + } + + public String getPollHeaders() { + return pollHeaders; + } + + public void setPollHeaders(String pollHeaders) { + this.pollHeaders = pollHeaders; + } + + public int getPollFrequency() { + return pollFrequency; + } + + public void setPollFrequency(int pollFrequency) { + this.pollFrequency = pollFrequency; + } + + public AuthPollFrequencyUnits getPollFrequencyUnits() { + return pollFrequencyUnits; + } + + public void setPollFrequencyUnits(AuthPollFrequencyUnits pollFrequencyUnits) { + this.pollFrequencyUnits = pollFrequencyUnits; + } + + /** + * Sets the callback used to replace user-specific data (e.g. username/password tokens) in poll + * requests. If {@code null}, no replacement is performed. + * + * @param userDataReplacer the callback, or {@code null} + */ + public void setUserDataReplacer(BiConsumer userDataReplacer) { + this.userDataReplacer = userDataReplacer; + } + + /** + * Gets the callback used to replace user-specific data in poll requests. + * + * @return the callback, or {@code null} if no replacement is configured + */ + public BiConsumer getUserDataReplacer() { + return userDataReplacer; + } + + /** + * Creates a deep copy of this verification method. + * + * @return a deep copy + */ + public VerificationMethod copy(BiConsumer userDataReplacer) { + VerificationMethod clone = new VerificationMethod(); + clone.authCheckingStrategy = this.authCheckingStrategy; + clone.pollUrl = this.pollMethod; + clone.pollUrl = this.pollUrl; + clone.pollData = this.pollData; + clone.pollHeaders = this.pollHeaders; + clone.pollFrequency = this.pollFrequency; + clone.pollFrequencyUnits = this.pollFrequencyUnits; + clone.loggedInIndicatorPattern = this.loggedInIndicatorPattern; + clone.loggedOutIndicatorPattern = this.loggedOutIndicatorPattern; + clone.userDataReplacer = userDataReplacer; + return clone; + } + + @Override + public int hashCode() { + return Objects.hash( + loggedInIndicatorPattern == null ? null : loggedInIndicatorPattern.pattern(), + loggedOutIndicatorPattern == null ? null : loggedOutIndicatorPattern.pattern(), + authCheckingStrategy, + pollMethod, + pollUrl, + pollData, + pollHeaders, + pollFrequency, + pollFrequencyUnits); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + VerificationMethod other = (VerificationMethod) obj; + if (!isSamePattern(loggedInIndicatorPattern, other.loggedInIndicatorPattern)) { + return false; + } + if (!isSamePattern(loggedOutIndicatorPattern, other.loggedOutIndicatorPattern)) { + return false; + } + if (!this.authCheckingStrategy.equals(other.authCheckingStrategy)) { + return false; + } + if (!Objects.equals(pollMethod, other.pollMethod)) { + return false; + } + if (!Objects.equals(this.pollUrl, other.pollUrl)) { + return false; + } + if (!Objects.equals(this.pollData, other.pollData)) { + return false; + } + if (!Objects.equals(this.pollHeaders, other.pollHeaders)) { + return false; + } + if (this.pollFrequency != other.pollFrequency) { + return false; + } + return this.pollFrequencyUnits.equals(other.pollFrequencyUnits); + } + + private static boolean isSamePattern(Pattern pattern, Pattern other) { + if (pattern == null) { + return other == null; + } + return other != null && pattern.pattern().equals(other.pattern()); + } +} diff --git a/zap/src/main/java/org/zaproxy/zap/extension/api/ApiGeneratorUtils.java b/zap/src/main/java/org/zaproxy/zap/extension/api/ApiGeneratorUtils.java index 30ae98b5165..a9667abca88 100644 --- a/zap/src/main/java/org/zaproxy/zap/extension/api/ApiGeneratorUtils.java +++ b/zap/src/main/java/org/zaproxy/zap/extension/api/ApiGeneratorUtils.java @@ -27,6 +27,7 @@ import org.zaproxy.zap.extension.anticsrf.AntiCsrfParam; import org.zaproxy.zap.extension.ascan.ActiveScanAPI; import org.zaproxy.zap.extension.authentication.AuthenticationAPI; +import org.zaproxy.zap.extension.authentication.VerificationAPI; import org.zaproxy.zap.extension.authorization.AuthorizationAPI; import org.zaproxy.zap.extension.autoupdate.AutoUpdateAPI; import org.zaproxy.zap.extension.autoupdate.OptionsParamCheckForUpdates; @@ -85,6 +86,8 @@ public static List getAllImplementors() { imps.add(new AuthenticationAPI(null)); + imps.add(new VerificationAPI()); + imps.add(new AuthorizationAPI()); imps.add(new RuleConfigAPI(null)); diff --git a/zap/src/main/java/org/zaproxy/zap/extension/api/ContextAPI.java b/zap/src/main/java/org/zaproxy/zap/extension/api/ContextAPI.java index 24cd21002a6..def43773f20 100644 --- a/zap/src/main/java/org/zaproxy/zap/extension/api/ContextAPI.java +++ b/zap/src/main/java/org/zaproxy/zap/extension/api/ContextAPI.java @@ -46,6 +46,7 @@ import org.zaproxy.zap.authentication.AuthenticationMethod.AuthCheckingStrategy; import org.zaproxy.zap.authentication.AuthenticationMethod.AuthPollFrequencyUnits; import org.zaproxy.zap.authentication.AuthenticationMethodType; +import org.zaproxy.zap.authentication.VerificationMethod; import org.zaproxy.zap.extension.api.ApiException.Type; import org.zaproxy.zap.extension.authorization.AuthorizationDetectionMethod; import org.zaproxy.zap.model.Context; @@ -113,7 +114,7 @@ public ContextAPI() { new ApiAction( ACTION_SET_CONTEXT_REGEXS, new String[] {CONTEXT_NAME, INC_REGEXS_PARAM, EXC_REGEXS_PARAM})); - this.addApiAction( + ApiAction setCheckingStrategyAction = new ApiAction( ACTION_SET_CONTEXT_CHECKING_STRATEGY, new String[] {CONTEXT_NAME, PARAM_CHECKING_STRATEGRY}, @@ -123,7 +124,11 @@ public ContextAPI() { PARAM_POLL_HEADERS, PARAM_POLL_FREQ, PARAM_POLL_FREQ_UNITS - })); + }); + setCheckingStrategyAction.setDeprecated(true); + setCheckingStrategyAction.setDeprecatedDescription( + "Use verification/setVerificationMethod instead."); + this.addApiAction(setCheckingStrategyAction); this.addApiAction(new ApiAction(ACTION_NEW_CONTEXT, contextNameOnlyParam)); this.addApiAction(new ApiAction(ACTION_REMOVE_CONTEXT, contextNameOnlyParam)); this.addApiAction( @@ -244,13 +249,13 @@ public ApiResponse handleApiAction(String name, JSONObject params) throws ApiExc throw new ApiException( ApiException.Type.ILLEGAL_PARAMETER, PARAM_POLL_FREQ); } - context.getAuthenticationMethod().setPollUrl(pollUrl); - context.getAuthenticationMethod().setPollData(pollData); - context.getAuthenticationMethod().setPollHeaders(pollHeaders); - context.getAuthenticationMethod().setPollFrequency(freq); - context.getAuthenticationMethod().setPollFrequencyUnits(units); + context.getVerificationMethod().setPollUrl(pollUrl); + context.getVerificationMethod().setPollData(pollData); + context.getVerificationMethod().setPollHeaders(pollHeaders); + context.getVerificationMethod().setPollFrequency(freq); + context.getVerificationMethod().setPollFrequencyUnits(units); } - context.getAuthenticationMethod().setAuthCheckingStrategy(checkingStrategy); + context.getVerificationMethod().setAuthCheckingStrategy(checkingStrategy); Model.getSingleton().getSession().saveContext(context); break; case ACTION_NEW_CONTEXT: @@ -487,22 +492,22 @@ private ApiResponse buildResponseFromContext(Context c) { AuthenticationMethod authenticationMethod = c.getAuthenticationMethod(); if (authenticationMethod != null) { - Pattern pattern = authenticationMethod.getLoggedInIndicatorPattern(); + VerificationMethod vm = c.getVerificationMethod(); + Pattern pattern = vm.getLoggedInIndicatorPattern(); fields.put("loggedInPattern", pattern == null ? "" : pattern.toString()); - pattern = authenticationMethod.getLoggedOutIndicatorPattern(); + pattern = vm.getLoggedOutIndicatorPattern(); fields.put("loggedOutPattern", pattern == null ? "" : pattern.toString()); AuthenticationMethodType type = authenticationMethod.getType(); fields.put("authType", type == null ? "" : type.getName()); - AuthCheckingStrategy strategy = authenticationMethod.getAuthCheckingStrategy(); + AuthCheckingStrategy strategy = vm.getAuthCheckingStrategy(); fields.put(PARAM_CHECKING_STRATEGRY, strategy == null ? "" : strategy.name()); if (AuthCheckingStrategy.POLL_URL.equals(strategy)) { - fields.put(PARAM_POLL_URL, authenticationMethod.getPollUrl()); - fields.put(PARAM_POLL_DATA, authenticationMethod.getPollData()); - fields.put(PARAM_POLL_HEADERS, authenticationMethod.getPollHeaders()); - fields.put( - PARAM_POLL_FREQ, Integer.toString(authenticationMethod.getPollFrequency())); - AuthPollFrequencyUnits units = authenticationMethod.getPollFrequencyUnits(); + fields.put(PARAM_POLL_URL, vm.getPollUrl()); + fields.put(PARAM_POLL_DATA, vm.getPollData()); + fields.put(PARAM_POLL_HEADERS, vm.getPollHeaders()); + fields.put(PARAM_POLL_FREQ, Integer.toString(vm.getPollFrequency())); + AuthPollFrequencyUnits units = vm.getPollFrequencyUnits(); fields.put(PARAM_POLL_FREQ_UNITS, units == null ? "" : units.name()); } } diff --git a/zap/src/main/java/org/zaproxy/zap/extension/authentication/AuthenticationAPI.java b/zap/src/main/java/org/zaproxy/zap/extension/authentication/AuthenticationAPI.java index 68a9ba712a0..b8521a04995 100644 --- a/zap/src/main/java/org/zaproxy/zap/extension/authentication/AuthenticationAPI.java +++ b/zap/src/main/java/org/zaproxy/zap/extension/authentication/AuthenticationAPI.java @@ -81,23 +81,44 @@ public AuthenticationAPI(ExtensionAuthentication extension) { this.addApiView( new ApiView(VIEW_GET_METHOD_CONFIG_PARAMETERS, new String[] {PARAM_METHOD_NAME})); this.addApiView(new ApiView(VIEW_GET_AUTHENTICATION, new String[] {PARAM_CONTEXT_ID})); - this.addApiView(new ApiView(VIEW_GET_LOGGED_IN_INDICATOR, new String[] {PARAM_CONTEXT_ID})); - this.addApiView( - new ApiView(VIEW_GET_LOGGED_OUT_INDICATOR, new String[] {PARAM_CONTEXT_ID})); + + ApiView getLoggedInView = + new ApiView(VIEW_GET_LOGGED_IN_INDICATOR, new String[] {PARAM_CONTEXT_ID}); + getLoggedInView.setDeprecated(true); + getLoggedInView.setDeprecatedDescription( + "Use verification/" + VIEW_GET_LOGGED_IN_INDICATOR + " instead."); + this.addApiView(getLoggedInView); + + ApiView getLoggedOutView = + new ApiView(VIEW_GET_LOGGED_OUT_INDICATOR, new String[] {PARAM_CONTEXT_ID}); + getLoggedOutView.setDeprecated(true); + getLoggedOutView.setDeprecatedDescription( + "Use verification/" + VIEW_GET_LOGGED_OUT_INDICATOR + " instead."); + this.addApiView(getLoggedOutView); this.addApiAction( new ApiAction( ACTION_SET_METHOD, new String[] {PARAM_CONTEXT_ID, PARAM_METHOD_NAME}, new String[] {PARAM_METHOD_CONFIG_PARAMS})); - this.addApiAction( + + ApiAction setLoggedInAction = new ApiAction( ACTION_SET_LOGGED_IN_INDICATOR, - new String[] {PARAM_CONTEXT_ID, PARAM_LOGGED_IN_INDICATOR})); - this.addApiAction( + new String[] {PARAM_CONTEXT_ID, PARAM_LOGGED_IN_INDICATOR}); + setLoggedInAction.setDeprecated(true); + setLoggedInAction.setDeprecatedDescription( + "Use verification/" + ACTION_SET_LOGGED_IN_INDICATOR + " instead."); + this.addApiAction(setLoggedInAction); + + ApiAction setLoggedOutAction = new ApiAction( ACTION_SET_LOGGED_OUT_INDICATOR, - new String[] {PARAM_CONTEXT_ID, PARAM_LOGGED_OUT_INDICATOR})); + new String[] {PARAM_CONTEXT_ID, PARAM_LOGGED_OUT_INDICATOR}); + setLoggedOutAction.setDeprecated(true); + setLoggedOutAction.setDeprecatedDescription( + "Use verification/" + ACTION_SET_LOGGED_OUT_INDICATOR + " instead."); + this.addApiAction(setLoggedOutAction); } @Override @@ -114,13 +135,13 @@ public ApiResponse handleApiView(String name, JSONObject params) throws ApiExcep return getContext(params).getAuthenticationMethod().getApiResponseRepresentation(); case VIEW_GET_LOGGED_IN_INDICATOR: Pattern loggedInPattern = - getContext(params).getAuthenticationMethod().getLoggedInIndicatorPattern(); + getContext(params).getVerificationMethod().getLoggedInIndicatorPattern(); if (loggedInPattern != null) return new ApiResponseElement("logged_in_regex", loggedInPattern.toString()); else return new ApiResponseElement("logged_in_regex", ""); case VIEW_GET_LOGGED_OUT_INDICATOR: Pattern loggedOutPattern = - getContext(params).getAuthenticationMethod().getLoggedOutIndicatorPattern(); + getContext(params).getVerificationMethod().getLoggedOutIndicatorPattern(); if (loggedOutPattern != null) return new ApiResponseElement("logged_out_regex", loggedOutPattern.toString()); else return new ApiResponseElement("logged_out_regex", ""); @@ -149,7 +170,7 @@ public ApiResponse handleApiAction(String name, JSONObject params) throws ApiExc if (loggedInIndicator == null || loggedInIndicator.isEmpty()) throw new ApiException(Type.MISSING_PARAMETER, PARAM_LOGGED_IN_INDICATOR); context = getContext(params); - context.getAuthenticationMethod().setLoggedInIndicatorPattern(loggedInIndicator); + context.getVerificationMethod().setLoggedInIndicatorPattern(loggedInIndicator); context.save(); return ApiResponseElement.OK; @@ -158,7 +179,7 @@ public ApiResponse handleApiAction(String name, JSONObject params) throws ApiExc if (loggedOutIndicator == null || loggedOutIndicator.isEmpty()) throw new ApiException(Type.MISSING_PARAMETER, PARAM_LOGGED_OUT_INDICATOR); context = getContext(params); - context.getAuthenticationMethod().setLoggedOutIndicatorPattern(loggedOutIndicator); + context.getVerificationMethod().setLoggedOutIndicatorPattern(loggedOutIndicator); context.save(); return ApiResponseElement.OK; diff --git a/zap/src/main/java/org/zaproxy/zap/extension/authentication/ContextAuthenticationPanel.java b/zap/src/main/java/org/zaproxy/zap/extension/authentication/ContextAuthenticationPanel.java index 417ec404678..ed8e86b5afd 100644 --- a/zap/src/main/java/org/zaproxy/zap/extension/authentication/ContextAuthenticationPanel.java +++ b/zap/src/main/java/org/zaproxy/zap/extension/authentication/ContextAuthenticationPanel.java @@ -21,54 +21,35 @@ import java.awt.BorderLayout; import java.awt.CardLayout; -import java.awt.Component; import java.awt.Dimension; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; -import java.util.ArrayList; import java.util.List; import java.util.Vector; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; -import javax.swing.ImageIcon; -import javax.swing.JButton; import javax.swing.JComboBox; -import javax.swing.JFormattedTextField; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; -import javax.swing.JSpinner; import javax.swing.border.EmptyBorder; -import org.apache.commons.httpclient.URI; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.parosproxy.paros.Constant; import org.parosproxy.paros.control.Control; -import org.parosproxy.paros.model.Model; import org.parosproxy.paros.model.Session; -import org.parosproxy.paros.model.SiteNode; -import org.parosproxy.paros.network.HttpRequestHeader; -import org.parosproxy.paros.view.View; import org.zaproxy.zap.authentication.AbstractAuthenticationMethodOptionsPanel; -import org.zaproxy.zap.authentication.AuthenticationIndicatorsPanel; import org.zaproxy.zap.authentication.AuthenticationMethod; -import org.zaproxy.zap.authentication.AuthenticationMethod.AuthCheckingStrategy; -import org.zaproxy.zap.authentication.AuthenticationMethod.AuthPollFrequencyUnits; import org.zaproxy.zap.authentication.AuthenticationMethodType; +import org.zaproxy.zap.authentication.VerificationMethod; import org.zaproxy.zap.extension.users.ExtensionUserManagement; import org.zaproxy.zap.model.Context; import org.zaproxy.zap.users.User; import org.zaproxy.zap.utils.FontUtils; import org.zaproxy.zap.utils.ZapHtmlLabel; -import org.zaproxy.zap.utils.ZapNumberSpinner; -import org.zaproxy.zap.utils.ZapTextArea; -import org.zaproxy.zap.utils.ZapTextField; import org.zaproxy.zap.view.AbstractContextPropertiesPanel; import org.zaproxy.zap.view.LayoutHelper; -import org.zaproxy.zap.view.NodeSelectDialog; /** The Context Panel shown for configuring a Context's authentication methods. */ @SuppressWarnings("serial") @@ -81,36 +62,21 @@ public class ContextAuthenticationPanel extends AbstractContextPropertiesPanel { private static final String PANEL_NAME = Constant.messages.getString("authentication.panel.title"); - private static final String FIELD_LABEL_LOGGED_IN_INDICATOR = - Constant.messages.getString("authentication.panel.label.loggedIn"); - private static final String FIELD_LABEL_LOGGED_OUT_INDICATOR = - Constant.messages.getString("authentication.panel.label.loggedOut"); private static final String FIELD_LABEL_TYPE_SELECT = Constant.messages.getString("authentication.panel.label.typeSelect"); private static final String LABEL_DESCRIPTION = Constant.messages.getString("authentication.panel.label.description"); private static final String PANEL_TITLE_CONFIG = Constant.messages.getString("authentication.panel.label.configTitle"); - private static final String PANEL_TITLE_VERIF = - Constant.messages.getString("authentication.panel.label.verifTitle"); - private static final String LABEL_POLL_URL = - Constant.messages.getString("authentication.panel.label.pollurl"); - private static final String LABEL_POLL_DATA = - Constant.messages.getString("authentication.panel.label.polldata"); - private static final String LABEL_POLL_HEADERS = - Constant.messages.getString("authentication.panel.label.pollheaders"); - private static final String LABEL_POLL_FREQUENCY = - Constant.messages.getString("authentication.panel.label.freq"); private static final String LABEL_CONFIG_NOT_NEEDED = Constant.messages.getString("sessionmanagement.panel.label.noConfigPanel"); - private static final String LABEL_STRATEGY = - Constant.messages.getString("authentication.panel.label.strategy"); - private static final String STRATEGY_PREFIX = "authentication.panel.label.strategy."; - private static final String FREQUENCY_UNITS_PREFIX = "authentication.panel.label.units."; /** The extension. */ private ExtensionAuthentication extension; + /** The verification panel — owns all verification UI fields. */ + private ContextVerificationPanel verificationPanel; + /** The authentication method types combo box. */ private JComboBox authenticationMethodsComboBox; @@ -126,38 +92,35 @@ public class ContextAuthenticationPanel extends AbstractContextPropertiesPanel { /** The container panel for the authentication method's configuration. */ private JPanel configContainerPanel; - /** The container panel for the authentication verification configuration. */ - private JPanel verifContainerPanel; - - private JComboBox authenticationVerifComboBox; - private JComboBox authFrequencyUnitsComboBox; - - private JButton pollUrlSelectButton = null; - private ZapTextField pollUrlField = null; - private ZapTextField pollDataField = null; - private ZapTextArea pollHeadersField = null; - private ZapNumberSpinner pollFrequency = null; - - private ZapTextField loggedInIndicatorRegexField = null; - private ZapTextField loggedOutIndicatorRegexField = null; - /** Hacked used to make sure a confirmation is not needed if changes where done during init. */ private boolean needsConfirm = true; - private AuthenticationIndicatorsPanel authenticationIndicatorsPanel; - /** * Instantiates a new context authentication configuration panel. * * @param extension the extension * @param context the context + * @param verificationPanel the sibling verification panel that owns indicator fields */ - public ContextAuthenticationPanel(ExtensionAuthentication extension, Context context) { + public ContextAuthenticationPanel( + ExtensionAuthentication extension, + Context context, + ContextVerificationPanel verificationPanel) { super(context.getId()); this.extension = extension; + this.verificationPanel = verificationPanel; initialize(); } + /** + * @deprecated Use {@link #ContextAuthenticationPanel(ExtensionAuthentication, Context, + * ContextVerificationPanel)} instead. + */ + @Deprecated + public ContextAuthenticationPanel(ExtensionAuthentication extension, Context context) { + this(extension, context, new ContextVerificationPanel(context)); + } + public static String buildName(int contextId) { return contextId + ": " + PANEL_NAME; } @@ -173,7 +136,7 @@ private void initialize() { panel.setLayout(new GridBagLayout()); // Only known way to minimise the horizontal space taken up // needs to be big enough to cope with Form based auth panel - panel.setPreferredSize(new Dimension(400, 800)); + panel.setPreferredSize(new Dimension(400, 600)); JScrollPane scrollPanel = new JScrollPane(); scrollPanel.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); @@ -194,70 +157,14 @@ private void initialize() { getConfigContainerPanel(), LayoutHelper.getGBC(0, 3, 1, 1.0d, new Insets(10, 0, 10, 0))); - // Verification panel container - panel.add( - getVerifContainerPanel(), - LayoutHelper.getGBC(0, 4, 1, 1.0d, new Insets(10, 0, 10, 0))); - - int y = 0; - int fullWidth = 3; - getVerifContainerPanel() - .add(new JLabel(LABEL_STRATEGY), LayoutHelper.getGBC(0, y++, fullWidth, 1.0D)); - getVerifContainerPanel() - .add( - getAuthenticationVerifComboBox(), - LayoutHelper.getGBC(0, y++, fullWidth, 1.0D)); - - getVerifContainerPanel() - .add( - new JLabel(FIELD_LABEL_LOGGED_IN_INDICATOR), - LayoutHelper.getGBC(0, y++, fullWidth, 1.0D)); - getVerifContainerPanel() - .add( - getLoggedInIndicatorRegexField(), - LayoutHelper.getGBC(0, y++, fullWidth, 1.0D)); - getVerifContainerPanel() - .add( - new JLabel(FIELD_LABEL_LOGGED_OUT_INDICATOR), - LayoutHelper.getGBC(0, y++, fullWidth, 1.0D)); - getVerifContainerPanel() - .add( - getLoggedOutIndicatorRegexField(), - LayoutHelper.getGBC(0, y++, fullWidth, 1.0D)); - - getVerifContainerPanel() - .add(new JLabel(LABEL_POLL_FREQUENCY), LayoutHelper.getGBC(0, y, 1, 0.34D)); - getVerifContainerPanel() - .add(getPollFrequencySpinner(), LayoutHelper.getGBC(1, y, 1, 0.33D)); - getVerifContainerPanel() - .add(getAuthFrequencyUnitsComboBox(), LayoutHelper.getGBC(2, y++, 1, 0.33D)); - - getVerifContainerPanel() - .add(new JLabel(LABEL_POLL_URL), LayoutHelper.getGBC(0, y++, fullWidth, 1.0D)); - JPanel urlPanel = new JPanel(new GridBagLayout()); - urlPanel.add(this.getPollUrlField(), LayoutHelper.getGBC(0, 0, 1, 1.0D)); - urlPanel.add(getPollUrlSelectButton(), LayoutHelper.getGBC(1, 0, 1, 0.0D)); - getVerifContainerPanel().add(urlPanel, LayoutHelper.getGBC(0, y++, fullWidth, 1.0D)); - getVerifContainerPanel() - .add(new JLabel(LABEL_POLL_DATA), LayoutHelper.getGBC(0, y++, fullWidth, 1.0D)); - getVerifContainerPanel() - .add(this.getPollDataField(), LayoutHelper.getGBC(0, y++, fullWidth, 1.0D)); - getVerifContainerPanel() - .add(new JLabel(LABEL_POLL_HEADERS), LayoutHelper.getGBC(0, y++, fullWidth, 1.0D)); - getVerifContainerPanel() - .add(this.getPollHeadersField(), LayoutHelper.getGBC(0, y++, fullWidth, 1.0D)); - // Padding panel.add(new JLabel(), LayoutHelper.getGBC(0, 99, 1, 1.0D, 1.0D)); } /** - * Changes the shown method's configuration panel (used to display brief info about the method - * and configure it) with a new one, based on a new method type. If {@code null} is provided as - * a parameter, nothing is shown. If the provided method type does not require configuration, a - * simple message is shown stating that no configuration is needed. + * Changes the shown method's configuration panel with a new one based on a new method type. * - * @param newMethodType the new method type. If null, nothing is shown. + * @param newMethodType the new method type; if null, nothing is shown */ private void changeMethodConfigPanel(AuthenticationMethodType newMethodType) { // If there's no new method, don't display anything @@ -330,23 +237,23 @@ public void itemStateChanged(ItemEvent e) { return; } } - resetLoggedInOutIndicators(); - - // If no authentication method was previously selected or it's a - // different - // class, create a new authentication method object if (selectedAuthenticationMethod == null || !type.isTypeForMethod(selectedAuthenticationMethod)) { selectedAuthenticationMethod = type.createAuthenticationMethod(getContextId()); + // Copy current verification state into the new auth method's VM + VerificationMethod vmCopy = + verificationPanel.buildVerificationMethodFromUI(); + vmCopy.setUserDataReplacer( + selectedAuthenticationMethod + ::replaceUserDataInPollRequest); + selectedAuthenticationMethod.setVerificationMethod(vmCopy); } - // Show the configuration panel changeMethodConfigPanel(type); if (type.hasOptionsPanel()) { shownConfigPanel.bindMethod( - selectedAuthenticationMethod, - getAuthenticationIndicatorsPanel()); + selectedAuthenticationMethod, verificationPanel); } } } @@ -355,72 +262,6 @@ public void itemStateChanged(ItemEvent e) { return authenticationMethodsComboBox; } - private JComboBox getAuthenticationVerifComboBox() { - if (authenticationVerifComboBox == null) { - authenticationVerifComboBox = new JComboBox<>(); - for (AuthCheckingStrategyType acst : AuthCheckingStrategyType.getAllValues()) { - authenticationVerifComboBox.addItem(acst); - } - // Prepare the listener for the change of selection - authenticationVerifComboBox.addItemListener( - new ItemListener() { - - @Override - public void itemStateChanged(ItemEvent e) { - if (e.getStateChange() == ItemEvent.SELECTED - && !e.getItem().equals(shownMethodType)) { - - setPollFieldStatuses(((AuthCheckingStrategyType) e.getItem())); - } - } - }); - } - return authenticationVerifComboBox; - } - - private void setPollFieldStatuses(AuthCheckingStrategyType type) { - boolean isPoll = type.getStrategy().equals(AuthCheckingStrategy.POLL_URL); - getAuthFrequencyUnitsComboBox().setEnabled(isPoll); - getPollFrequencySpinner().setEnabled(isPoll); - getPollUrlSelectButton().setEnabled(isPoll); - getPollUrlField().setEnabled(isPoll); - getPollDataField().setEnabled(isPoll); - getPollHeadersField().setEnabled(isPoll); - boolean isAutoDetect = type.getStrategy().equals(AuthCheckingStrategy.AUTO_DETECT); - this.getLoggedInIndicatorRegexField().setEnabled(!isAutoDetect); - this.getLoggedOutIndicatorRegexField().setEnabled(!isAutoDetect); - } - - private JComboBox getAuthFrequencyUnitsComboBox() { - if (authFrequencyUnitsComboBox == null) { - authFrequencyUnitsComboBox = new JComboBox<>(); - for (AuthPollFrequencyUnitsType acst : AuthPollFrequencyUnitsType.getAllValues()) { - authFrequencyUnitsComboBox.addItem(acst); - } - } - return authFrequencyUnitsComboBox; - } - - private ZapNumberSpinner getPollFrequencySpinner() { - if (pollFrequency == null) { - pollFrequency = - new ZapNumberSpinner( - 1, AuthenticationMethod.DEFAULT_POLL_FREQUENCY, Integer.MAX_VALUE); - // Reduce the field size otherwise it takes up too much space - Component mySpinnerEditor = pollFrequency.getEditor(); - JFormattedTextField jftf = ((JSpinner.DefaultEditor) mySpinnerEditor).getTextField(); - jftf.setColumns(6); - } - return pollFrequency; - } - - private AuthenticationIndicatorsPanel getAuthenticationIndicatorsPanel() { - if (authenticationIndicatorsPanel == null) { - authenticationIndicatorsPanel = new AuthenticationIndicatorsPanelImpl(); - } - return authenticationIndicatorsPanel; - } - /** * Make sure the user acknowledges the Users corresponding to this context will have the * credentials changed with the new type of authentication method. @@ -479,120 +320,6 @@ private JPanel getConfigContainerPanel() { return configContainerPanel; } - private JPanel getVerifContainerPanel() { - if (verifContainerPanel == null) { - verifContainerPanel = new JPanel(new GridBagLayout()); - verifContainerPanel.setBorder( - javax.swing.BorderFactory.createTitledBorder( - null, - PANEL_TITLE_VERIF, - javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION, - javax.swing.border.TitledBorder.DEFAULT_POSITION, - FontUtils.getFont(FontUtils.Size.standard))); - } - return verifContainerPanel; - } - - private JButton getPollUrlSelectButton() { - if (pollUrlSelectButton == null) { - pollUrlSelectButton = new JButton(Constant.messages.getString("all.button.select")); - pollUrlSelectButton.setIcon( - new ImageIcon( - View.class.getResource("/resource/icon/16/094.png"))); // Globe Icon - // Add behaviour for Node Select dialog - pollUrlSelectButton.addActionListener( - new java.awt.event.ActionListener() { - @Override - public void actionPerformed(java.awt.event.ActionEvent e) { - NodeSelectDialog nsd = - new NodeSelectDialog(View.getSingleton().getMainFrame()); - // Try to pre-select the node according to what has been inserted in the - // fields - SiteNode node = null; - if (getPollUrlField().getText().trim().length() > 0) - try { - // If it's a POST query - if (getPollDataField().getText().trim().length() > 0) - node = - Model.getSingleton() - .getSession() - .getSiteTree() - .findNode( - new URI( - getPollUrlField().getText(), - false), - HttpRequestHeader.POST, - getPollDataField().getText()); - else - node = - Model.getSingleton() - .getSession() - .getSiteTree() - .findNode( - new URI( - getPollUrlField().getText(), - false)); - } catch (Exception e2) { - // Ignore. It means we could not properly get a node for the - // existing - // value and does not have any harmful effects - } - - // Show the dialog and wait for input - node = nsd.showDialog(node); - if (node != null && node.getHistoryReference() != null) { - try { - - getPollUrlField() - .setText( - node.getHistoryReference().getURI().toString()); - getPollDataField() - .setText( - node.getHistoryReference() - .getHttpMessage() - .getRequestBody() - .toString()); - } catch (Exception e1) { - LOGGER.error(e1.getMessage(), e1); - } - } - } - }); - } - return pollUrlSelectButton; - } - - private ZapTextField getPollUrlField() { - if (pollUrlField == null) { - pollUrlField = new ZapTextField(); - } - return pollUrlField; - } - - private ZapTextField getPollDataField() { - if (pollDataField == null) { - pollDataField = new ZapTextField(); - } - return pollDataField; - } - - private ZapTextArea getPollHeadersField() { - if (pollHeadersField == null) { - pollHeadersField = new ZapTextArea(2, 0); - } - return pollHeadersField; - } - - private ZapTextField getLoggedInIndicatorRegexField() { - if (loggedInIndicatorRegexField == null) loggedInIndicatorRegexField = new ZapTextField(); - return loggedInIndicatorRegexField; - } - - private ZapTextField getLoggedOutIndicatorRegexField() { - if (loggedOutIndicatorRegexField == null) loggedOutIndicatorRegexField = new ZapTextField(); - return loggedOutIndicatorRegexField; - } - @Override public String getHelpIndex() { return "ui.dialogs.context-auth"; @@ -606,45 +333,7 @@ public void initContextData(Session session, Context uiSharedContext) { selectedAuthenticationMethod, uiSharedContext.getName()); - resetLoggedInOutIndicators(); - - // If something was already configured, find the type and set the UI accordingly if (selectedAuthenticationMethod != null) { - // Set verification - if (selectedAuthenticationMethod.getAuthCheckingStrategy() != null) { - getAuthenticationVerifComboBox() - .getModel() - .setSelectedItem( - new AuthCheckingStrategyType( - selectedAuthenticationMethod.getAuthCheckingStrategy())); - } - setPollFieldStatuses( - (AuthCheckingStrategyType) getAuthenticationVerifComboBox().getSelectedItem()); - - getPollUrlField().setText(selectedAuthenticationMethod.getPollUrl()); - getPollDataField().setText(selectedAuthenticationMethod.getPollData()); - getPollHeadersField().setText(selectedAuthenticationMethod.getPollHeaders()); - getPollFrequencySpinner().setValue(selectedAuthenticationMethod.getPollFrequency()); - getAuthFrequencyUnitsComboBox() - .setSelectedItem( - new AuthPollFrequencyUnitsType( - selectedAuthenticationMethod.getPollFrequencyUnits())); - - if (selectedAuthenticationMethod.getLoggedInIndicatorPattern() != null) - getLoggedInIndicatorRegexField() - .setText( - selectedAuthenticationMethod - .getLoggedInIndicatorPattern() - .pattern()); - else getLoggedInIndicatorRegexField().setText(""); - if (selectedAuthenticationMethod.getLoggedOutIndicatorPattern() != null) - getLoggedOutIndicatorRegexField() - .setText( - selectedAuthenticationMethod - .getLoggedOutIndicatorPattern() - .pattern()); - else getLoggedOutIndicatorRegexField().setText(""); - // If the proper type is already selected, just rebind the data if (shownMethodType != null && shownMethodType.isTypeForMethod(selectedAuthenticationMethod)) { @@ -652,8 +341,7 @@ public void initContextData(Session session, Context uiSharedContext) { LOGGER.debug( "Binding authentication method to existing panel of proper type for context {}", uiSharedContext.getName()); - shownConfigPanel.bindMethod( - selectedAuthenticationMethod, getAuthenticationIndicatorsPanel()); + shownConfigPanel.bindMethod(selectedAuthenticationMethod, verificationPanel); } return; } @@ -661,13 +349,9 @@ public void initContextData(Session session, Context uiSharedContext) { // Select what needs to be selected for (AuthenticationMethodType type : extension.getAuthenticationMethodTypes()) if (type.isTypeForMethod(selectedAuthenticationMethod)) { - // Selecting the type here will also force the selection listener to run and - // change the config panel accordingly LOGGER.debug( "Binding authentication method to new panel of proper type for context {}", uiSharedContext.getName()); - // Add hack to make sure no confirmation is needed if a change has been done - // somewhere else (e.g. API) needsConfirm = false; getAuthenticationMethodsComboBox().setSelectedItem(type); needsConfirm = true; @@ -676,84 +360,13 @@ public void initContextData(Session session, Context uiSharedContext) { } } - /** - * Resets the tool tip and enables the fields of the logged in/out indicators. - * - * @see #getLoggedInIndicatorRegexField() - * @see #getLoggedOutIndicatorRegexField() - */ - private void resetLoggedInOutIndicators() { - getLoggedInIndicatorRegexField().setToolTipText(null); - getLoggedInIndicatorRegexField().setEnabled(true); - getLoggedOutIndicatorRegexField().setToolTipText(null); - getLoggedOutIndicatorRegexField().setEnabled(true); - } - @Override public void validateContextData(Session session) throws Exception { if (shownConfigPanel != null) shownConfigPanel.validateFields(); - try { - Pattern.compile(getLoggedInIndicatorRegexField().getText()); - Pattern.compile(getLoggedOutIndicatorRegexField().getText()); - } catch (PatternSyntaxException e) { - throw new IllegalStateException( - Constant.messages.getString( - "authentication.panel.error.illegalPattern", - getUISharedContext().getName()), - e); - } - if (((AuthCheckingStrategyType) getAuthenticationVerifComboBox().getSelectedItem()) - .getStrategy() - .equals(AuthCheckingStrategy.POLL_URL)) { - String url = this.getPollUrlField().getText(); - if (url.length() == 0) { - throw new IllegalStateException( - Constant.messages.getString( - "authentication.panel.error.nopollurl", - getUISharedContext().getName())); - } else { - try { - new URI(url, true); - } catch (Exception e) { - throw new IllegalStateException( - Constant.messages.getString( - "authentication.panel.error.badpollurl", - getUISharedContext().getName()), - e); - } - } - for (String header : this.getPollHeadersField().getText().split("\n")) { - if (header.trim().length() > 0) { - String[] headerValue = header.split(":", 2); - if (headerValue.length != 2) { - throw new IllegalStateException( - Constant.messages.getString( - "authentication.panel.error.badpollheaders", - getUISharedContext().getName())); - } - } - } - } } private void saveMethod() { if (shownConfigPanel != null) shownConfigPanel.saveMethod(); - - selectedAuthenticationMethod.setAuthCheckingStrategy( - ((AuthCheckingStrategyType) getAuthenticationVerifComboBox().getSelectedItem()) - .getStrategy()); - selectedAuthenticationMethod.setPollUrl(this.getPollUrlField().getText()); - selectedAuthenticationMethod.setPollData(this.getPollDataField().getText()); - selectedAuthenticationMethod.setPollHeaders(this.getPollHeadersField().getText()); - selectedAuthenticationMethod.setPollFrequency(this.getPollFrequencySpinner().getValue()); - selectedAuthenticationMethod.setPollFrequencyUnits( - ((AuthPollFrequencyUnitsType) - this.getAuthFrequencyUnitsComboBox().getSelectedItem()) - .getUnits()); - selectedAuthenticationMethod.setLoggedInIndicatorPattern( - getLoggedInIndicatorRegexField().getText()); - selectedAuthenticationMethod.setLoggedOutIndicatorPattern( - getLoggedOutIndicatorRegexField().getText()); } @Override @@ -761,145 +374,25 @@ public void saveContextData(Session session) throws Exception { saveMethod(); Context context = session.getContext(getContextId()); - // Notify the previously saved method that it's being discarded so the changes can be - // reflected in the UI if (context.getAuthenticationMethod() != null) if (!shownMethodType.isTypeForMethod(context.getAuthenticationMethod())) context.getAuthenticationMethod().onMethodDiscarded(); context.setAuthenticationMethod(selectedAuthenticationMethod); - // Notify the newly saved method that it's being persisted so the changes can be - // reflected in the UI selectedAuthenticationMethod.onMethodPersisted(); } @Override public void saveTemporaryContextData(Context uiSharedContext) { saveMethod(); - uiSharedContext.setAuthenticationMethod(selectedAuthenticationMethod); - } - - private class AuthenticationIndicatorsPanelImpl implements AuthenticationIndicatorsPanel { - - @Override - public String getLoggedInIndicatorPattern() { - return getLoggedInIndicatorRegexField().getText(); - } - - @Override - public void setLoggedInIndicatorPattern(String loggedInIndicatorPattern) { - getLoggedInIndicatorRegexField().setText(loggedInIndicatorPattern); - } - - @Override - public void setLoggedInIndicatorEnabled(boolean enabled) { - getLoggedInIndicatorRegexField().setEnabled(enabled); - } - - @Override - public void setLoggedInIndicatorToolTip(String toolTip) { - getLoggedInIndicatorRegexField().setToolTipText(toolTip); - } - - @Override - public String getLoggedOutIndicatorPattern() { - return getLoggedOutIndicatorRegexField().getText(); - } - - @Override - public void setLoggedOutIndicatorPattern(String loggedOutIndicatorPattern) { - getLoggedOutIndicatorRegexField().setText(loggedOutIndicatorPattern); - } - - @Override - public void setLoggedOutIndicatorEnabled(boolean enabled) { - getLoggedOutIndicatorRegexField().setEnabled(enabled); - } - @Override - public void setLoggedOutIndicatorToolTip(String toolTip) { - getLoggedOutIndicatorRegexField().setToolTipText(toolTip); - } - } - - private static class AuthCheckingStrategyType { - private AuthCheckingStrategy strategy; - - public AuthCheckingStrategyType(AuthCheckingStrategy strategy) { - super(); - this.strategy = strategy; - } - - public AuthCheckingStrategy getStrategy() { - return strategy; - } - - @Override - public String toString() { - return Constant.messages.getString(STRATEGY_PREFIX + strategy.name().toLowerCase()); - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof AuthCheckingStrategyType)) { - return false; - } - return this.strategy.equals(((AuthCheckingStrategyType) o).getStrategy()); - } - - @Override - public int hashCode() { - return this.strategy.hashCode(); - } - - public static List getAllValues() { - List list = new ArrayList<>(); - for (AuthenticationMethod.AuthCheckingStrategy strategy : - AuthenticationMethod.AuthCheckingStrategy.values()) { - list.add(new AuthCheckingStrategyType(strategy)); - } - return list; - } - } - - private static class AuthPollFrequencyUnitsType { - private AuthPollFrequencyUnits units; - - public AuthPollFrequencyUnitsType(AuthPollFrequencyUnits units) { - super(); - this.units = units; - } - - public AuthPollFrequencyUnits getUnits() { - return units; - } - - @Override - public String toString() { - return Constant.messages.getString(FREQUENCY_UNITS_PREFIX + units.name().toLowerCase()); - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof AuthPollFrequencyUnitsType)) { - return false; - } - return this.units.equals(((AuthPollFrequencyUnitsType) o).getUnits()); - } - - @Override - public int hashCode() { - return this.units.hashCode(); - } - - public static List getAllValues() { - List list = new ArrayList<>(); - for (AuthenticationMethod.AuthPollFrequencyUnits strategy : - AuthenticationMethod.AuthPollFrequencyUnits.values()) { - list.add(new AuthPollFrequencyUnitsType(strategy)); - } - return list; + // Preserve the current verification method — it is owned by the verification panel. + // setAuthenticationMethod would replace the VM reference, so we restore it afterwards. + VerificationMethod currentVm = uiSharedContext.getVerificationMethod(); + uiSharedContext.setAuthenticationMethod(selectedAuthenticationMethod); + if (currentVm != null) { + uiSharedContext.setVerificationMethod(currentVm); } } } diff --git a/zap/src/main/java/org/zaproxy/zap/extension/authentication/ContextVerificationPanel.java b/zap/src/main/java/org/zaproxy/zap/extension/authentication/ContextVerificationPanel.java new file mode 100644 index 00000000000..e873816e16d --- /dev/null +++ b/zap/src/main/java/org/zaproxy/zap/extension/authentication/ContextVerificationPanel.java @@ -0,0 +1,565 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2016 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.zap.extension.authentication; + +import java.awt.CardLayout; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; +import java.util.stream.Stream; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JFormattedTextField; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSpinner; +import javax.swing.border.EmptyBorder; +import org.apache.commons.httpclient.URI; +import org.parosproxy.paros.Constant; +import org.parosproxy.paros.model.Model; +import org.parosproxy.paros.model.Session; +import org.parosproxy.paros.model.SiteNode; +import org.parosproxy.paros.network.HttpRequestHeader; +import org.parosproxy.paros.view.View; +import org.zaproxy.zap.authentication.AuthenticationIndicatorsPanel; +import org.zaproxy.zap.authentication.AuthenticationMethod; +import org.zaproxy.zap.authentication.AuthenticationMethod.AuthCheckingStrategy; +import org.zaproxy.zap.authentication.AuthenticationMethod.AuthPollFrequencyUnits; +import org.zaproxy.zap.authentication.VerificationMethod; +import org.zaproxy.zap.model.Context; +import org.zaproxy.zap.utils.ZapHtmlLabel; +import org.zaproxy.zap.utils.ZapNumberSpinner; +import org.zaproxy.zap.utils.ZapTextArea; +import org.zaproxy.zap.utils.ZapTextField; +import org.zaproxy.zap.view.AbstractContextPropertiesPanel; +import org.zaproxy.zap.view.LayoutHelper; +import org.zaproxy.zap.view.NodeSelectDialog; + +/** + * The Context Panel shown for configuring a Context's verification method. + * + * @since 2.18.0 + */ +@SuppressWarnings("serial") +public class ContextVerificationPanel extends AbstractContextPropertiesPanel + implements AuthenticationIndicatorsPanel { + + private static final long serialVersionUID = 1L; + + static final String PANEL_NAME = Constant.messages.getString("verification.panel.title"); + + private static final String LABEL_DESCRIPTION = + Constant.messages.getString("verification.panel.label.description"); + + private static final String FIELD_LABEL_LOGGED_IN_INDICATOR = + Constant.messages.getString("authentication.panel.label.loggedIn"); + private static final String FIELD_LABEL_LOGGED_OUT_INDICATOR = + Constant.messages.getString("authentication.panel.label.loggedOut"); + private static final String LABEL_POLL_METHOD = + Constant.messages.getString("authentication.panel.label.pollmethod"); + private static final String LABEL_POLL_URL = + Constant.messages.getString("authentication.panel.label.pollurl"); + private static final String LABEL_POLL_DATA = + Constant.messages.getString("authentication.panel.label.polldata"); + private static final String LABEL_POLL_HEADERS = + Constant.messages.getString("authentication.panel.label.pollheaders"); + private static final String LABEL_POLL_FREQUENCY = + Constant.messages.getString("authentication.panel.label.freq"); + private static final String LABEL_STRATEGY = + Constant.messages.getString("authentication.panel.label.strategy"); + private static final String STRATEGY_PREFIX = "authentication.panel.label.strategy."; + private static final String FREQUENCY_UNITS_PREFIX = "authentication.panel.label.units."; + + private JComboBox authenticationVerifComboBox; + private JComboBox authFrequencyUnitsComboBox; + + private JButton pollUrlSelectButton = null; + private JComboBox pollMethodComboBox; + private ZapTextField pollUrlField = null; + private ZapTextField pollDataField = null; + private ZapTextArea pollHeadersField = null; + private ZapNumberSpinner pollFrequency = null; + + private ZapTextField loggedInIndicatorRegexField = null; + private ZapTextField loggedOutIndicatorRegexField = null; + + public ContextVerificationPanel(Context context) { + super(context.getId()); + initialize(); + } + + public static String buildName(int contextId) { + return contextId + ": " + PANEL_NAME; + } + + private void initialize() { + this.setLayout(new CardLayout()); + this.setName(buildName(getContextId())); + this.setBorder(new EmptyBorder(2, 2, 2, 2)); + + JPanel panel = new JPanel(); + panel.setLayout(new GridBagLayout()); + panel.setPreferredSize(new Dimension(400, 500)); + + JScrollPane scrollPanel = new JScrollPane(); + scrollPanel.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + scrollPanel.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); + scrollPanel.setViewportView(panel); + this.add(scrollPanel); + + int y = 0; + int fullWidth = 3; + panel.add( + new ZapHtmlLabel(LABEL_DESCRIPTION), LayoutHelper.getGBC(0, y++, fullWidth, 1.0D)); + panel.add( + new JLabel(LABEL_STRATEGY), + LayoutHelper.getGBC(0, y++, fullWidth, 1.0D, new Insets(20, 0, 5, 5))); + panel.add(getAuthenticationVerifComboBox(), LayoutHelper.getGBC(0, y++, fullWidth, 1.0D)); + + panel.add( + new JLabel(FIELD_LABEL_LOGGED_IN_INDICATOR), + LayoutHelper.getGBC(0, y++, fullWidth, 1.0D)); + panel.add(getLoggedInIndicatorRegexField(), LayoutHelper.getGBC(0, y++, fullWidth, 1.0D)); + panel.add( + new JLabel(FIELD_LABEL_LOGGED_OUT_INDICATOR), + LayoutHelper.getGBC(0, y++, fullWidth, 1.0D)); + panel.add(getLoggedOutIndicatorRegexField(), LayoutHelper.getGBC(0, y++, fullWidth, 1.0D)); + + panel.add(new JLabel(LABEL_POLL_FREQUENCY), LayoutHelper.getGBC(0, y, 1, 0.34D)); + panel.add(getPollFrequencySpinner(), LayoutHelper.getGBC(1, y, 1, 0.33D)); + panel.add(getAuthFrequencyUnitsComboBox(), LayoutHelper.getGBC(2, y++, 1, 0.33D)); + + panel.add(new JLabel(LABEL_POLL_METHOD), LayoutHelper.getGBC(0, y, 1, 0.25D)); + panel.add(getPollMethodComboBox(), LayoutHelper.getGBC(1, y++, 2, 0.75D)); + panel.add(new JLabel(LABEL_POLL_URL), LayoutHelper.getGBC(0, y++, fullWidth, 1.0D)); + JPanel urlPanel = new JPanel(new GridBagLayout()); + urlPanel.add(this.getPollUrlField(), LayoutHelper.getGBC(0, 0, 1, 1.0D)); + urlPanel.add(getPollUrlSelectButton(), LayoutHelper.getGBC(1, 0, 1, 0.0D)); + panel.add(urlPanel, LayoutHelper.getGBC(0, y++, fullWidth, 1.0D)); + panel.add(new JLabel(LABEL_POLL_DATA), LayoutHelper.getGBC(0, y++, fullWidth, 1.0D)); + panel.add(this.getPollDataField(), LayoutHelper.getGBC(0, y++, fullWidth, 1.0D)); + panel.add(new JLabel(LABEL_POLL_HEADERS), LayoutHelper.getGBC(0, y++, fullWidth, 1.0D)); + panel.add(this.getPollHeadersField(), LayoutHelper.getGBC(0, y++, fullWidth, 1.0D)); + + // Padding + panel.add(new JLabel(), LayoutHelper.getGBC(0, 99, 1, 1.0D, 1.0D)); + } + + private JComboBox getAuthenticationVerifComboBox() { + if (authenticationVerifComboBox == null) { + authenticationVerifComboBox = new JComboBox<>(); + for (AuthCheckingStrategyType acst : AuthCheckingStrategyType.getAllValues()) { + authenticationVerifComboBox.addItem(acst); + } + authenticationVerifComboBox.addItemListener( + new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { + setPollFieldStatuses((AuthCheckingStrategyType) e.getItem()); + } + } + }); + } + return authenticationVerifComboBox; + } + + void setPollFieldStatuses(AuthCheckingStrategyType type) { + boolean isPoll = type.getStrategy().equals(AuthCheckingStrategy.POLL_URL); + getAuthFrequencyUnitsComboBox().setEnabled(isPoll); + getPollFrequencySpinner().setEnabled(isPoll); + getPollUrlSelectButton().setEnabled(isPoll); + getPollMethodComboBox().setEnabled(isPoll); + getPollUrlField().setEnabled(isPoll); + getPollDataField().setEnabled(isPoll); + getPollHeadersField().setEnabled(isPoll); + boolean isAutoDetect = type.getStrategy().equals(AuthCheckingStrategy.AUTO_DETECT); + getLoggedInIndicatorRegexField().setEnabled(!isAutoDetect); + getLoggedOutIndicatorRegexField().setEnabled(!isAutoDetect); + } + + private JComboBox getAuthFrequencyUnitsComboBox() { + if (authFrequencyUnitsComboBox == null) { + authFrequencyUnitsComboBox = new JComboBox<>(); + for (AuthPollFrequencyUnitsType acst : AuthPollFrequencyUnitsType.getAllValues()) { + authFrequencyUnitsComboBox.addItem(acst); + } + } + return authFrequencyUnitsComboBox; + } + + private ZapNumberSpinner getPollFrequencySpinner() { + if (pollFrequency == null) { + pollFrequency = + new ZapNumberSpinner( + 1, VerificationMethod.DEFAULT_POLL_FREQUENCY, Integer.MAX_VALUE); + Component mySpinnerEditor = pollFrequency.getEditor(); + JFormattedTextField jftf = ((JSpinner.DefaultEditor) mySpinnerEditor).getTextField(); + jftf.setColumns(6); + } + return pollFrequency; + } + + private JButton getPollUrlSelectButton() { + if (pollUrlSelectButton == null) { + pollUrlSelectButton = new JButton(Constant.messages.getString("all.button.select")); + pollUrlSelectButton.setIcon( + new ImageIcon( + View.class.getResource("/resource/icon/16/094.png"))); // Globe Icon + pollUrlSelectButton.addActionListener( + new java.awt.event.ActionListener() { + @Override + public void actionPerformed(java.awt.event.ActionEvent e) { + NodeSelectDialog nsd = + new NodeSelectDialog(View.getSingleton().getMainFrame()); + SiteNode node = null; + if (getPollUrlField().getText().trim().length() > 0) + try { + if (getPollDataField().getText().trim().length() > 0) + node = + Model.getSingleton() + .getSession() + .getSiteTree() + .findNode( + new URI( + getPollUrlField().getText(), + false), + HttpRequestHeader.POST, + getPollDataField().getText()); + else + node = + Model.getSingleton() + .getSession() + .getSiteTree() + .findNode( + new URI( + getPollUrlField().getText(), + false)); + } catch (Exception e2) { + // Could not find node for existing value, no harm done + } + + node = nsd.showDialog(node); + if (node != null && node.getHistoryReference() != null) { + try { + getPollUrlField() + .setText( + node.getHistoryReference().getURI().toString()); + getPollDataField() + .setText( + node.getHistoryReference() + .getHttpMessage() + .getRequestBody() + .toString()); + } catch (Exception e1) { + // Ignore + } + } + } + }); + } + return pollUrlSelectButton; + } + + private JComboBox getPollMethodComboBox() { + if (pollMethodComboBox == null) { + pollMethodComboBox = new JComboBox<>(); + pollMethodComboBox.setEditable(true); + Stream.of(HttpRequestHeader.GET, HttpRequestHeader.POST) + .sorted() + .forEach(pollMethodComboBox::addItem); + } + return pollMethodComboBox; + } + + private ZapTextField getPollUrlField() { + if (pollUrlField == null) { + pollUrlField = new ZapTextField(); + } + return pollUrlField; + } + + private ZapTextField getPollDataField() { + if (pollDataField == null) { + pollDataField = new ZapTextField(); + } + return pollDataField; + } + + private ZapTextArea getPollHeadersField() { + if (pollHeadersField == null) { + pollHeadersField = new ZapTextArea(2, 0); + } + return pollHeadersField; + } + + private ZapTextField getLoggedInIndicatorRegexField() { + if (loggedInIndicatorRegexField == null) loggedInIndicatorRegexField = new ZapTextField(); + return loggedInIndicatorRegexField; + } + + private ZapTextField getLoggedOutIndicatorRegexField() { + if (loggedOutIndicatorRegexField == null) loggedOutIndicatorRegexField = new ZapTextField(); + return loggedOutIndicatorRegexField; + } + + @Override + public String getLoggedInIndicatorPattern() { + return getLoggedInIndicatorRegexField().getText(); + } + + @Override + public void setLoggedInIndicatorPattern(String loggedInIndicatorPattern) { + getLoggedInIndicatorRegexField().setText(loggedInIndicatorPattern); + } + + @Override + public void setLoggedInIndicatorEnabled(boolean enabled) { + getLoggedInIndicatorRegexField().setEnabled(enabled); + } + + @Override + public void setLoggedInIndicatorToolTip(String toolTip) { + getLoggedInIndicatorRegexField().setToolTipText(toolTip); + } + + @Override + public String getLoggedOutIndicatorPattern() { + return getLoggedOutIndicatorRegexField().getText(); + } + + @Override + public void setLoggedOutIndicatorPattern(String loggedOutIndicatorPattern) { + getLoggedOutIndicatorRegexField().setText(loggedOutIndicatorPattern); + } + + @Override + public void setLoggedOutIndicatorEnabled(boolean enabled) { + getLoggedOutIndicatorRegexField().setEnabled(enabled); + } + + @Override + public void setLoggedOutIndicatorToolTip(String toolTip) { + getLoggedOutIndicatorRegexField().setToolTipText(toolTip); + } + + /** Builds a VerificationMethod from the current UI state. */ + VerificationMethod buildVerificationMethodFromUI() { + VerificationMethod vm = new VerificationMethod(); + vm.setAuthCheckingStrategy( + ((AuthCheckingStrategyType) getAuthenticationVerifComboBox().getSelectedItem()) + .getStrategy()); + vm.setPollMethod((String) getPollMethodComboBox().getSelectedItem()); + vm.setPollUrl(getPollUrlField().getText()); + vm.setPollData(getPollDataField().getText()); + vm.setPollHeaders(getPollHeadersField().getText()); + vm.setPollFrequency(getPollFrequencySpinner().getValue()); + vm.setPollFrequencyUnits( + ((AuthPollFrequencyUnitsType) getAuthFrequencyUnitsComboBox().getSelectedItem()) + .getUnits()); + vm.setLoggedInIndicatorPattern(getLoggedInIndicatorRegexField().getText()); + vm.setLoggedOutIndicatorPattern(getLoggedOutIndicatorRegexField().getText()); + return vm; + } + + private void populateUIFromVm(VerificationMethod vm) { + if (vm.getAuthCheckingStrategy() != null) { + getAuthenticationVerifComboBox() + .getModel() + .setSelectedItem(new AuthCheckingStrategyType(vm.getAuthCheckingStrategy())); + } + setPollFieldStatuses( + (AuthCheckingStrategyType) getAuthenticationVerifComboBox().getSelectedItem()); + + String pollMethod = vm.getPollMethod(); + getPollMethodComboBox().setSelectedItem(pollMethod != null ? pollMethod : ""); + getPollUrlField().setText(vm.getPollUrl()); + getPollDataField().setText(vm.getPollData()); + getPollHeadersField().setText(vm.getPollHeaders()); + getPollFrequencySpinner().setValue(vm.getPollFrequency()); + getAuthFrequencyUnitsComboBox() + .setSelectedItem(new AuthPollFrequencyUnitsType(vm.getPollFrequencyUnits())); + + if (vm.getLoggedInIndicatorPattern() != null) + getLoggedInIndicatorRegexField().setText(vm.getLoggedInIndicatorPattern().pattern()); + else getLoggedInIndicatorRegexField().setText(""); + if (vm.getLoggedOutIndicatorPattern() != null) + getLoggedOutIndicatorRegexField().setText(vm.getLoggedOutIndicatorPattern().pattern()); + else getLoggedOutIndicatorRegexField().setText(""); + } + + @Override + public String getHelpIndex() { + return "ui.dialogs.context-auth"; + } + + @Override + public void initContextData(Session session, Context uiSharedContext) { + VerificationMethod vm = uiSharedContext.getVerificationMethod(); + if (vm != null) { + populateUIFromVm(vm); + } + } + + @Override + public void validateContextData(Session session) throws Exception { + try { + Pattern.compile(getLoggedInIndicatorRegexField().getText()); + Pattern.compile(getLoggedOutIndicatorRegexField().getText()); + } catch (PatternSyntaxException e) { + throw new IllegalStateException( + Constant.messages.getString( + "authentication.panel.error.illegalPattern", + getUISharedContext().getName()), + e); + } + if (((AuthCheckingStrategyType) getAuthenticationVerifComboBox().getSelectedItem()) + .getStrategy() + .equals(AuthCheckingStrategy.POLL_URL)) { + String url = getPollUrlField().getText(); + if (url.length() == 0) { + throw new IllegalStateException( + Constant.messages.getString( + "authentication.panel.error.nopollurl", + getUISharedContext().getName())); + } else { + try { + new URI(url, true); + } catch (Exception e) { + throw new IllegalStateException( + Constant.messages.getString( + "authentication.panel.error.badpollurl", + getUISharedContext().getName()), + e); + } + } + for (String header : getPollHeadersField().getText().split("\n")) { + if (header.trim().length() > 0) { + String[] headerValue = header.split(":", 2); + if (headerValue.length != 2) { + throw new IllegalStateException( + Constant.messages.getString( + "authentication.panel.error.badpollheaders", + getUISharedContext().getName())); + } + } + } + } + } + + @Override + public void saveTemporaryContextData(Context uiSharedContext) { + VerificationMethod vm = buildVerificationMethodFromUI(); + uiSharedContext.setVerificationMethod(vm); + } + + @Override + public void saveContextData(Session session) throws Exception { + VerificationMethod vm = buildVerificationMethodFromUI(); + session.getContext(getContextId()).setVerificationMethod(vm); + } + + static class AuthCheckingStrategyType { + private AuthCheckingStrategy strategy; + + public AuthCheckingStrategyType(AuthCheckingStrategy strategy) { + this.strategy = strategy; + } + + public AuthCheckingStrategy getStrategy() { + return strategy; + } + + @Override + public String toString() { + return Constant.messages.getString(STRATEGY_PREFIX + strategy.name().toLowerCase()); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof AuthCheckingStrategyType)) { + return false; + } + return this.strategy.equals(((AuthCheckingStrategyType) o).getStrategy()); + } + + @Override + public int hashCode() { + return this.strategy.hashCode(); + } + + public static List getAllValues() { + List list = new ArrayList<>(); + for (AuthenticationMethod.AuthCheckingStrategy strategy : + AuthenticationMethod.AuthCheckingStrategy.values()) { + list.add(new AuthCheckingStrategyType(strategy)); + } + return list; + } + } + + static class AuthPollFrequencyUnitsType { + private AuthPollFrequencyUnits units; + + public AuthPollFrequencyUnitsType(AuthPollFrequencyUnits units) { + this.units = units; + } + + public AuthPollFrequencyUnits getUnits() { + return units; + } + + @Override + public String toString() { + return Constant.messages.getString(FREQUENCY_UNITS_PREFIX + units.name().toLowerCase()); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof AuthPollFrequencyUnitsType)) { + return false; + } + return this.units.equals(((AuthPollFrequencyUnitsType) o).getUnits()); + } + + @Override + public int hashCode() { + return this.units.hashCode(); + } + + public static List getAllValues() { + List list = new ArrayList<>(); + for (AuthenticationMethod.AuthPollFrequencyUnits strategy : + AuthenticationMethod.AuthPollFrequencyUnits.values()) { + list.add(new AuthPollFrequencyUnitsType(strategy)); + } + return list; + } + } +} diff --git a/zap/src/main/java/org/zaproxy/zap/extension/authentication/ExtensionAuthentication.java b/zap/src/main/java/org/zaproxy/zap/extension/authentication/ExtensionAuthentication.java index c6d11d458f9..5fe4c61b0d4 100644 --- a/zap/src/main/java/org/zaproxy/zap/extension/authentication/ExtensionAuthentication.java +++ b/zap/src/main/java/org/zaproxy/zap/extension/authentication/ExtensionAuthentication.java @@ -47,6 +47,7 @@ import org.zaproxy.zap.authentication.JsonBasedAuthenticationMethodType; import org.zaproxy.zap.authentication.ManualAuthenticationMethodType; import org.zaproxy.zap.authentication.ScriptBasedAuthenticationMethodType; +import org.zaproxy.zap.authentication.VerificationMethod; import org.zaproxy.zap.extension.stdmenus.PopupContextMenuItemFactory; import org.zaproxy.zap.model.Context; import org.zaproxy.zap.model.ContextDataFactory; @@ -74,6 +75,8 @@ public class ExtensionAuthentication extends ExtensionAdaptor /** The context panels map. */ private Map contextPanelsMap = new HashMap<>(); + private Map verificationPanelsMap = new HashMap<>(); + private PopupContextMenuItemFactory popupFlagLoggedInIndicatorMenuFactory; private PopupContextMenuItemFactory popupFlagLoggedOutIndicatorMenuFactory; @@ -113,8 +116,29 @@ public void hook(ExtensionHook extensionHook) { extensionHook.getHookMenu().addPopupMenuItem(getPopupFlagLoggedInIndicatorMenu()); extensionHook.getHookMenu().addPopupMenuItem(getPopupFlagLoggedOutIndicatorMenu()); - // Factory for generating Session Context UserAuth panels + // Factory for generating Session Context Authentication panels extensionHook.getHookView().addContextPanelFactory(this); + // Factory for generating Session Context Verification panels + extensionHook + .getHookView() + .addContextPanelFactory( + new ContextPanelFactory() { + @Override + public AbstractContextPropertiesPanel getContextPanel( + Context context) { + return getVerificationPanel(context); + } + + @Override + public void discardContext(Context ctx) { + verificationPanelsMap.remove(ctx.getId()); + } + + @Override + public void discardContexts() { + verificationPanelsMap.clear(); + } + }); } // Load the Authentication and Session Management methods @@ -123,6 +147,7 @@ public void hook(ExtensionHook extensionHook) { // Register the api this.api = new AuthenticationAPI(this); extensionHook.addApiImplementor(api); + extensionHook.addApiImplementor(new VerificationAPI()); extensionHook.addHttpSenderListener(getHttpSenderAuthHeaderListener()); } @@ -131,12 +156,22 @@ public void hook(ExtensionHook extensionHook) { public AbstractContextPropertiesPanel getContextPanel(Context context) { ContextAuthenticationPanel panel = this.contextPanelsMap.get(context.getId()); if (panel == null) { - panel = new ContextAuthenticationPanel(this, context); + ContextVerificationPanel verifPanel = getVerificationPanel(context); + panel = new ContextAuthenticationPanel(this, context, verifPanel); this.contextPanelsMap.put(context.getId(), panel); } return panel; } + private ContextVerificationPanel getVerificationPanel(Context context) { + ContextVerificationPanel panel = this.verificationPanelsMap.get(context.getId()); + if (panel == null) { + panel = new ContextVerificationPanel(context); + this.verificationPanelsMap.put(context.getId(), panel); + } + return panel; + } + @Override public String getAuthor() { return Constant.ZAP_TEAM; @@ -260,71 +295,61 @@ public void loadContextData(Session session, Context context) { context.setAuthenticationMethod( t.loadMethodFromSession(session, context.getId())); + VerificationMethod vm = context.getVerificationMethod(); + String strategy = session.getContextDataString( context.getId(), RecordContext.TYPE_AUTH_VERIF_STRATEGY, null); if (strategy != null) { try { - context.getAuthenticationMethod() - .setAuthCheckingStrategy( - AuthCheckingStrategy.valueOf(strategy)); + vm.setAuthCheckingStrategy(AuthCheckingStrategy.valueOf(strategy)); } catch (Exception e) { LOGGER.error("Failed to parse auth checking strategy {}", strategy, e); } } - context.getAuthenticationMethod() - .setPollUrl( - session.getContextDataString( - context.getId(), - RecordContext.TYPE_AUTH_POLL_URL, - null)); - - context.getAuthenticationMethod() - .setPollData( - session.getContextDataString( - context.getId(), - RecordContext.TYPE_AUTH_POLL_DATA, - null)); - - context.getAuthenticationMethod() - .setPollHeaders( - session.getContextDataString( - context.getId(), - RecordContext.TYPE_AUTH_POLL_HEADERS, - null)); - - context.getAuthenticationMethod() - .setPollFrequency( - session.getContextDataInteger( - context.getId(), RecordContext.TYPE_AUTH_POLL_FREQ, 0)); + vm.setPollUrl( + session.getContextDataString( + context.getId(), RecordContext.TYPE_AUTH_POLL_URL, null)); + + vm.setPollData( + session.getContextDataString( + context.getId(), RecordContext.TYPE_AUTH_POLL_DATA, null)); + + vm.setPollHeaders( + session.getContextDataString( + context.getId(), RecordContext.TYPE_AUTH_POLL_HEADERS, null)); + + vm.setPollFrequency( + session.getContextDataInteger( + context.getId(), RecordContext.TYPE_AUTH_POLL_FREQ, 0)); String freqUnits = session.getContextDataString( context.getId(), RecordContext.TYPE_AUTH_POLL_FREQ_UNITS, null); if (freqUnits != null) { try { - context.getAuthenticationMethod() - .setPollFrequencyUnits( - AuthPollFrequencyUnits.valueOf(freqUnits)); + vm.setPollFrequencyUnits(AuthPollFrequencyUnits.valueOf(freqUnits)); } catch (Exception e) { LOGGER.error("Failed to parse auth frequency units {}", freqUnits, e); } } - context.getAuthenticationMethod() - .setLoggedInIndicatorPattern( - session.getContextDataString( - context.getId(), - RecordContext.TYPE_AUTH_METHOD_LOGGEDIN_INDICATOR, - null)); - - context.getAuthenticationMethod() - .setLoggedOutIndicatorPattern( - session.getContextDataString( - context.getId(), - RecordContext.TYPE_AUTH_METHOD_LOGGEDOUT_INDICATOR, - null)); + vm.setPollMethod( + session.getContextDataString( + context.getId(), RecordContext.TYPE_AUTH_POLL_METHOD, null)); + + vm.setLoggedInIndicatorPattern( + session.getContextDataString( + context.getId(), + RecordContext.TYPE_AUTH_METHOD_LOGGEDIN_INDICATOR, + null)); + + vm.setLoggedOutIndicatorPattern( + session.getContextDataString( + context.getId(), + RecordContext.TYPE_AUTH_METHOD_LOGGEDOUT_INDICATOR, + null)); } } @@ -343,64 +368,67 @@ public void persistContextData(Session session, Context context) { RecordContext.TYPE_AUTH_METHOD_TYPE, Integer.toString(t.getUniqueIdentifier())); - if (context.getAuthenticationMethod().getAuthCheckingStrategy() != null) { + VerificationMethod vm = context.getVerificationMethod(); + + if (vm.getAuthCheckingStrategy() != null) { session.setContextData( contextIdx, RecordContext.TYPE_AUTH_VERIF_STRATEGY, - context.getAuthenticationMethod().getAuthCheckingStrategy().name()); + vm.getAuthCheckingStrategy().name()); } else { session.clearContextDataForType(contextIdx, RecordContext.TYPE_AUTH_VERIF_STRATEGY); } - if (context.getAuthenticationMethod().getPollUrl() != null) { + if (vm.getPollUrl() != null) { session.setContextData( - contextIdx, - RecordContext.TYPE_AUTH_POLL_URL, - context.getAuthenticationMethod().getPollUrl()); + contextIdx, RecordContext.TYPE_AUTH_POLL_URL, vm.getPollUrl()); } else { session.clearContextDataForType(contextIdx, RecordContext.TYPE_AUTH_POLL_URL); } - if (context.getAuthenticationMethod().getPollData() != null) { + if (vm.getPollData() != null) { session.setContextData( - contextIdx, - RecordContext.TYPE_AUTH_POLL_DATA, - context.getAuthenticationMethod().getPollData()); + contextIdx, RecordContext.TYPE_AUTH_POLL_DATA, vm.getPollData()); } else { session.clearContextDataForType(contextIdx, RecordContext.TYPE_AUTH_POLL_DATA); } - if (context.getAuthenticationMethod().getPollHeaders() != null) { + if (vm.getPollHeaders() != null) { session.setContextData( - contextIdx, - RecordContext.TYPE_AUTH_POLL_HEADERS, - context.getAuthenticationMethod().getPollHeaders()); + contextIdx, RecordContext.TYPE_AUTH_POLL_HEADERS, vm.getPollHeaders()); } else { session.clearContextDataForType(contextIdx, RecordContext.TYPE_AUTH_POLL_HEADERS); } session.setContextData( contextIdx, RecordContext.TYPE_AUTH_POLL_FREQ, - Integer.toString(context.getAuthenticationMethod().getPollFrequency())); + Integer.toString(vm.getPollFrequency())); - if (context.getAuthenticationMethod().getPollFrequencyUnits() != null) { + if (vm.getPollFrequencyUnits() != null) { session.setContextData( contextIdx, RecordContext.TYPE_AUTH_POLL_FREQ_UNITS, - context.getAuthenticationMethod().getPollFrequencyUnits().name()); + vm.getPollFrequencyUnits().name()); } else { - session.clearContextDataForType(contextIdx, RecordContext.TYPE_AUTH_VERIF_STRATEGY); + session.clearContextDataForType( + contextIdx, RecordContext.TYPE_AUTH_POLL_FREQ_UNITS); + } + if (vm.getPollMethod() != null) { + session.setContextData( + contextIdx, RecordContext.TYPE_AUTH_POLL_METHOD, vm.getPollMethod()); + } else { + session.clearContextDataForType(contextIdx, RecordContext.TYPE_AUTH_POLL_METHOD); } persistLoggedIndicator( session, contextIdx, RecordContext.TYPE_AUTH_METHOD_LOGGEDIN_INDICATOR, - context.getAuthenticationMethod().getLoggedInIndicatorPattern()); + vm.getLoggedInIndicatorPattern()); persistLoggedIndicator( session, contextIdx, RecordContext.TYPE_AUTH_METHOD_LOGGEDOUT_INDICATOR, - context.getAuthenticationMethod().getLoggedOutIndicatorPattern()); + vm.getLoggedOutIndicatorPattern()); t.persistMethodToSession(session, contextIdx, context.getAuthenticationMethod()); } catch (DatabaseException e) { @@ -421,11 +449,13 @@ private static void persistLoggedIndicator( @Override public void discardContexts() { contextPanelsMap.clear(); + verificationPanelsMap.clear(); } @Override public void discardContext(Context ctx) { contextPanelsMap.remove(ctx.getId()); + verificationPanelsMap.remove(ctx.getId()); } @Override @@ -433,37 +463,35 @@ public void exportContextData(Context ctx, Configuration config) { config.setProperty( AuthenticationMethod.CONTEXT_CONFIG_AUTH_TYPE, ctx.getAuthenticationMethod().getType().getUniqueIdentifier()); - if (ctx.getAuthenticationMethod().getAuthCheckingStrategy() != null) { + + VerificationMethod vm = ctx.getVerificationMethod(); + if (vm.getAuthCheckingStrategy() != null) { config.setProperty( AuthenticationMethod.CONTEXT_CONFIG_AUTH_STRATEGY, - ctx.getAuthenticationMethod().getAuthCheckingStrategy().name()); + vm.getAuthCheckingStrategy().name()); } + config.setProperty(AuthenticationMethod.CONTEXT_CONFIG_AUTH_POLL_URL, vm.getPollUrl()); + config.setProperty(AuthenticationMethod.CONTEXT_CONFIG_AUTH_POLL_DATA, vm.getPollData()); config.setProperty( - AuthenticationMethod.CONTEXT_CONFIG_AUTH_POLL_URL, - ctx.getAuthenticationMethod().getPollUrl()); + AuthenticationMethod.CONTEXT_CONFIG_AUTH_POLL_HEADERS, vm.getPollHeaders()); config.setProperty( - AuthenticationMethod.CONTEXT_CONFIG_AUTH_POLL_DATA, - ctx.getAuthenticationMethod().getPollData()); - config.setProperty( - AuthenticationMethod.CONTEXT_CONFIG_AUTH_POLL_HEADERS, - ctx.getAuthenticationMethod().getPollHeaders()); - config.setProperty( - AuthenticationMethod.CONTEXT_CONFIG_AUTH_POLL_FREQ, - ctx.getAuthenticationMethod().getPollFrequency()); - if (ctx.getAuthenticationMethod().getPollFrequencyUnits() != null) { + AuthenticationMethod.CONTEXT_CONFIG_AUTH_POLL_FREQ, vm.getPollFrequency()); + if (vm.getPollFrequencyUnits() != null) { config.setProperty( AuthenticationMethod.CONTEXT_CONFIG_AUTH_POLL_UNITS, - ctx.getAuthenticationMethod().getPollFrequencyUnits().name()); + vm.getPollFrequencyUnits().name()); } - if (ctx.getAuthenticationMethod().getLoggedInIndicatorPattern() != null) { + config.setProperty( + AuthenticationMethod.CONTEXT_CONFIG_AUTH_POLL_METHOD, vm.getPollMethod()); + if (vm.getLoggedInIndicatorPattern() != null) { config.setProperty( AuthenticationMethod.CONTEXT_CONFIG_AUTH_LOGGEDIN, - ctx.getAuthenticationMethod().getLoggedInIndicatorPattern().toString()); + vm.getLoggedInIndicatorPattern().toString()); } - if (ctx.getAuthenticationMethod().getLoggedOutIndicatorPattern() != null) { + if (vm.getLoggedOutIndicatorPattern() != null) { config.setProperty( AuthenticationMethod.CONTEXT_CONFIG_AUTH_LOGGEDOUT, - ctx.getAuthenticationMethod().getLoggedOutIndicatorPattern().toString()); + vm.getLoggedOutIndicatorPattern().toString()); } ctx.getAuthenticationMethod().getType().exportData(config, ctx.getAuthenticationMethod()); } @@ -481,37 +509,48 @@ public void importContextData(Context ctx, Configuration config) throws Configur return; } ctx.setAuthenticationMethod(authMethodType.createAuthenticationMethod(ctx.getId())); - AuthenticationMethod method = ctx.getAuthenticationMethod(); - AuthCheckingStrategy strategy = - AuthCheckingStrategy.valueOf( - config.getString( - AuthenticationMethod.CONTEXT_CONFIG_AUTH_STRATEGY, - AuthCheckingStrategy.EACH_RESP.name())); - method.setAuthCheckingStrategy(strategy); - - method.setPollUrl(config.getString(AuthenticationMethod.CONTEXT_CONFIG_AUTH_POLL_URL, "")); - method.setPollData( - config.getString(AuthenticationMethod.CONTEXT_CONFIG_AUTH_POLL_DATA, "")); - method.setPollHeaders( + VerificationMethod vm = ctx.getVerificationMethod(); + + String pollUrl = config.getString(AuthenticationMethod.CONTEXT_CONFIG_AUTH_POLL_URL, ""); + String strategyName = + config.getString(AuthenticationMethod.CONTEXT_CONFIG_AUTH_STRATEGY, null); + AuthCheckingStrategy strategy; + if (strategyName != null && !strategyName.isBlank()) { + strategy = AuthCheckingStrategy.valueOf(strategyName); + } else { + // Older context files had no strategy field; indicator-only configs checked each + // response. Prefer POLL_URL only when a poll URL is present. + strategy = + pollUrl != null && !pollUrl.isBlank() + ? AuthCheckingStrategy.POLL_URL + : AuthCheckingStrategy.EACH_RESP; + } + vm.setAuthCheckingStrategy(strategy); + + vm.setPollMethod( + config.getString(AuthenticationMethod.CONTEXT_CONFIG_AUTH_POLL_METHOD, null)); + vm.setPollUrl(pollUrl); + vm.setPollData(config.getString(AuthenticationMethod.CONTEXT_CONFIG_AUTH_POLL_DATA, "")); + vm.setPollHeaders( config.getString(AuthenticationMethod.CONTEXT_CONFIG_AUTH_POLL_HEADERS, "")); - method.setPollFrequency( + vm.setPollFrequency( config.getInt( AuthenticationMethod.CONTEXT_CONFIG_AUTH_POLL_FREQ, - AuthenticationMethod.DEFAULT_POLL_FREQUENCY)); + VerificationMethod.DEFAULT_POLL_FREQUENCY)); AuthPollFrequencyUnits units = AuthPollFrequencyUnits.valueOf( config.getString( AuthenticationMethod.CONTEXT_CONFIG_AUTH_POLL_UNITS, - AuthPollFrequencyUnits.REQUESTS.name())); - method.setPollFrequencyUnits(units); + AuthPollFrequencyUnits.SECONDS.name())); + vm.setPollFrequencyUnits(units); - method.setLoggedInIndicatorPattern( + vm.setLoggedInIndicatorPattern( config.getString(AuthenticationMethod.CONTEXT_CONFIG_AUTH_LOGGEDIN, "")); - method.setLoggedOutIndicatorPattern( + vm.setLoggedOutIndicatorPattern( config.getString(AuthenticationMethod.CONTEXT_CONFIG_AUTH_LOGGEDOUT, "")); - method.getType().importData(config, method); + ctx.getAuthenticationMethod().getType().importData(config, ctx.getAuthenticationMethod()); } private HttpSenderAuthHeaderListener getHttpSenderAuthHeaderListener() { diff --git a/zap/src/main/java/org/zaproxy/zap/extension/authentication/PopupFlagLoggedInIndicatorMenu.java b/zap/src/main/java/org/zaproxy/zap/extension/authentication/PopupFlagLoggedInIndicatorMenu.java index a1daf30235d..6d905b2e569 100644 --- a/zap/src/main/java/org/zaproxy/zap/extension/authentication/PopupFlagLoggedInIndicatorMenu.java +++ b/zap/src/main/java/org/zaproxy/zap/extension/authentication/PopupFlagLoggedInIndicatorMenu.java @@ -63,7 +63,7 @@ public void performAction() { Context uiSharedContext = sessionDialog.getUISharedContext(this.contextId); uiSharedContext - .getAuthenticationMethod() + .getVerificationMethod() .setLoggedInIndicatorPattern(Pattern.quote(getSelectedText())); // Show the session dialog without recreating UI Shared contexts diff --git a/zap/src/main/java/org/zaproxy/zap/extension/authentication/PopupFlagLoggedOutIndicatorMenu.java b/zap/src/main/java/org/zaproxy/zap/extension/authentication/PopupFlagLoggedOutIndicatorMenu.java index 0f24713e0d3..99564d6e745 100644 --- a/zap/src/main/java/org/zaproxy/zap/extension/authentication/PopupFlagLoggedOutIndicatorMenu.java +++ b/zap/src/main/java/org/zaproxy/zap/extension/authentication/PopupFlagLoggedOutIndicatorMenu.java @@ -63,7 +63,7 @@ public void performAction() { Context uiSharedContext = sessionDialog.getUISharedContext(this.contextId); uiSharedContext - .getAuthenticationMethod() + .getVerificationMethod() .setLoggedOutIndicatorPattern(Pattern.quote(getSelectedText())); // Show the session dialog without recreating UI Shared contexts diff --git a/zap/src/main/java/org/zaproxy/zap/extension/authentication/VerificationAPI.java b/zap/src/main/java/org/zaproxy/zap/extension/authentication/VerificationAPI.java new file mode 100644 index 00000000000..75f4e40ad27 --- /dev/null +++ b/zap/src/main/java/org/zaproxy/zap/extension/authentication/VerificationAPI.java @@ -0,0 +1,265 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2026 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.zap.extension.authentication; + +import java.io.IOException; +import java.util.regex.Pattern; +import net.sf.json.JSONObject; +import org.apache.commons.httpclient.URI; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.control.Control; +import org.parosproxy.paros.model.Model; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.zap.authentication.AuthenticationMethod.AuthCheckingStrategy; +import org.zaproxy.zap.authentication.AuthenticationMethod.AuthPollFrequencyUnits; +import org.zaproxy.zap.authentication.VerificationMethod; +import org.zaproxy.zap.extension.api.ApiAction; +import org.zaproxy.zap.extension.api.ApiException; +import org.zaproxy.zap.extension.api.ApiException.Type; +import org.zaproxy.zap.extension.api.ApiImplementor; +import org.zaproxy.zap.extension.api.ApiResponse; +import org.zaproxy.zap.extension.api.ApiResponseConversionUtils; +import org.zaproxy.zap.extension.api.ApiResponseElement; +import org.zaproxy.zap.extension.api.ApiResponseSet; +import org.zaproxy.zap.extension.api.ApiView; +import org.zaproxy.zap.extension.users.ExtensionUserManagement; +import org.zaproxy.zap.model.Context; +import org.zaproxy.zap.users.User; +import org.zaproxy.zap.utils.ApiUtils; + +/** + * The API for manipulating the {@link org.zaproxy.zap.authentication.VerificationMethod + * VerificationMethod} for {@link Context Contexts}. + */ +public class VerificationAPI extends ApiImplementor { + + private static final Logger LOGGER = LogManager.getLogger(VerificationAPI.class); + + private static final String PREFIX = "verification"; + + private static final String VIEW_GET_LOGGED_IN_INDICATOR = "getLoggedInIndicator"; + private static final String VIEW_GET_LOGGED_OUT_INDICATOR = "getLoggedOutIndicator"; + + private static final String ACTION_SET_LOGGED_IN_INDICATOR = "setLoggedInIndicator"; + private static final String ACTION_SET_LOGGED_OUT_INDICATOR = "setLoggedOutIndicator"; + private static final String ACTION_SET_VERIFICATION_METHOD = "setVerificationMethod"; + private static final String ACTION_POLL_AS_USER = "pollAsUser"; + + public static final String PARAM_CONTEXT_ID = "contextId"; + public static final String PARAM_USER_ID = "userId"; + private static final String PARAM_LOGGED_IN_INDICATOR = "loggedInIndicatorRegex"; + private static final String PARAM_LOGGED_OUT_INDICATOR = "loggedOutIndicatorRegex"; + private static final String PARAM_CHECKING_STRATEGY = "checkingStrategy"; + private static final String PARAM_POLL_URL = "pollUrl"; + private static final String PARAM_POLL_DATA = "pollData"; + private static final String PARAM_POLL_HEADERS = "pollHeaders"; + private static final String PARAM_POLL_FREQ = "pollFrequency"; + private static final String PARAM_POLL_FREQ_UNITS = "pollFrequencyUnits"; + + public VerificationAPI() { + super(); + + this.addApiView(new ApiView(VIEW_GET_LOGGED_IN_INDICATOR, new String[] {PARAM_CONTEXT_ID})); + this.addApiView( + new ApiView(VIEW_GET_LOGGED_OUT_INDICATOR, new String[] {PARAM_CONTEXT_ID})); + + this.addApiAction( + new ApiAction( + ACTION_SET_LOGGED_IN_INDICATOR, + new String[] {PARAM_CONTEXT_ID, PARAM_LOGGED_IN_INDICATOR})); + this.addApiAction( + new ApiAction( + ACTION_SET_LOGGED_OUT_INDICATOR, + new String[] {PARAM_CONTEXT_ID, PARAM_LOGGED_OUT_INDICATOR})); + this.addApiAction( + new ApiAction( + ACTION_SET_VERIFICATION_METHOD, + new String[] {PARAM_CONTEXT_ID, PARAM_CHECKING_STRATEGY}, + new String[] { + PARAM_POLL_URL, + PARAM_POLL_DATA, + PARAM_POLL_HEADERS, + PARAM_POLL_FREQ, + PARAM_POLL_FREQ_UNITS + })); + this.addApiAction( + new ApiAction(ACTION_POLL_AS_USER, new String[] {PARAM_CONTEXT_ID, PARAM_USER_ID})); + } + + @Override + public String getPrefix() { + return PREFIX; + } + + @Override + public ApiResponse handleApiView(String name, JSONObject params) throws ApiException { + LOGGER.debug("handleApiView {} {}", name, params); + + switch (name) { + case VIEW_GET_LOGGED_IN_INDICATOR: + Pattern loggedInPattern = + getContext(params).getVerificationMethod().getLoggedInIndicatorPattern(); + return new ApiResponseElement( + "logged_in_regex", + loggedInPattern != null ? loggedInPattern.toString() : ""); + case VIEW_GET_LOGGED_OUT_INDICATOR: + Pattern loggedOutPattern = + getContext(params).getVerificationMethod().getLoggedOutIndicatorPattern(); + return new ApiResponseElement( + "logged_out_regex", + loggedOutPattern != null ? loggedOutPattern.toString() : ""); + default: + throw new ApiException(ApiException.Type.BAD_VIEW); + } + } + + @Override + public ApiResponse handleApiAction(String name, JSONObject params) throws ApiException { + LOGGER.debug("handleApiAction {} {}", name, params); + + Context context; + switch (name) { + case ACTION_SET_LOGGED_IN_INDICATOR: + String loggedInIndicator = params.getString(PARAM_LOGGED_IN_INDICATOR); + if (loggedInIndicator == null || loggedInIndicator.isEmpty()) + throw new ApiException(Type.MISSING_PARAMETER, PARAM_LOGGED_IN_INDICATOR); + context = getContext(params); + context.getVerificationMethod().setLoggedInIndicatorPattern(loggedInIndicator); + context.save(); + return ApiResponseElement.OK; + + case ACTION_SET_LOGGED_OUT_INDICATOR: + String loggedOutIndicator = params.getString(PARAM_LOGGED_OUT_INDICATOR); + if (loggedOutIndicator == null || loggedOutIndicator.isEmpty()) + throw new ApiException(Type.MISSING_PARAMETER, PARAM_LOGGED_OUT_INDICATOR); + context = getContext(params); + context.getVerificationMethod().setLoggedOutIndicatorPattern(loggedOutIndicator); + context.save(); + return ApiResponseElement.OK; + + case ACTION_SET_VERIFICATION_METHOD: + context = getContext(params); + AuthCheckingStrategy strategy; + try { + strategy = + AuthCheckingStrategy.valueOf(params.getString(PARAM_CHECKING_STRATEGY)); + } catch (Exception e) { + throw new ApiException( + ApiException.Type.ILLEGAL_PARAMETER, PARAM_CHECKING_STRATEGY); + } + if (AuthCheckingStrategy.POLL_URL.equals(strategy)) { + AuthPollFrequencyUnits units; + try { + units = + AuthPollFrequencyUnits.valueOf( + params.getString(PARAM_POLL_FREQ_UNITS)); + } catch (Exception e) { + throw new ApiException( + ApiException.Type.ILLEGAL_PARAMETER, PARAM_POLL_FREQ_UNITS); + } + String pollUrl = params.getString(PARAM_POLL_URL); + if (pollUrl == null || pollUrl.isEmpty()) { + throw new ApiException(ApiException.Type.ILLEGAL_PARAMETER, PARAM_POLL_URL); + } + try { + new URI(pollUrl, true); + } catch (Exception e) { + throw new ApiException(ApiException.Type.ILLEGAL_PARAMETER, PARAM_POLL_URL); + } + int freq; + try { + freq = params.getInt(PARAM_POLL_FREQ); + } catch (Exception e) { + throw new ApiException( + ApiException.Type.ILLEGAL_PARAMETER, PARAM_POLL_FREQ); + } + if (freq <= 0) { + throw new ApiException( + ApiException.Type.ILLEGAL_PARAMETER, PARAM_POLL_FREQ); + } + String pollData = params.getString(PARAM_POLL_DATA); + String pollHeaders = params.getString(PARAM_POLL_HEADERS); + context.getVerificationMethod().setPollUrl(pollUrl); + context.getVerificationMethod().setPollData(pollData); + context.getVerificationMethod().setPollHeaders(pollHeaders); + context.getVerificationMethod().setPollFrequency(freq); + context.getVerificationMethod().setPollFrequencyUnits(units); + } + context.getVerificationMethod().setAuthCheckingStrategy(strategy); + Model.getSingleton().getSession().saveContext(context); + return ApiResponseElement.OK; + + case ACTION_POLL_AS_USER: + User user = getUser(params); + try { + VerificationMethod verifMethod = user.getContext().getVerificationMethod(); + if (!verifMethod + .getAuthCheckingStrategy() + .equals(AuthCheckingStrategy.POLL_URL)) { + throw new ApiException(Type.ILLEGAL_PARAMETER, PARAM_CHECKING_STRATEGY); + } + if (StringUtils.isBlank(verifMethod.getPollUrl())) { + throw new ApiException(Type.ILLEGAL_PARAMETER, PARAM_POLL_URL); + } + HttpMessage msg = verifMethod.pollAsUser(user); + int href = -1; + if (msg.getHistoryRef() != null) { + href = msg.getHistoryRef().getHistoryId(); + } + ApiResponseSet responseSet = + ApiResponseConversionUtils.httpMessageToSet(href, msg); + responseSet.put( + "pollSuccessful", + Boolean.toString( + verifMethod.evaluateAuthRequest( + msg, user.getAuthenticationState()))); + return responseSet; + } catch (IllegalArgumentException e) { + throw new ApiException(Type.ILLEGAL_PARAMETER, PARAM_CONTEXT_ID); + } catch (IOException e) { + throw new ApiException(Type.INTERNAL_ERROR, e); + } + + default: + throw new ApiException(Type.BAD_ACTION); + } + } + + private Context getContext(JSONObject params) throws ApiException { + return ApiUtils.getContextByParamId(params, PARAM_CONTEXT_ID); + } + + private User getUser(JSONObject params) throws ApiException { + int contextId = ApiUtils.getIntParam(params, PARAM_CONTEXT_ID); + int userId = ApiUtils.getIntParam(params, PARAM_USER_ID); + ExtensionUserManagement usersExtension = + Control.getSingleton() + .getExtensionLoader() + .getExtension(ExtensionUserManagement.class); + if (usersExtension == null) { + throw new ApiException(Type.DOES_NOT_EXIST, PARAM_USER_ID); + } + User user = usersExtension.getContextUserAuthManager(contextId).getUserById(userId); + if (user == null) throw new ApiException(Type.USER_NOT_FOUND, PARAM_USER_ID); + return user; + } +} diff --git a/zap/src/main/java/org/zaproxy/zap/extension/users/UsersAPI.java b/zap/src/main/java/org/zaproxy/zap/extension/users/UsersAPI.java index abd2f1ac079..8b2f4b2580c 100644 --- a/zap/src/main/java/org/zaproxy/zap/extension/users/UsersAPI.java +++ b/zap/src/main/java/org/zaproxy/zap/extension/users/UsersAPI.java @@ -141,8 +141,11 @@ public UsersAPI(ExtensionUserManagement extension) { new ApiAction( ACTION_AUTHENTICATE_AS_USER, new String[] {PARAM_CONTEXT_ID, PARAM_USER_ID})); - this.addApiAction( - new ApiAction(ACTION_POLL_AS_USER, new String[] {PARAM_CONTEXT_ID, PARAM_USER_ID})); + ApiAction pollAsUserAction = + new ApiAction(ACTION_POLL_AS_USER, new String[] {PARAM_CONTEXT_ID, PARAM_USER_ID}); + pollAsUserAction.setDeprecated(true); + pollAsUserAction.setDeprecatedDescription("Use verification/pollAsUser instead."); + this.addApiAction(pollAsUserAction); this.addApiAction( new ApiAction( ACTION_SET_AUTH_STATE, @@ -326,7 +329,7 @@ public ApiResponse handleApiAction(String name, JSONObject params) throws ApiExc "authSuccessful", Boolean.toString( user.getContext() - .getAuthenticationMethod() + .getVerificationMethod() .evaluateAuthRequest( authMsg, user.getAuthenticationState()))); @@ -343,7 +346,7 @@ public ApiResponse handleApiAction(String name, JSONObject params) throws ApiExc case ACTION_POLL_AS_USER: user = getUser(params); try { - HttpMessage msg = user.getContext().getAuthenticationMethod().pollAsUser(user); + HttpMessage msg = user.getContext().getVerificationMethod().pollAsUser(user); int href = -1; if (msg.getHistoryRef() != null) { href = msg.getHistoryRef().getHistoryId(); @@ -354,7 +357,7 @@ public ApiResponse handleApiAction(String name, JSONObject params) throws ApiExc "pollSuccessful", Boolean.toString( user.getContext() - .getAuthenticationMethod() + .getVerificationMethod() .evaluateAuthRequest( msg, user.getAuthenticationState()))); diff --git a/zap/src/main/java/org/zaproxy/zap/model/Context.java b/zap/src/main/java/org/zaproxy/zap/model/Context.java index 337d1bc6f06..a9956a32990 100644 --- a/zap/src/main/java/org/zaproxy/zap/model/Context.java +++ b/zap/src/main/java/org/zaproxy/zap/model/Context.java @@ -41,6 +41,7 @@ import org.parosproxy.paros.view.View; import org.zaproxy.zap.authentication.AuthenticationMethod; import org.zaproxy.zap.authentication.ManualAuthenticationMethodType.ManualAuthenticationMethod; +import org.zaproxy.zap.authentication.VerificationMethod; import org.zaproxy.zap.extension.authorization.AuthorizationDetectionMethod; import org.zaproxy.zap.extension.authorization.BasicAuthorizationDetectionMethod; import org.zaproxy.zap.extension.authorization.BasicAuthorizationDetectionMethod.LogicalOperator; @@ -87,6 +88,9 @@ public class Context { /** The authentication method. */ private AuthenticationMethod authenticationMethod = null; + /** The verification method. */ + private VerificationMethod verificationMethod = null; + /** The session management method. */ private SessionManagementMethod sessionManagementMethod; @@ -106,6 +110,7 @@ public Context(Session session, int id) { this.name = String.valueOf(id); this.sessionManagementMethod = new CookieBasedSessionManagementMethod(id); this.authenticationMethod = new ManualAuthenticationMethod(id); + this.verificationMethod = this.authenticationMethod.getVerificationMethod(); this.authorizationDetectionMethod = new BasicAuthorizationDetectionMethod(null, null, null, LogicalOperator.AND); this.urlParamParser.setContext(this); @@ -526,10 +531,39 @@ public AuthenticationMethod getAuthenticationMethod() { /** * Sets the authentication method corresponding to this context. * + *

Setting a new authentication method also links this context's verification method to the + * one owned by the new authentication method. To use an independent verification method, call + * {@link #setVerificationMethod(VerificationMethod)} afterwards. + * * @param authenticationMethod the new authentication method */ public void setAuthenticationMethod(AuthenticationMethod authenticationMethod) { this.authenticationMethod = authenticationMethod; + this.verificationMethod = authenticationMethod.getVerificationMethod(); + } + + /** + * Gets the verification method corresponding to this context. + * + * @return the verification method + * @since 2.18.0 + */ + public VerificationMethod getVerificationMethod() { + return verificationMethod; + } + + /** + * Sets the verification method corresponding to this context, independently of the + * authentication method. + * + * @param verificationMethod the new verification method + * @since 2.18.0 + */ + public void setVerificationMethod(VerificationMethod verificationMethod) { + this.verificationMethod = verificationMethod; + if (this.authenticationMethod != null) { + this.authenticationMethod.setVerificationMethod(verificationMethod); + } } /** @@ -888,7 +922,10 @@ public Context duplicate() { newContext.excludeFromPatterns = new ArrayList<>(this.excludeFromPatterns); newContext.inScope = this.inScope; newContext.techSet = new TechSet(this.techSet); - newContext.authenticationMethod = this.authenticationMethod.clone(); + AuthenticationMethod am = this.authenticationMethod.clone(); + newContext.setAuthenticationMethod(am); + newContext.setVerificationMethod( + this.verificationMethod.copy(am::replaceUserDataInPollRequest)); newContext.sessionManagementMethod = this.sessionManagementMethod.clone(); newContext.urlParamParser = this.urlParamParser.clone(); newContext.postParamParser = this.postParamParser.clone(); diff --git a/zap/src/main/java/org/zaproxy/zap/users/User.java b/zap/src/main/java/org/zaproxy/zap/users/User.java index 825f9322d5b..b7516e78a3a 100644 --- a/zap/src/main/java/org/zaproxy/zap/users/User.java +++ b/zap/src/main/java/org/zaproxy/zap/users/User.java @@ -250,7 +250,7 @@ protected long getLastSuccessfulAuthTime() { * @return true, if is authenticated */ public boolean isAuthenticated(HttpMessage msg) { - return getContext().getAuthenticationMethod().isAuthenticated(msg, this); + return getContext().getVerificationMethod().isAuthenticated(msg, this); } /** diff --git a/zap/src/main/resources/org/zaproxy/zap/resources/Messages.properties b/zap/src/main/resources/org/zaproxy/zap/resources/Messages.properties index b83d3cb7981..ed5a3be1842 100644 --- a/zap/src/main/resources/org/zaproxy/zap/resources/Messages.properties +++ b/zap/src/main/resources/org/zaproxy/zap/resources/Messages.properties @@ -710,6 +710,7 @@ authentication.panel.label.loggedOut = Regex pattern used to identify Logged Out authentication.panel.label.noConfigPanel = This method is fully configured and does not require any configuration. authentication.panel.label.polldata = Poll Request POST Data (if any): authentication.panel.label.pollheaders = Additional Poll Request Headers: +authentication.panel.label.pollmethod = Poll Request Method: authentication.panel.label.pollurl = URL to Poll for Verification: authentication.panel.label.strategy = Verification Strategy: authentication.panel.label.strategy.auto_detect = Auto-Detect @@ -2971,6 +2972,31 @@ variant.shortname.urlpath = URL Path variant.shortname.userdefined = User Defined variant.shortname.xml = XML Tag/Attribute +verification.api.action.pollAsUser = Tries to poll as the identified user, returning the poll request and whether it appears to have succeeded. This will only work if the polling verification strategy has been configured. +verification.api.action.pollAsUser.param.contextId = The Context ID +verification.api.action.pollAsUser.param.userId = The User ID +verification.api.action.setLoggedInIndicator = Sets the logged in indicator for the context with the given ID. +verification.api.action.setLoggedInIndicator.param.contextId = The Context ID +verification.api.action.setLoggedInIndicator.param.loggedInIndicatorRegex = The regular expression to match against when determining if the user is logged in. +verification.api.action.setLoggedOutIndicator = Sets the logged out indicator for the context with the given ID. +verification.api.action.setLoggedOutIndicator.param.contextId = The Context ID +verification.api.action.setLoggedOutIndicator.param.loggedOutIndicatorRegex = The regular expression to match against when determining if the user is logged out. +verification.api.action.setVerificationMethod = Sets the verification method and related configuration for the context with the given ID. +verification.api.action.setVerificationMethod.param.checkingStrategy = One of EACH_RESP, EACH_REQ, EACH_REQ_RESP, POLL_URL +verification.api.action.setVerificationMethod.param.contextId = The Context ID +verification.api.action.setVerificationMethod.param.pollData = The POST data to supply to the pollUrl, optional and only takes effect if checkingStrategy = POLL_URL +verification.api.action.setVerificationMethod.param.pollFrequency = An integer greater than zero, must be supplied if checkingStrategy = POLL_URL, otherwise ignored +verification.api.action.setVerificationMethod.param.pollFrequencyUnits = One of REQUESTS, SECONDS, must be supplied if checkingStrategy = POLL_URL, otherwise ignored +verification.api.action.setVerificationMethod.param.pollHeaders = Any additional headers to include in the poll request, separated by '\\n' characters, optional and only takes effect if checkingStrategy = POLL_URL +verification.api.action.setVerificationMethod.param.pollUrl = The URL for ZAP to poll, must be supplied if checkingStrategy = POLL_URL, otherwise ignored +verification.api.desc = API for managing the verification method used to determine if a user is authenticated. +verification.api.view.getLoggedInIndicator = Gets the logged in indicator for the context with the given ID. +verification.api.view.getLoggedInIndicator.param.contextId = The Context ID +verification.api.view.getLoggedOutIndicator = Gets the logged out indicator for the context with the given ID. +verification.api.view.getLoggedOutIndicator.param.contextId = The Context ID +verification.panel.label.description =

This panel allows you to change the verification strategy for this Context.

+verification.panel.title = Verification + view.dialog.dontPrompt = Do not show this message again. view.dialog.remember = Remember my choice and do not show this message again. view.href.table.cell.alert.risk.label.high = High diff --git a/zap/src/test/java/org/zaproxy/zap/authentication/AuthenticationMethodIndicatorsUnitTest.java b/zap/src/test/java/org/zaproxy/zap/authentication/AuthenticationMethodIndicatorsUnitTest.java deleted file mode 100644 index 6925e137d7a..00000000000 --- a/zap/src/test/java/org/zaproxy/zap/authentication/AuthenticationMethodIndicatorsUnitTest.java +++ /dev/null @@ -1,417 +0,0 @@ -/* - * Zed Attack Proxy (ZAP) and its related class files. - * - * ZAP is an HTTP/HTTPS proxy for assessing web application security. - * - * Copyright 2013 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.zap.authentication; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -import org.apache.commons.httpclient.URI; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; -import org.parosproxy.paros.network.HttpMessage; -import org.parosproxy.paros.network.HttpRequestHeader; -import org.zaproxy.zap.authentication.AuthenticationMethod.AuthCheckingStrategy; -import org.zaproxy.zap.users.AuthenticationState; -import org.zaproxy.zap.users.User; - -@ExtendWith(MockitoExtension.class) -class AuthenticationMethodIndicatorsUnitTest { - - private static final String LOGGED_OUT_COMPLEX_INDICATOR = "User [^\\s]* logged out"; - private static final String LOGGED_OUT_COMPLEX_BODY = - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " - + "Pellentesque auctor nulla id turpis placerat vulputate. User Test logged out. " - + " Proin tempor bibendum eros rutrum. "; - private static final String LOGGED_IN_INDICATOR = "logged in"; - private static final String LOGGED_OUT_INDICATOR = "logged out"; - private static final String LOGGED_IN_BODY = - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " - + "Pellentesque auctor nulla id turpis placerat vulputate." - + LOGGED_IN_INDICATOR - + " Proin tempor bibendum eros rutrum. "; - private static final String LOGGED_OUT_BODY = - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " - + "Pellentesque auctor nulla id turpis placerat vulputate." - + LOGGED_OUT_INDICATOR - + " Proin tempor bibendum eros rutrum. "; - - private HttpMessage loginMessage; - private AuthenticationMethod method; - - @BeforeEach - void setUp() throws Exception { - loginMessage = new HttpMessage(); - HttpRequestHeader header = new HttpRequestHeader(); - header.setURI(new URI("http://www.example.com", true)); - loginMessage.setRequestHeader(header); - method = Mockito.mock(AuthenticationMethod.class, Mockito.CALLS_REAL_METHODS); - method.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_RESP); - } - - @Test - void shouldStoreSetLoggedInIndicator() { - // Given - method.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); - - // When/Then - assertEquals(LOGGED_IN_INDICATOR, method.getLoggedInIndicatorPattern().pattern()); - } - - @Test - void shouldStoreSetLoggedOutIndicator() { - // Given - method.setLoggedOutIndicatorPattern(LOGGED_OUT_INDICATOR); - - // When/Then - assertEquals(LOGGED_OUT_INDICATOR, method.getLoggedOutIndicatorPattern().pattern()); - } - - @Test - void shouldNotStoreNullOrEmptyLoggedInIndicator() { - // Given - method.setLoggedInIndicatorPattern(null); - - // When/Then - assertNull(method.getLoggedInIndicatorPattern()); - - // Given - method.setLoggedInIndicatorPattern(" "); - - // When/Then - assertNull(method.getLoggedInIndicatorPattern()); - } - - @Test - void shouldNotStoreNullOrEmptyLoggedOutIndicator() { - // Given - method.setLoggedOutIndicatorPattern(null); - - // When/Then - assertNull(method.getLoggedOutIndicatorPattern()); - - // Given - method.setLoggedOutIndicatorPattern(" "); - - // When/Then - assertNull(method.getLoggedOutIndicatorPattern()); - } - - @Test - void shouldIdentifyLoggedInResponseBodyWhenLoggedInIndicatorIsSet() { - // Given - method.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); - loginMessage.setResponseBody(LOGGED_IN_BODY); - - User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); - - // When/Then - assertThat(method.isAuthenticated(loginMessage, user), is(true)); - } - - @Test - void shouldIdentifyLoggedOutResponseBodyWhenLoggedInIndicatorIsSet() { - // Given - method.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); - loginMessage.setResponseBody(LOGGED_OUT_BODY); - - User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); - - // When/Then - assertThat(method.isAuthenticated(loginMessage, user), is(false)); - } - - @Test - void shouldIdentifyLoggedInResponseHeaderWhenLoggedInIndicatorIsSet() { - // Given - method.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); - loginMessage.getResponseHeader().addHeader("test", LOGGED_IN_INDICATOR); - - User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); - - // When/Then - assertThat(method.isAuthenticated(loginMessage, user), is(true)); - } - - @Test - void shouldIdentifyLoggedOutResponseHeaderWhenLoggedInIndicatorIsSet() { - // Given - method.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); - loginMessage.getResponseHeader().addHeader("test", LOGGED_OUT_INDICATOR); - - User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); - - // When/Then - assertThat(method.isAuthenticated(loginMessage, user), is(false)); - } - - @Test - void shouldIdentifyLoggedOutResponseBodyWhenLoggedOutIndicatorIsSet() { - // Given - method.setLoggedOutIndicatorPattern(LOGGED_OUT_INDICATOR); - loginMessage.setResponseBody(LOGGED_OUT_BODY); - - User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); - - // When/Then - assertThat(method.isAuthenticated(loginMessage, user), is(false)); - } - - @Test - void shouldIdentifyLoggedInResponseBodyWhenLoggedOutIndicatorIsSet() { - // Given - method.setLoggedOutIndicatorPattern(LOGGED_OUT_INDICATOR); - loginMessage.setResponseBody(LOGGED_IN_BODY); - - User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); - - // When/Then - assertThat(method.isAuthenticated(loginMessage, user), is(true)); - } - - @Test - void shouldIdentifyLoggedOutResponseHeaderWhenLoggedOutIndicatorIsSet() { - // Given - method.setLoggedOutIndicatorPattern(LOGGED_OUT_INDICATOR); - loginMessage.getResponseHeader().addHeader("test", LOGGED_OUT_INDICATOR); - - User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); - - // When/Then - assertThat(method.isAuthenticated(loginMessage, user), is(false)); - } - - @Test - void shouldIdentifyLoggedInResponseHeaderWhenLoggedOutIndicatorIsSet() { - // Given - method.setLoggedOutIndicatorPattern(LOGGED_OUT_INDICATOR); - loginMessage.getResponseHeader().addHeader("test", LOGGED_IN_INDICATOR); - - User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); - - // When/Then - assertThat(method.isAuthenticated(loginMessage, user), is(true)); - } - - @Test - void shouldIdentifyLoggedOutResponseWithComplexRegex() { - // Given - method.setLoggedOutIndicatorPattern(LOGGED_OUT_COMPLEX_INDICATOR); - loginMessage.setResponseBody(LOGGED_OUT_COMPLEX_BODY); - - User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); - - // When/Then - assertThat(method.isAuthenticated(loginMessage, user), is(false)); - } - - @Test - void shouldIdentifyLoggedInResponseWithComplexRegex() { - // Given - method.setLoggedOutIndicatorPattern(LOGGED_OUT_COMPLEX_INDICATOR); - loginMessage.setResponseBody(LOGGED_OUT_BODY); - - User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); - - // When/Then - assertThat(method.isAuthenticated(loginMessage, user), is(true)); - } - - @Test - void shouldIdentifyResponseAsLoggedInWhenNoIndicatorIsSet() { - // Given - loginMessage.setResponseBody(LOGGED_OUT_BODY); - - User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); - - // When/Then - assertThat(method.isAuthenticated(loginMessage, user), is(true)); - } - - @Test - void shouldIdentifyLoggedInRequestBodyWhenLoggedInIndicatorIsSet() { - // Given - method.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); - method.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_REQ); - loginMessage.setRequestBody(LOGGED_IN_BODY); - - User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); - - // When/Then - assertThat(method.isAuthenticated(loginMessage, user), is(true)); - } - - @Test - void shouldIdentifyLoggedOutRequestBodyWhenLoggedInIndicatorIsSet() { - // Given - method.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); - method.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_REQ); - loginMessage.setRequestBody(LOGGED_OUT_BODY); - - User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); - - // When/Then - assertThat(method.isAuthenticated(loginMessage, user), is(false)); - } - - @Test - void shouldIdentifyLoggedInRequestHeaderWhenLoggedInIndicatorIsSet() { - // Given - method.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); - method.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_REQ); - loginMessage.getRequestHeader().addHeader("test", LOGGED_IN_INDICATOR); - - User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); - - // When/Then - assertThat(method.isAuthenticated(loginMessage, user), is(true)); - } - - @Test - void shouldIdentifyLoggedOutRequestHeaderWhenLoggedInIndicatorIsSet() { - // Given - method.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); - method.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_REQ); - loginMessage.getRequestHeader().addHeader("test", LOGGED_OUT_INDICATOR); - - User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); - - // When/Then - assertThat(method.isAuthenticated(loginMessage, user), is(false)); - } - - @Test - void shouldIdentifyLoggedOutRequestBodyWhenLoggedOutIndicatorIsSet() { - // Given - method.setLoggedOutIndicatorPattern(LOGGED_OUT_INDICATOR); - method.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_REQ); - loginMessage.setRequestBody(LOGGED_OUT_BODY); - - User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); - - // When/Then - assertThat(method.isAuthenticated(loginMessage, user), is(false)); - } - - @Test - void shouldIdentifyLoggedInRequestBodyWhenLoggedOutIndicatorIsSet() { - // Given - method.setLoggedOutIndicatorPattern(LOGGED_OUT_INDICATOR); - method.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_REQ); - loginMessage.setRequestBody(LOGGED_IN_BODY); - - User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); - - // When/Then - assertThat(method.isAuthenticated(loginMessage, user), is(true)); - } - - @Test - void shouldIdentifyLoggedOutRequestHeaderWhenLoggedOutIndicatorIsSet() { - // Given - method.setLoggedOutIndicatorPattern(LOGGED_OUT_INDICATOR); - method.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_REQ); - loginMessage.getRequestHeader().addHeader("test", LOGGED_OUT_INDICATOR); - - User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); - - // When/Then - assertThat(method.isAuthenticated(loginMessage, user), is(false)); - } - - @Test - void shouldIdentifyLoggedInRequestHeaderWhenLoggedOutIndicatorIsSet() { - // Given - method.setLoggedOutIndicatorPattern(LOGGED_OUT_INDICATOR); - method.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_REQ); - loginMessage.getRequestHeader().addHeader("test", LOGGED_IN_INDICATOR); - - User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); - - // When/Then - assertThat(method.isAuthenticated(loginMessage, user), is(true)); - } - - @Test - void shouldIdentifyLoggedOutRequestWithComplexRegex() { - // Given - method.setLoggedOutIndicatorPattern(LOGGED_OUT_COMPLEX_INDICATOR); - method.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_REQ); - loginMessage.setRequestBody(LOGGED_OUT_COMPLEX_BODY); - - User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); - - // When/Then - assertThat(method.isAuthenticated(loginMessage, user), is(false)); - } - - @Test - void shouldIdentifyLoggedInRequestWithComplexRegex() { - // Given - method.setLoggedOutIndicatorPattern(LOGGED_OUT_COMPLEX_INDICATOR); - method.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_REQ); - loginMessage.setRequestBody(LOGGED_OUT_BODY); - - User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); - - // When/Then - assertThat(method.isAuthenticated(loginMessage, user), is(true)); - } - - @Test - void shouldIdentifyRequestAsLoggedInWhenNoIndicatorIsSet() { - // Given - loginMessage.setRequestBody(LOGGED_OUT_BODY); - method.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_REQ); - - User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); - - // When/Then - assertThat(method.isAuthenticated(loginMessage, user), is(true)); - } -} diff --git a/zap/src/test/java/org/zaproxy/zap/authentication/AuthenticationMethodUnitTest.java b/zap/src/test/java/org/zaproxy/zap/authentication/AuthenticationMethodUnitTest.java index bcf6b75dd5b..90d340e97d3 100644 --- a/zap/src/test/java/org/zaproxy/zap/authentication/AuthenticationMethodUnitTest.java +++ b/zap/src/test/java/org/zaproxy/zap/authentication/AuthenticationMethodUnitTest.java @@ -22,12 +22,11 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.sameInstance; import static org.mockito.Mockito.mock; -import org.apache.commons.httpclient.URI; import org.junit.jupiter.api.Test; import org.parosproxy.paros.network.HttpMessage; -import org.zaproxy.zap.authentication.AuthenticationMethod.AuthCheckingStrategy; import org.zaproxy.zap.extension.api.ApiResponse; import org.zaproxy.zap.session.SessionManagementMethod; import org.zaproxy.zap.session.WebSession; @@ -40,68 +39,8 @@ class AuthenticationMethodUnitTest { void shouldBeEqualToItself() { // Given AuthenticationMethod authMethod = new AuthenticationMethodTest(); - // when - boolean equals = authMethod.equals(authMethod); - // Then - assertThat(equals, is(equalTo(true))); - } - - @Test - void shouldBeEqualToDifferentAuthenticationMethodWithSameContents() { - // Given - String loggedInIndicator = "loggedInIndicator"; - String loggedOutIndicator = "loggedOutIndicator"; - AuthenticationMethod authMethod = - createAuthenticationMethod(loggedInIndicator, loggedOutIndicator); - AuthenticationMethod otherAuthMethod = - createAuthenticationMethod(loggedInIndicator, loggedOutIndicator); - // When - boolean equals = authMethod.equals(otherAuthMethod) & otherAuthMethod.equals(authMethod); - // Then - assertThat(equals, is(equalTo(true))); - } - - @Test - void shouldBeEqualToDifferentAuthenticationMethodWithNullLoggedInIndicator() { - // Given - String loggedInIndicator = null; - String loggedOutIndicator = "loggedOutIndicator"; - AuthenticationMethod authMethod = - createAuthenticationMethod(loggedInIndicator, loggedOutIndicator); - AuthenticationMethod otherAuthMethod = - createAuthenticationMethod(loggedInIndicator, loggedOutIndicator); // When - boolean equals = authMethod.equals(otherAuthMethod) & otherAuthMethod.equals(authMethod); - // Then - assertThat(equals, is(equalTo(true))); - } - - @Test - void shouldBeEqualToDifferentAuthenticationMethodWithNullLoggedOutIndicator() { - // Given - String loggedInIndicator = "loggedInIndicator"; - String loggedOutIndicator = null; - AuthenticationMethod authMethod = - createAuthenticationMethod(loggedInIndicator, loggedOutIndicator); - AuthenticationMethod otherAuthMethod = - createAuthenticationMethod(loggedInIndicator, loggedOutIndicator); - // When - boolean equals = authMethod.equals(otherAuthMethod) & otherAuthMethod.equals(authMethod); - // Then - assertThat(equals, is(equalTo(true))); - } - - @Test - void shouldBeEqualToDifferentAuthenticationMethodWithNullIndicators() { - // Given - String loggedInIndicator = null; - String loggedOutIndicator = null; - AuthenticationMethod authMethod = - createAuthenticationMethod(loggedInIndicator, loggedOutIndicator); - AuthenticationMethod otherAuthMethod = - createAuthenticationMethod(loggedInIndicator, loggedOutIndicator); - // When - boolean equals = authMethod.equals(otherAuthMethod) & otherAuthMethod.equals(authMethod); + boolean equals = authMethod.equals(authMethod); // Then assertThat(equals, is(equalTo(true))); } @@ -116,60 +55,6 @@ void shouldNotBeEqualToNull() { assertThat(equals, is(false)); } - @Test - void shouldNotBeEqualToAuthenticationMethodWithJustDifferentLoggedInIndicator() { - // Given - String loggedOutIndicator = "loggedOutIndicator"; - AuthenticationMethod authMethod = - createAuthenticationMethod("loggedInIndicator", loggedOutIndicator); - AuthenticationMethod otherAuthMethod = - createAuthenticationMethod("otherLoggedInIndicator", loggedOutIndicator); - // When - boolean equals = authMethod.equals(otherAuthMethod) | otherAuthMethod.equals(authMethod); - // Then - assertThat(equals, is(false)); - } - - @Test - void shouldNotBeEqualToAuthenticationMethodWithJustDifferentNullLoggedInIndicator() { - // Given - String loggedOutIndicator = "loggedOutIndicator"; - AuthenticationMethod authMethod = - createAuthenticationMethod("loggedInIndicator", loggedOutIndicator); - AuthenticationMethod otherAuthMethod = createAuthenticationMethod(null, loggedOutIndicator); - // When - boolean equals = authMethod.equals(otherAuthMethod) | otherAuthMethod.equals(authMethod); - // Then - assertThat(equals, is(false)); - } - - @Test - void shouldNotBeEqualToAuthenticationMethodWithJustDifferentLoggedOutIndicator() { - // Given - String loggedInIndicator = "loggedInIndicator"; - AuthenticationMethod authMethod = - createAuthenticationMethod(loggedInIndicator, "loggedOutIndicator"); - AuthenticationMethod otherAuthMethod = - createAuthenticationMethod(loggedInIndicator, "otherLoggedOutIndicator"); - // When - boolean equals = authMethod.equals(otherAuthMethod) | otherAuthMethod.equals(authMethod); - // Then - assertThat(equals, is(false)); - } - - @Test - void shouldNotBeEqualToAuthenticationMethodWithJustDifferentNullLoggedOutIndicator() { - // Given - String loggedInIndicator = "loggedInIndicator"; - AuthenticationMethod authMethod = - createAuthenticationMethod(loggedInIndicator, "loggedOutIndicator"); - AuthenticationMethod otherAuthMethod = createAuthenticationMethod(loggedInIndicator, null); - // When - boolean equals = authMethod.equals(otherAuthMethod) | otherAuthMethod.equals(authMethod); - // Then - assertThat(equals, is(false)); - } - @Test void shouldNotBeEqualToExtendedAuthenticationMethod() { // Given @@ -184,76 +69,16 @@ void shouldNotBeEqualToExtendedAuthenticationMethod() { } @Test - void shouldFailAuthIfMessageNull() { - // Given - AuthenticationMethod authMethod = new AuthenticationMethodTest(); - User user = mock(User.class); - // When - boolean auth = authMethod.isAuthenticated(null, user); - // Then - assertThat(auth, is(false)); - } - - @Test - void shouldFailAuthIfUserNull() { + void shouldWireVerificationMethodUserDataReplacer() { // Given - AuthenticationMethod authMethod = new AuthenticationMethodTest(); - HttpMessage msg = mock(HttpMessage.class); - // When - boolean auth = authMethod.isAuthenticated(msg, null); - // Then - assertThat(auth, is(false)); - } - - @Test - void shouldFailAuthIfVerifIsAutoDetect() { - // Given - AuthenticationMethod authMethod = new AuthenticationMethodTest(); - authMethod.setAuthCheckingStrategy(AuthCheckingStrategy.AUTO_DETECT); - HttpMessage msg = mock(HttpMessage.class); + RecordingAuthenticationMethod method = new RecordingAuthenticationMethod(); + HttpMessage msg = new HttpMessage(); User user = mock(User.class); // When - boolean auth = authMethod.isAuthenticated(msg, user); + method.getVerificationMethod().getUserDataReplacer().accept(msg, user); // Then - assertThat(auth, is(false)); - } - - @Test - void shouldPassAuthIfMsgContainsLoggedInIndicator() throws Exception { - // Given - AuthenticationMethod authMethod = new AuthenticationMethodTest(); - authMethod.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_RESP); - authMethod.setLoggedInIndicatorPattern("loggedin"); - HttpMessage msg = new HttpMessage(new URI("http://example.com/", true)); - msg.setResponseBody("The user is loggedin again."); - User user = mock(User.class); - // When - boolean auth = authMethod.isAuthenticated(msg, user); - // Then - assertThat(auth, is(true)); - } - - @Test - void shouldFailAuthIfMsgContainsLoggedOutIndicator() throws Exception { - // Given - AuthenticationMethod authMethod = new AuthenticationMethodTest(); - authMethod.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_RESP); - authMethod.setLoggedOutIndicatorPattern("loggedout"); - HttpMessage msg = new HttpMessage(new URI("http://example.com/", true)); - msg.setResponseBody("The user is loggedout this time."); - User user = mock(User.class); - // When - boolean auth = authMethod.isAuthenticated(msg, user); - // Then - assertThat(auth, is(false)); - } - - private static AuthenticationMethod createAuthenticationMethod( - String loggedInIndicator, String loggedOutIndicator) { - AuthenticationMethod authMethod = new AuthenticationMethodTest(); - authMethod.setLoggedInIndicatorPattern(loggedInIndicator); - authMethod.setLoggedOutIndicatorPattern(loggedOutIndicator); - return authMethod; + assertThat(method.replacedMsg, is(sameInstance(msg))); + assertThat(method.replacedUser, is(sameInstance(user))); } private static class AuthenticationMethodTest extends AuthenticationMethod { @@ -295,4 +120,16 @@ public ApiResponse getApiResponseRepresentation() { @Override public void replaceUserDataInPollRequest(HttpMessage msg, User user) {} } + + private static class RecordingAuthenticationMethod extends AuthenticationMethodTest { + + private HttpMessage replacedMsg; + private User replacedUser; + + @Override + public void replaceUserDataInPollRequest(HttpMessage msg, User user) { + this.replacedMsg = msg; + this.replacedUser = user; + } + } } diff --git a/zap/src/test/java/org/zaproxy/zap/authentication/FormBasedAuthenticationMethodTypeUnitTest.java b/zap/src/test/java/org/zaproxy/zap/authentication/FormBasedAuthenticationMethodTypeUnitTest.java index b900e05c1eb..849bf3ee57a 100644 --- a/zap/src/test/java/org/zaproxy/zap/authentication/FormBasedAuthenticationMethodTypeUnitTest.java +++ b/zap/src/test/java/org/zaproxy/zap/authentication/FormBasedAuthenticationMethodTypeUnitTest.java @@ -24,159 +24,43 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; -import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; import org.apache.commons.httpclient.URI; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.parosproxy.paros.network.HttpMessage; import org.zaproxy.zap.WithConfigsTest; -import org.zaproxy.zap.authentication.AuthenticationMethod.AuthCheckingStrategy; -import org.zaproxy.zap.authentication.AuthenticationMethod.AuthPollFrequencyUnits; import org.zaproxy.zap.authentication.PostBasedAuthenticationMethodType.PostBasedAuthenticationMethod; -import org.zaproxy.zap.users.AuthenticationState; import org.zaproxy.zap.users.User; class FormBasedAuthenticationMethodTypeUnitTest extends WithConfigsTest { - private static final String LOGGED_IN_INDICATOR = "logged in"; - private static final String LOGGED_IN_BODY = - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " - + "Pellentesque auctor nulla id turpis placerat vulputate." - + LOGGED_IN_INDICATOR - + " Proin tempor bibendum eros rutrum. "; - private AuthenticationMethod method; - private FormBasedAuthenticationMethodType type; @BeforeEach void setUp() throws Exception { - - type = new FormBasedAuthenticationMethodType(); + FormBasedAuthenticationMethodType type = new FormBasedAuthenticationMethodType(); method = type.createAuthenticationMethod(1); - method.setAuthCheckingStrategy(AuthCheckingStrategy.POLL_URL); - method.setPollFrequencyUnits(AuthPollFrequencyUnits.REQUESTS); - method.setPollFrequency(5); - method.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); - } - - @Test - void shouldReplaceUsernameInPollRequest() throws NullPointerException, IOException { - // Given - String test = "/shouldReplaceUsernameInPollRequest/test"; - String encodedPattern = - URLEncoder.encode( - PostBasedAuthenticationMethod.MSG_USER_PATTERN, - StandardCharsets.UTF_8.name()); - String pollUrl = "/shouldReplaceUsernameInPollRequest/pollUrl"; - String pollData = "user=" + PostBasedAuthenticationMethod.MSG_USER_PATTERN; - String username = "user"; - final List orderedReqUrls = new ArrayList<>(); - final List orderedReqData = new ArrayList<>(); - - setMessageHandler( - msg -> { - URI uri = msg.getRequestHeader().getURI(); - if (pollUrl.equals(uri.getPath())) { - orderedReqUrls.add(uri.getEscapedPathQuery()); - orderedReqData.add(msg.getRequestBody().toString()); - msg.setResponseBody(LOGGED_IN_BODY); - } - }); - HttpMessage testMsg = this.getHttpMessage(test); - HttpMessage pollMsg = this.getHttpMessage(pollUrl + "?" + encodedPattern); - - method.setPollUrl(pollMsg.getRequestHeader().getURI().toString()); - method.setPollData(pollData); - - User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); - given(user.getAuthenticationCredentials()) - .willReturn(new UsernamePasswordAuthenticationCredentials(username, "")); - - // When/Then - assertThat(method.isAuthenticated(testMsg, user), is(true)); - assertThat(orderedReqUrls.size(), is(1)); - assertThat(orderedReqUrls.get(0), is(pollUrl + "?" + username)); - assertThat(orderedReqData.size(), is(1)); - assertThat( - orderedReqData.get(0), - is(pollData.replace(PostBasedAuthenticationMethod.MSG_USER_PATTERN, username))); } @Test - void shouldNotReplacePasswordInPollRequest() throws NullPointerException, IOException { + void shouldUrlEncodeUsernameInPollRequestBody() throws Exception { // Given - String test = "/shouldNotReplacePasswordInPollRequest/test"; - String pollUrl = "/shouldNotReplacePasswordInPollRequest/pollUrl"; - String pollData = "pwd=" + PostBasedAuthenticationMethod.MSG_PASS_PATTERN; - String password = "password123!"; - final List orderedReqData = new ArrayList<>(); - - setMessageHandler( - msg -> { - URI uri = msg.getRequestHeader().getURI(); - if (pollUrl.equals(uri.getPath())) { - orderedReqData.add(msg.getRequestBody().toString()); - msg.setResponseBody(LOGGED_IN_BODY); - } - }); - HttpMessage testMsg = this.getHttpMessage(test); - HttpMessage pollMsg = this.getHttpMessage(pollUrl); - - method.setPollUrl(pollMsg.getRequestHeader().getURI().toString()); - method.setPollData(pollData); - - User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); - given(user.getAuthenticationCredentials()) - .willReturn(new UsernamePasswordAuthenticationCredentials("", password)); - - // When/Then - assertThat(method.isAuthenticated(testMsg, user), is(true)); - assertThat(orderedReqData.size(), is(1)); - assertThat(orderedReqData.get(0), is(pollData)); - } - - @Test - void shouldUrlEncodeUsernameInPollRequestBody() throws NullPointerException, IOException { - // Given - String test = "/shouldEncodeSpacesInBody/test"; - String pollUrl = "/shouldEncodeSpacesInBody/pollUrl"; - String pollData = "user=" + PostBasedAuthenticationMethod.MSG_USER_PATTERN; String username = "user name"; - final List orderedReqData = new ArrayList<>(); - - setMessageHandler( - msg -> { - URI uri = msg.getRequestHeader().getURI(); - if (pollUrl.equals(uri.getPath())) { - orderedReqData.add(msg.getRequestBody().toString()); - msg.setResponseBody(LOGGED_IN_BODY); - } - }); - HttpMessage testMsg = this.getHttpMessage(test); - HttpMessage pollMsg = this.getHttpMessage(pollUrl); - - method.setPollUrl(pollMsg.getRequestHeader().getURI().toString()); - method.setPollData(pollData); + HttpMessage msg = new HttpMessage(new URI("http://example.com/pollUrl", true)); + msg.setRequestBody("user=" + PostBasedAuthenticationMethod.MSG_USER_PATTERN); User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); given(user.getAuthenticationCredentials()) .willReturn(new UsernamePasswordAuthenticationCredentials(username, "")); - // When/Then - assertThat(method.isAuthenticated(testMsg, user), is(true)); - assertThat(orderedReqData.size(), is(1)); + // When + method.replaceUserDataInPollRequest(msg, user); + + // Then assertThat( - orderedReqData.get(0), - is( - pollData.replace( - PostBasedAuthenticationMethod.MSG_USER_PATTERN, - URLEncoder.encode(username, StandardCharsets.UTF_8.name())))); + msg.getRequestBody().toString(), + is("user=" + URLEncoder.encode(username, StandardCharsets.UTF_8.name()))); } } diff --git a/zap/src/test/java/org/zaproxy/zap/authentication/JsonBasedAuthenticationMethodTypeUnitTest.java b/zap/src/test/java/org/zaproxy/zap/authentication/JsonBasedAuthenticationMethodTypeUnitTest.java index c84cda1a709..c9b13679390 100644 --- a/zap/src/test/java/org/zaproxy/zap/authentication/JsonBasedAuthenticationMethodTypeUnitTest.java +++ b/zap/src/test/java/org/zaproxy/zap/authentication/JsonBasedAuthenticationMethodTypeUnitTest.java @@ -24,157 +24,40 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; -import java.io.IOException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; import org.apache.commons.httpclient.URI; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.parosproxy.paros.network.HttpMessage; import org.zaproxy.zap.WithConfigsTest; -import org.zaproxy.zap.authentication.AuthenticationMethod.AuthCheckingStrategy; -import org.zaproxy.zap.authentication.AuthenticationMethod.AuthPollFrequencyUnits; import org.zaproxy.zap.authentication.PostBasedAuthenticationMethodType.PostBasedAuthenticationMethod; -import org.zaproxy.zap.users.AuthenticationState; import org.zaproxy.zap.users.User; class JsonBasedAuthenticationMethodTypeUnitTest extends WithConfigsTest { - private static final String LOGGED_IN_INDICATOR = "logged in"; - private static final String LOGGED_IN_BODY = - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " - + "Pellentesque auctor nulla id turpis placerat vulputate." - + LOGGED_IN_INDICATOR - + " Proin tempor bibendum eros rutrum. "; - private AuthenticationMethod method; - private JsonBasedAuthenticationMethodType type; @BeforeEach void setUp() throws Exception { - - type = new JsonBasedAuthenticationMethodType(); + JsonBasedAuthenticationMethodType type = new JsonBasedAuthenticationMethodType(); method = type.createAuthenticationMethod(1); - method.setAuthCheckingStrategy(AuthCheckingStrategy.POLL_URL); - method.setPollFrequencyUnits(AuthPollFrequencyUnits.REQUESTS); - method.setPollFrequency(5); - method.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); - } - - @Test - void shouldReplaceUsernameInPollRequest() throws NullPointerException, IOException { - // Given - String test = "/shouldReplaceUsernameInPollRequest/test"; - String encodedPattern = - URLEncoder.encode( - PostBasedAuthenticationMethod.MSG_USER_PATTERN, - StandardCharsets.UTF_8.name()); - String pollUrl = "/shouldReplaceUsernameInPollRequest/pollUrl"; - String pollData = "user=" + PostBasedAuthenticationMethod.MSG_USER_PATTERN; - String username = "user"; - final List orderedReqUrls = new ArrayList<>(); - final List orderedReqData = new ArrayList<>(); - - setMessageHandler( - msg -> { - URI uri = msg.getRequestHeader().getURI(); - if (pollUrl.equals(uri.getPath())) { - orderedReqUrls.add(uri.getEscapedPathQuery()); - orderedReqData.add(msg.getRequestBody().toString()); - msg.setResponseBody(LOGGED_IN_BODY); - } - }); - HttpMessage testMsg = this.getHttpMessage(test); - HttpMessage pollMsg = this.getHttpMessage(pollUrl + "?" + encodedPattern); - - method.setPollUrl(pollMsg.getRequestHeader().getURI().toString()); - method.setPollData(pollData); - - User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); - given(user.getAuthenticationCredentials()) - .willReturn(new UsernamePasswordAuthenticationCredentials(username, "")); - - // When/Then - assertThat(method.isAuthenticated(testMsg, user), is(true)); - assertThat(orderedReqUrls.size(), is(1)); - assertThat(orderedReqUrls.get(0), is(pollUrl + "?" + username)); - assertThat(orderedReqData.size(), is(1)); - assertThat( - orderedReqData.get(0), - is(pollData.replace(PostBasedAuthenticationMethod.MSG_USER_PATTERN, username))); } @Test - void shouldNotReplacePasswordInPollRequest() throws NullPointerException, IOException { + void shouldNotUrlEncodeUsernameInPollRequestBody() throws Exception { // Given - String test = "/shouldNotReplacePasswordInPollRequest/test"; - String pollUrl = "/shouldNotReplacePasswordInPollRequest/pollUrl"; - String pollData = "pwd=" + PostBasedAuthenticationMethod.MSG_PASS_PATTERN; - String password = "password123!"; - final List orderedReqData = new ArrayList<>(); - - setMessageHandler( - msg -> { - URI uri = msg.getRequestHeader().getURI(); - if (pollUrl.equals(uri.getPath())) { - orderedReqData.add(msg.getRequestBody().toString()); - msg.setResponseBody(LOGGED_IN_BODY); - } - }); - HttpMessage testMsg = this.getHttpMessage(test); - HttpMessage pollMsg = this.getHttpMessage(pollUrl); - - method.setPollUrl(pollMsg.getRequestHeader().getURI().toString()); - method.setPollData(pollData); - - User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); - given(user.getAuthenticationCredentials()) - .willReturn(new UsernamePasswordAuthenticationCredentials("", password)); - - // When/Then - assertThat(method.isAuthenticated(testMsg, user), is(true)); - assertThat(orderedReqData.size(), is(1)); - assertThat(orderedReqData.get(0), is(pollData)); - } - - @Test - void shouldNotUrlEncodeUsernameInPollRequestBody() throws NullPointerException, IOException { - // Given - String test = "/shouldEncodeSpacesInBody/test"; - String pollUrl = "/shouldEncodeSpacesInBody/pollUrl"; - String pollData = - "{ \"user\": \"" + PostBasedAuthenticationMethod.MSG_USER_PATTERN + "\" }"; String username = "user name"; - final List orderedReqData = new ArrayList<>(); - - setMessageHandler( - msg -> { - URI uri = msg.getRequestHeader().getURI(); - if (pollUrl.equals(uri.getPath())) { - orderedReqData.add(msg.getRequestBody().toString()); - msg.setResponseBody(LOGGED_IN_BODY); - } - }); - HttpMessage testMsg = this.getHttpMessage(test); - HttpMessage pollMsg = this.getHttpMessage(pollUrl); - - method.setPollUrl(pollMsg.getRequestHeader().getURI().toString()); - method.setPollData(pollData); + HttpMessage msg = new HttpMessage(new URI("http://example.com/pollUrl", true)); + msg.setRequestBody( + "{ \"user\": \"" + PostBasedAuthenticationMethod.MSG_USER_PATTERN + "\" }"); User user = mock(User.class); - given(user.getAuthenticationState()).willReturn(new AuthenticationState()); given(user.getAuthenticationCredentials()) .willReturn(new UsernamePasswordAuthenticationCredentials(username, "")); - // When/Then - assertThat(method.isAuthenticated(testMsg, user), is(true)); - assertThat(orderedReqData.size(), is(1)); - assertThat( - orderedReqData.get(0), - is(pollData.replace(PostBasedAuthenticationMethod.MSG_USER_PATTERN, username))); + // When + method.replaceUserDataInPollRequest(msg, user); + + // Then + assertThat(msg.getRequestBody().toString(), is("{ \"user\": \"" + username + "\" }")); } } diff --git a/zap/src/test/java/org/zaproxy/zap/authentication/PostBasedAuthenticationMethodTypeUnitTest.java b/zap/src/test/java/org/zaproxy/zap/authentication/PostBasedAuthenticationMethodTypeUnitTest.java index bd3053146de..9028dabb77b 100644 --- a/zap/src/test/java/org/zaproxy/zap/authentication/PostBasedAuthenticationMethodTypeUnitTest.java +++ b/zap/src/test/java/org/zaproxy/zap/authentication/PostBasedAuthenticationMethodTypeUnitTest.java @@ -33,6 +33,8 @@ import static org.mockito.Mockito.withSettings; import static org.zaproxy.zap.authentication.PostBasedAuthenticationMethodTypeUnitTest.ReplaceAntiCsrfTokenValueIfRequired.token; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -47,7 +49,6 @@ import org.parosproxy.paros.model.Model; import org.parosproxy.paros.network.HttpMessage; import org.zaproxy.zap.WithConfigsTest; -import org.zaproxy.zap.authentication.FormBasedAuthenticationMethodType.FormBasedAuthenticationMethod; import org.zaproxy.zap.authentication.PostBasedAuthenticationMethodType.PostBasedAuthenticationMethod; import org.zaproxy.zap.extension.anticsrf.AntiCsrfToken; import org.zaproxy.zap.extension.anticsrf.ExtensionAntiCSRF; @@ -292,10 +293,107 @@ public String apply(String value) { } } - static class FormBasedAuthenticationMethodTest extends WithConfigsTest { + /** + * Test {@link + * PostBasedAuthenticationMethodType#replaceUserCredentialsDataInPollRequest(HttpMessage, User, + * UnaryOperator)}. + */ + static class ReplaceUserCredentialsDataInPollRequest { + + private static final String USER_PATTERN = PostBasedAuthenticationMethod.MSG_USER_PATTERN; + private static final String PASS_PATTERN = PostBasedAuthenticationMethod.MSG_PASS_PATTERN; + + private User user; + + @BeforeEach + void setup() { + user = mock(User.class); + } + + @Test + void shouldReplaceUsernameInUrlAndBody() throws Exception { + // Given + given(user.getAuthenticationCredentials()) + .willReturn(new UsernamePasswordAuthenticationCredentials("alice", "secret")); + String encodedPattern = URLEncoder.encode(USER_PATTERN, StandardCharsets.UTF_8.name()); + HttpMessage msg = + new HttpMessage( + new URI("http://example.com/poll?user=" + encodedPattern, true)); + msg.setRequestBody("user=" + USER_PATTERN); + // When + PostBasedAuthenticationMethodType.replaceUserCredentialsDataInPollRequest( + msg, user, UnaryOperator.identity()); + // Then + assertThat(msg.getRequestHeader().getURI().getEscapedQuery(), is("user=alice")); + assertThat(msg.getRequestBody().toString(), is("user=alice")); + } + + @Test + void shouldNotReplacePasswordInBody() throws Exception { + // Given + given(user.getAuthenticationCredentials()) + .willReturn(new UsernamePasswordAuthenticationCredentials("alice", "secret")); + HttpMessage msg = new HttpMessage(new URI("http://example.com/poll", true)); + msg.setRequestBody("pwd=" + PASS_PATTERN); + // When + PostBasedAuthenticationMethodType.replaceUserCredentialsDataInPollRequest( + msg, user, UnaryOperator.identity()); + // Then + assertThat(msg.getRequestBody().toString(), is("pwd=" + PASS_PATTERN)); + } + + @Test + void shouldEncodeUsernameInBodyUsingProvidedEncoder() throws Exception { + // Given + given(user.getAuthenticationCredentials()) + .willReturn(new UsernamePasswordAuthenticationCredentials("user name", "")); + HttpMessage msg = new HttpMessage(new URI("http://example.com/poll", true)); + msg.setRequestBody("user=" + USER_PATTERN); + // When + PostBasedAuthenticationMethodType.replaceUserCredentialsDataInPollRequest( + msg, user, value -> value.replace(" ", "+")); + // Then + assertThat(msg.getRequestBody().toString(), is("user=user+name")); + } + + @Test + void shouldNotReplaceAnythingWhenUserIsNull() throws Exception { + // Given + HttpMessage msg = new HttpMessage(new URI("http://example.com/poll", true)); + msg.setRequestBody("user=" + USER_PATTERN); + String originalBody = msg.getRequestBody().toString(); + // When + PostBasedAuthenticationMethodType.replaceUserCredentialsDataInPollRequest( + msg, null, UnaryOperator.identity()); + // Then + assertThat(msg.getRequestBody().toString(), is(originalBody)); + } - private AuthenticationMethod method; - private FormBasedAuthenticationMethodType type; + @Test + void shouldNotReplaceAnythingWhenCredentialsAreNotUsernamePassword() throws Exception { + // Given + given(user.getAuthenticationCredentials()) + .willReturn(mock(AuthenticationCredentials.class)); + HttpMessage msg = new HttpMessage(new URI("http://example.com/poll", true)); + msg.setRequestBody("user=" + USER_PATTERN); + String originalBody = msg.getRequestBody().toString(); + // When + PostBasedAuthenticationMethodType.replaceUserCredentialsDataInPollRequest( + msg, user, UnaryOperator.identity()); + // Then + assertThat(msg.getRequestBody().toString(), is(originalBody)); + } + } + + /** + * Test the shared {@code authenticate} behaviour of {@link PostBasedAuthenticationMethod}, + * using a minimal concrete implementation since the behaviour under test does not depend on the + * POST content format (form vs JSON). + */ + static class Authenticate extends WithConfigsTest { + + private PostBasedAuthenticationMethod method; + private TestPostBasedAuthenticationMethodType type; private Context context; private ExtensionAntiCSRF extAntiCsrf; @@ -307,10 +405,9 @@ void setUp() throws Exception { Control.initSingletonForTesting(); extAntiCsrf = mock(ExtensionAntiCSRF.class); - type = spy(new FormBasedAuthenticationMethodType()); + type = spy(new TestPostBasedAuthenticationMethodType()); method = type.createAuthenticationMethod(1); - given(type.createAuthenticationMethod(anyInt())) - .willReturn((FormBasedAuthenticationMethod) method); + given(type.createAuthenticationMethod(anyInt())).willReturn(method); context = Model.getSingleton().getSession().getNewContext("test"); } @@ -378,4 +475,52 @@ void shouldSetCorrectContentLengthWithAntiCsrfTokens() assertThat(expectedRequestBody, is(orderedReqData.get(1))); } } + + /** + * Minimal concrete {@link PostBasedAuthenticationMethodType}, used to exercise the behaviour + * shared by all post-based authentication methods without depending on a specific POST content + * format (form vs JSON). + */ + private static class TestPostBasedAuthenticationMethodType + extends PostBasedAuthenticationMethodType { + + TestPostBasedAuthenticationMethodType() { + super("Test Based Authentication", 100, "testBasedAuthentication", "test.popup", false); + } + + @Override + public PostBasedAuthenticationMethod createAuthenticationMethod(int contextId) { + return new TestPostBasedAuthenticationMethod(null); + } + + @Override + public AbstractAuthenticationMethodOptionsPanel buildOptionsPanel(Context uiSharedContext) { + return null; + } + + @Override + public boolean isTypeForMethod(AuthenticationMethod method) { + return method instanceof TestPostBasedAuthenticationMethod; + } + + class TestPostBasedAuthenticationMethod extends PostBasedAuthenticationMethod { + + TestPostBasedAuthenticationMethod(TestPostBasedAuthenticationMethod copy) { + super("test/content-type", UnaryOperator.identity(), copy); + } + + @Override + public AuthenticationMethodType getType() { + return TestPostBasedAuthenticationMethodType.this; + } + + @Override + public AuthenticationMethod duplicate() { + return new TestPostBasedAuthenticationMethod(this); + } + + @Override + public void replaceUserDataInPollRequest(HttpMessage msg, User user) {} + } + } } diff --git a/zap/src/test/java/org/zaproxy/zap/authentication/AuthenticationMethodPollUrlUnitTest.java b/zap/src/test/java/org/zaproxy/zap/authentication/VerificationMethodPollUrlUnitTest.java similarity index 50% rename from zap/src/test/java/org/zaproxy/zap/authentication/AuthenticationMethodPollUrlUnitTest.java rename to zap/src/test/java/org/zaproxy/zap/authentication/VerificationMethodPollUrlUnitTest.java index 0542ec55f9e..c1f0758a0ee 100644 --- a/zap/src/test/java/org/zaproxy/zap/authentication/AuthenticationMethodPollUrlUnitTest.java +++ b/zap/src/test/java/org/zaproxy/zap/authentication/VerificationMethodPollUrlUnitTest.java @@ -28,10 +28,8 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -import org.apache.commons.httpclient.URI; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; import org.parosproxy.paros.network.HttpMessage; import org.parosproxy.paros.network.HttpRequestHeader; import org.zaproxy.zap.authentication.AuthenticationMethod.AuthCheckingStrategy; @@ -40,7 +38,8 @@ import org.zaproxy.zap.users.AuthenticationState; import org.zaproxy.zap.users.User; -class AuthenticationMethodPollUrlUnitTest extends TestUtils { +/** Unit test for {@link VerificationMethod} poll URL behaviour. */ +class VerificationMethodPollUrlUnitTest extends TestUtils { private static final String LOGGED_IN_INDICATOR = "logged in"; private static final String LOGGED_IN_BODY = @@ -49,17 +48,12 @@ class AuthenticationMethodPollUrlUnitTest extends TestUtils { + LOGGED_IN_INDICATOR + " Proin tempor bibendum eros rutrum. "; - private HttpMessage loginMessage; - private AuthenticationMethod method; + private VerificationMethod vm; @BeforeEach void setUp() throws Exception { - loginMessage = new HttpMessage(); - HttpRequestHeader header = new HttpRequestHeader(); - header.setURI(new URI("http://www.example.com", true)); - loginMessage.setRequestHeader(header); - method = Mockito.mock(AuthenticationMethod.class, Mockito.CALLS_REAL_METHODS); - method.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_RESP); + vm = new VerificationMethod(); + vm.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_RESP); } @Test @@ -80,17 +74,17 @@ void shouldPollOnFirstRequest() throws NullPointerException, IOException { HttpMessage testMsg = this.getHttpMessage(test); HttpMessage pollMsg = this.getHttpMessage(pollUrl); - method.setAuthCheckingStrategy(AuthCheckingStrategy.POLL_URL); - method.setPollUrl(pollMsg.getRequestHeader().getURI().toString()); - method.setPollFrequencyUnits(AuthPollFrequencyUnits.REQUESTS); - method.setPollFrequency(5); - method.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); + vm.setAuthCheckingStrategy(AuthCheckingStrategy.POLL_URL); + vm.setPollUrl(pollMsg.getRequestHeader().getURI().toString()); + vm.setPollFrequencyUnits(AuthPollFrequencyUnits.REQUESTS); + vm.setPollFrequency(5); + vm.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); User user = mock(User.class); given(user.getAuthenticationState()).willReturn(new AuthenticationState()); // When/Then - assertThat(method.isAuthenticated(testMsg, user), is(true)); + assertThat(vm.isAuthenticated(testMsg, user), is(true)); assertThat(orderedReqs.size(), is(1)); assertThat(orderedReqs.get(0), is(pollUrl)); } @@ -113,25 +107,25 @@ void shouldPollOnSpecifiedNumberOfRequests() throws NullPointerException, IOExce HttpMessage testMsg = this.getHttpMessage(test); HttpMessage pollMsg = this.getHttpMessage(pollUrl); - method.setAuthCheckingStrategy(AuthCheckingStrategy.POLL_URL); - method.setPollUrl(pollMsg.getRequestHeader().getURI().toString()); - method.setPollFrequencyUnits(AuthPollFrequencyUnits.REQUESTS); - method.setPollFrequency(5); - method.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); + vm.setAuthCheckingStrategy(AuthCheckingStrategy.POLL_URL); + vm.setPollUrl(pollMsg.getRequestHeader().getURI().toString()); + vm.setPollFrequencyUnits(AuthPollFrequencyUnits.REQUESTS); + vm.setPollFrequency(5); + vm.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); User user = mock(User.class); given(user.getAuthenticationState()).willReturn(new AuthenticationState()); // When/Then - assertThat(method.isAuthenticated(testMsg, user), is(true)); + assertThat(vm.isAuthenticated(testMsg, user), is(true)); assertThat(orderedReqs.size(), is(1)); - assertThat(method.isAuthenticated(testMsg, user), is(true)); - assertThat(method.isAuthenticated(testMsg, user), is(true)); - assertThat(method.isAuthenticated(testMsg, user), is(true)); - assertThat(method.isAuthenticated(testMsg, user), is(true)); - assertThat(method.isAuthenticated(testMsg, user), is(true)); + assertThat(vm.isAuthenticated(testMsg, user), is(true)); + assertThat(vm.isAuthenticated(testMsg, user), is(true)); + assertThat(vm.isAuthenticated(testMsg, user), is(true)); + assertThat(vm.isAuthenticated(testMsg, user), is(true)); + assertThat(vm.isAuthenticated(testMsg, user), is(true)); assertThat(orderedReqs.size(), is(1)); - assertThat(method.isAuthenticated(testMsg, user), is(true)); + assertThat(vm.isAuthenticated(testMsg, user), is(true)); assertThat(orderedReqs.size(), is(2)); assertThat(orderedReqs.get(0), is(pollUrl)); assertThat(orderedReqs.get(1), is(pollUrl)); @@ -154,23 +148,23 @@ void shouldPollEveryFailingRequest() throws NullPointerException, IOException { HttpMessage testMsg = this.getHttpMessage(test); HttpMessage pollMsg = this.getHttpMessage(pollUrl); - method.setAuthCheckingStrategy(AuthCheckingStrategy.POLL_URL); - method.setPollUrl(pollMsg.getRequestHeader().getURI().toString()); - method.setPollFrequencyUnits(AuthPollFrequencyUnits.REQUESTS); - method.setPollFrequency(5); - method.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); + vm.setAuthCheckingStrategy(AuthCheckingStrategy.POLL_URL); + vm.setPollUrl(pollMsg.getRequestHeader().getURI().toString()); + vm.setPollFrequencyUnits(AuthPollFrequencyUnits.REQUESTS); + vm.setPollFrequency(5); + vm.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); User user = mock(User.class); given(user.getAuthenticationState()).willReturn(new AuthenticationState()); // When/Then - assertThat(method.isAuthenticated(testMsg, user), is(false)); + assertThat(vm.isAuthenticated(testMsg, user), is(false)); assertThat(orderedReqs.size(), is(1)); - assertThat(method.isAuthenticated(testMsg, user), is(false)); + assertThat(vm.isAuthenticated(testMsg, user), is(false)); assertThat(orderedReqs.size(), is(2)); - assertThat(method.isAuthenticated(testMsg, user), is(false)); + assertThat(vm.isAuthenticated(testMsg, user), is(false)); assertThat(orderedReqs.size(), is(3)); - assertThat(method.isAuthenticated(testMsg, user), is(false)); + assertThat(vm.isAuthenticated(testMsg, user), is(false)); assertThat(orderedReqs.size(), is(4)); } @@ -195,24 +189,24 @@ void shouldPollWhenForced() throws NullPointerException, IOException { HttpMessage testMsg = this.getHttpMessage(test); HttpMessage pollMsg = this.getHttpMessage(pollUrl); - method.setAuthCheckingStrategy(AuthCheckingStrategy.POLL_URL); - method.setPollUrl(pollMsg.getRequestHeader().getURI().toString()); - method.setPollFrequencyUnits(AuthPollFrequencyUnits.REQUESTS); - method.setPollFrequency(500); - method.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); + vm.setAuthCheckingStrategy(AuthCheckingStrategy.POLL_URL); + vm.setPollUrl(pollMsg.getRequestHeader().getURI().toString()); + vm.setPollFrequencyUnits(AuthPollFrequencyUnits.REQUESTS); + vm.setPollFrequency(500); + vm.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); // When/Then - assertThat(method.isAuthenticated(testMsg, user), is(true)); + assertThat(vm.isAuthenticated(testMsg, user), is(true)); assertThat(orderedReqs.size(), is(1)); - assertThat(method.isAuthenticated(testMsg, user), is(true)); - assertThat(method.isAuthenticated(testMsg, user), is(true)); - assertThat(method.isAuthenticated(testMsg, user), is(true)); - assertThat(method.isAuthenticated(testMsg, user), is(true)); - assertThat(method.isAuthenticated(testMsg, user), is(true)); - assertThat(method.isAuthenticated(testMsg, user), is(true)); + assertThat(vm.isAuthenticated(testMsg, user), is(true)); + assertThat(vm.isAuthenticated(testMsg, user), is(true)); + assertThat(vm.isAuthenticated(testMsg, user), is(true)); + assertThat(vm.isAuthenticated(testMsg, user), is(true)); + assertThat(vm.isAuthenticated(testMsg, user), is(true)); + assertThat(vm.isAuthenticated(testMsg, user), is(true)); assertThat(orderedReqs.size(), is(1)); user.getAuthenticationState().setLastPollResult(false); - assertThat(method.isAuthenticated(testMsg, user), is(true)); + assertThat(vm.isAuthenticated(testMsg, user), is(true)); assertThat(orderedReqs.size(), is(2)); assertThat(orderedReqs.get(0), is(pollUrl)); assertThat(orderedReqs.get(1), is(pollUrl)); @@ -236,11 +230,11 @@ void shouldPollOnSpecifiedNumberOfRequestsPerUser() throws NullPointerException, HttpMessage testMsg = this.getHttpMessage(test); HttpMessage pollMsg = this.getHttpMessage(pollUrl); - method.setAuthCheckingStrategy(AuthCheckingStrategy.POLL_URL); - method.setPollUrl(pollMsg.getRequestHeader().getURI().toString() + "?"); - method.setPollFrequencyUnits(AuthPollFrequencyUnits.REQUESTS); - method.setPollFrequency(5); - method.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); + vm.setAuthCheckingStrategy(AuthCheckingStrategy.POLL_URL); + vm.setPollUrl(pollMsg.getRequestHeader().getURI().toString() + "?"); + vm.setPollFrequencyUnits(AuthPollFrequencyUnits.REQUESTS); + vm.setPollFrequency(5); + vm.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); User user1 = mock(User.class); given(user1.getAuthenticationState()).willReturn(new AuthenticationState()); @@ -248,39 +242,131 @@ void shouldPollOnSpecifiedNumberOfRequestsPerUser() throws NullPointerException, given(user2.getAuthenticationState()).willReturn(new AuthenticationState()); // When/Then - assertThat(method.isAuthenticated(testMsg, user1), is(true)); + assertThat(vm.isAuthenticated(testMsg, user1), is(true)); // First poll for user1 assertThat(orderedReqs.size(), is(1)); - assertThat(method.isAuthenticated(testMsg, user1), is(true)); - assertThat(method.isAuthenticated(testMsg, user1), is(true)); + assertThat(vm.isAuthenticated(testMsg, user1), is(true)); + assertThat(vm.isAuthenticated(testMsg, user1), is(true)); - assertThat(method.isAuthenticated(testMsg, user2), is(true)); + assertThat(vm.isAuthenticated(testMsg, user2), is(true)); // First poll for user2 assertThat(orderedReqs.size(), is(2)); - assertThat(method.isAuthenticated(testMsg, user2), is(true)); + assertThat(vm.isAuthenticated(testMsg, user2), is(true)); - assertThat(method.isAuthenticated(testMsg, user1), is(true)); - assertThat(method.isAuthenticated(testMsg, user1), is(true)); - assertThat(method.isAuthenticated(testMsg, user1), is(true)); + assertThat(vm.isAuthenticated(testMsg, user1), is(true)); + assertThat(vm.isAuthenticated(testMsg, user1), is(true)); + assertThat(vm.isAuthenticated(testMsg, user1), is(true)); // Should not have changed yet assertThat(orderedReqs.size(), is(2)); - assertThat(method.isAuthenticated(testMsg, user1), is(true)); + assertThat(vm.isAuthenticated(testMsg, user1), is(true)); // Second poll for user1 assertThat(orderedReqs.size(), is(3)); - assertThat(method.isAuthenticated(testMsg, user1), is(true)); - assertThat(method.isAuthenticated(testMsg, user1), is(true)); - assertThat(method.isAuthenticated(testMsg, user1), is(true)); - assertThat(method.isAuthenticated(testMsg, user2), is(true)); - assertThat(method.isAuthenticated(testMsg, user2), is(true)); - assertThat(method.isAuthenticated(testMsg, user2), is(true)); - assertThat(method.isAuthenticated(testMsg, user2), is(true)); + assertThat(vm.isAuthenticated(testMsg, user1), is(true)); + assertThat(vm.isAuthenticated(testMsg, user1), is(true)); + assertThat(vm.isAuthenticated(testMsg, user1), is(true)); + assertThat(vm.isAuthenticated(testMsg, user2), is(true)); + assertThat(vm.isAuthenticated(testMsg, user2), is(true)); + assertThat(vm.isAuthenticated(testMsg, user2), is(true)); + assertThat(vm.isAuthenticated(testMsg, user2), is(true)); // Should not have changed yet assertThat(orderedReqs.size(), is(3)); - assertThat(method.isAuthenticated(testMsg, user2), is(true)); + assertThat(vm.isAuthenticated(testMsg, user2), is(true)); // Second poll for user2 assertThat(orderedReqs.size(), is(4)); } + @Test + void shouldUseGetWhenPollMethodNullAndDataEmpty() throws Exception { + // Given + String pollUrl = "/pollUrl"; + List pollMessages = new ArrayList<>(); + setMessageHandler(pollMessages::add); + + vm.setAuthCheckingStrategy(AuthCheckingStrategy.POLL_URL); + vm.setPollUrl(getHttpMessage(pollUrl).getRequestHeader().getURI().toString()); + vm.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); + + User user = mock(User.class); + given(user.getAuthenticationState()).willReturn(new AuthenticationState()); + + // When + vm.pollAsUser(user); + + // Then + assertThat(pollMessages, hasSize(1)); + assertThat(pollMessages.get(0).getRequestHeader().getMethod(), is(HttpRequestHeader.GET)); + } + + @Test + void shouldUsePostWhenPollMethodNullAndDataNonEmpty() throws Exception { + // Given + String pollUrl = "/pollUrl"; + List pollMessages = new ArrayList<>(); + setMessageHandler(pollMessages::add); + + vm.setAuthCheckingStrategy(AuthCheckingStrategy.POLL_URL); + vm.setPollUrl(getHttpMessage(pollUrl).getRequestHeader().getURI().toString()); + vm.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); + vm.setPollData("param=value"); + + User user = mock(User.class); + given(user.getAuthenticationState()).willReturn(new AuthenticationState()); + + // When + vm.pollAsUser(user); + + // Then + assertThat(pollMessages, hasSize(1)); + assertThat(pollMessages.get(0).getRequestHeader().getMethod(), is(HttpRequestHeader.POST)); + } + + @Test + void shouldUseConfiguredMethodWhenPollMethodSet() throws Exception { + // Given + String pollUrl = "/pollUrl"; + List pollMessages = new ArrayList<>(); + setMessageHandler(pollMessages::add); + + vm.setAuthCheckingStrategy(AuthCheckingStrategy.POLL_URL); + vm.setPollUrl(getHttpMessage(pollUrl).getRequestHeader().getURI().toString()); + vm.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); + vm.setPollData("param=value"); + vm.setPollMethod(HttpRequestHeader.GET); + + User user = mock(User.class); + given(user.getAuthenticationState()).willReturn(new AuthenticationState()); + + // When + vm.pollAsUser(user); + + // Then + assertThat(pollMessages, hasSize(1)); + assertThat(pollMessages.get(0).getRequestHeader().getMethod(), is(HttpRequestHeader.GET)); + } + + @Test + void shouldUseConfiguredPostMethodWhenPollMethodSetAndDataEmpty() throws Exception { + // Given + String pollUrl = "/pollUrl"; + List pollMessages = new ArrayList<>(); + setMessageHandler(pollMessages::add); + + vm.setAuthCheckingStrategy(AuthCheckingStrategy.POLL_URL); + vm.setPollUrl(getHttpMessage(pollUrl).getRequestHeader().getURI().toString()); + vm.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); + vm.setPollMethod(HttpRequestHeader.POST); + + User user = mock(User.class); + given(user.getAuthenticationState()).willReturn(new AuthenticationState()); + + // When + vm.pollAsUser(user); + + // Then + assertThat(pollMessages, hasSize(1)); + assertThat(pollMessages.get(0).getRequestHeader().getMethod(), is(HttpRequestHeader.POST)); + } + @Test void shouldHandlePollHeadersWithColonsInValues() throws Exception { // Given @@ -292,11 +378,11 @@ void shouldHandlePollHeadersWithColonsInValues() throws Exception { HttpMessage testMsg = this.getHttpMessage(test); - method.setAuthCheckingStrategy(AuthCheckingStrategy.POLL_URL); - method.setPollUrl(getHttpMessage(pollUrl).getRequestHeader().getURI().toString()); - method.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); + vm.setAuthCheckingStrategy(AuthCheckingStrategy.POLL_URL); + vm.setPollUrl(getHttpMessage(pollUrl).getRequestHeader().getURI().toString()); + vm.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); - method.setPollHeaders( + vm.setPollHeaders( """ Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyIn0:signature X-Custom-Time: 2025-07-19T10:30:45.123Z @@ -307,7 +393,7 @@ void shouldHandlePollHeadersWithColonsInValues() throws Exception { given(user.getAuthenticationState()).willReturn(new AuthenticationState()); // When - method.isAuthenticated(testMsg, user); + vm.isAuthenticated(testMsg, user); // Then assertThat(pollMessages, hasSize(1)); @@ -318,4 +404,34 @@ void shouldHandlePollHeadersWithColonsInValues() throws Exception { assertThat(requestHeader.getHeader("X-Custom-Time"), is("2025-07-19T10:30:45.123Z")); assertThat(requestHeader.getHeader("Content-Type"), is("application/json")); } + + @Test + void shouldInvokeUserDataReplacerBeforeSendingPollRequest() throws Exception { + // Given + String pollUrl = "/shouldInvokeUserDataReplacer/pollUrl"; + List sentBodies = new ArrayList<>(); + + setMessageHandler( + msg -> { + if (pollUrl.equals(msg.getRequestHeader().getURI().getPath())) { + sentBodies.add(msg.getRequestBody().toString()); + msg.setResponseBody(LOGGED_IN_BODY); + } + }); + + vm.setAuthCheckingStrategy(AuthCheckingStrategy.POLL_URL); + vm.setPollUrl(getHttpMessage(pollUrl).getRequestHeader().getURI().toString()); + vm.setLoggedInIndicatorPattern(LOGGED_IN_INDICATOR); + vm.setUserDataReplacer((msg, user) -> msg.setRequestBody("replaced")); + + User user = mock(User.class); + given(user.getAuthenticationState()).willReturn(new AuthenticationState()); + + // When + vm.isAuthenticated(this.getHttpMessage("/test"), user); + + // Then + assertThat(sentBodies, hasSize(1)); + assertThat(sentBodies.get(0), is("replaced")); + } } diff --git a/zap/src/test/java/org/zaproxy/zap/authentication/VerificationMethodUnitTest.java b/zap/src/test/java/org/zaproxy/zap/authentication/VerificationMethodUnitTest.java new file mode 100644 index 00000000000..2907da78ca0 --- /dev/null +++ b/zap/src/test/java/org/zaproxy/zap/authentication/VerificationMethodUnitTest.java @@ -0,0 +1,506 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2026 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.zap.authentication; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.sameInstance; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.apache.commons.httpclient.URI; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.zap.authentication.AuthenticationMethod.AuthCheckingStrategy; +import org.zaproxy.zap.authentication.AuthenticationMethod.AuthPollFrequencyUnits; +import org.zaproxy.zap.users.AuthenticationState; +import org.zaproxy.zap.users.User; + +/** Unit test for {@link VerificationMethod}. */ +class VerificationMethodUnitTest { + + private VerificationMethod vm; + + @BeforeEach + void setUp() { + vm = new VerificationMethod(); + } + + @Test + void shouldHaveExpectedDefaults() { + assertThat(vm.getAuthCheckingStrategy(), is(AuthCheckingStrategy.POLL_URL)); + assertThat(vm.getPollFrequencyUnits(), is(AuthPollFrequencyUnits.SECONDS)); + assertThat(vm.getPollFrequency(), is(VerificationMethod.DEFAULT_POLL_FREQUENCY)); + assertThat(vm.getLoggedInIndicatorPattern(), is(nullValue())); + assertThat(vm.getLoggedOutIndicatorPattern(), is(nullValue())); + } + + @Nested + class SetAuthCheckingStrategy { + + @Test + void shouldThrowOnNull() { + assertThrows(NullPointerException.class, () -> vm.setAuthCheckingStrategy(null)); + } + } + + @Nested + class IndicatorPatterns { + + @Test + void shouldStoreNonEmptyLoggedInIndicator() { + vm.setLoggedInIndicatorPattern("loggedin"); + assertThat(vm.getLoggedInIndicatorPattern().pattern(), is("loggedin")); + } + + @Test + void shouldStoreNonEmptyLoggedOutIndicator() { + vm.setLoggedOutIndicatorPattern("loggedout"); + assertThat(vm.getLoggedOutIndicatorPattern().pattern(), is("loggedout")); + } + + @Test + void shouldClearLoggedInIndicatorWhenSetToNull() { + vm.setLoggedInIndicatorPattern("loggedin"); + vm.setLoggedInIndicatorPattern(null); + assertThat(vm.getLoggedInIndicatorPattern(), is(nullValue())); + } + + @Test + void shouldClearLoggedOutIndicatorWhenSetToNull() { + vm.setLoggedOutIndicatorPattern("loggedout"); + vm.setLoggedOutIndicatorPattern(null); + assertThat(vm.getLoggedOutIndicatorPattern(), is(nullValue())); + } + + @Test + void shouldClearLoggedInIndicatorWhenSetToBlank() { + vm.setLoggedInIndicatorPattern("loggedin"); + vm.setLoggedInIndicatorPattern(" "); + assertThat(vm.getLoggedInIndicatorPattern(), is(nullValue())); + } + + @Test + void shouldClearLoggedOutIndicatorWhenSetToBlank() { + vm.setLoggedOutIndicatorPattern("loggedout"); + vm.setLoggedOutIndicatorPattern(" "); + assertThat(vm.getLoggedOutIndicatorPattern(), is(nullValue())); + } + } + + @Nested + class IsAuthenticated { + + @Test + void shouldReturnFalseWhenMessageNull() { + User user = mock(User.class); + assertThat(vm.isAuthenticated(null, user), is(false)); + } + + @Test + void shouldReturnFalseWhenUserNull() throws Exception { + HttpMessage msg = new HttpMessage(new URI("http://example.com/", true)); + assertThat(vm.isAuthenticated(msg, null), is(false)); + } + + @Test + void shouldReturnFalseWhenStrategyIsAutoDetect() throws Exception { + vm.setAuthCheckingStrategy(AuthCheckingStrategy.AUTO_DETECT); + HttpMessage msg = new HttpMessage(new URI("http://example.com/", true)); + User user = mock(User.class); + assertThat(vm.isAuthenticated(msg, user), is(false)); + } + + @Test + void shouldReturnTrueWhenNoIndicatorsSet() throws Exception { + vm.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_RESP); + HttpMessage msg = new HttpMessage(new URI("http://example.com/", true)); + msg.setResponseBody("some body"); + User user = mock(User.class); + given(user.getAuthenticationState()).willReturn(new AuthenticationState()); + assertThat(vm.isAuthenticated(msg, user), is(true)); + } + + @Test + void shouldReturnTrueWhenResponseContainsLoggedInIndicator() throws Exception { + vm.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_RESP); + vm.setLoggedInIndicatorPattern("loggedin"); + HttpMessage msg = new HttpMessage(new URI("http://example.com/", true)); + msg.setResponseBody("The user is loggedin again."); + User user = mock(User.class); + given(user.getAuthenticationState()).willReturn(new AuthenticationState()); + assertThat(vm.isAuthenticated(msg, user), is(true)); + } + + @Test + void shouldReturnFalseWhenResponseContainsLoggedOutIndicator() throws Exception { + vm.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_RESP); + vm.setLoggedOutIndicatorPattern("loggedout"); + HttpMessage msg = new HttpMessage(new URI("http://example.com/", true)); + msg.setResponseBody("The user is loggedout this time."); + User user = mock(User.class); + given(user.getAuthenticationState()).willReturn(new AuthenticationState()); + assertThat(vm.isAuthenticated(msg, user), is(false)); + } + + @Test + void shouldReturnTrueWhenResponseLacksLoggedOutIndicator() throws Exception { + vm.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_RESP); + vm.setLoggedOutIndicatorPattern("loggedout"); + HttpMessage msg = new HttpMessage(new URI("http://example.com/", true)); + msg.setResponseBody("Welcome back, user."); + User user = mock(User.class); + given(user.getAuthenticationState()).willReturn(new AuthenticationState()); + assertThat(vm.isAuthenticated(msg, user), is(true)); + } + + @Test + void shouldReturnFalseWhenResponseLacksLoggedInIndicatorAndNoLoggedOutIndicatorSet() + throws Exception { + vm.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_RESP); + vm.setLoggedInIndicatorPattern("loggedin"); + HttpMessage msg = new HttpMessage(new URI("http://example.com/", true)); + msg.setResponseBody("Welcome, you are logged out."); + User user = mock(User.class); + given(user.getAuthenticationState()).willReturn(new AuthenticationState()); + assertThat(vm.isAuthenticated(msg, user), is(false)); + } + + @Test + void shouldReturnTrueWhenResponseHeaderContainsLoggedInIndicator() throws Exception { + vm.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_RESP); + vm.setLoggedInIndicatorPattern("loggedin"); + HttpMessage msg = new HttpMessage(new URI("http://example.com/", true)); + msg.getResponseHeader().addHeader("X-Test", "loggedin"); + User user = mock(User.class); + given(user.getAuthenticationState()).willReturn(new AuthenticationState()); + assertThat(vm.isAuthenticated(msg, user), is(true)); + } + + @Test + void shouldReturnFalseWhenResponseHeaderContainsLoggedOutIndicator() throws Exception { + vm.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_RESP); + vm.setLoggedOutIndicatorPattern("loggedout"); + HttpMessage msg = new HttpMessage(new URI("http://example.com/", true)); + msg.getResponseHeader().addHeader("X-Test", "loggedout"); + User user = mock(User.class); + given(user.getAuthenticationState()).willReturn(new AuthenticationState()); + assertThat(vm.isAuthenticated(msg, user), is(false)); + } + + @Test + void shouldReturnFalseWhenResponseMatchesComplexLoggedOutRegex() throws Exception { + vm.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_RESP); + vm.setLoggedOutIndicatorPattern("User [^\\s]* logged out"); + HttpMessage msg = new HttpMessage(new URI("http://example.com/", true)); + msg.setResponseBody("Some text. User Test logged out. More text."); + User user = mock(User.class); + given(user.getAuthenticationState()).willReturn(new AuthenticationState()); + assertThat(vm.isAuthenticated(msg, user), is(false)); + } + + @Test + void shouldReturnTrueWhenResponseDoesNotMatchComplexLoggedOutRegex() throws Exception { + vm.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_RESP); + vm.setLoggedOutIndicatorPattern("User [^\\s]* logged out"); + HttpMessage msg = new HttpMessage(new URI("http://example.com/", true)); + msg.setResponseBody("Welcome, you are logged out."); + User user = mock(User.class); + given(user.getAuthenticationState()).willReturn(new AuthenticationState()); + assertThat(vm.isAuthenticated(msg, user), is(true)); + } + + @Test + void shouldReturnTrueWhenRequestBodyContainsLoggedInIndicatorAndStrategyIsEachReq() + throws Exception { + vm.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_REQ); + vm.setLoggedInIndicatorPattern("loggedin"); + HttpMessage msg = new HttpMessage(new URI("http://example.com/", true)); + msg.setRequestBody("The user is loggedin again."); + User user = mock(User.class); + given(user.getAuthenticationState()).willReturn(new AuthenticationState()); + assertThat(vm.isAuthenticated(msg, user), is(true)); + } + + @Test + void shouldReturnTrueWhenRequestHeaderContainsLoggedInIndicatorAndStrategyIsEachReq() + throws Exception { + vm.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_REQ); + vm.setLoggedInIndicatorPattern("loggedin"); + HttpMessage msg = new HttpMessage(new URI("http://example.com/", true)); + msg.getRequestHeader().addHeader("X-Test", "loggedin"); + User user = mock(User.class); + given(user.getAuthenticationState()).willReturn(new AuthenticationState()); + assertThat(vm.isAuthenticated(msg, user), is(true)); + } + + @Test + void shouldReturnFalseWhenRequestBodyContainsLoggedOutIndicatorAndStrategyIsEachReq() + throws Exception { + vm.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_REQ); + vm.setLoggedOutIndicatorPattern("loggedout"); + HttpMessage msg = new HttpMessage(new URI("http://example.com/", true)); + msg.setRequestBody("The user is loggedout this time."); + User user = mock(User.class); + given(user.getAuthenticationState()).willReturn(new AuthenticationState()); + assertThat(vm.isAuthenticated(msg, user), is(false)); + } + } + + @Nested + class PollAsUser { + + @Test + void shouldThrowWhenStrategyIsNotPollUrl() { + vm.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_RESP); + User user = mock(User.class); + assertThrows(IllegalArgumentException.class, () -> vm.pollAsUser(user)); + } + + @Test + void shouldThrowWhenPollUrlIsNull() { + User user = mock(User.class); + IllegalArgumentException ex = + assertThrows(IllegalArgumentException.class, () -> vm.pollAsUser(user)); + assertThat(ex.getMessage(), is("Poll URL is not set")); + } + + @Test + void shouldThrowWhenPollUrlIsBlank() { + vm.setPollUrl(" "); + User user = mock(User.class); + IllegalArgumentException ex = + assertThrows(IllegalArgumentException.class, () -> vm.pollAsUser(user)); + assertThat(ex.getMessage(), is("Poll URL is not set")); + } + + @Test + void shouldInvokeUserDataReplacerBeforeSending() { + // Given + List replacedMessages = new ArrayList<>(); + vm.setPollUrl("http://127.0.0.1:1/"); + vm.setUserDataReplacer((msg, user) -> replacedMessages.add(msg)); + User user = mock(User.class); + // When + assertThrows(IOException.class, () -> vm.pollAsUser(user)); + // Then + assertThat(replacedMessages, hasSize(1)); + } + } + + @Nested + class Copy { + + @Test + void shouldCopyAllFields() { + vm.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_REQ); + vm.setPollUrl("http://example.com/poll"); + vm.setPollData("data=value"); + vm.setPollHeaders("X-Header: value"); + vm.setPollFrequency(30); + vm.setPollFrequencyUnits(AuthPollFrequencyUnits.REQUESTS); + vm.setLoggedInIndicatorPattern("loggedin"); + vm.setLoggedOutIndicatorPattern("loggedout"); + + VerificationMethod copy = vm.copy(null); + + assertThat(copy, is(not(sameInstance(vm)))); + assertThat(copy.getAuthCheckingStrategy(), is(AuthCheckingStrategy.EACH_REQ)); + assertThat(copy.getPollUrl(), is("http://example.com/poll")); + assertThat(copy.getPollData(), is("data=value")); + assertThat(copy.getPollHeaders(), is("X-Header: value")); + assertThat(copy.getPollFrequency(), is(30)); + assertThat(copy.getPollFrequencyUnits(), is(AuthPollFrequencyUnits.REQUESTS)); + assertThat(copy.getLoggedInIndicatorPattern().pattern(), is("loggedin")); + assertThat(copy.getLoggedOutIndicatorPattern().pattern(), is("loggedout")); + } + + @Test + void shouldBeIndependentOfOriginal() { + vm.setLoggedInIndicatorPattern("original"); + VerificationMethod copy = vm.copy(null); + + vm.setLoggedInIndicatorPattern("modified"); + + assertThat(copy.getLoggedInIndicatorPattern().pattern(), is("original")); + } + + @Test + void shouldSetProvidedUserDataReplacer() { + java.util.function.BiConsumer replacer = (msg, user) -> {}; + + VerificationMethod copy = vm.copy(replacer); + + assertThat(copy.getUserDataReplacer(), is(sameInstance(replacer))); + } + } + + @Nested + class Equals { + + @Test + void shouldBeEqualToItself() { + assertThat(vm.equals(vm), is(equalTo(true))); + } + + @Test + void shouldBeEqualToMethodWithSameContents() { + VerificationMethod other = createMethod("loggedin", "loggedout"); + vm = createMethod("loggedin", "loggedout"); + assertThat(vm.equals(other) & other.equals(vm), is(true)); + } + + @Test + void shouldBeEqualWhenBothIndicatorsNull() { + VerificationMethod other = new VerificationMethod(); + assertThat(vm.equals(other) & other.equals(vm), is(true)); + } + + @Test + void shouldBeEqualWhenLoggedInIndicatorNull() { + vm = createMethod(null, "loggedout"); + VerificationMethod other = createMethod(null, "loggedout"); + assertThat(vm.equals(other) & other.equals(vm), is(true)); + } + + @Test + void shouldBeEqualWhenLoggedOutIndicatorNull() { + vm = createMethod("loggedin", null); + VerificationMethod other = createMethod("loggedin", null); + assertThat(vm.equals(other) & other.equals(vm), is(true)); + } + + @Test + void shouldNotBeEqualToNull() { + assertThat(vm.equals(null), is(false)); + } + + @Test + void shouldNotBeEqualWhenLoggedInIndicatorDiffers() { + vm = createMethod("loggedinA", "loggedout"); + VerificationMethod other = createMethod("loggedinB", "loggedout"); + assertThat(vm.equals(other) | other.equals(vm), is(false)); + } + + @Test + void shouldNotBeEqualWhenOneLoggedInIndicatorIsNull() { + vm = createMethod("loggedin", "loggedout"); + VerificationMethod other = createMethod(null, "loggedout"); + assertThat(vm.equals(other) | other.equals(vm), is(false)); + } + + @Test + void shouldNotBeEqualWhenLoggedOutIndicatorDiffers() { + vm = createMethod("loggedin", "loggedoutA"); + VerificationMethod other = createMethod("loggedin", "loggedoutB"); + assertThat(vm.equals(other) | other.equals(vm), is(false)); + } + + @Test + void shouldNotBeEqualWhenOneLoggedOutIndicatorIsNull() { + vm = createMethod("loggedin", "loggedout"); + VerificationMethod other = createMethod("loggedin", null); + assertThat(vm.equals(other) | other.equals(vm), is(false)); + } + + @Test + void shouldNotBeEqualWhenStrategyDiffers() { + vm.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_RESP); + VerificationMethod other = new VerificationMethod(); + other.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_REQ); + assertThat(vm.equals(other) | other.equals(vm), is(false)); + } + + @Test + void shouldNotBeEqualWhenPollUrlDiffers() { + vm.setPollUrl("http://example.com/a"); + VerificationMethod other = new VerificationMethod(); + other.setPollUrl("http://example.com/b"); + assertThat(vm.equals(other) | other.equals(vm), is(false)); + } + + @Test + void shouldNotBeEqualWhenPollFrequencyDiffers() { + vm.setPollFrequency(10); + VerificationMethod other = new VerificationMethod(); + other.setPollFrequency(20); + assertThat(vm.equals(other) | other.equals(vm), is(false)); + } + + @Test + void shouldNotBeEqualWhenPollFrequencyUnitsDiffer() { + vm.setPollFrequencyUnits(AuthPollFrequencyUnits.REQUESTS); + VerificationMethod other = new VerificationMethod(); + other.setPollFrequencyUnits(AuthPollFrequencyUnits.SECONDS); + assertThat(vm.equals(other) | other.equals(vm), is(false)); + } + } + + @Nested + class HashCode { + + @Test + void shouldBeEqualForEqualMethods() { + vm = createMethod("loggedin", "loggedout"); + VerificationMethod other = createMethod("loggedin", "loggedout"); + assertThat(vm.hashCode(), is(equalTo(other.hashCode()))); + } + + @Test + void shouldBeEqualForEqualMethodsWithSamePollSettings() { + vm = createMethod("loggedin", "loggedout"); + vm.setAuthCheckingStrategy(AuthCheckingStrategy.POLL_URL); + vm.setPollUrl("http://example.com/poll"); + vm.setPollData("a=b"); + vm.setPollHeaders("X-Test: 1"); + vm.setPollFrequency(10); + vm.setPollFrequencyUnits(AuthPollFrequencyUnits.REQUESTS); + + VerificationMethod other = createMethod("loggedin", "loggedout"); + other.setAuthCheckingStrategy(AuthCheckingStrategy.POLL_URL); + other.setPollUrl("http://example.com/poll"); + other.setPollData("a=b"); + other.setPollHeaders("X-Test: 1"); + other.setPollFrequency(10); + other.setPollFrequencyUnits(AuthPollFrequencyUnits.REQUESTS); + + assertThat(vm.equals(other), is(true)); + assertThat(vm.hashCode(), is(equalTo(other.hashCode()))); + } + } + + private static VerificationMethod createMethod(String loggedIn, String loggedOut) { + VerificationMethod m = new VerificationMethod(); + m.setAuthCheckingStrategy(AuthCheckingStrategy.EACH_RESP); + m.setLoggedInIndicatorPattern(loggedIn); + m.setLoggedOutIndicatorPattern(loggedOut); + return m; + } +} diff --git a/zap/src/test/java/org/zaproxy/zap/extension/authentication/ExtensionAuthenticationUnitTest.java b/zap/src/test/java/org/zaproxy/zap/extension/authentication/ExtensionAuthenticationUnitTest.java index b6417890e0e..315c702e2a2 100644 --- a/zap/src/test/java/org/zaproxy/zap/extension/authentication/ExtensionAuthenticationUnitTest.java +++ b/zap/src/test/java/org/zaproxy/zap/extension/authentication/ExtensionAuthenticationUnitTest.java @@ -80,6 +80,7 @@ void shouldExportAllAuthContextData() { Context context = new Context(null, 0); String loggedInIndicator = "logged in"; String loggedOutIndicator = "logged out"; + String pollMethod = "Method"; String pollUrl = "https://www.example.com/poll"; String pollData = "example-poll-data"; String pollHeaders = "aaa : bbb\\Nccc : ddd"; @@ -87,15 +88,16 @@ void shouldExportAllAuthContextData() { FormBasedAuthenticationMethodType type = new FormBasedAuthenticationMethodType(); FormBasedAuthenticationMethod method = type.createAuthenticationMethod(0); - method.setAuthCheckingStrategy(AuthCheckingStrategy.POLL_URL); - method.setPollUrl(pollUrl); - method.setPollData(pollData); - method.setPollHeaders(pollHeaders); - method.setPollFrequencyUnits(AuthPollFrequencyUnits.REQUESTS); - method.setPollFrequency(pollFreq); - method.setLoggedInIndicatorPattern(loggedInIndicator); - method.setLoggedOutIndicatorPattern(loggedOutIndicator); context.setAuthenticationMethod(method); + context.getVerificationMethod().setAuthCheckingStrategy(AuthCheckingStrategy.POLL_URL); + context.getVerificationMethod().setPollMethod(pollMethod); + context.getVerificationMethod().setPollUrl(pollUrl); + context.getVerificationMethod().setPollData(pollData); + context.getVerificationMethod().setPollHeaders(pollHeaders); + context.getVerificationMethod().setPollFrequencyUnits(AuthPollFrequencyUnits.REQUESTS); + context.getVerificationMethod().setPollFrequency(pollFreq); + context.getVerificationMethod().setLoggedInIndicatorPattern(loggedInIndicator); + context.getVerificationMethod().setLoggedOutIndicatorPattern(loggedOutIndicator); Configuration config = new ZapXmlConfiguration(); // When @@ -106,6 +108,9 @@ void shouldExportAllAuthContextData() { assertThat( config.getString(AuthenticationMethod.CONTEXT_CONFIG_AUTH_STRATEGY), is(AuthCheckingStrategy.POLL_URL.name())); + assertThat( + config.getString(AuthenticationMethod.CONTEXT_CONFIG_AUTH_POLL_METHOD), + is(pollMethod)); assertThat( config.getString(AuthenticationMethod.CONTEXT_CONFIG_AUTH_POLL_URL), is(pollUrl)); assertThat( @@ -131,6 +136,7 @@ void shouldImportAllAuthContextData() throws ConfigurationException { Context context = new Context(null, 0); String loggedInIndicator = "logged in"; String loggedOutIndicator = "logged out"; + String pollMethod = "Method"; String pollUrl = "https://www.example.com/poll"; String pollData = "example-poll-data"; String pollHeaders = "aaa : bbb\\Nccc : ddd"; @@ -141,6 +147,7 @@ void shouldImportAllAuthContextData() throws ConfigurationException { config.setProperty( AuthenticationMethod.CONTEXT_CONFIG_AUTH_STRATEGY, AuthCheckingStrategy.POLL_URL.name()); + config.setProperty(AuthenticationMethod.CONTEXT_CONFIG_AUTH_POLL_METHOD, pollMethod); config.setProperty(AuthenticationMethod.CONTEXT_CONFIG_AUTH_POLL_URL, pollUrl); config.setProperty(AuthenticationMethod.CONTEXT_CONFIG_AUTH_POLL_DATA, pollData); config.setProperty(AuthenticationMethod.CONTEXT_CONFIG_AUTH_POLL_HEADERS, pollHeaders); @@ -156,20 +163,28 @@ void shouldImportAllAuthContextData() throws ConfigurationException { // When extensionAuthentication.importContextData(context, config); - AuthenticationMethod method = context.getAuthenticationMethod(); // Then assertThat( - method.getClass().getCanonicalName(), + context.getAuthenticationMethod().getClass().getCanonicalName(), is(FormBasedAuthenticationMethod.class.getCanonicalName())); - assertThat(method.getAuthCheckingStrategy(), is(AuthCheckingStrategy.POLL_URL)); - assertThat(method.getPollUrl(), is(pollUrl)); - assertThat(method.getPollData(), is(pollData)); - assertThat(method.getPollHeaders(), is(pollHeaders)); - assertThat(method.getPollFrequencyUnits(), is(AuthPollFrequencyUnits.REQUESTS)); - assertThat(method.getPollFrequency(), is(pollFreq)); - assertThat(method.getLoggedInIndicatorPattern().toString(), is(loggedInIndicator)); - assertThat(method.getLoggedOutIndicatorPattern().toString(), is(loggedOutIndicator)); + assertThat( + context.getVerificationMethod().getAuthCheckingStrategy(), + is(AuthCheckingStrategy.POLL_URL)); + assertThat(context.getVerificationMethod().getPollMethod(), is(pollMethod)); + assertThat(context.getVerificationMethod().getPollUrl(), is(pollUrl)); + assertThat(context.getVerificationMethod().getPollData(), is(pollData)); + assertThat(context.getVerificationMethod().getPollHeaders(), is(pollHeaders)); + assertThat( + context.getVerificationMethod().getPollFrequencyUnits(), + is(AuthPollFrequencyUnits.REQUESTS)); + assertThat(context.getVerificationMethod().getPollFrequency(), is(pollFreq)); + assertThat( + context.getVerificationMethod().getLoggedInIndicatorPattern().toString(), + is(loggedInIndicator)); + assertThat( + context.getVerificationMethod().getLoggedOutIndicatorPattern().toString(), + is(loggedOutIndicator)); } @Test @@ -189,14 +204,44 @@ void shouldImportContextWithNoPollData() throws ConfigurationException { // When extensionAuthentication.importContextData(context, config); - AuthenticationMethod method = context.getAuthenticationMethod(); // Then assertThat( - method.getClass().getCanonicalName(), + context.getAuthenticationMethod().getClass().getCanonicalName(), is(FormBasedAuthenticationMethod.class.getCanonicalName())); - assertThat(method.getAuthCheckingStrategy(), is(AuthCheckingStrategy.EACH_RESP)); - assertThat(method.getLoggedInIndicatorPattern().toString(), is(loggedInIndicator)); - assertThat(method.getLoggedOutIndicatorPattern().toString(), is(loggedOutIndicator)); + assertThat( + context.getVerificationMethod().getAuthCheckingStrategy(), + is(AuthCheckingStrategy.EACH_RESP)); + assertThat( + context.getVerificationMethod().getLoggedInIndicatorPattern().toString(), + is(loggedInIndicator)); + assertThat( + context.getVerificationMethod().getLoggedOutIndicatorPattern().toString(), + is(loggedOutIndicator)); + } + + @Test + void shouldImportContextWithPollUrlButNoStrategyAsPollUrl() throws ConfigurationException { + // Given + Context context = new Context(null, 0); + String pollUrl = "https://www.example.com/poll"; + String loggedInIndicator = "logged in"; + + Configuration config = new ZapXmlConfiguration(); + config.setProperty(AuthenticationMethod.CONTEXT_CONFIG_AUTH_TYPE, 2); + config.setProperty(AuthenticationMethod.CONTEXT_CONFIG_AUTH_POLL_URL, pollUrl); + config.setProperty(AuthenticationMethod.CONTEXT_CONFIG_AUTH_LOGGEDIN, loggedInIndicator); + + ExtensionHook hook = new ExtensionHook(Model.getSingleton(), null); + extensionAuthentication.hook(hook); + + // When + extensionAuthentication.importContextData(context, config); + + // Then + assertThat( + context.getVerificationMethod().getAuthCheckingStrategy(), + is(AuthCheckingStrategy.POLL_URL)); + assertThat(context.getVerificationMethod().getPollUrl(), is(pollUrl)); } } diff --git a/zap/src/test/java/org/zaproxy/zap/model/ContextUnitTest.java b/zap/src/test/java/org/zaproxy/zap/model/ContextUnitTest.java index 55487289b43..b3282d0ef73 100644 --- a/zap/src/test/java/org/zaproxy/zap/model/ContextUnitTest.java +++ b/zap/src/test/java/org/zaproxy/zap/model/ContextUnitTest.java @@ -22,6 +22,8 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.sameInstance; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; @@ -38,6 +40,7 @@ import org.parosproxy.paros.model.Session; import org.parosproxy.paros.model.SiteMap; import org.parosproxy.paros.model.SiteNode; +import org.zaproxy.zap.authentication.AuthenticationMethod.AuthCheckingStrategy; import org.zaproxy.zap.utils.I18N; /** Unit test for {@link Context}. */ @@ -178,4 +181,30 @@ void shouldExcludeNodesWithParamsFromContext() { assertThat(context.isExcluded(endNode), is(true)); assertThat(context.isInContext(endNode), is(false)); } + + @Test + void shouldKeepAuthenticationAndVerificationMethodsLinkedWhenDuplicated() { + // Given + context.getVerificationMethod().setAuthCheckingStrategy(AuthCheckingStrategy.EACH_RESP); + context.getVerificationMethod().setLoggedInIndicatorPattern("loggedin"); + context.getVerificationMethod().setPollUrl("http://example.com/poll"); + + // When + Context duplicate = context.duplicate(); + + // Then + assertThat( + duplicate.getVerificationMethod(), + is(sameInstance(duplicate.getAuthenticationMethod().getVerificationMethod()))); + assertThat( + duplicate.getVerificationMethod(), + is(not(sameInstance(context.getVerificationMethod())))); + assertThat( + duplicate.getVerificationMethod().getAuthCheckingStrategy(), + is(AuthCheckingStrategy.EACH_RESP)); + assertThat( + duplicate.getVerificationMethod().getLoggedInIndicatorPattern().pattern(), + is("loggedin")); + assertThat(duplicate.getVerificationMethod().getPollUrl(), is("http://example.com/poll")); + } }