From 197021625d61b823b37102c8c05771c4a237985d Mon Sep 17 00:00:00 2001 From: Mathias Vandaele Date: Thu, 27 Jun 2024 10:26:23 +0200 Subject: [PATCH] feature-2774 webhook connector can now handle json array in request body 2 --- .../inbound/HttpWebhookExecutable.java | 4 +- .../authorization/ApiKeyAuthHandler.java | 2 +- .../strategy/URLAndBodyEncodingStrategy.java | 33 +++++--- .../inbound/utils/HttpWebhookUtil.java | 4 +- .../inbound/HttpWebhookExecutableTest.java | 77 +++++++++++++++++++ 5 files changed, 106 insertions(+), 14 deletions(-) diff --git a/connectors/webhook/src/main/java/io/camunda/connector/inbound/HttpWebhookExecutable.java b/connectors/webhook/src/main/java/io/camunda/connector/inbound/HttpWebhookExecutable.java index 2a6937fa82..7394630e90 100644 --- a/connectors/webhook/src/main/java/io/camunda/connector/inbound/HttpWebhookExecutable.java +++ b/connectors/webhook/src/main/java/io/camunda/connector/inbound/HttpWebhookExecutable.java @@ -138,7 +138,7 @@ private void validateHttpMethod(WebhookProcessingPayload payload) { private static MappedHttpRequest mapRequest(WebhookProcessingPayload payload) { return new MappedHttpRequest( - HttpWebhookUtil.transformRawBodyToMap( + HttpWebhookUtil.transformRawBodyToObject( payload.rawBody(), HttpWebhookUtil.extractContentType(payload.headers())), payload.headers(), payload.params()); @@ -215,7 +215,7 @@ public WebhookHttpResponse verify(WebhookProcessingPayload payload) { "request", Map.of( "body", - HttpWebhookUtil.transformRawBodyToMap( + HttpWebhookUtil.transformRawBodyToObject( payload.rawBody(), HttpWebhookUtil.extractContentType(payload.headers())), "headers", diff --git a/connectors/webhook/src/main/java/io/camunda/connector/inbound/authorization/ApiKeyAuthHandler.java b/connectors/webhook/src/main/java/io/camunda/connector/inbound/authorization/ApiKeyAuthHandler.java index b0cc7c350b..ac337f14b7 100644 --- a/connectors/webhook/src/main/java/io/camunda/connector/inbound/authorization/ApiKeyAuthHandler.java +++ b/connectors/webhook/src/main/java/io/camunda/connector/inbound/authorization/ApiKeyAuthHandler.java @@ -30,7 +30,7 @@ public AuthorizationResult checkAuthorization(WebhookProcessingPayload payload) WebhookTriggerResultContext result = new WebhookTriggerResultContext( new MappedHttpRequest( - HttpWebhookUtil.transformRawBodyToMap( + HttpWebhookUtil.transformRawBodyToObject( payload.rawBody(), HttpWebhookUtil.extractContentType(payload.headers())), payload.headers(), payload.params()), diff --git a/connectors/webhook/src/main/java/io/camunda/connector/inbound/signature/strategy/URLAndBodyEncodingStrategy.java b/connectors/webhook/src/main/java/io/camunda/connector/inbound/signature/strategy/URLAndBodyEncodingStrategy.java index beef5de5d5..612b14dd40 100644 --- a/connectors/webhook/src/main/java/io/camunda/connector/inbound/signature/strategy/URLAndBodyEncodingStrategy.java +++ b/connectors/webhook/src/main/java/io/camunda/connector/inbound/signature/strategy/URLAndBodyEncodingStrategy.java @@ -15,25 +15,23 @@ import java.util.Map; public final class URLAndBodyEncodingStrategy implements HMACEncodingStrategy { - @Override - public byte[] getBytesToSign(final WebhookProcessingPayload payload) throws IOException { - return (payload.requestURL() + extractSignatureData(payload)).getBytes(); - } - private static String extractSignatureData(final WebhookProcessingPayload payload) - throws IOException { + private static String extractSignatureData(final WebhookProcessingPayload payload) { if (payload.rawBody() == null || payload.rawBody().length == 0) { throw new NullPointerException( "Can't extract signature data from body, because body is null"); } Map signatureData = - HttpWebhookUtil.transformRawBodyToMap( - payload.rawBody(), HttpWebhookUtil.extractContentType(payload.headers())); + checkedCastToMap( + HttpWebhookUtil.transformRawBodyToObject( + payload.rawBody(), HttpWebhookUtil.extractContentType(payload.headers())), + String.class); - StringBuilder builder = new StringBuilder(); List sortedKeys = new ArrayList<>(signatureData.keySet()); Collections.sort(sortedKeys); + StringBuilder builder = new StringBuilder(); + for (String key : sortedKeys) { builder.append(key); String value = signatureData.get(key); @@ -41,4 +39,21 @@ private static String extractSignatureData(final WebhookProcessingPayload payloa } return builder.toString(); } + + private static Map checkedCastToMap(Object o, Class tClass) { + if (o instanceof Map map) { + for (Map.Entry entry : map.entrySet()) { + if (!(tClass.isInstance(entry.getValue())) || !(tClass.isInstance(entry.getKey()))) { + return Map.of(); + } + } + return (Map) map; + } + return Map.of(); + } + + @Override + public byte[] getBytesToSign(final WebhookProcessingPayload payload) throws IOException { + return (payload.requestURL() + extractSignatureData(payload)).getBytes(); + } } diff --git a/connectors/webhook/src/main/java/io/camunda/connector/inbound/utils/HttpWebhookUtil.java b/connectors/webhook/src/main/java/io/camunda/connector/inbound/utils/HttpWebhookUtil.java index e617e6bbee..e5b829c1f1 100644 --- a/connectors/webhook/src/main/java/io/camunda/connector/inbound/utils/HttpWebhookUtil.java +++ b/connectors/webhook/src/main/java/io/camunda/connector/inbound/utils/HttpWebhookUtil.java @@ -27,7 +27,7 @@ public static String extractContentType(Map headers) { return caseInsensitiveMap.getOrDefault(HttpHeaders.CONTENT_TYPE, "").toString(); } - public static Map transformRawBodyToMap(byte[] rawBody, String contentTypeHeader) { + public static Object transformRawBodyToObject(byte[] rawBody, String contentTypeHeader) { if (rawBody == null) { return Collections.emptyMap(); } @@ -41,7 +41,7 @@ public static Map transformRawBodyToMap(byte[] rawBody, String contentTypeHeader } else { // Do our best to parse to JSON (throws exception otherwise) try { - return ConnectorsObjectMapperSupplier.getCopy().readValue(rawBody, Map.class); + return ConnectorsObjectMapperSupplier.getCopy().readValue(rawBody, Object.class); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/connectors/webhook/src/test/java/io/camunda/connector/inbound/HttpWebhookExecutableTest.java b/connectors/webhook/src/test/java/io/camunda/connector/inbound/HttpWebhookExecutableTest.java index 039c472553..d7495a1406 100644 --- a/connectors/webhook/src/test/java/io/camunda/connector/inbound/HttpWebhookExecutableTest.java +++ b/connectors/webhook/src/test/java/io/camunda/connector/inbound/HttpWebhookExecutableTest.java @@ -27,6 +27,7 @@ import io.camunda.connector.test.inbound.InboundConnectorContextBuilder; import io.netty.handler.codec.http.HttpResponseStatus; import java.nio.charset.StandardCharsets; +import java.util.List; import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -105,6 +106,82 @@ void triggerWebhook_ResponseExpression_HappyCase() { assertEquals("value", response.body()); } + @Test + void triggerWebhook_ResponseIsArrayOfPrimitive_HappyCase() { + InboundConnectorContext ctx = + InboundConnectorContextBuilder.create() + .properties( + Map.of( + "inbound", + Map.of( + "context", + "webhookContext", + "method", + "any", + "auth", + Map.of("type", "NONE"), + "responseExpression", + "=if request.body.key != null then {body: request.body.key} else null"))) + .build(); + + WebhookProcessingPayload payload = Mockito.mock(WebhookProcessingPayload.class); + Mockito.when(payload.method()).thenReturn(HttpMethods.any.name()); + Mockito.when(payload.headers()) + .thenReturn(Map.of(HttpHeaders.CONTENT_TYPE, MediaType.JSON_UTF_8.toString())); + Mockito.when(payload.rawBody()) + .thenReturn(("[ \"test1\", \"test2\" ]").getBytes(StandardCharsets.UTF_8)); + + testObject.activate(ctx); + var result = testObject.triggerWebhook(payload); + + assertNotNull(result.response()); + assertThat((List) result.request().body()).contains("test1", "test2"); + + var request = new MappedHttpRequest(Map.of("key", "value"), null, null); + var context = new WebhookResultContext(request, null, null); + var response = result.response().apply(context); + assertEquals("value", response.body()); + } + + @Test + void triggerWebhook_ResponseIsArrayOfObject_HappyCase() { + InboundConnectorContext ctx = + InboundConnectorContextBuilder.create() + .properties( + Map.of( + "inbound", + Map.of( + "context", + "webhookContext", + "method", + "any", + "auth", + Map.of("type", "NONE"), + "responseExpression", + "=if request.body.key != null then {body: request.body.key} else null"))) + .build(); + + WebhookProcessingPayload payload = Mockito.mock(WebhookProcessingPayload.class); + Mockito.when(payload.method()).thenReturn(HttpMethods.any.name()); + Mockito.when(payload.headers()) + .thenReturn(Map.of(HttpHeaders.CONTENT_TYPE, MediaType.JSON_UTF_8.toString())); + Mockito.when(payload.rawBody()) + .thenReturn( + ("[{\"key\": \"value\"}, {\"key\": \"value\"}]").getBytes(StandardCharsets.UTF_8)); + + testObject.activate(ctx); + var result = testObject.triggerWebhook(payload); + + assertNotNull(result.response()); + assertThat((List) result.request().body()) + .contains(Map.of("key", "value"), Map.of("key", "value")); + + var request = new MappedHttpRequest(Map.of("key", "value"), null, null); + var context = new WebhookResultContext(request, null, null); + var response = result.response().apply(context); + assertEquals("value", response.body()); + } + @Test void triggerWebhook_FormDataBody_HappyCase() { InboundConnectorContext ctx =