Skip to content

Commit

Permalink
Allow checking signatures in dev mode
Browse files Browse the repository at this point in the history
  • Loading branch information
gsmet committed Aug 23, 2024
1 parent 4bbf214 commit 64b3518
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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("/")
Expand Down
4 changes: 2 additions & 2 deletions docs/modules/ROOT/pages/includes/quarkus-github-app.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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+++[]
Expand Down
29 changes: 24 additions & 5 deletions runtime/src/main/java/io/quarkiverse/githubapp/runtime/Routes.java
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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;
}

Expand All @@ -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);
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down

0 comments on commit 64b3518

Please sign in to comment.