diff --git a/addOns/alertFilters/CHANGELOG.md b/addOns/alertFilters/CHANGELOG.md index 15658653b58..3d48f82873b 100644 --- a/addOns/alertFilters/CHANGELOG.md +++ b/addOns/alertFilters/CHANGELOG.md @@ -4,7 +4,8 @@ All notable changes to this add-on will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased - +### Added +- Added parameter descriptions for the ZAP API. ## [23] - 2025-01-09 ### Changed diff --git a/addOns/alertFilters/src/main/java/org/zaproxy/zap/extension/alertFilters/AlertFilterAPI.java b/addOns/alertFilters/src/main/java/org/zaproxy/zap/extension/alertFilters/AlertFilterAPI.java index 2672efa6b41..abbe053efcd 100644 --- a/addOns/alertFilters/src/main/java/org/zaproxy/zap/extension/alertFilters/AlertFilterAPI.java +++ b/addOns/alertFilters/src/main/java/org/zaproxy/zap/extension/alertFilters/AlertFilterAPI.java @@ -91,16 +91,10 @@ public AlertFilterAPI(ExtensionAlertFilters extension) { super(); this.extension = extension; - ApiView alertFilterList = - new ApiView(VIEW_ALERT_FILTER_LIST, new String[] {PARAM_CONTEXT_ID}); - alertFilterList.setDescriptionTag("alertFilters.api.view.alertFilterList"); - this.addApiView(alertFilterList); + this.addApiView(new ApiView(VIEW_ALERT_FILTER_LIST, new String[] {PARAM_CONTEXT_ID})); + this.addApiView(new ApiView(VIEW_GLOBAL_ALERT_FILTER_LIST)); - ApiView globalAlertFilterList = new ApiView(VIEW_GLOBAL_ALERT_FILTER_LIST); - globalAlertFilterList.setDescriptionTag("alertFilters.api.view.globalAlertFilterList"); - this.addApiView(globalAlertFilterList); - - ApiAction addAlertFilter = + this.addApiAction( new ApiAction( ACTION_ADD_ALERT_FILTER, new String[] {PARAM_CONTEXT_ID, PARAM_RULE_ID, PARAM_NEW_LEVEL}, @@ -115,11 +109,8 @@ public AlertFilterAPI(ExtensionAlertFilters extension) { PARAM_EVIDENCE, PARAM_EVIDENCE_IS_REGEX, PARAM_METHODS, - }); - addAlertFilter.setDescriptionTag("alertFilters.api.action.addAlertFilter"); - this.addApiAction(addAlertFilter); - - ApiAction removeAlertFilter = + })); + this.addApiAction( new ApiAction( ACTION_REMOVE_ALERT_FILTER, new String[] {PARAM_CONTEXT_ID, PARAM_RULE_ID, PARAM_NEW_LEVEL}, @@ -134,11 +125,8 @@ public AlertFilterAPI(ExtensionAlertFilters extension) { PARAM_EVIDENCE, PARAM_EVIDENCE_IS_REGEX, PARAM_METHODS, - }); - removeAlertFilter.setDescriptionTag("alertFilters.api.action.removeAlertFilter"); - this.addApiAction(removeAlertFilter); - - ApiAction addGlobalAlertFilter = + })); + this.addApiAction( new ApiAction( ACTION_ADD_GLOBAL_ALERT_FILTER, new String[] {PARAM_RULE_ID, PARAM_NEW_LEVEL}, @@ -153,11 +141,8 @@ public AlertFilterAPI(ExtensionAlertFilters extension) { PARAM_EVIDENCE, PARAM_EVIDENCE_IS_REGEX, PARAM_METHODS, - }); - addGlobalAlertFilter.setDescriptionTag("alertFilters.api.action.addGlobalAlertFilter"); - this.addApiAction(addGlobalAlertFilter); - - ApiAction removeGlobalAlertFilter = + })); + this.addApiAction( new ApiAction( ACTION_REMOVE_GLOBAL_ALERT_FILTER, new String[] {PARAM_RULE_ID, PARAM_NEW_LEVEL}, @@ -172,34 +157,13 @@ public AlertFilterAPI(ExtensionAlertFilters extension) { PARAM_EVIDENCE, PARAM_EVIDENCE_IS_REGEX, PARAM_METHODS, - }); - removeGlobalAlertFilter.setDescriptionTag( - "alertFilters.api.action.removeGlobalAlertFilter"); - this.addApiAction(removeGlobalAlertFilter); - - ApiAction applyAll = new ApiAction(ACTION_APPLY_ALL); - applyAll.setDescriptionTag("alertFilters.api.action.applyAll"); - this.addApiAction(applyAll); - - ApiAction applyContext = new ApiAction(ACTION_APPLY_CONTEXT); - applyContext.setDescriptionTag("alertFilters.api.action.applyContext"); - this.addApiAction(applyContext); - - ApiAction applyGlobal = new ApiAction(ACTION_APPLY_GLOBAL); - applyGlobal.setDescriptionTag("alertFilters.api.action.applyGlobal"); - this.addApiAction(applyGlobal); - - ApiAction testAll = new ApiAction(ACTION_TEST_ALL); - testAll.setDescriptionTag("alertFilters.api.action.testAll"); - this.addApiAction(testAll); - - ApiAction testContext = new ApiAction(ACTION_TEST_CONTEXT); - testContext.setDescriptionTag("alertFilters.api.action.testContext"); - this.addApiAction(testContext); - - ApiAction testGlobal = new ApiAction(ACTION_TEST_GLOBAL); - testGlobal.setDescriptionTag("alertFilters.api.action.testGlobal"); - this.addApiAction(testGlobal); + })); + this.addApiAction(new ApiAction(ACTION_APPLY_ALL)); + this.addApiAction(new ApiAction(ACTION_APPLY_CONTEXT)); + this.addApiAction(new ApiAction(ACTION_APPLY_GLOBAL)); + this.addApiAction(new ApiAction(ACTION_TEST_ALL)); + this.addApiAction(new ApiAction(ACTION_TEST_CONTEXT)); + this.addApiAction(new ApiAction(ACTION_TEST_GLOBAL)); } @Override @@ -207,6 +171,11 @@ public String getPrefix() { return PREFIX; } + @Override + protected String getI18nPrefix() { + return ExtensionAlertFilters.PREFIX; + } + @Override public ApiResponse handleApiView(String name, JSONObject params) throws ApiException { LOGGER.debug("handleApiView {} {}", name, params); diff --git a/addOns/alertFilters/src/main/resources/org/zaproxy/zap/extension/alertFilters/resources/Messages.properties b/addOns/alertFilters/src/main/resources/org/zaproxy/zap/extension/alertFilters/resources/Messages.properties index 1ee31edb529..3d33e4086e4 100644 --- a/addOns/alertFilters/src/main/resources/org/zaproxy/zap/extension/alertFilters/resources/Messages.properties +++ b/addOns/alertFilters/src/main/resources/org/zaproxy/zap/extension/alertFilters/resources/Messages.properties @@ -1,14 +1,68 @@ -alertFilters.api.action.addAlertFilter = Adds a new alert filter for the context with the given ID. -alertFilters.api.action.addGlobalAlertFilter = Adds a new global alert filter. +alertFilters.api.action.addAlertFilter = Adds a new alert filter for the context with the given ID. +alertFilters.api.action.addAlertFilter.param.attack = The attack value for which the filter should apply (can be regex). +alertFilters.api.action.addAlertFilter.param.attackIsRegex = A boolean indicating whether or not the attack value is a regex. +alertFilters.api.action.addAlertFilter.param.contextId = The numeric ID of the context for which the filter should be added. +alertFilters.api.action.addAlertFilter.param.enabled = A boolean indicating whether or not the filter should be enabled. +alertFilters.api.action.addAlertFilter.param.evidence = The evidence value for which the filter should apply (can be regex). +alertFilters.api.action.addAlertFilter.param.evidenceIsRegex = A boolean indicating whether or not the evidence value is a regex. +alertFilters.api.action.addAlertFilter.param.methods = The HTTP methods (comma separated) for which the filter should apply. +alertFilters.api.action.addAlertFilter.param.newLevel = The numeric risk representation ('0 - Informational' through '3 - High') ['-1 - False Positive']. +alertFilters.api.action.addAlertFilter.param.parameter = The parameter name for which the filter should apply (can be regex). +alertFilters.api.action.addAlertFilter.param.parameterIsRegex = A boolean indicating whether or not the parameter name is a regex. +alertFilters.api.action.addAlertFilter.param.ruleId = The numeric ID of the rule for which the filter should apply. +alertFilters.api.action.addAlertFilter.param.url = The URL for which the filter should apply (can be regex). +alertFilters.api.action.addAlertFilter.param.urlIsRegex = A boolean indicating whether or not the URL is a regex. +alertFilters.api.action.addGlobalAlertFilter = Adds a new global alert filter. +alertFilters.api.action.addGlobalAlertFilter.param.attack = The attack value for which the filter should apply (can be regex). +alertFilters.api.action.addGlobalAlertFilter.param.attackIsRegex = A boolean indicating whether or not the attack value is a regex. +alertFilters.api.action.addGlobalAlertFilter.param.contextId = The numeric ID of the context for which the filter should be added. +alertFilters.api.action.addGlobalAlertFilter.param.enabled = A boolean indicating whether or not the filter should be enabled. +alertFilters.api.action.addGlobalAlertFilter.param.evidence = The evidence value for which the filter should apply (can be regex). +alertFilters.api.action.addGlobalAlertFilter.param.evidenceIsRegex = A boolean indicating whether or not the evidence value is a regex. +alertFilters.api.action.addGlobalAlertFilter.param.methods = The HTTP methods (comma separated) for which the filter should apply. +alertFilters.api.action.addGlobalAlertFilter.param.newLevel = The numeric risk representation ('0 - Informational' through '3 - High') ['-1 - False Positive']. +alertFilters.api.action.addGlobalAlertFilter.param.parameter = The parameter name for which the filter should apply (can be regex). +alertFilters.api.action.addGlobalAlertFilter.param.parameterIsRegex = A boolean indicating whether or not the parameter name is a regex. +alertFilters.api.action.addGlobalAlertFilter.param.ruleId = The numeric ID of the rule for which the filter should apply. +alertFilters.api.action.addGlobalAlertFilter.param.url = The URL for which the filter should apply (can be regex). +alertFilters.api.action.addGlobalAlertFilter.param.urlIsRegex = A boolean indicating whether or not the URL is a regex. alertFilters.api.action.applyAll = Applies all currently enabled Global and Context alert filters. alertFilters.api.action.applyContext = Applies all currently enabled Context alert filters. alertFilters.api.action.applyGlobal = Applies all currently enabled Global alert filters. alertFilters.api.action.removeAlertFilter = Removes an alert filter from the context with the given ID. +alertFilters.api.action.removeAlertFilter.param.attack = The attack value for which the filter applies (can be regex). +alertFilters.api.action.removeAlertFilter.param.attackIsRegex = A boolean indicating whether or not the attack value is a regex. +alertFilters.api.action.removeAlertFilter.param.contextId = The numeric ID of the context for which the filter should be removed. +alertFilters.api.action.removeAlertFilter.param.enabled = A boolean indicating whether or not the filter should be enabled. +alertFilters.api.action.removeAlertFilter.param.evidence = The evidence value for which the filter applies (can be regex). +alertFilters.api.action.removeAlertFilter.param.evidenceIsRegex = A boolean indicating whether or not the evidence value is a regex. +alertFilters.api.action.removeAlertFilter.param.methods = The HTTP methods (comma separated) for which the filter applies. +alertFilters.api.action.removeAlertFilter.param.newLevel = The numeric risk representation ('0 - Informational' through '3 - High') ['-1 - False Positive']. +alertFilters.api.action.removeAlertFilter.param.parameter = The parameter name for which the filter should apply (can be regex). +alertFilters.api.action.removeAlertFilter.param.parameterIsRegex = A boolean indicating whether or not the parameter name is a regex. +alertFilters.api.action.removeAlertFilter.param.ruleId = The numeric ID of the rule for which the filter applies. +alertFilters.api.action.removeAlertFilter.param.url = The URL for which the filter applies (can be regex). +alertFilters.api.action.removeAlertFilter.param.urlIsRegex = A boolean indicating whether or not the URL is a regex. alertFilters.api.action.removeGlobalAlertFilter = Removes a global alert filter. +alertFilters.api.action.removeGlobalAlertFilter.param.attack = The attack value for which the filter applies (can be regex). +alertFilters.api.action.removeGlobalAlertFilter.param.attackIsRegex = A boolean indicating whether or not the attack value is a regex. +alertFilters.api.action.removeGlobalAlertFilter.param.contextId = The numeric ID of the context for which the filter should be removed. +alertFilters.api.action.removeGlobalAlertFilter.param.enabled = A boolean indicating whether or not the filter should be enabled. +alertFilters.api.action.removeGlobalAlertFilter.param.evidence = The evidence value for which the filter applies (can be regex). +alertFilters.api.action.removeGlobalAlertFilter.param.evidenceIsRegex = A boolean indicating whether or not the evidence value is a regex. +alertFilters.api.action.removeGlobalAlertFilter.param.methods = The HTTP methods (comma separated) for which the filter applies. +alertFilters.api.action.removeGlobalAlertFilter.param.newLevel = The numeric risk representation ('0 - Informational' through '3 - High') ['-1 - False Positive']. +alertFilters.api.action.removeGlobalAlertFilter.param.parameter = The parameter name for which the filter should apply (can be regex). +alertFilters.api.action.removeGlobalAlertFilter.param.parameterIsRegex = A boolean indicating whether or not the parameter name is a regex. +alertFilters.api.action.removeGlobalAlertFilter.param.ruleId = The numeric ID of the rule for which the filter applies. +alertFilters.api.action.removeGlobalAlertFilter.param.url = The URL for which the filter applies (can be regex). +alertFilters.api.action.removeGlobalAlertFilter.param.urlIsRegex = A boolean indicating whether or not the URL is a regex. alertFilters.api.action.testAll = Tests all currently enabled Global and Context alert filters. alertFilters.api.action.testContext = Tests all currently enabled Context alert filters. alertFilters.api.action.testGlobal = Tests all currently enabled Global alert filters. +alertFilters.api.desc = Facilitates the configuration and use of Alert Filters functionality. alertFilters.api.view.alertFilterList = Lists the alert filters of the context with the given ID. +alertFilters.api.view.alertFilterList.param.contextId = The numeric ID of the context for which the filters should be listed. alertFilters.api.view.globalAlertFilterList = Lists the global alert filters. alertFilters.automation.desc = Alert Filters Automation Framework Integration diff --git a/addOns/alertFilters/src/test/java/org/zaproxy/zap/extension/alertFilters/AlertFilterApiUnitTest.java b/addOns/alertFilters/src/test/java/org/zaproxy/zap/extension/alertFilters/AlertFilterApiUnitTest.java new file mode 100644 index 00000000000..6c504258e59 --- /dev/null +++ b/addOns/alertFilters/src/test/java/org/zaproxy/zap/extension/alertFilters/AlertFilterApiUnitTest.java @@ -0,0 +1,182 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 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.alertFilters; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.emptyString; +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.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.List; +import net.sf.json.JSONObject; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EmptySource; +import org.junit.jupiter.params.provider.ValueSource; +import org.parosproxy.paros.Constant; +import org.parosproxy.paros.network.HttpMessage; +import org.zaproxy.zap.extension.api.API; +import org.zaproxy.zap.extension.api.API.RequestType; +import org.zaproxy.zap.extension.api.ApiElement; +import org.zaproxy.zap.extension.api.ApiException; +import org.zaproxy.zap.extension.api.ApiImplementor; +import org.zaproxy.zap.extension.api.ApiParameter; +import org.zaproxy.zap.testutils.TestUtils; + +/** Unit test for {@link AlertFilterAPI}. */ +class AlertFilterApiUnitTest extends TestUtils { + + private AlertFilterAPI alertFiltersApi; + + @BeforeEach + void setUp() { + mockMessages(new ExtensionAlertFilters()); + + alertFiltersApi = new AlertFilterAPI(); + } + + @AfterAll + static void cleanUp() { + Constant.messages = null; + } + + @Test + void shouldHavePrefix() { + // Given / When + String prefix = alertFiltersApi.getPrefix(); + // Then + assertThat(prefix, is(equalTo("alertFilter"))); + } + + @Test + void shouldAddApiElements() { + // Given / When + alertFiltersApi = new AlertFilterAPI(); + // Then + assertThat(alertFiltersApi.getApiActions(), hasSize(10)); + assertThat(alertFiltersApi.getApiViews(), hasSize(2)); + assertThat(alertFiltersApi.getApiOthers(), hasSize(0)); + } + + @ParameterizedTest + @EmptySource + @ValueSource(strings = {"unknown", "something"}) + void shouldThrowApiExceptionForUnknownAction(String name) { + // Given + JSONObject params = new JSONObject(); + // When + ApiException exception = + assertThrows( + ApiException.class, () -> alertFiltersApi.handleApiAction(name, params)); + // Then + assertThat(exception.getType(), is(equalTo(ApiException.Type.BAD_ACTION))); + } + + @ParameterizedTest + @EmptySource + @ValueSource(strings = {"unknown", "something"}) + void shouldThrowApiExceptionForUnknownOther(String name) { + // Given + HttpMessage message = new HttpMessage(); + JSONObject params = new JSONObject(); + // When + ApiException exception = + assertThrows( + ApiException.class, + () -> alertFiltersApi.handleApiOther(message, name, params)); + // Then + assertThat(exception.getType(), is(equalTo(ApiException.Type.BAD_OTHER))); + } + + @ParameterizedTest + @EmptySource + @ValueSource(strings = {"unknown", "something"}) + void shouldThrowApiExceptionForUnknownView(String name) { + // Given + JSONObject params = new JSONObject(); + // When + ApiException exception = + assertThrows(ApiException.class, () -> alertFiltersApi.handleApiView(name, params)); + // Then + assertThat(exception.getType(), is(equalTo(ApiException.Type.BAD_VIEW))); + } + + @Test + void shouldHaveDescriptionsForAllApiElements() { + alertFiltersApi = new AlertFilterAPI(); + List missingKeys = new ArrayList<>(); + List missingDescriptions = new ArrayList<>(); + checkKey(alertFiltersApi.getDescriptionKey(), missingKeys, missingDescriptions); + checkApiElements( + alertFiltersApi, + alertFiltersApi.getApiActions(), + API.RequestType.action, + missingKeys, + missingDescriptions); + checkApiElements( + alertFiltersApi, + alertFiltersApi.getApiOthers(), + API.RequestType.other, + missingKeys, + missingDescriptions); + checkApiElements( + alertFiltersApi, + alertFiltersApi.getApiViews(), + API.RequestType.view, + missingKeys, + missingDescriptions); + assertThat(missingKeys, is(empty())); + assertThat(missingDescriptions, is(empty())); + } + + private static void checkKey(String key, List missingKeys, List missingDescs) { + if (!Constant.messages.containsKey(key)) { + missingKeys.add(key); + } else if (Constant.messages.getString(key).isBlank()) { + missingDescs.add(key); + } + } + + private static void checkApiElements( + ApiImplementor api, + List elements, + RequestType type, + List missingKeys, + List missingDescriptions) { + elements.sort((a, b) -> a.getName().compareTo(b.getName())); + for (ApiElement element : elements) { + assertThat( + "API " + type + " element: " + api.getPrefix() + "/" + element.getName(), + element.getDescriptionTag(), + is(not(emptyString()))); + checkKey(element.getDescriptionTag(), missingKeys, missingDescriptions); + element.getParameters().stream() + .map(ApiParameter::getDescriptionKey) + .forEach(key -> checkKey(key, missingKeys, missingDescriptions)); + } + } +}