/* * 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.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.regex.Pattern; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.ConversionException; import org.apache.commons.configuration.HierarchicalConfiguration; import org.apache.log4j.Logger; import org.parosproxy.paros.common.AbstractParam; import org.zaproxy.zap.network.DomainMatcher; public class OptionsParamApi extends AbstractParam { private static final Logger LOGGER = Logger.getLogger(OptionsParamApi.class); public static final String ENABLED = "api.enabled"; public static final String UI_ENABLED = "api.uienabled"; public static final String SECURE_ONLY = "api.secure"; public static final String API_KEY = "api.key"; private static final String DISABLE_KEY = "api.disablekey"; private static final String INC_ERROR_DETAILS = "api.incerrordetails"; private static final String AUTOFILL_KEY = "api.autofillkey"; private static final String ENABLE_JSONP = "api.enablejsonp"; private static final String NO_KEY_FOR_SAFE_OPS = "api.nokeyforsafeops"; private static final String REPORT_PERM_ERRORS = "api.reportpermerrors"; private static final String NONCE_TTL_IN_SECS = "api.noncettlsecs"; private static final String PROXY_PERMITTED_ADDRS_KEY = "api.addrs"; private static final String ADDRESS_KEY = PROXY_PERMITTED_ADDRS_KEY + ".addr"; private static final String ADDRESS_VALUE_KEY = "name"; private static final String ADDRESS_REGEX_KEY = "regex"; private static final String ADDRESS_ENABLED_KEY = "enabled"; private static final String CONFIRM_REMOVE_ADDRESS = "api.addrs.confirmRemoveAddr"; private static final int DEFAULT_NONCE_TTL_IN_SECS = 5 * 60; // 5 mins private boolean enabled = true; private boolean uiEnabled = true; private boolean secureOnly; private boolean disableKey; private boolean incErrorDetails; private boolean autofillKey; private boolean enableJSONP; private boolean noKeyForSafeOps; private boolean reportPermErrors; private boolean confirmRemovePermittedAddress = true; private List<DomainMatcher> permittedAddresses = new ArrayList<>(0); private List<DomainMatcher> permittedAddressesEnabled = new ArrayList<>(0); private int nonceTimeToLiveInSecs = DEFAULT_NONCE_TTL_IN_SECS; private String key = ""; public OptionsParamApi() { } @Override protected void parse() { enabled = getBooleanFromConfig(ENABLED, true); uiEnabled = getBooleanFromConfig(UI_ENABLED, true); secureOnly = getBooleanFromConfig(SECURE_ONLY, false); disableKey = getBooleanFromConfig(DISABLE_KEY, false); incErrorDetails = getBooleanFromConfig(INC_ERROR_DETAILS, false); autofillKey = getBooleanFromConfig(AUTOFILL_KEY, false); enableJSONP = getBooleanFromConfig(ENABLE_JSONP, false); noKeyForSafeOps = getBooleanFromConfig(NO_KEY_FOR_SAFE_OPS, false); reportPermErrors = getBooleanFromConfig(REPORT_PERM_ERRORS, false); nonceTimeToLiveInSecs = getIntFromConfig(NONCE_TTL_IN_SECS, DEFAULT_NONCE_TTL_IN_SECS); try { key = getConfig().getString(API_KEY, ""); } catch (ConversionException e) { LOGGER.warn("Failed to load the option '" + key + "' caused by:", e); key = ""; } loadPermittedAddresses(); try { this.confirmRemovePermittedAddress = getConfig().getBoolean(CONFIRM_REMOVE_ADDRESS, true); } catch (ConversionException e) { LOGGER.error("Error while loading the confirm remove permitted address option: " + e.getMessage(), e); } } private boolean getBooleanFromConfig(String key, boolean defaultValue) { try { return getConfig().getBoolean(key, defaultValue); } catch (ConversionException e) { LOGGER.warn("Failed to load the option '" + key + "' caused by:", e); return defaultValue; } } private int getIntFromConfig(String key, int defaultValue) { try { return getConfig().getInteger(key, defaultValue); } catch (ConversionException e) { LOGGER.warn("Failed to load the option '" + key + "' caused by:", e); return defaultValue; } } @Override public OptionsParamApi clone() { return (OptionsParamApi) super.clone(); } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; getConfig().setProperty(ENABLED, enabled); } public boolean isUiEnabled() { return uiEnabled; } public void setUiEnabled(boolean uiEnabled) { this.uiEnabled = uiEnabled; getConfig().setProperty(UI_ENABLED, uiEnabled); } public boolean isSecureOnly() { return secureOnly; } public void setSecureOnly(boolean secureOnly) { this.secureOnly = secureOnly; getConfig().setProperty(SECURE_ONLY, secureOnly); } public boolean isDisableKey() { return disableKey; } public void setDisableKey(boolean disableKey) { this.disableKey = disableKey; getConfig().setProperty(DISABLE_KEY, disableKey); } public boolean isIncErrorDetails() { return incErrorDetails; } public void setIncErrorDetails(boolean incErrorDetails) { this.incErrorDetails = incErrorDetails; getConfig().setProperty(INC_ERROR_DETAILS, incErrorDetails); } public boolean isAutofillKey() { return autofillKey; } public void setAutofillKey(boolean autofillKey) { this.autofillKey = autofillKey; getConfig().setProperty(AUTOFILL_KEY, autofillKey); } public boolean isEnableJSONP() { return enableJSONP; } public void setEnableJSONP(boolean enableJSONP) { this.enableJSONP = enableJSONP; getConfig().setProperty(ENABLE_JSONP, enableJSONP); } public boolean isNoKeyForSafeOps() { return noKeyForSafeOps; } public void setNoKeyForSafeOps(boolean noKeyForSafeOps) { this.noKeyForSafeOps = noKeyForSafeOps; getConfig().setProperty(NO_KEY_FOR_SAFE_OPS, noKeyForSafeOps); } public boolean isReportPermErrors() { return reportPermErrors; } public void setReportPermErrors(boolean reportErrors) { this.reportPermErrors = reportErrors; getConfig().setProperty(REPORT_PERM_ERRORS, reportErrors); } /** * Gets the time to live for API nonces. This should not be accessible via the API. * @return the time to live for API nonces * @since 2.6.0 */ @ZapApiIgnore public int getNonceTimeToLiveInSecs() { return nonceTimeToLiveInSecs; } protected String getRealKey() { return key; } protected String getKey() { if (this.isDisableKey()) { return ""; } else if (key == null || key.length() == 0) { key = ExtensionAPI.generateApiKey(); if (getConfig() != null) { getConfig().setProperty(API_KEY, key); try { getConfig().save(); } catch (ConfigurationException e) { // Ignore } } } return key; } public void setKey(String key) { this.key = key; getConfig().setProperty(API_KEY, key); } /** * Tells whether or not the given client address is allowed to access the API. * * @param addr the client address to be checked * @return {@code true} if the given client address is allowed to access the API, {@code false} otherwise. * @since 2.6.0 */ public boolean isPermittedAddress(String addr) { if (addr == null || addr.isEmpty()) { return false; } for (DomainMatcher permAddr : permittedAddressesEnabled) { if (permAddr.matches(addr)) { return true; } } return false; } /** * Returns the client addresses that are allowed to access the API. * * @return the client addresses that are allowed to access the API. * @since 2.6.0 */ @ZapApiIgnore public List<DomainMatcher> getPermittedAddresses() { return permittedAddresses; } /** * Returns the enabled client addresses that are allowed to access the API. * * @return the enabled client addresses that are allowed to access the API. * @since 2.6.0 */ @ZapApiIgnore public List<DomainMatcher> getPermittedAddressesEnabled() { return permittedAddressesEnabled; } /** * Sets the client addresses that will be allowed to access the API. * * @param addrs the client addresses that will be allowed to access the API. * @since 2.6.0 */ public void setPermittedAddresses(List<DomainMatcher> addrs) { if (addrs == null || addrs.isEmpty()) { ((HierarchicalConfiguration) getConfig()).clearTree(ADDRESS_KEY); this.permittedAddresses = Collections.emptyList(); this.permittedAddressesEnabled = Collections.emptyList(); return; } this.permittedAddresses = new ArrayList<>(addrs); ((HierarchicalConfiguration) getConfig()).clearTree(ADDRESS_KEY); int size = addrs.size(); ArrayList<DomainMatcher> enabledAddrs = new ArrayList<>(size); for (int i = 0; i < size; ++i) { String elementBaseKey = ADDRESS_KEY + "(" + i + ")."; DomainMatcher addr = addrs.get(i); getConfig().setProperty(elementBaseKey + ADDRESS_VALUE_KEY, addr.getValue()); getConfig().setProperty(elementBaseKey + ADDRESS_REGEX_KEY, Boolean.valueOf(addr.isRegex())); getConfig().setProperty( elementBaseKey + ADDRESS_ENABLED_KEY, Boolean.valueOf(addr.isEnabled())); if (addr.isEnabled()) { enabledAddrs.add(addr); } } enabledAddrs.trimToSize(); this.permittedAddressesEnabled = enabledAddrs; } private void loadPermittedAddresses() { List<HierarchicalConfiguration> fields = ((HierarchicalConfiguration) getConfig()).configurationsAt(ADDRESS_KEY); this.permittedAddresses = new ArrayList<>(fields.size()); ArrayList<DomainMatcher> addrsEnabled = new ArrayList<>(fields.size()); for (HierarchicalConfiguration sub : fields) { String value = sub.getString(ADDRESS_VALUE_KEY, ""); if (value.isEmpty()) { LOGGER.warn("Failed to read a permitted address entry, required value is empty."); continue; } DomainMatcher addr = null; boolean regex = sub.getBoolean(ADDRESS_REGEX_KEY, false); if (regex) { try { Pattern pattern = DomainMatcher.createPattern(value); addr = new DomainMatcher(pattern); } catch (IllegalArgumentException e) { LOGGER.error("Failed to read a permitted address entry with regex: " + value, e); } } else { addr = new DomainMatcher(value); } if (addr != null) { addr.setEnabled(sub.getBoolean(ADDRESS_ENABLED_KEY, true)); permittedAddresses.add(addr); if (addr.isEnabled()) { addrsEnabled.add(addr); } } } addrsEnabled.trimToSize(); if (permittedAddresses.size() == 0) { // None specified - add in the defaults (which can then be disabled) DomainMatcher addr = new DomainMatcher("127.0.0.1"); permittedAddresses.add(addr); addrsEnabled.add(addr); addr = new DomainMatcher("localhost"); permittedAddresses.add(addr); addrsEnabled.add(addr); addr = new DomainMatcher("zap"); permittedAddresses.add(addr); addrsEnabled.add(addr); } this.permittedAddressesEnabled = addrsEnabled; } /** * Tells whether or not the removal of a permitted address needs confirmation. * * @return {@code true} if the removal needs confirmation, {@code false} otherwise. * @since TODO */ @ZapApiIgnore public boolean isConfirmRemovePermittedAddress() { return this.confirmRemovePermittedAddress; } /** * Sets whether or not the removal of a permitted address needs confirmation. * * @param confirmRemove {@code true} if the removal needs confirmation, {@code false} otherwise. * @since TODO */ @ZapApiIgnore public void setConfirmRemovePermittedAddress(boolean confirmRemove) { this.confirmRemovePermittedAddress = confirmRemove; getConfig().setProperty(CONFIRM_REMOVE_ADDRESS, Boolean.valueOf(confirmRemovePermittedAddress)); } }