protected static final String ZAP_DEFAULT_CONTEXT_ID = "1"; public class ZapHelper {

	private static final Logger LOGGER = LoggerFactory.getLogger(ZapHelper.class);
	
	private static final String ZAP_SUCCESS_RESPONSE = "OK";
	private static final String ZAP_DEFAULT_CONTEXT_NAME = "Default Context";
	
	public static String extractResponse(ApiResponse response) {
		return ((ApiResponseElement) response).getValue();
	}
	
	public static void validateResponse(ApiResponse response, String operationDescription) {
		String responseValue = extractResponse(response);
		if (!responseValue.equals(ZAP_SUCCESS_RESPONSE)) {
			String message = "ZAP API did not respond '" + ZAP_SUCCESS_RESPONSE + "' during the following operation: "
					 + operationDescription + ". Actual response: " + responseValue;
			LOGGER.error(message);
			throw new ZapClientException(message);
		}
	}
	
	public static void includeInContext(ClientApi api, String apiKey, AnalysisInfo analysisInfo) {
		String[] context = analysisInfo.getContext();
		LOGGER.debug("Including target '{}' in context.", Arrays.toString(context));
		
		try {
			for (String contextUrl : context) {
				if (isContextUrlRelative(contextUrl)) {
					contextUrl = resolveContextUrl(contextUrl, analysisInfo);
				}
				ApiResponse response = api.context.includeInContext(apiKey, ZAP_DEFAULT_CONTEXT_NAME, "\\Q" + contextUrl + "\\E.*");
				validateResponse(response, "Include target in context.");
			}
		} catch (ClientApiException e) {
			LOGGER.error("Error including target in context.", e);
			throw new ZapClientException(e);
		}
	}
	
	private static boolean isContextUrlRelative(String contextUrl) {
		return !contextUrl.startsWith("http");
	}
	
	private static String resolveContextUrl(String contextUrl, AnalysisInfo analysisInfo) {
		String activeScanStartingPointUrl = analysisInfo.getActiveScanStartingPointUrl();
		if (activeScanStartingPointUrl.endsWith("/")) {
			activeScanStartingPointUrl = activeScanStartingPointUrl.substring(0, activeScanStartingPointUrl.length()-1);
		}
		if (contextUrl.startsWith("/")) {
			contextUrl = contextUrl.substring(1);
		}
		return activeScanStartingPointUrl + "/" + contextUrl;
	}
	
	public static void setTechnologiesInContext(ClientApi api, String apiKey, AnalysisInfo analysisInfo) {
		String technologies = analysisInfo.getTechnologiesSeparatedByComma();
		if (technologies == null) {
			return;
		}
		LOGGER.debug("Setting technologies in context: {}.", technologies);
		
		try {
			ApiResponse responseFromExcludingTechnologies = api.context.excludeAllContextTechnologies(apiKey, ZAP_DEFAULT_CONTEXT_NAME);
			validateResponse(responseFromExcludingTechnologies, "Exclude all context technologies.");
			
			ApiResponse responseFromIncludingTechnologies = api.context.includeContextTechnologies(apiKey, ZAP_DEFAULT_CONTEXT_NAME, technologies);
			validateResponse(responseFromIncludingTechnologies, "Exclude all context technologies.");
		} catch (ClientApiException e) {
			LOGGER.error("Error setting technologies in context.", e);
			throw new ZapClientException(e);
		}
	}
	
	private ZapHelper() {}

} public class ZapClientException extends RuntimeException {

	private static final long serialVersionUID = -4867749606526224619L;
	
	public ZapClientException(String message) {
		super(message);
	}
	
	public ZapClientException(Throwable e) {
		super(e);
	}
	
	public ZapClientException(String message, Throwable e) {
		super(message, e);
	}
	
} public class AnalysisInfo {

	private static final long DEFAULT_ANALYSIS_TIMEOUT_IN_MINUTES = 480;
	private static final AnalysisType DEFAULT_ANALYSIS_TYPE = AnalysisType.WITH_SPIDER;
	private static final boolean DEFAULT_SHOULD_START_NEW_SESSION = true;
	
	private String targetUrl;
	private String spiderStartingPointUrl;
	private String activeScanStartingPointUrl;
	private String[] context;
	private String[] technologies;
	private String technologiesSeparatedByComma;
	
	private long analysisTimeoutInMinutes;
	private AnalysisType analysisType;
	private boolean shouldStartNewSession;
	
	public static Builder builder() {
		return new Builder();
	}
	
	public String getTargetUrl() {
		return targetUrl;
	}
	
	public String getSpiderStartingPointUrl() {
		if (spiderStartingPointUrl != null) {
			return spiderStartingPointUrl;
		}
		return targetUrl;
	}

	public String getActiveScanStartingPointUrl() {
		if (activeScanStartingPointUrl != null) {
			return activeScanStartingPointUrl;
		}
		return targetUrl;
	}
	
	public String[] getContext() {
		if (context != null && context.length > 0) {
			return context;
		}
		return new String[]{targetUrl};
	}

	public String[] getTechnologies() {
		return technologies;
	}
	
	public String getTechnologiesSeparatedByComma() {
		return technologiesSeparatedByComma;
	}
	
	public long getAnalysisTimeoutInMillis() {
		return TimeUnit.MILLISECONDS.convert(analysisTimeoutInMinutes, TimeUnit.MINUTES);
	}
	
	public long getAnalysisTimeoutInMinutes() {
		return analysisTimeoutInMinutes;
	}

	public AnalysisType getAnalysisType() {
		return analysisType;
	}
	
	public boolean shouldStartNewSession() {
		return shouldStartNewSession;
	}
	
	public static class Builder {
		
		private String targetUrl;
		private String spiderStartingPointUrl;
		private String activeScanStartingPointUrl;
		private String[] context;
		private String[] technologies;
		private String technologiesSeparatedByComma;
		private long analysisTimeoutInMinutes = DEFAULT_ANALYSIS_TIMEOUT_IN_MINUTES;
		private AnalysisType analysisType = DEFAULT_ANALYSIS_TYPE;
		private boolean shouldStartNewSession = DEFAULT_SHOULD_START_NEW_SESSION;
		
		/**
		 * Sets the target URL.
		 * 
		 * @param targetUrl URL of the application that will be analyzed (e.g. {@code http://myapp.com}).
		 * @return this {@code Builder} instance.
		 */
		public Builder targetUrl(String targetUrl) {
			this.targetUrl = targetUrl;
			return this;
		}
		
		/**
		 * Sets the starting point URL for the Spider (and AJAX Spider, in case it runs).
		 * 
		 * @param spiderStartingPointUrl the starting point URL for the Spiders (default: {@code targetUrl}). 
		 * @return this {@code Builder} instance.
		 */
		public Builder spiderStartingPointUrl(String spiderStartingPointUrl) {
			this.spiderStartingPointUrl = spiderStartingPointUrl;
			return this;
		}
		
		/**
		 * Sets the starting point URL for the Active Scan.
		 * 
		 * @param activeScanStartingPointUrl the starting point URL for the Active Scan (default: {@code targetUrl}). 
		 * @return this {@code Builder} instance.
		 */
		public Builder activeScanStartingPointUrl(String activeScanStartingPointUrl) {
			this.activeScanStartingPointUrl = activeScanStartingPointUrl;
			return this;
		}
		
		/**
		 * Sets the URLs to be set as the context of ZAP.
		 * 
		 * @param context an array of URLs (absolute or relative) to be set on ZAP's context.
		 * @return this {@code Builder} instance.
		 */
		public Builder context(String... context) {
			this.context = context;
			return this;
		}
		
		/**
		 * Sets the technologies that will be considered during the scan.
		 * The default behavior is to consider all the technologies.
		 * 
		 * @param technologies an array of technologies to be considered during the scan.
		 * @return this {@code Builder} instance.
		 */
		public Builder technologies(String... technologies) {
			this.technologies = technologies;
			if (technologies != null) {
				technologiesSeparatedByComma = StringUtils.join(technologies, ","); 
			} else {
				technologiesSeparatedByComma = null;
			}
			return this;
		}
		
		/**
		 * Sets the analysis timeout in minutes.
		 * 
		 * @param analysisTimeoutInMinutes the timeout in minutes for the analysis (default: {@code 480}). 
		 * @return this {@code Builder} instance.
		 */
		public Builder analysisTimeoutInMinutes(long analysisTimeoutInMinutes) {
			this.analysisTimeoutInMinutes = analysisTimeoutInMinutes;
			return this;
		}
		
		/**
		 * Sets the analysis type.
		 * 
		 * @param analysisType the analysis type indicating which analysis should be done:
		 * <ul>
		 *     <li><b>WITH_SPIDER</b>: default analysis, which runs the Spider before runnning the Active Scan.</li>
	  	 *     <li><b>WITH_AJAX_SPIDER</b>: after running the default Spider, the AJAX Spider is executed before the Active Scan.
	 	 *                                  This is useful for applications that rely on AJAX.</li>
	 	 *     <li><b>ACTIVE_SCAN_ONLY</b>: no Spider is executed before the Active Scan. This is useful in case the application
	 	 *                                  navigation is done via proxy (proxied Selenium tests, for instance).</li>
	 	 *     <li><b>SPIDER_ONLY</b>: </li> no Active Scan is executed after the Spider. This is useful when on wants to run the
	 	 *                                   passive scan only.
	 	 *     <li><b>SPIDER_AND_AJAX_SPIDER_ONLY</b>: Just like the previous, but including the AJAX Spider.</li>
	 	 * </ul> 
		 * @return this {@code Builder} instance.
		 */
		public Builder analysisType(AnalysisType analysisType) {
			this.analysisType = analysisType;
			return this;
		}
		
		/**
		 * Sets the analysis type.
		 * 
		 * @param analysisType the analysis type as a string (case-insensitive) indicating which analysis should be done:
		 * <ul>
		 *     <li><b>WITH_SPIDER</b>: default analysis, which runs the Spider before runnning the Active Scan.</li>
	  	 *     <li><b>WITH_AJAX_SPIDER</b>: after running the default Spider, the AJAX Spider is executed before the Active Scan.
	 	 *                                  This is useful for applications that rely on AJAX.</li>
	 	 *     <li><b>ACTIVE_SCAN_ONLY</b>: no Spider is executed before the Active Scan. This is useful in case the application
	 	 *                                  navigation is done via proxy (proxied Selenium tests, for instance).</li>
	 	 *     <li><b>SPIDER_ONLY</b>: </li> no Active Scan is executed after the Spider. This is useful when on wants to run the
	 	 *                                   passive scan only.
	 	 *     <li><b>SPIDER_AND_AJAX_SPIDER_ONLY</b>: Just like the previous, but including the AJAX Spider.</li>
	 	 * </ul> 
		 * @return this {@code Builder} instance.
		 */
		public Builder analysisType(String analysisType) {
			if (analysisType != null) {
				this.analysisType = AnalysisType.valueOf(analysisType.toUpperCase());
			}
			return this;
		}
		
		/**
		 * Sets whether a new session should be started on ZAP before the analysis.
		 * 
		 * @param shouldStartNewSession {@code true} if a new session on ZAP should be started before the analysis,
		 *                              {@code false} otherwise (default: {@code true}). 
		 * @return this {@code Builder} instance.
		 */
		public Builder shouldStartNewSession(boolean shouldStartNewSession) {
			this.shouldStartNewSession = shouldStartNewSession;
			return this;
		}
		
		/**
		 * Builds a {@link AnalysisInfo} instance based on the builder parameters.
		 * 
		 * @return a {@link AnalysisInfo} instance.
		 */
		public AnalysisInfo build() {
			return new AnalysisInfo(this);
		}
		
	}
	
	private AnalysisInfo(Builder builder) {
		this.targetUrl                    = builder.targetUrl;
		this.spiderStartingPointUrl       = builder.spiderStartingPointUrl;
		this.activeScanStartingPointUrl   = builder.activeScanStartingPointUrl;
		this.context                      = builder.context;
		this.technologies                 = builder.technologies;
		this.technologiesSeparatedByComma = builder.technologiesSeparatedByComma;
		this.analysisTimeoutInMinutes     = builder.analysisTimeoutInMinutes;
		this.analysisType                 = builder.analysisType;
		this.shouldStartNewSession        = builder.shouldStartNewSession;
	}
	
	@Override
	public String toString() {
		return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
				.append("targetUrl", targetUrl)
				.append("spiderStartingPointUrl", spiderStartingPointUrl)
				.append("activeScanStartingPointUrl", activeScanStartingPointUrl)
				.append("context", Arrays.toString(context))
				.append("technologies", Arrays.toString(technologies))
				.append("analysisTimeoutInMinutes", analysisTimeoutInMinutes)
				.append("analysisType", analysisType)
				.append("shouldStartNewSession", shouldStartNewSession)
				.toString();
	}
	
} public final class AuthenticationInfo {

	private static final String DEFAULT_USERNAME_PARAMETER = "username";
	private static final String DEFAULT_PASSWORD_PARAMETER = "password";
	private static final String DEFAULT_LOGIN_REQUEST_DATA = DEFAULT_USERNAME_PARAMETER + "={%username%}&" + DEFAULT_PASSWORD_PARAMETER + "={%password%}";
	private static final SeleniumDriver DEFAULT_SELENIUM_DRIVER = SeleniumDriver.FIREFOX;
	
	private AuthenticationType type;
	private String loginUrl;
	private String username;
	private String password;
	private String extraPostData;
	private String loggedInRegex;
	private String loggedOutRegex;
	private String[] excludeFromScan;
	private String[] protectedPages;
	private String protectedPagesSeparatedByComma;
	private String loginRequestData;
	private String usernameParameter;
	private String passwordParameter;
	private String[] httpSessionTokens;
	private SeleniumDriver seleniumDriver;
	private String hostname;
	private String realm;
	private int port;

	public static Builder builder() {
		return new Builder();
	}
	
	public AuthenticationType getType() {
		return type;
	}

	public String getLoginUrl() {
		return loginUrl;
	}

	public String getUsername() {
		return username;
	}

	public String getPassword() {
		return password;
	}
	
	public String getExtraPostData() {
		return extraPostData;
	}
	
	public String getFullLoginRequestData() {
		if (extraPostData != null) {
			return loginRequestData + "&" + extraPostData; 			
		}
		return loginRequestData;
	}

	public String getLoggedInRegex() {
		return loggedInRegex;
	}
	
	public String getLoggedOutRegex() {
		return loggedOutRegex;
	}

	public String[] getExcludeFromScan() {
		return excludeFromScan;
	}
	
	public String[] getProtectedPages() {
		return protectedPages;
	}

	public String getProtectedPagesSeparatedByComma() {
		return protectedPagesSeparatedByComma;
	}
	
	public String getLoginRequestData() {
		return loginRequestData;
	}
	
	public String getUsernameParameter() {
		return usernameParameter;
	}
	
	public String getPasswordParameter() {
		return passwordParameter;
	}
	
	public String[] getHttpSessionTokens() {
		return httpSessionTokens;
	}
	
	public SeleniumDriver getSeleniumDriver() {
		return seleniumDriver;
	}
	
	public String getHostname() {
		return hostname;
	}

	public String getRealm() {
		return realm;
	}
	
	public int getPort() {
		return port;
	}
	
	public String getPortAsString() {
		return String.valueOf(port);
	}
	
	public static class Builder {
		
		private AuthenticationType type;
		private String loginUrl;
		private String username;
		private String password;
		private String extraPostData;
		private String loggedInRegex;
		private String loggedOutRegex;
		private String[] excludeFromScan;
		private String[] protectedPages;
		private String protectedPagesSeparatedByComma;
		private String loginRequestData = DEFAULT_LOGIN_REQUEST_DATA;
		private String usernameParameter = DEFAULT_USERNAME_PARAMETER;
		private String passwordParameter = DEFAULT_PASSWORD_PARAMETER;
		private String[] httpSessionTokens;
		private SeleniumDriver seleniumDriver = DEFAULT_SELENIUM_DRIVER;
		private String hostname;
		private String realm;
		private int port;
		
		/**
		 * Builds an {@code AuthenticationInfo} instance with the minimum information required for HTTP authentication.
		 * 
		 * @param hostname the host name for HTTP authentication.
		 * @param realm the realm for HTTP authentication.
		 * @return the built {@code AuthenticationInfo} instance.
		 */
		public AuthenticationInfo buildHttpAuthenticationInfo(String username, String password, String hostname, String realm) {
			return type(AuthenticationType.HTTP)
					.username(username)
					.password(password)
					.hostname(hostname)
					.realm(realm)
					.build();
		}
		
		/**
		 * Builds an {@code AuthenticationInfo} instance with the minimum information required for form authentication.
		 * 
		 * @param loginUrl the login URL (i.e. {@code http://myapp.com/login}).
		 * @param username the username that will be authenticated.
		 * @param password the user's password.
		 * @return the built {@code AuthenticationInfo} instance.
		 */
		public AuthenticationInfo buildFormAuthenticationInfo(String loginUrl, String username, String password) {
			return type(AuthenticationType.FORM)
					.loginUrl(loginUrl)
					.username(username)
					.password(password)
					.build();
		}
		
		/**
		 * Builds an {@code AuthenticationInfo} instance with the minimum information required for CAS authentication.
		 * <p>
		 * The {@code protectedPages} parameter is needed so we can access them after the authentication and before ZAP's
		 * analysis. This is required to avoid circular redirections during the scan, not supported by ZAP.
		 * 
		 * @param loginUrl the login URL (i.e. {@code http://myapp.com/login}).
		 * @param username the username that will be authenticated.
		 * @param password the user's password.
		 * @param protectedPages an array of URLs with at least one protected page for each context that will be analyzed.
		 * @return the built {@code AuthenticationInfo} instance.
		 */
		public AuthenticationInfo buildCasAuthenticationInfo(String loginUrl, String username, String password, String... protectedPages) {
			return type(AuthenticationType.CAS)
					.loginUrl(loginUrl)
					.username(username)
					.password(password)
					.protectedPages(protectedPages)
					.build();
		}
		
		/**
		 * Builds an {@code AuthenticationInfo} instance with the minimum information required for Selenium authentication.
		 * 
		 * @param loginUrl the login URL (i.e. {@code http://myapp.com/login}).
		 * @param username the username that will be authenticated.
		 * @param password the user's password.
		 * @return the built {@code AuthenticationInfo} instance.
		 */
		public AuthenticationInfo buildSeleniumAuthenticationInfo(String loginUrl, String username, String password) {
			return type(AuthenticationType.SELENIUM)
					.loginUrl(loginUrl)
					.username(username)
					.password(password)
					.build();
		}
		
		/**
		 * Sets the {@link AuthenticationType}.
		 * 
		 * @param type the {@link AuthenticationType} ({@code HTTP}, {@code CAS}, {@code FORM} or {@code SELENIUM}).
		 * @return this {@code Builder} instance.
		 */
		public Builder type(AuthenticationType type) {
			this.type = type;
			return this;
		}
		
		/**
		 * Sets the {@link AuthenticationType}.
		 * 
		 * @param type the {@link AuthenticationType} as a string ({@code HTTP}, {@code CAS}, {@code FORM} or {@code SELENIUM}, case-insensitive).
		 * @return this {@code Builder} instance.
		 */
		public Builder type(String type) {
			if (type != null) {
				this.type = AuthenticationType.valueOf(type.toUpperCase());
			}
			return this;
		}
		
		/**
		 * Sets the login URL. <b>This is a required parameter.</b>
		 * 
		 * @param loginUrl the login URL (i.e. {@code http://myapp.com/login}).
		 * @return this {@code Builder} instance.
		 */
		public Builder loginUrl(String loginUrl) {
			this.loginUrl = loginUrl;
			return this;
		}
		
		/**
		 * Sets the username. <b>This is a required parameter.</b>
		 * 
		 * @param username the username that will be authenticated.
		 * @return this {@code Builder} instance.
		 */
		public Builder username(String username) {
			this.username = username;
			return this;
		}
		
		/**
		 * Sets the password. <b>This is a required parameter.</b>
		 * 
		 * @param password the user's password.
		 * @return this {@code Builder} instance.
		 */
		public Builder password(String password) {
			this.password = password;
			return this;
		}
		
		/**
		 * Sets any extra post data needed for the authentication.
		 * 
		 * @param extraPostData extra post data that will be sent in the login request (i.e. {@code domain=someDomain&param=value}).
		 * @return this {@code Builder} instance.
		 */
		public Builder extraPostData(String extraPostData) {
			this.extraPostData = extraPostData;
			return this;
		}
		
		/**
		 * Sets the logged in regex, thus enabling reauthentication.
		 * <p>
		 * This regex should match logged in response messages, so ZAP is able reauthenticate
		 * in case it logs itself out during the scan.
		 * <p>
		 * <b>There is no need to set both {@code loggedInRegex} and {@code loggedOutRegex} options, only one is enough.</b>
		 * 
		 * @param loggedInRegex the regex that matches logged in response messages.
		 * @return this {@code Builder} instance.
		 */
		public Builder loggedInRegex(String loggedInRegex) {
			this.loggedInRegex = loggedInRegex;
			return this;
		}
		
		/**
		 * Sets the logged out regex, thus enabling reauthentication.
		 * <p>
		 * This regex should match logged in response messages, so ZAP is able reauthenticate
		 * in case it logs itself out during the scan.
		 * <p>
		 * <b>There is no need to set both {@code loggedInRegex} and {@code loggedOutRegex} options, only one is enough.</b>
		 * 
		 * @param loggedOutRegex the regex that matches logged out response messages.
		 * @return this {@code Builder} instance.
		 */
		public Builder loggedOutRegex(String loggedOutRegex) {
			this.loggedOutRegex = loggedOutRegex;
			return this;
		}
		
		/**
		 * Sets the URLs that will not be scanned.
		 * 
		 * @param excludeFromScan an array of URLs to be excluded from the scan.
		 * @return this {@code Builder} instance.
		 */
		public Builder excludeFromScan(String... excludeFromScan) {
			this.excludeFromScan = excludeFromScan;
			return this;
		}
		
		/**
		 * <b>This is a required parameter for CAS authentication.</b> 
		 * <p>
		 * For CAS authentication, the login is done directly at the CAS server. So, when accessing the app
		 * for the first time, the user will be redirected to the CAS server, which will redirect the user back to the app
		 * since the user is authenticated. ZAP doesn't support circular redirection, thus it can't handle this process.
		 * <p>
		 * The URLs defined here will be accessed once after the authentication and before the scan, triggering the
		 * circular redirections and avoiding them to happen during the analysis.
		 * 
		 * @param protectedPages an array of URLs with at least one protected page for each context that will be analyzed.
		 * @return this {@code Builder} instance.
		 */
		public Builder protectedPages(String... protectedPages) {
			this.protectedPages = protectedPages;
			if (protectedPages != null) {
				protectedPagesSeparatedByComma = StringUtils.join(protectedPages, ","); 
			} else {
				protectedPagesSeparatedByComma = null;
			}
			return this;
		}
		
		/**
		 * Sets the login request data used for form authentication.
		 * 
		 * @param loginRequestData the login request data for form based authentication 
		 *                         (default: <code>username=&#123;%username%&#125;&amp;password=&#123;%password%&#125;</code>).
		 * @return this {@code Builder} instance.
		 */
		public Builder loginRequestData(String loginRequestData) {
			if (loginRequestData != null) {
				this.loginRequestData = loginRequestData;
			}
			return this;
		}
		
		/**
		 * Builds the login request data based on the {@code usernameParameter} 
		 * and {@code passwordParameter} current values.
		 * 
		 * @return this {@code Builder} instance.
		 */
		public Builder loginRequestData() {
			this.loginRequestData = usernameParameter + "={%username%}&" + passwordParameter + "={%password%}";
			return this;
		}
		
		/**
		 * Sets the username parameter name.
		 * 
		 * @param usernameParameter the username parameter that holds the username (default: {@code username}).
		 * @return this {@code Builder} instance.
		 */
		public Builder usernameParameter(String usernameParameter) {
			this.usernameParameter = usernameParameter;
			return this;
		}
		
		/**
		 * Sets the password parameter name.
		 * 
		 * @param passwordParameter the password parameter that holds the password (default: {@code password}).
		 * @return this {@code Builder} instance.
		 */
		public Builder passwordParameter(String passwordParameter) {
			this.passwordParameter = passwordParameter;
			return this;
		}
		
		/**
		 * Includes additional HTTP session tokens to ZAP.  
		 * 
		 * @param httpSessionTokens the session tokens to be added to ZAP.
		 * @return this {@code Builder} instance.
		 */
		public Builder httpSessionTokens(String... httpSessionTokens) {
			if (httpSessionTokens != null) {
				this.httpSessionTokens = httpSessionTokens;
			}
			return this;
		}
		
		/**
		 * Sets the Selenium web driver that will be used to perform authentication.
		 * 
		 * @param seleniumDriver the Selenium web driver to be used during authentication. 
		 * @return this {@code Builder} instance.
		 */
		public Builder seleniumDriver(SeleniumDriver seleniumDriver) {
			this.seleniumDriver = seleniumDriver;
			return this;
		}
		
		/**
		 * Sets the Selenium web driver that will be used to perform authentication.
		 * 
		 * @param seleniumDriver the Selenium web driver as a string (case-insensitive) to be used during authentication. 
		 * @return this {@code Builder} instance.
		 */
		public Builder seleniumDriver(String seleniumDriver) {
			if (seleniumDriver != null) {
				this.seleniumDriver = SeleniumDriver.valueOf(seleniumDriver.toUpperCase());
			}
			return this;
		}
		
		/**
		 * Sets the host name for HTTP authentication.
		 * 
		 * @param hostname the host name for HTTP authentication. 
		 * @return this {@code Builder} instance.
		 */
		public Builder hostname(String hostname) {
			this.hostname = hostname;
			return this;
		}
		
		/**
		 * Sets the realm for HTTP authentication.
		 * 
		 * @param realm the realm for HTTP authentication. 
		 * @return this {@code Builder} instance.
		 */
		public Builder realm(String realm) {
			this.realm = realm;
			return this;
		}
		
		/**
		 * Sets the port for HTTP authentication.
		 * 
		 * @param port the port for HTTP authentication. 
		 * @return this {@code Builder} instance.
		 */
		public Builder port(int port) {
			this.port = port;
			return this;
		}
		
		/**
		 * Validates and builds an {@code AuthenticationInfo} instance based on the builder parameters.
		 * 
		 * @return a {@code AuthenticationInfo} instance.
		 */
		public AuthenticationInfo build() {
			AuthenticationInfo authenticationInfo = new AuthenticationInfo(this);
			AuthenticationInfoValidator.validate(authenticationInfo);
			return authenticationInfo;
		}
		
	}
	
	private AuthenticationInfo(Builder builder) {
		this.type                           = builder.type;
		this.loginUrl                       = builder.loginUrl;
		this.username                       = builder.username;
		this.password                       = builder.password;
		this.extraPostData                  = builder.extraPostData;
		this.loggedInRegex                  = builder.loggedInRegex;
		this.loggedOutRegex                 = builder.loggedOutRegex;
		this.excludeFromScan                = builder.excludeFromScan;
		this.protectedPages                 = builder.protectedPages;
		this.protectedPagesSeparatedByComma = builder.protectedPagesSeparatedByComma;
		this.loginRequestData               = builder.loginRequestData;
		this.usernameParameter              = builder.usernameParameter;
		this.passwordParameter              = builder.passwordParameter;
		this.httpSessionTokens              = builder.httpSessionTokens;
		this.seleniumDriver                 = builder.seleniumDriver;
		this.hostname                       = builder.hostname;
		this.realm                          = builder.realm;
		this.port                           = builder.port;
	}
	
	@Override
	public String toString() {
		return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
				.append("type", type.name())
				.append("loginUrl", loginUrl)
				.append("username", username)
				.append("password", password)
				.append("extraPostData", extraPostData)
				.append("loggedInRegex", loggedInRegex)
				.append("loggedOutRegex", loggedOutRegex)
				.append("excludeFromScan", excludeFromScan)
				.append("protectedPages", protectedPages)
				.append("loginRequestData", loginRequestData)
				.append("usernameParameter", usernameParameter)
				.append("passwordParameter", passwordParameter)
				.append("httpSessionTokens", httpSessionTokens)
				.append("seleniumDriver", seleniumDriver)
				.append("hostname", hostname)
				.append("realm", realm)
				.append("port", port)
				.toString();
	}

} public class BaseIT {

	protected ZapInfo zapInfo;
	protected ZapClient zapClient;
	protected ClientApi api;
	
	@BeforeClass
	public void init() {
		zapInfo   = ZapInfo.builder().buildToUseRunningZap(ZapProperties.getHost(), ZapProperties.getPort());
		zapClient = new ZapClient(zapInfo);
		api       = new ClientApi(zapInfo.getHost(), zapInfo.getPort());
	}
	
} public abstract class ApiResponse {

	private String name = null;

	public ApiResponse(String name) {
		super();
		this.name = name;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public abstract String toString(int indent);

} public class ApiResponseElement extends ApiResponse {
	
	public static ApiResponseElement OK = new ApiResponseElement("Result", "OK"); 
	public static ApiResponseElement FAIL = new ApiResponseElement("Result", "FAIL"); 
	
	private String value = null;

	public ApiResponseElement(String name) {
		super(name);
	}

	public ApiResponseElement(String name, String value) {
		super(name);
		this.value = value;
	}

	public ApiResponseElement(Node node, ApiResponse template) {
		super(node.getNodeName());
		this.value = node.getTextContent();

	}

	public ApiResponseElement(Node node) {
		super(node.getNodeName());
		this.value = node.getTextContent();
	}

	public String getValue() {
		return value;
	}

	@Override
	public String toString(int indent) {
		StringBuilder sb = new StringBuilder();
		for (int i=0 ; i < indent; i++) {
			sb.append("\t");
		}
		sb.append("ApiResponseElement ");
		sb.append(this.getName());
		sb.append(" = " );
		sb.append(this.getValue());
		sb.append("\n");
		return sb.toString();
	}

} public class ApiResponseList extends ApiResponse {
	
	private List<ApiResponse> list = null;

	public ApiResponseList(String name) {
		super(name);
		this.list = new ArrayList<ApiResponse>();
	}

	public ApiResponseList(Node node) 
			throws ClientApiException {
		this(node.getNodeName());
		Node child = node.getFirstChild();
		while (child != null) {
			this.addItem(ApiResponseFactory.getResponse(child));
			child = child.getNextSibling();
		}
	}

	public ApiResponseList(Node node, ApiResponseList template) 
			throws ClientApiException {
		super(node.getNodeName());
		try {
			this.list = new ArrayList<ApiResponse>();
			Class<? extends ApiResponse> clazz = template.getItemsClass();
			if (clazz != null) {

				Node child = node.getFirstChild();
				while (child != null) {
					Constructor<? extends ApiResponse> cons = clazz.getConstructor(Node.class, ApiResponse.class);
					this.addItem(cons.newInstance(child, template.list.get(0)));
					child = child.getNextSibling();
				}
			}
		} catch (Exception e) {
			throw new ClientApiException(e);
		}
	}

	public ApiResponseList(String name, ApiResponse[] array) {
		super(name);
		this.list = new ArrayList<ApiResponse>();
		for (ApiResponse resp: array) {
			list.add(resp);
		}
	}

	public ApiResponseList(String name, List<ApiResponse> list) {
		super(name);
		this.list = list;
	}
	
	public void addItem(ApiResponse item) {
		this.list.add(item);
	}
	
	public List<ApiResponse> getItems() {
		return this.list;
	}
	
	public Class<? extends ApiResponse> getItemsClass() {
		if (this.list == null || this.list.size() == 0) {
			return null;
		}
		return this.list.get(0).getClass();
	}

	@Override
	public String toString(int indent) {
		StringBuilder sb = new StringBuilder();
		for (int i=0 ; i < indent; i++) {
			sb.append("\t");
		}
		sb.append("ApiResponseList ");
		sb.append(this.getName());
		sb.append(" : [\n");
		for (ApiResponse resp: this.list) {
			sb.append(resp.toString(indent+1));
		}
		for (int i=0 ; i < indent; i++) {
			sb.append("\t");
		}
		sb.append("]\n");
		return sb.toString();
	}

	
} public class ApiResponseSet extends ApiResponse {
	
	private String[] attributes = null;
	private Map<String, String> values = null;

	public ApiResponseSet(String name, String[] attributes) {
		super(name);
		this.attributes = attributes;
	}

	public ApiResponseSet(String name, Map<String, String> values) {
		super(name);
		this.values = values;
	}

	public ApiResponseSet(Node node) throws ClientApiException {
		super(node.getNodeName());
		Node child = node.getFirstChild();
		this.values = new HashMap<String, String>();
		while (child != null) {
			ApiResponseElement elem = (ApiResponseElement) ApiResponseFactory.getResponse(child);
			values.put(elem.getName(), elem.getValue());
			child = child.getNextSibling();
		}
	}

	public String[] getAttributes() {
		return attributes;
	}
	
	public String getAttribute(String name) {
		return this.values.get(name);
	}

	@Override
	public String toString(int indent) {
		StringBuilder sb = new StringBuilder();
		for (int i=0 ; i < indent; i++) {
			sb.append("\t");
		}
		sb.append("ApiResponseSet ");
		sb.append(this.getName());
		sb.append(" : [\n");
		for (Entry<String, String> val  : values.entrySet()) {
			for (int i=0 ; i < indent+1; i++) {
				sb.append("\t");
			}
			sb.append(val.getKey());
			sb.append(" = ");
			sb.append(val.getValue());
			sb.append("\n");
		}
		for (int i=0 ; i < indent; i++) {
			sb.append("\t");
		}
		sb.append("]\n");
		return sb.toString();
	}

	
} public class ClientApiException extends Exception {

	private static final long serialVersionUID = 1L;
	
	private final String code;
	private final String detail;
	
	public ClientApiException(Exception e) {
		super(e);
		this.code = null;
		this.detail = null;
	}

	public ClientApiException(String s) {
		super(s);
		this.code = null;
		this.detail = null;
	}

	public ClientApiException(String message, String code, String detail) {
		super(message);
		this.code = code;
		this.detail = detail;
	}

	public String getCode() {
		return code;
	}

	public String getDetail() {
		return detail;
	}
	
}
import static br.com.softplan.security.zap.api.authentication.AbstractAuthenticationHandler.ZAP_DEFAULT_CONTEXT_ID;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import org.testng.annotations.Test;
import br.com.softplan.security.zap.api.ZapHelper;
import br.com.softplan.security.zap.api.exception.ZapClientException;
import br.com.softplan.security.zap.api.model.AnalysisInfo;
import br.com.softplan.security.zap.api.model.AuthenticationInfo;
import br.com.softplan.security.zap.api.util.BaseIT;
import br.com.softplan.security.zap.zaproxy.clientapi.core.ApiResponse;
import br.com.softplan.security.zap.zaproxy.clientapi.core.ApiResponseElement;
import br.com.softplan.security.zap.zaproxy.clientapi.core.ApiResponseList;
import br.com.softplan.security.zap.zaproxy.clientapi.core.ApiResponseSet;
import br.com.softplan.security.zap.zaproxy.clientapi.core.ClientApiException;
package br.com.softplan.security.zap.api.authentication;




public class AbstractAuthenticationHandlerIT extends BaseIT {

	@Test
	public void includeTargetInContextTest() throws ClientApiException {
		String targetUrl = "http://targetUrl";
		ZapHelper.includeInContext(api, zapInfo.getApiKey(), AnalysisInfo.builder().targetUrl(targetUrl).build());
		
		ApiResponseElement response = (ApiResponseElement) api.context.includeRegexs("Default Context");
		String includedInContext = response.getValue();
		assertTrue(includedInContext.contains("\\Q" + targetUrl + "\\E"));
	}
	
	@Test
	public void excludeUrlsFromScannerTest() throws ClientApiException {
		String apiKey = "";
		api.spider.clearExcludedFromScan(apiKey);
		api.ascan.clearExcludedFromScan(apiKey);
		
		AuthenticationInfo authenticationInfo = AuthenticationInfo.builder()
				.excludeFromScan(new String[]{"excludeFromScan1", "excludeFromScan2"})
				.buildFormAuthenticationInfo("loginUrl", "username", "password");
		
		AbstractAuthenticationHandler nilHandler = new NilAuthenticationHandler(api, zapInfo, authenticationInfo);
		
		nilHandler.excludeUrlsFromScanners();
		
		checkExcludedUrlsFromResponse(authenticationInfo, (ApiResponseList) api.spider.excludedFromScan());
		checkExcludedUrlsFromResponse(authenticationInfo, (ApiResponseList) api.ascan.excludedFromScan());
	}

	private void checkExcludedUrlsFromResponse(AuthenticationInfo authenticationInfo, ApiResponseList response) {
		assertTrue(response.getItems().size() == authenticationInfo.getExcludeFromScan().length);
		
		String excludedUrl1 = ((ApiResponseElement) response.getItems().get(0)).getValue();
		String excludedUrl2 = ((ApiResponseElement) response.getItems().get(1)).getValue();
		
		assertEquals(excludedUrl1, "\\Q" + authenticationInfo.getExcludeFromScan()[0] + "\\E");
		assertEquals(excludedUrl2, "\\Q" + authenticationInfo.getExcludeFromScan()[1] + "\\E");
	}
	
	@Test
	public void setupLoggedInAndOutRegexTest() throws ClientApiException {
		AuthenticationInfo authenticationInfo = AuthenticationInfo.builder()
					.loggedInRegex("loggedInRegex")
					.loggedOutRegex("loggedOutRegex")
					.buildFormAuthenticationInfo("loginUrl", "username", "password");
		AbstractAuthenticationHandler nilHandler = new NilAuthenticationHandler(api, zapInfo, authenticationInfo);
		
		nilHandler.setupLoggedInAndOutRegex();
		
		ApiResponseElement responseIn = (ApiResponseElement) api.authentication.getLoggedInIndicator(ZAP_DEFAULT_CONTEXT_ID);
		ApiResponseElement responseOut = (ApiResponseElement) api.authentication.getLoggedOutIndicator(ZAP_DEFAULT_CONTEXT_ID);
		
		String loggedInRegex = responseIn.getValue();
		String loggedOutRegex = responseOut.getValue();
		
		assertEquals(loggedInRegex, authenticationInfo.getLoggedInRegex());
		assertEquals(loggedOutRegex, authenticationInfo.getLoggedOutRegex());
	}
	
	@Test
	public void createAndEnableUserTest() throws ClientApiException {
		AuthenticationInfo authenticationInfo = AuthenticationInfo.builder()
				.buildFormAuthenticationInfo("loginUrl", "username", "password");
		
		AbstractAuthenticationHandler nilHandler = new NilAuthenticationHandler(api, zapInfo, authenticationInfo);
		
		String userId = nilHandler.createAndEnableUser();
		
		ApiResponseSet response = (ApiResponseSet) api.users.getUserById(ZAP_DEFAULT_CONTEXT_ID, userId);