Skip to content

Commit

Permalink
feature-2774 webhook connector can now handle json array in request b…
Browse files Browse the repository at this point in the history
…ody 2
  • Loading branch information
mathias-vandaele committed Jun 27, 2024
1 parent 66df2f2 commit 1970216
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -215,7 +215,7 @@ public WebhookHttpResponse verify(WebhookProcessingPayload payload) {
"request",
Map.of(
"body",
HttpWebhookUtil.transformRawBodyToMap(
HttpWebhookUtil.transformRawBodyToObject(
payload.rawBody(),
HttpWebhookUtil.extractContentType(payload.headers())),
"headers",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,45 @@
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<String, String> 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<String> sortedKeys = new ArrayList<>(signatureData.keySet());
Collections.sort(sortedKeys);

StringBuilder builder = new StringBuilder();

for (String key : sortedKeys) {
builder.append(key);
String value = signatureData.get(key);
builder.append(value == null ? "" : value);
}
return builder.toString();
}

private static <T> Map<T, T> checkedCastToMap(Object o, Class<T> 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<T, T>) map;
}
return Map.of();
}

@Override
public byte[] getBytesToSign(final WebhookProcessingPayload payload) throws IOException {
return (payload.requestURL() + extractSignatureData(payload)).getBytes();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public static String extractContentType(Map<String, String> 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();
}
Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String>) 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<Map>) 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 =
Expand Down

0 comments on commit 1970216

Please sign in to comment.