diff --git a/deployment/src/test/java/io/quarkiverse/githubapp/deployment/OptionalConfigFileTest.java b/deployment/src/test/java/io/quarkiverse/githubapp/deployment/OptionalConfigFileTest.java index 84987dd6..e2c82549 100644 --- a/deployment/src/test/java/io/quarkiverse/githubapp/deployment/OptionalConfigFileTest.java +++ b/deployment/src/test/java/io/quarkiverse/githubapp/deployment/OptionalConfigFileTest.java @@ -1,6 +1,7 @@ package io.quarkiverse.githubapp.deployment; import java.util.Optional; +import java.util.UUID; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; @@ -30,6 +31,7 @@ public void testOptionalConfigFile() { RestAssured .given() .header(Headers.X_GITHUB_EVENT, "label") + .header(Headers.X_GITHUB_DELIVERY, UUID.randomUUID()) .contentType("application/json") .body(Thread.currentThread().getContextClassLoader().getResourceAsStream(PAYLOAD)) .when().post("/") diff --git a/docs/modules/ROOT/pages/includes/quarkus-github-app.adoc b/docs/modules/ROOT/pages/includes/quarkus-github-app.adoc index c1ed6f81..484bbc65 100644 --- a/docs/modules/ROOT/pages/includes/quarkus-github-app.adoc +++ b/docs/modules/ROOT/pages/includes/quarkus-github-app.adoc @@ -246,13 +246,13 @@ a| [[quarkus-github-app_quarkus-github-app-personal-access-token]]`link:#quarkus [.description] -- -A personal access token for use with `TokenGitHubClients`. +A personal access token for use with `TokenGitHubClients` or when no installation id is provided in the payload. For standard use cases, you will use the installation client which comes with the installation permissions. It can be injected directly in your method. However, if your payload comes from a webhook and doesn't have an installation id, it's handy to be able to use a client authenticated with a personal access token as the application client permissions are very limited. -This token will be used to authenticate the clients provided by `TokenGitHubClients`. +This token will be used to authenticate the clients provided by `TokenGitHubClients` and clients authenticated with this personal access token will be automatically provided when injecting `GitHub` or `DynamicGraphQLClient` in your method, when the payload doesn't provide an installation id. ifdef::add-copy-button-to-env-var[] Environment variable: env_var_with_copy_button:+++QUARKUS_GITHUB_APP_PERSONAL_ACCESS_TOKEN+++[] diff --git a/runtime/src/main/java/io/quarkiverse/githubapp/runtime/Routes.java b/runtime/src/main/java/io/quarkiverse/githubapp/runtime/Routes.java index 1152ebc1..946748d1 100644 --- a/runtime/src/main/java/io/quarkiverse/githubapp/runtime/Routes.java +++ b/runtime/src/main/java/io/quarkiverse/githubapp/runtime/Routes.java @@ -92,21 +92,39 @@ private void handleRequest(RoutingContext routingContext, String hubSignature, String deliveryId, String event, - String replayed) { + String replayedHeader) { - if (!launchMode.isDevOrTest() && (isBlank(deliveryId) || isBlank(hubSignature))) { + boolean replayed = "true".equals(replayedHeader) && LaunchMode.current().isDevOrTest(); + boolean checkSignatures = !replayed && LaunchMode.current() != LaunchMode.TEST; + + if (isBlank(deliveryId)) { + routingExchange.response().setStatusCode(400).end(); + LOG.debug("Request received without delivery id. It has been ignored."); + return; + } + + if (checkSignatures && isBlank(hubSignature)) { routingExchange.response().setStatusCode(400).end(); + + if (LaunchMode.current() == LaunchMode.DEVELOPMENT) { + LOG.warn( + "Request received without signature. This is only permitted for replayed events. It has been ignored."); + } + return; } if (routingContext.body().buffer() == null) { routingExchange.ok().end(); + LOG.debug("Request received without a body. It has been ignored."); return; } byte[] bodyBytes = routingContext.body().buffer().getBytes(); - if (checkedConfigProvider.webhookSecret().isPresent() && !launchMode.isDevOrTest()) { + if (checkSignatures && checkedConfigProvider.webhookSecret().isPresent()) { + System.out.println("Signature checked!"); + if (!payloadSignatureChecker.matches(bodyBytes, hubSignature)) { StringBuilder signatureError = new StringBuilder("Invalid signature for delivery: ").append(deliveryId) .append("\n"); @@ -120,6 +138,7 @@ private void handleRequest(RoutingContext routingContext, if (bodyBytes.length == 0) { routingExchange.ok().end(); + LOG.debug("Request received without a body. It has been ignored."); return; } @@ -128,7 +147,7 @@ private void handleRequest(RoutingContext routingContext, String action = payloadObject.getString("action"); - if (!isBlank(deliveryId) && checkedConfigProvider.debug().payloadDirectory().isPresent()) { + if (checkedConfigProvider.debug().payloadDirectory().isPresent()) { String fileName = DATE_TIME_FORMATTER.format(LocalDateTime.now()) + "-" + event + "-" + (!isBlank(action) ? action + "-" : "") + deliveryId + ".json"; Path path = checkedConfigProvider.debug().payloadDirectory().get().resolve(fileName); @@ -142,7 +161,7 @@ private void handleRequest(RoutingContext routingContext, Long installationId = extractInstallationId(payloadObject); String repository = extractRepository(payloadObject); GitHubEvent gitHubEvent = new GitHubEvent(installationId, checkedConfigProvider.appName().orElse(null), deliveryId, - repository, event, action, payload, payloadObject, "true".equals(replayed) ? true : false); + repository, event, action, payload, payloadObject, replayed); if (launchMode == LaunchMode.DEVELOPMENT && replayRouteInstance.isResolvable()) { replayRouteInstance.get().pushEvent(gitHubEvent); diff --git a/runtime/src/main/java/io/quarkiverse/githubapp/runtime/smee/SmeeIoForwarder.java b/runtime/src/main/java/io/quarkiverse/githubapp/runtime/smee/SmeeIoForwarder.java index acf4b264..f8d2162f 100644 --- a/runtime/src/main/java/io/quarkiverse/githubapp/runtime/smee/SmeeIoForwarder.java +++ b/runtime/src/main/java/io/quarkiverse/githubapp/runtime/smee/SmeeIoForwarder.java @@ -110,11 +110,11 @@ public void onEvent(HttpEventStreamClient client, Event event) { try { JsonNode rootNode = objectMapper.readTree(data); - JsonNode body = rootNode.get("body"); + JsonNode rawData = rootNode.get("rawdata"); - if (body != null) { + if (rawData != null) { HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(localUrl) - .POST(BodyPublishers.ofString(objectMapper.writeValueAsString(rootNode.get("body")))); + .POST(BodyPublishers.ofString(objectMapper.writeValueAsString(rootNode.get("rawdata")))); for (String forwardedHeader : FORWARDED_HEADERS) { JsonNode headerValue = rootNode.get(forwardedHeader.toLowerCase(Locale.ROOT));