Skip to content

Commit

Permalink
Merge pull request #4989 from kingthorin/cust-pay-api
Browse files Browse the repository at this point in the history
custompayloads: Add Initial API (views)
  • Loading branch information
psiinon authored Oct 19, 2023
2 parents fbcebea + 877a9a0 commit c16fa91
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 0 deletions.
5 changes: 5 additions & 0 deletions addOns/custompayloads/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Maintenance changes.
- Promoted to Beta.

### Added
- Initial API support:
- View payload categories.
- View payloads (optionally filtered by category).

## [0.12.0] - 2022-09-23
### Changed
- Maintenance changes.
Expand Down
9 changes: 9 additions & 0 deletions addOns/custompayloads/custompayloads.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,13 @@ zapAddOn {
author.set("ZAP Dev Team")
url.set("https://www.zaproxy.org/docs/desktop/addons/custom-payloads/")
}

apiClientGen {
api.set("org.zaproxy.zap.extension.custompayloads.CustomPayloadsApi")
messages.set(file("src/main/resources/org/zaproxy/zap/extension/custompayloads/resources/Messages.properties"))
}
}

dependencies {
testImplementation(project(":testutils"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2023 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.custompayloads;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import net.sf.json.JSONObject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.zaproxy.zap.extension.api.ApiException;
import org.zaproxy.zap.extension.api.ApiImplementor;
import org.zaproxy.zap.extension.api.ApiResponse;
import org.zaproxy.zap.extension.api.ApiResponseElement;
import org.zaproxy.zap.extension.api.ApiResponseList;
import org.zaproxy.zap.extension.api.ApiResponseSet;
import org.zaproxy.zap.extension.api.ApiView;

/** The API for manipulating {@link CustomPayload custom payloads}. */
public class CustomPayloadsApi extends ApiImplementor {

private static final Logger LOGGER = LogManager.getLogger(CustomPayloadsApi.class);
private static final String PREFIX = "custompayloads";
private static final String VIEW_CUSTOM_PAYLOADS_CATEGORY_LIST = "customPayloadsCategories";
private static final String VIEW_CUSTOM_PAYLOADS_LIST = "customPayloads";

private static final String PARAM_CATEGORY = "category";
private ExtensionCustomPayloads extension;

/** Provided only for API client generator usage. */
public CustomPayloadsApi() {
this(null);
}

public CustomPayloadsApi(ExtensionCustomPayloads ext) {
this.extension = ext;
this.addApiView(new ApiView(VIEW_CUSTOM_PAYLOADS_CATEGORY_LIST));
this.addApiView(new ApiView(VIEW_CUSTOM_PAYLOADS_LIST, List.of(), List.of(PARAM_CATEGORY)));
}

@Override
public String getPrefix() {
return PREFIX;
}

@Override
public ApiResponse handleApiView(String name, JSONObject params) throws ApiException {
LOGGER.debug("handleApiView {} {}", name, params);

switch (name) {
case VIEW_CUSTOM_PAYLOADS_CATEGORY_LIST:
ApiResponseList listResponse = new ApiResponseList(name);

for (String cat : extension.getParam().getCategoriesNames()) {
listResponse.addItem(new ApiResponseElement("category", cat));
}
return listResponse;

case VIEW_CUSTOM_PAYLOADS_LIST:
ApiResponseList payloadsList = new ApiResponseList(name);
String category = params.optString(PARAM_CATEGORY);
getPayloads(category)
.forEach(payload -> payloadsList.addItem(payloadToResponse(payload)));
return payloadsList;

default:
throw new ApiException(ApiException.Type.BAD_VIEW);
}
}

private List<CustomPayload> getPayloads(String category) throws ApiException {
if (!category.isBlank() && !isValidCategory(category)) {
throw new ApiException(
ApiException.Type.DOES_NOT_EXIST, "Could not find category: " + category);
}

return category.isBlank()
? extension.getParam().getPayloads()
: extension.getParam().getPayloads().stream()
.filter(payload -> payload.getCategory().equalsIgnoreCase(category))
.collect(Collectors.toList());
}

private boolean isValidCategory(String category) {
return extension.getParam().getCategoriesNames().stream()
.anyMatch(x -> x.equalsIgnoreCase(category));
}

private static ApiResponse payloadToResponse(CustomPayload payload) {
Map<String, Object> map = new HashMap<>();
map.put("category", payload.getCategory());
map.put("payload", payload.getPayload());
map.put("enabled", payload.isEnabled());
return new ApiResponseSet<>("custompayload", map);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public void hook(ExtensionHook extensionHook) {
super.hook(extensionHook);

extensionHook.addOptionsParamSet(getParam());
extensionHook.addApiImplementor(new CustomPayloadsApi(this));

if (hasView()) {
extensionHook.getHookView().addOptionPanel(getOptionsPanel());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
custompayloads.api.desc = Functionality facilitating the display and manipulation of Custom Payloads.
custompayloads.api.view.customPayloads = Display all payloads (category, payload, and enabled state).
custompayloads.api.view.customPayloads.param.category = The category for which the payloads should be displayed.
custompayloads.api.view.customPayloadsCategories = Display all categories.

custompayloads.desc = Ability to add, edit or remove payloads that are used i.e. by active scanners

custompayloads.name = Custom Payloads
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2023 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.custompayloads;

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.is;
import static org.hamcrest.Matchers.not;

import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.parosproxy.paros.Constant;
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.ApiImplementor;
import org.zaproxy.zap.extension.api.ApiParameter;
import org.zaproxy.zap.testutils.TestUtils;

/** Unit test for {@link CustomPayloadsApi}. */
class CustomPayloadsApiUnitTest extends TestUtils {

private CustomPayloadsApi api;

@BeforeEach
void setUp() {
mockMessages(new ExtensionCustomPayloads());
api = new CustomPayloadsApi();
}

@Test
void shouldHavePrefix() throws Exception {
// Given / When
String prefix = api.getPrefix();
// Then
assertThat(prefix, is(equalTo("custompayloads")));
}

@Test
void shouldHaveDescriptionsForAllApiElements() {
List<String> missingKeys = new ArrayList<>();
checkKey(api.getDescriptionKey(), missingKeys);
checkApiElements(api, api.getApiActions(), API.RequestType.action, missingKeys);
checkApiElements(api, api.getApiOthers(), API.RequestType.other, missingKeys);
checkApiElements(api, api.getApiViews(), API.RequestType.view, missingKeys);
assertThat(missingKeys, is(empty()));
}

private static void checkApiElements(
ApiImplementor api,
List<? extends ApiElement> elements,
RequestType type,
List<String> missingKeys) {
elements.sort((a, b) -> a.getName().compareTo(b.getName()));
for (ApiElement element : elements) {
assertThat(
"API element: " + api.getPrefix() + "/" + element.getName(),
element.getDescriptionTag(),
is(not(emptyString())));
checkKey(element.getDescriptionTag(), missingKeys);
element.getParameters().stream()
.map(ApiParameter::getDescriptionKey)
.forEach(key -> checkKey(key, missingKeys));
}
}

private static void checkKey(String key, List<String> missingKeys) {
if (!Constant.messages.containsKey(key)) {
missingKeys.add(key);
}
}
}

0 comments on commit c16fa91

Please sign in to comment.