org.zaproxy.zap.model.Context Maven / Gradle / Ivy
Show all versions of zap Show documentation
/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2012 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.model;
import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.swing.tree.TreeNode;
import org.apache.log4j.Logger;
import org.parosproxy.paros.core.scanner.Alert;
import org.parosproxy.paros.model.HistoryReference;
import org.parosproxy.paros.model.Session;
import org.parosproxy.paros.model.SiteMap;
import org.parosproxy.paros.model.SiteNode;
import org.parosproxy.paros.network.HttpRequestHeader;
import org.parosproxy.paros.view.View;
import org.zaproxy.zap.authentication.AuthenticationMethod;
import org.zaproxy.zap.authentication.ManualAuthenticationMethodType.ManualAuthenticationMethod;
import org.zaproxy.zap.extension.authorization.AuthorizationDetectionMethod;
import org.zaproxy.zap.extension.authorization.BasicAuthorizationDetectionMethod;
import org.zaproxy.zap.extension.authorization.BasicAuthorizationDetectionMethod.LogicalOperator;
import org.zaproxy.zap.session.CookieBasedSessionManagementMethodType.CookieBasedSessionManagementMethod;
import org.zaproxy.zap.session.SessionManagementMethod;
public class Context {
public static final String CONTEXT_CONFIG = "context";
public static final String CONTEXT_CONFIG_NAME = CONTEXT_CONFIG + ".name";
public static final String CONTEXT_CONFIG_DESC = CONTEXT_CONFIG + ".desc";
public static final String CONTEXT_CONFIG_INSCOPE = CONTEXT_CONFIG + ".inscope";
public static final String CONTEXT_CONFIG_INC_REGEXES = CONTEXT_CONFIG + ".incregexes";
public static final String CONTEXT_CONFIG_EXC_REGEXES = CONTEXT_CONFIG + ".excregexes";
public static final String CONTEXT_CONFIG_TECH = CONTEXT_CONFIG + ".tech";
public static final String CONTEXT_CONFIG_TECH_INCLUDE = CONTEXT_CONFIG_TECH + ".include";
public static final String CONTEXT_CONFIG_TECH_EXCLUDE = CONTEXT_CONFIG_TECH + ".exclude";
public static final String CONTEXT_CONFIG_URLPARSER = CONTEXT_CONFIG + ".urlparser";
public static final String CONTEXT_CONFIG_URLPARSER_CLASS = CONTEXT_CONFIG_URLPARSER + ".class";
public static final String CONTEXT_CONFIG_URLPARSER_CONFIG =
CONTEXT_CONFIG_URLPARSER + ".config";
public static final String CONTEXT_CONFIG_POSTPARSER = CONTEXT_CONFIG + ".postparser";
public static final String CONTEXT_CONFIG_POSTPARSER_CLASS =
CONTEXT_CONFIG_POSTPARSER + ".class";
public static final String CONTEXT_CONFIG_POSTPARSER_CONFIG =
CONTEXT_CONFIG_POSTPARSER + ".config";
public static final String CONTEXT_CONFIG_DATA_DRIVEN_NODES = CONTEXT_CONFIG + ".ddns";
private static Logger log = Logger.getLogger(Context.class);
private Session session;
private int id;
private String name;
private String description = "";
private List includeInRegexs = new ArrayList<>();
private List excludeFromRegexs = new ArrayList<>();
private List includeInPatterns = new ArrayList<>();
private List excludeFromPatterns = new ArrayList<>();
private List dataDrivenNodes = new ArrayList<>();
/** The authentication method. */
private AuthenticationMethod authenticationMethod = null;
/** The session management method. */
private SessionManagementMethod sessionManagementMethod;
/** The authorization detection method used for this context. */
private AuthorizationDetectionMethod authorizationDetectionMethod;
private TechSet techSet = new TechSet(Tech.builtInTech);
private boolean inScope = true;
private ParameterParser urlParamParser = new StandardParameterParser();
private ParameterParser postParamParser = new StandardParameterParser();
public Context(Session session, int id) {
this.session = session;
this.id = id;
this.name = String.valueOf(id);
this.sessionManagementMethod = new CookieBasedSessionManagementMethod(id);
this.authenticationMethod = new ManualAuthenticationMethod(id);
this.authorizationDetectionMethod =
new BasicAuthorizationDetectionMethod(null, null, null, LogicalOperator.AND);
this.urlParamParser.setContext(this);
this.postParamParser.setContext(this);
}
public boolean isIncludedInScope(SiteNode sn) {
if (!this.inScope) {
return false;
}
return this.isIncluded(sn);
}
public boolean isIncluded(SiteNode sn) {
if (sn == null) {
return false;
}
return isIncluded(sn.getHierarchicNodeName());
}
/*
* Not needed right now, but may be needed in the future? public boolean
* isExplicitlyIncluded(SiteNode sn) { if (sn == null) { return false; } return
* isExplicitlyIncluded(sn.getHierarchicNodeName()); }
*
* public boolean isExplicitlyIncluded(String url) { if (url == null) { return false; } try {
* return this.includeInPatterns.contains(this.getPatternUrl(url, false)) ||
* this.includeInPatterns.contains(this.getPatternUrl(url, false)); } catch (Exception e) {
* return false; } }
*/
public boolean isIncluded(String url) {
if (url == null) {
return false;
}
if (url.indexOf("?") > 0) {
// Strip off any parameters
url = url.substring(0, url.indexOf("?"));
}
for (Pattern p : this.includeInPatterns) {
if (p.matcher(url).matches()) {
return true;
}
}
return false;
}
public boolean isExcludedFromScope(SiteNode sn) {
if (!this.inScope) {
return false;
}
return this.isExcluded(sn);
}
public boolean isExcluded(SiteNode sn) {
if (sn == null) {
return false;
}
return isExcluded(sn.getHierarchicNodeName());
}
public boolean isExcluded(String url) {
if (url == null) {
return false;
}
if (url.indexOf("?") > 0) {
// Strip off any parameters
url = url.substring(0, url.indexOf("?"));
}
for (Pattern p : this.excludeFromPatterns) {
if (p.matcher(url).matches()) {
return true;
}
}
return false;
}
public boolean isInContext(HistoryReference href) {
if (href == null) {
return false;
}
if (href.getSiteNode() != null) {
return this.isInContext(href.getSiteNode());
}
try {
return this.isInContext(href.getURI().toString());
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return false;
}
public boolean isInContext(SiteNode sn) {
if (sn == null) {
return false;
}
return isInContext(sn.getHierarchicNodeName());
}
public boolean isInContext(String url) {
if (url.indexOf("?") > 0) {
// String off any parameters
url = url.substring(0, url.indexOf("?"));
}
if (!this.isIncluded(url)) {
// Not explicitly included
return false;
}
// Check to see if its explicitly excluded
return !this.isExcluded(url);
}
/**
* Gets the nodes from the site tree which are "In Scope". Searches recursively starting from
* the root node. Should be used with care, as it is time-consuming, querying the database for
* every node in the Site Tree.
*
* @return the nodes in scope from site tree
* @see #hasNodesInContextFromSiteTree()
*/
public List getNodesInContextFromSiteTree() {
List nodes = new LinkedList<>();
SiteNode rootNode = session.getSiteTree().getRoot();
fillNodesInContext(rootNode, nodes);
return nodes;
}
/**
* Tells whether or not there's at least one node from the sites tree in context.
*
* @return {@code true} if the context has at least one node from the sites tree in context,
* {@code false} otherwise
* @since 2.5.0
* @see #getNodesInContextFromSiteTree()
*/
public boolean hasNodesInContextFromSiteTree() {
return hasNodesInContext(session.getSiteTree().getRoot());
}
/**
* Tells whether or not there's at least one node from the sites tree in context.
*
* The whole tree is traversed until is found one node in context.
*
* @param node the node to check, recursively
* @return {@code true} if the context has at least one node from the sites tree in context,
* {@code false} otherwise
*/
private boolean hasNodesInContext(SiteNode node) {
@SuppressWarnings("unchecked")
Enumeration en = node.children();
while (en.hasMoreElements()) {
SiteNode sn = (SiteNode) en.nextElement();
if (isInContext(sn)) {
return true;
}
if (hasNodesInContext(sn)) {
return true;
}
}
return false;
}
/**
* Gets the nodes from the site tree which are "In Scope". Searches recursively starting from
* the root node. Should be used with care, as it is time-consuming, querying the database for
* every node in the Site Tree.
*
* @return the nodes in scope from site tree
*/
public List getTopNodesInContextFromSiteTree() {
List nodes = new LinkedList<>();
SiteNode rootNode = session.getSiteTree().getRoot();
@SuppressWarnings("unchecked")
Enumeration en = rootNode.children();
while (en.hasMoreElements()) {
SiteNode sn = (SiteNode) en.nextElement();
if (isContainsNodesInContext(sn)) {
nodes.add(sn);
}
}
return nodes;
}
/**
* Fills a given list with nodes in scope, searching recursively.
*
* @param rootNode the root node
* @param nodesList the nodes list
*/
private void fillNodesInContext(SiteNode rootNode, List nodesList) {
@SuppressWarnings("unchecked")
Enumeration en = rootNode.children();
while (en.hasMoreElements()) {
SiteNode sn = (SiteNode) en.nextElement();
if (isInContext(sn)) {
nodesList.add(sn);
}
fillNodesInContext(sn, nodesList);
}
}
/**
* Tells whether or not the given node or any of its child nodes are in context.
*
* @param node the node to start the check
* @return {@code true} if at least one node is in context, {@code false} otherwise
*/
private boolean isContainsNodesInContext(SiteNode node) {
if (isInContext(node)) {
return true;
}
@SuppressWarnings("unchecked")
Enumeration en = node.children();
while (en.hasMoreElements()) {
SiteNode sn = (SiteNode) en.nextElement();
if (isContainsNodesInContext(sn)) {
return true;
}
}
return false;
}
public List getIncludeInContextRegexs() {
return Collections.unmodifiableList(includeInRegexs);
}
/**
* Validates that the given regular expressions are not {@code null} nor invalid.
*
* @param regexs the regular expressions to be validated, must not be {@code null}
* @throws IllegalArgumentException if one of the regular expressions is {@code null}.
* @throws PatternSyntaxException if one of the regular expressions is invalid.
*/
private static void validateRegexs(List regexs) {
for (String regex : regexs) {
validateRegex(regex);
}
}
/**
* Validates that the given regular expression is not {@code null} nor invalid.
*
* @param regex the regular expression to be validated
* @throws IllegalArgumentException if the regular expression is {@code null}.
* @throws PatternSyntaxException if the regular expression is invalid.
*/
private static void validateRegex(String regex) {
if (regex == null) {
throw new IllegalArgumentException("The regular expression must not be null.");
}
String trimmedRegex = regex.trim();
if (!trimmedRegex.isEmpty()) {
Pattern.compile(trimmedRegex, Pattern.CASE_INSENSITIVE);
}
}
/**
* Sets the regular expressions used to include a URL in context.
*
* @param includeRegexs the regular expressions
* @throws IllegalArgumentException if one of the regular expressions is {@code null}.
* @throws PatternSyntaxException if one of the regular expressions is invalid.
*/
public void setIncludeInContextRegexs(List includeRegexs) {
validateRegexs(includeRegexs);
// Check if they've been changed
if (includeInRegexs.size() == includeRegexs.size()) {
boolean changed = false;
for (int i = 0; i < includeInRegexs.size(); i++) {
if (!includeInRegexs.get(i).equals(includeRegexs.get(i))) {
changed = true;
break;
}
}
if (!changed) {
// No point reapplying the same regexs
return;
}
}
includeInRegexs.clear();
includeInPatterns.clear();
for (String url : includeRegexs) {
url = url.trim();
if (url.length() > 0) {
Pattern p = Pattern.compile(url, Pattern.CASE_INSENSITIVE);
includeInRegexs.add(url);
includeInPatterns.add(p);
}
}
}
public void excludeFromContext(SiteNode sn, boolean recurse) throws Exception {
excludeFromContext(new StructuralSiteNode(sn), recurse);
}
public void excludeFromContext(StructuralNode sn, boolean recurse) throws Exception {
addExcludeFromContextRegex(sn.getRegexPattern(recurse));
}
public void addIncludeInContextRegex(String includeRegex) {
validateRegex(includeRegex);
includeInPatterns.add(Pattern.compile(includeRegex, Pattern.CASE_INSENSITIVE));
includeInRegexs.add(includeRegex);
}
public List getExcludeFromContextRegexs() {
return Collections.unmodifiableList(excludeFromRegexs);
}
/**
* Sets the regular expressions used to exclude a URL from the context.
*
* @param excludeRegexs the regular expressions
* @throws IllegalArgumentException if one of the regular expressions is {@code null}.
* @throws PatternSyntaxException if one of the regular expressions is invalid.
*/
public void setExcludeFromContextRegexs(List excludeRegexs) {
validateRegexs(excludeRegexs);
// Check if they've been changed
if (excludeFromRegexs.size() == excludeRegexs.size()) {
boolean changed = false;
for (int i = 0; i < excludeFromRegexs.size(); i++) {
if (!excludeFromRegexs.get(i).equals(excludeRegexs.get(i))) {
changed = true;
break;
}
}
if (!changed) {
// No point reapplying the same regexs
return;
}
}
excludeFromRegexs.clear();
excludeFromPatterns.clear();
for (String url : excludeRegexs) {
url = url.trim();
if (url.length() > 0) {
Pattern p = Pattern.compile(url, Pattern.CASE_INSENSITIVE);
excludeFromPatterns.add(p);
excludeFromRegexs.add(url);
}
}
}
public void addExcludeFromContextRegex(String excludeRegex) {
validateRegex(excludeRegex);
excludeFromPatterns.add(Pattern.compile(excludeRegex, Pattern.CASE_INSENSITIVE));
excludeFromRegexs.add(excludeRegex);
}
public void save() {
this.session.saveContext(this);
}
public TechSet getTechSet() {
return techSet;
}
public void setTechSet(TechSet techSet) {
this.techSet = techSet;
}
/**
* Gets the name of the context.
*
* @return the name of the context, never {@code null} (since 2.6.0).
*/
public String getName() {
return name;
}
/**
* Sets the name of the context.
*
* @param name the new name of the context
* @throws IllegalContextNameException (since 2.6.0) if the given name is {@code null} or empty.
*/
public void setName(String name) {
if (name == null || name.isEmpty()) {
throw new IllegalContextNameException(
IllegalContextNameException.Reason.EMPTY_NAME,
"The context name must not be null nor empty.");
}
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
/**
* Returns the ID of the {@code Context}
*
* @deprecated (2.9.0) Use {@link #getId()} instead.
*/
@Deprecated
public int getIndex() {
return getId();
}
/**
* Returns the ID of the {@code Context}
*
* @since 2.9.0
*/
public int getId() {
return this.id;
}
public boolean isInScope() {
return inScope;
}
public void setInScope(boolean inScope) {
this.inScope = inScope;
}
/**
* Gets the authentication method corresponding to this context.
*
* @return the authentication method
*/
public AuthenticationMethod getAuthenticationMethod() {
return authenticationMethod;
}
/**
* Sets the authentication method corresponding to this context.
*
* @param authenticationMethod the new authentication method
*/
public void setAuthenticationMethod(AuthenticationMethod authenticationMethod) {
this.authenticationMethod = authenticationMethod;
}
/**
* Gets the session management method corresponding to this context.
*
* @return the session management method
*/
public SessionManagementMethod getSessionManagementMethod() {
return sessionManagementMethod;
}
/**
* Sets the session management method corresponding to this context.
*
* @param sessionManagementMethod the new session management method
*/
public void setSessionManagementMethod(SessionManagementMethod sessionManagementMethod) {
this.sessionManagementMethod = sessionManagementMethod;
}
/**
* Gets the authorization detection method corresponding to this context.
*
* @return the authorization detection method
*/
public AuthorizationDetectionMethod getAuthorizationDetectionMethod() {
return authorizationDetectionMethod;
}
/**
* Sets the authorization detection method corresponding to this context.
*
* @param authorizationDetectionMethod the new authorization detectionmethod
*/
public void setAuthorizationDetectionMethod(
AuthorizationDetectionMethod authorizationDetectionMethod) {
this.authorizationDetectionMethod = authorizationDetectionMethod;
}
public ParameterParser getUrlParamParser() {
return urlParamParser;
}
public void setUrlParamParser(ParameterParser paramParser) {
this.urlParamParser = paramParser;
}
public ParameterParser getPostParamParser() {
return postParamParser;
}
public void setPostParamParser(ParameterParser postParamParser) {
this.postParamParser = postParamParser;
}
public void restructureSiteTree() {
if (!View.isInitialised() || EventQueue.isDispatchThread()) {
restructureSiteTreeEventHandler();
} else {
try {
EventQueue.invokeLater(
new Runnable() {
@Override
public void run() {
restructureSiteTreeEventHandler();
}
});
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
private void restructureSiteTreeEventHandler() {
log.debug("Restructure site tree for context: " + this.getName());
List nodes = this.getTopNodesInContextFromSiteTree();
for (SiteNode sn : nodes) {
checkNode(sn);
}
}
private boolean checkNode(SiteNode sn) {
// Loop backwards through the children TODO change for lowmem!
// log.debug("checkNode " + sn.getHierarchicNodeName()); // Useful for debugging
int origChildren = sn.getChildCount();
int movedChildren = 0;
for (SiteNode childNode : getChildren(sn)) {
if (checkNode(childNode)) {
movedChildren++;
}
}
if (this.isInContext(sn)) {
SiteMap sitesTree = this.session.getSiteTree();
HistoryReference href = sn.getHistoryReference();
try {
SiteNode sn2;
if (HttpRequestHeader.POST.equals(href.getMethod())) {
// Have to go to the db as POST data can be used in the name
sn2 = sitesTree.findNode(href.getHttpMessage());
} else {
// This is better as it doesn't require a db read
sn2 = sitesTree.findNode(href.getURI());
}
if (sn2 == null
|| !sn.getHierarchicNodeName().equals(sn2.getHierarchicNodeName())) {
if (!sn.isDataDriven()) {
moveNode(sitesTree, sn);
return true;
}
}
if (movedChildren > 0 && movedChildren == origChildren && sn.getChildCount() == 0) {
if (href.getHistoryType() == HistoryReference.TYPE_TEMPORARY) {
// Remove temp old node, no need to add new one in -
// that will happen when moving child nodes (if required)
deleteNode(sitesTree, sn);
return true;
}
}
// log.debug("Didn't need to move " + sn.getHierarchicNodeName()); // Useful for
// debugging
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
return false;
}
/**
* Gets the child nodes of the given site node.
*
* @param siteNode the site node that will be used, must not be {@code null}
* @return a {@code List} with the child nodes, never {@code null}
*/
private List getChildren(SiteNode siteNode) {
int childCount = siteNode.getChildCount();
if (childCount == 0) {
return Collections.emptyList();
}
List children = new ArrayList<>(childCount);
for (int i = 0; i < childCount; i++) {
children.add((SiteNode) siteNode.getChildAt(i));
}
return children;
}
private void moveNode(SiteMap sitesTree, SiteNode sn) {
List alerts = sn.getAlerts();
// And delete the old one
deleteNode(sitesTree, sn);
// Add into the right place
SiteNode sn2 = sitesTree.addPath(sn.getHistoryReference());
log.debug(
"Moved node " + sn.getHierarchicNodeName() + " to " + sn2.getHierarchicNodeName());
// And sort out the alerts
for (Alert alert : alerts) {
sn2.addAlert(alert);
}
}
private void deleteNode(SiteMap sitesTree, SiteNode sn) {
log.debug("Deleting node " + sn.getHierarchicNodeName());
sn.deleteAlerts(sn.getAlerts());
// Remove old one
sitesTree.removeNodeFromParent(sn);
sitesTree.removeHistoryReference(sn.getHistoryReference().getHistoryId());
}
public List getDataDrivenNodes() {
List ddns = new ArrayList<>(this.dataDrivenNodes.size());
for (StructuralNodeModifier ddn : this.dataDrivenNodes) {
ddns.add(ddn.clone());
}
return ddns;
}
public void setDataDrivenNodes(List dataDrivenNodes) {
this.dataDrivenNodes = dataDrivenNodes;
}
public void addDataDrivenNodes(StructuralNodeModifier ddn) {
this.dataDrivenNodes.add(ddn.clone());
}
public String getDefaultDDNName() {
int i = 1;
while (true) {
boolean found = false;
String name = "DDN" + i;
for (StructuralNodeModifier ddn : this.dataDrivenNodes) {
if (ddn.getName().equals(name)) {
found = true;
break;
}
}
if (!found) {
return name;
}
i++;
}
}
/**
* Creates a copy of the Context. The copy is deep, with the exception of the TechSet.
*
* @return the context
*/
public Context duplicate() {
Context newContext = new Context(session, getId());
newContext.description = this.description;
newContext.name = this.name;
newContext.includeInRegexs = new ArrayList<>(this.includeInRegexs);
newContext.includeInPatterns = new ArrayList<>(this.includeInPatterns);
newContext.excludeFromRegexs = new ArrayList<>(this.excludeFromRegexs);
newContext.excludeFromPatterns = new ArrayList<>(this.excludeFromPatterns);
newContext.inScope = this.inScope;
newContext.techSet = new TechSet(this.techSet);
newContext.authenticationMethod = this.authenticationMethod.clone();
newContext.sessionManagementMethod = this.sessionManagementMethod.clone();
newContext.urlParamParser = this.urlParamParser.clone();
newContext.postParamParser = this.postParamParser.clone();
newContext.authorizationDetectionMethod = this.authorizationDetectionMethod.clone();
newContext.dataDrivenNodes = this.getDataDrivenNodes();
return newContext;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
Context other = (Context) obj;
if (id != other.id) return false;
return true;
}
}