/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* 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.api;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import net.sf.json.JSONObject;
import org.apache.log4j.Logger;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.model.Model;
import org.zaproxy.zap.authentication.AuthenticationMethod;
import org.zaproxy.zap.authentication.AuthenticationMethodType;
import org.zaproxy.zap.extension.api.ApiResponseElement;
import org.zaproxy.zap.extension.api.ApiException.Type;
import org.zaproxy.zap.extension.authorization.AuthorizationDetectionMethod;
import org.zaproxy.zap.model.Context;
import org.zaproxy.zap.model.IllegalContextNameException;
import org.zaproxy.zap.model.Tech;
import org.zaproxy.zap.model.TechSet;
import org.zaproxy.zap.utils.ApiUtils;
public class ContextAPI extends ApiImplementor {
private static final Logger log = Logger.getLogger(ContextAPI.class);
private static final String PREFIX = "context";
private static final String TECH_NAME = "technologyName";
private static final String EXCLUDE_FROM_CONTEXT_REGEX = "excludeFromContext";
private static final String INCLUDE_IN_CONTEXT_REGEX = "includeInContext";
private static final String ACTION_NEW_CONTEXT = "newContext";
private static final String ACTION_REMOVE_CONTEXT = "removeContext";
private static final String ACTION_SET_CONTEXT_IN_SCOPE = "setContextInScope";
private static final String ACTION_EXPORT_CONTEXT = "exportContext";
private static final String ACTION_IMPORT_CONTEXT = "importContext";
private static final String ACTION_INCLUDE_TECHS = "includeContextTechnologies";
private static final String ACTION_INCLUDE_ALL_TECHS = "includeAllContextTechnologies";
private static final String ACTION_EXCLUDE_TECHS = "excludeContextTechnologies";
private static final String ACTION_EXCLUDE_ALL_TECHS = "excludeAllContextTechnologies";
private static final String VIEW_EXCLUDE_REGEXS = "excludeRegexs";
private static final String VIEW_INCLUDE_REGEXS = "includeRegexs";
private static final String VIEW_CONTEXT_LIST = "contextList";
private static final String VIEW_CONTEXT = "context";
private static final String VIEW_ALL_TECHS = "technologyList";
private static final String VIEW_INCLUDED_TECHS = "includedTechnologyList";
private static final String VIEW_EXCLUDED_TECHS = "excludedTechnologyList";
private static final String REGEX_PARAM = "regex";
private static final String CONTEXT_NAME = "contextName";
private static final String IN_SCOPE = "booleanInScope";
private static final String CONTEXT_FILE_PARAM = "contextFile";
private static final String CONTEXT_ID = "contextId";
private static final String PARAM_TECH_NAMES = "technologyNames";
public ContextAPI() {
List<String> contextNameAndRegexParam = new ArrayList<>(2);
contextNameAndRegexParam.add(CONTEXT_NAME);
contextNameAndRegexParam.add(REGEX_PARAM);
List<String> contextNameOnlyParam = new ArrayList<>(1);
contextNameOnlyParam.add((CONTEXT_NAME));
String[] contextNameAndTechNames = new String[] { CONTEXT_NAME, PARAM_TECH_NAMES };
this.addApiAction(new ApiAction(EXCLUDE_FROM_CONTEXT_REGEX, contextNameAndRegexParam));
this.addApiAction(new ApiAction(INCLUDE_IN_CONTEXT_REGEX, contextNameAndRegexParam));
this.addApiAction(new ApiAction(ACTION_NEW_CONTEXT, contextNameOnlyParam));
this.addApiAction(new ApiAction(ACTION_REMOVE_CONTEXT, contextNameOnlyParam));
this.addApiAction(new ApiAction(ACTION_EXPORT_CONTEXT, new String[] {CONTEXT_NAME, CONTEXT_FILE_PARAM}, null));
this.addApiAction(new ApiAction(ACTION_IMPORT_CONTEXT, new String[] {CONTEXT_FILE_PARAM}, null));
this.addApiAction(new ApiAction(ACTION_INCLUDE_TECHS, contextNameAndTechNames));
this.addApiAction(new ApiAction(ACTION_INCLUDE_ALL_TECHS, contextNameOnlyParam));
this.addApiAction(new ApiAction(ACTION_EXCLUDE_TECHS, contextNameAndTechNames));
this.addApiAction(new ApiAction(ACTION_EXCLUDE_ALL_TECHS, contextNameOnlyParam));
List<String> contextInScopeParams = new ArrayList<>(2);
contextInScopeParams.add(CONTEXT_NAME);
contextInScopeParams.add(IN_SCOPE);
this.addApiAction(new ApiAction(ACTION_SET_CONTEXT_IN_SCOPE, contextInScopeParams));
this.addApiView(new ApiView(VIEW_CONTEXT_LIST));
this.addApiView(new ApiView(VIEW_EXCLUDE_REGEXS, contextNameOnlyParam));
this.addApiView(new ApiView(VIEW_INCLUDE_REGEXS, contextNameOnlyParam));
this.addApiView(new ApiView(VIEW_CONTEXT, contextNameOnlyParam));
this.addApiView(new ApiView(VIEW_ALL_TECHS));
this.addApiView(new ApiView(VIEW_INCLUDED_TECHS, contextNameOnlyParam));
this.addApiView(new ApiView(VIEW_EXCLUDED_TECHS, contextNameOnlyParam));
}
@Override
public String getPrefix() {
return PREFIX;
}
@Override
public ApiResponse handleApiAction(String name, JSONObject params) throws ApiException {
log.debug("handleApiAction " + name + " " + params.toString());
Context context;
TechSet techSet;
String[] techNames;
String filename;
File f;
switch(name) {
case EXCLUDE_FROM_CONTEXT_REGEX:
try {
addExcludeToContext(getContext(params), params.getString(REGEX_PARAM));
} catch (IllegalArgumentException e) {
throw new ApiException(ApiException.Type.ILLEGAL_PARAMETER, REGEX_PARAM, e);
}
break;
case INCLUDE_IN_CONTEXT_REGEX:
try {
addIncludeToContext(getContext(params), params.getString(REGEX_PARAM));
} catch (IllegalArgumentException e) {
throw new ApiException(ApiException.Type.ILLEGAL_PARAMETER, REGEX_PARAM, e);
}
break;
case ACTION_NEW_CONTEXT:
String contextName = params.getString(CONTEXT_NAME);
try {
context = Model.getSingleton().getSession().getNewContext(contextName);
} catch (IllegalContextNameException e) {
throw new ApiException(ApiException.Type.ALREADY_EXISTS, contextName, e);
}
Model.getSingleton().getSession().saveContext(context);
return new ApiResponseElement(CONTEXT_ID, String.valueOf(context.getIndex()));
case ACTION_REMOVE_CONTEXT:
context = getContext(params);
Model.getSingleton().getSession().deleteContext(context);
break;
case ACTION_SET_CONTEXT_IN_SCOPE:
context = getContext(params);
context.setInScope(params.getBoolean(IN_SCOPE));
Model.getSingleton().getSession().saveContext(context);
break;
case ACTION_IMPORT_CONTEXT:
filename = params.getString(CONTEXT_FILE_PARAM);
f = new File(filename);
if (! f.exists()) {
// Try relative to the contexts dir
f = new File(Constant.getContextsDir(), filename);
}
if (! f.exists()) {
throw new ApiException(ApiException.Type.DOES_NOT_EXIST, f.getAbsolutePath());
} else {
try {
context = Model.getSingleton().getSession().importContext(f);
} catch (IllegalContextNameException e) {
throw new ApiException(ApiException.Type.BAD_EXTERNAL_DATA, e);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new ApiException(ApiException.Type.INTERNAL_ERROR, e.getMessage());
}
}
return new ApiResponseElement(CONTEXT_ID, String.valueOf(context.getIndex()));
case ACTION_EXPORT_CONTEXT:
filename = params.getString(CONTEXT_FILE_PARAM);
context = getContext(params);
f = new File(filename);
if (! f.getAbsolutePath().equals(filename)) {
// Not an absolute filename, use one relative to the contexts dir
f = new File(Constant.getContextsDir(), filename);
}
if (! f.getParentFile().canWrite()) {
// Cant write to the parent dir so not looking good
throw new ApiException(ApiException.Type.NO_ACCESS, f.getAbsolutePath());
} else {
try {
Model.getSingleton().getSession().exportContext(context, f);
} catch (Exception e) {
throw new ApiException(ApiException.Type.INTERNAL_ERROR, e.getMessage());
}
}
break;
case ACTION_INCLUDE_TECHS:
context = getContext(params);
techSet = context.getTechSet();
techNames = getParam(params, PARAM_TECH_NAMES, "").split(",");
for (String techName : techNames) {
techSet.include(getTech(techName));
}
context.save();
break;
case ACTION_INCLUDE_ALL_TECHS:
context = getContext(params);
techSet = new TechSet(Tech.builtInTech);
context.setTechSet(techSet);
context.save();
break;
case ACTION_EXCLUDE_TECHS:
context = getContext(params);
techSet = context.getTechSet();
techNames = getParam(params, PARAM_TECH_NAMES, "").split(",");
for(String techName : techNames) {
techSet.exclude(getTech(techName));
}
context.save();
break;
case ACTION_EXCLUDE_ALL_TECHS:
context = getContext(params);
techSet = context.getTechSet();
for (Tech tech : Tech.builtInTech) {
techSet.exclude(tech);
}
context.save();
break;
default:
throw new ApiException(Type.BAD_ACTION);
}
return ApiResponseElement.OK;
}
private void addExcludeToContext(Context context, String regex) {
List<String> incRegexes = new ArrayList<String>(context.getIncludeInContextRegexs());
if (incRegexes.remove(regex)) {
// Its already explicitly included, removing it from the include list is safer and more useful
context.setIncludeInContextRegexs(incRegexes);
} else {
context.addExcludeFromContextRegex(regex);
}
Model.getSingleton().getSession().saveContext(context);
}
private void addIncludeToContext(Context context, String regex) {
context.addIncludeInContextRegex(regex);
Model.getSingleton().getSession().saveContext(context);
}
@Override
public ApiResponse handleApiView(String name, JSONObject params)
throws ApiException {
log.debug("handleApiView " + name + " " + params.toString());
ApiResponse result;
ApiResponseList resultList;
TechSet techSet;
switch(name) {
case VIEW_EXCLUDE_REGEXS:
result = new ApiResponseElement(name, getContext(params).getExcludeFromContextRegexs().toString());
break;
case VIEW_INCLUDE_REGEXS:
result = new ApiResponseElement(name, getContext(params).getIncludeInContextRegexs().toString());
break;
case VIEW_CONTEXT_LIST:
List<String> contextNames = new ArrayList<>();
List<Context> contexts = Model.getSingleton().getSession().getContexts();
for (Context context : contexts){
contextNames.add(context.getName());
}
result = new ApiResponseElement(name, contextNames.toString());
break;
case VIEW_CONTEXT:
result = new ApiResponseElement(buildResponseFromContext(getContext(params)));
break;
case VIEW_ALL_TECHS:
resultList = new ApiResponseList(name);
for(Tech tech : Tech.builtInTech) {
resultList.addItem(new ApiResponseElement(TECH_NAME, tech.toString()));
}
result = resultList;
break;
case VIEW_INCLUDED_TECHS:
resultList = new ApiResponseList(name);
techSet = getContext(params).getTechSet();
for(Tech tech : techSet.getIncludeTech()) {
resultList.addItem(new ApiResponseElement(TECH_NAME, tech.toString()));
}
result = resultList;
break;
case VIEW_EXCLUDED_TECHS:
resultList = new ApiResponseList(name);
techSet = getContext(params).getTechSet();
for(Tech tech : techSet.getExcludeTech()) {
resultList.addItem(new ApiResponseElement(TECH_NAME, tech.toString()));
}
result = resultList;
break;
default:
throw new ApiException(Type.BAD_VIEW);
}
return result;
}
/**
* Returns the {@code Context} with the given name. The context's name is obtained from the given parameters, whose name is
* {@value #CONTEXT_NAME}.
* <p>
* The parameter must exist, that is, it should be a mandatory parameter, otherwise a runtime exception is thrown.
*
* @param params the parameters that contain the context's name
* @return the {@code Context} with the given name
* @throws ApiException If the context with the given name does not exist
* @see JSONObject#getString(String)
*/
private Context getContext(JSONObject params) throws ApiException {
return ApiUtils.getContextByName(params, CONTEXT_NAME);
}
/**
* Builds the response describing an Context.
*
* @param c the context
* @return the api response
*/
private ApiResponse buildResponseFromContext(Context c) {
Map<String, String> fields = new HashMap<>();
fields.put("name", c.getName());
fields.put("id", Integer.toString(c.getIndex()));
fields.put("description", c.getDescription());
fields.put("inScope", Boolean.toString(c.isInScope()));
fields.put("excludeRegexs", c.getExcludeFromContextRegexs().toString());
fields.put("includeRegexs", c.getIncludeInContextRegexs().toString());
AuthenticationMethod authenticationMethod = c.getAuthenticationMethod();
if(authenticationMethod != null){
Pattern pattern = authenticationMethod.getLoggedInIndicatorPattern();
fields.put("loggedInPattern", pattern == null ? "" : pattern.toString());
pattern = authenticationMethod.getLoggedOutIndicatorPattern();
fields.put("loggedOutPattern", pattern == null ? "" : pattern.toString());
AuthenticationMethodType type = authenticationMethod.getType();
fields.put("authType", type == null ? "" : type.getName());
}
AuthorizationDetectionMethod authorizationDetectionMethod = c.getAuthorizationDetectionMethod();
if(authorizationDetectionMethod != null){
fields.put("authenticationDetectionMethodId",String.valueOf(authorizationDetectionMethod.getMethodUniqueIdentifier()));
}
fields.put("urlParameterParserClass", c.getUrlParamParser().getClass().getCanonicalName());
fields.put("urlParameterParserConfig", c.getUrlParamParser().getConfig());
fields.put("postParameterParserClass", c.getPostParamParser().getClass().getCanonicalName());
fields.put("postParameterParserConfig", c.getPostParamParser().getConfig());
return new ApiResponseSet<String>("context", fields);
}
/**
* Gets the tech that matches the techName
* or throws an exception if no tech matches
*
* @param techName the name of the tech
* @return the matching tech
* @throws ApiException the api exception
*/
private Tech getTech(String techName) throws ApiException {
for(Tech tech : Tech.builtInTech) {
if (tech.toString().equalsIgnoreCase(techName))
return tech;
}
throw new ApiException(Type.ILLEGAL_PARAMETER,
"The tech " + techName + " does not exist");
}
}