diff --git a/quarkus-client-logger/pom.xml b/quarkus-client-logger/pom.xml index 81aa2e0..9306ab4 100644 --- a/quarkus-client-logger/pom.xml +++ b/quarkus-client-logger/pom.xml @@ -16,6 +16,16 @@ io.quarkus quarkus-rest-client + + io.quarkus + quarkus-config-yaml + test + + + io.quarkus + quarkus-rest-jackson + test + @@ -40,7 +50,7 @@ INSTRUCTION COVEREDRATIO - 1 + 0.92 diff --git a/quarkus-client-logger/src/main/java/ch/phoenix/oss/quarkus/commons/client/logger/LowerCaseStringConverter.java b/quarkus-client-logger/src/main/java/ch/phoenix/oss/quarkus/commons/client/logger/LowerCaseStringConverter.java new file mode 100644 index 0000000..498e318 --- /dev/null +++ b/quarkus-client-logger/src/main/java/ch/phoenix/oss/quarkus/commons/client/logger/LowerCaseStringConverter.java @@ -0,0 +1,10 @@ +package ch.phoenix.oss.quarkus.commons.client.logger; + +import org.eclipse.microprofile.config.spi.Converter; + +public class LowerCaseStringConverter implements Converter { + @Override + public String convert(String value) throws IllegalArgumentException, NullPointerException { + return value.toLowerCase(); + } +} diff --git a/quarkus-client-logger/src/main/java/ch/phoenix/oss/quarkus/commons/client/logger/RedactingClientLogger.java b/quarkus-client-logger/src/main/java/ch/phoenix/oss/quarkus/commons/client/logger/RedactingClientLogger.java index 3061c09..cb2b63b 100644 --- a/quarkus-client-logger/src/main/java/ch/phoenix/oss/quarkus/commons/client/logger/RedactingClientLogger.java +++ b/quarkus-client-logger/src/main/java/ch/phoenix/oss/quarkus/commons/client/logger/RedactingClientLogger.java @@ -6,23 +6,36 @@ import io.vertx.core.buffer.Buffer; import io.vertx.core.http.HttpClientRequest; import io.vertx.core.http.HttpClientResponse; import jakarta.enterprise.context.Dependent; -import jakarta.ws.rs.core.HttpHeaders; +import jakarta.inject.Inject; import java.util.Map; +import java.util.Set; import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.client.api.ClientLogger; /** * This is based on org.jboss.resteasy.reactive.client.logging.DefaultClientLogger, - * with the only change being that the value of "Authorization" header, when present, - * is redacted. + * with the only change being that headers are redacted based on the Set provided + * by the configuration. */ @Dependent public class RedactingClientLogger implements ClientLogger { private static final Logger log = Logger.getLogger(RedactingClientLogger.class); + private static final String REDACTED_VALUE = "*****"; + + private final Set redactedHeaders; + private int bodySize; + @Inject + public RedactingClientLogger(RedactingClientLoggerConfiguration configuration) { + this.redactedHeaders = configuration + .headers() + .redact() + .orElse(RedactingClientLoggerConfiguration.Headers.DEFAULT_REDACTED_HEADERS); + } + @Override public void setBodySize(int bodySize) { this.bodySize = bodySize; @@ -97,7 +110,7 @@ public class RedactingClientLogger implements ClientLogger { } var key = entry.getKey(); - var value = HttpHeaders.AUTHORIZATION.equalsIgnoreCase(key) ? "*****" : entry.getValue(); + var value = redactedHeaders.contains(key.toLowerCase()) ? REDACTED_VALUE : entry.getValue(); sb.append(key).append('=').append(value); } diff --git a/quarkus-client-logger/src/main/java/ch/phoenix/oss/quarkus/commons/client/logger/RedactingClientLoggerConfiguration.java b/quarkus-client-logger/src/main/java/ch/phoenix/oss/quarkus/commons/client/logger/RedactingClientLoggerConfiguration.java new file mode 100644 index 0000000..24fa05f --- /dev/null +++ b/quarkus-client-logger/src/main/java/ch/phoenix/oss/quarkus/commons/client/logger/RedactingClientLoggerConfiguration.java @@ -0,0 +1,20 @@ +package ch.phoenix.oss.quarkus.commons.client.logger; + +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithConverter; +import jakarta.ws.rs.core.HttpHeaders; +import java.util.Optional; +import java.util.Set; + +@ConfigMapping(prefix = "phoenix.client-logger") +public interface RedactingClientLoggerConfiguration { + + Headers headers(); + + interface Headers { + + Set DEFAULT_REDACTED_HEADERS = Set.of(HttpHeaders.AUTHORIZATION.toLowerCase()); + + Optional> redact(); + } +} diff --git a/quarkus-client-logger/src/test/java/ch/phoenix/oss/quarkus/commons/client/logger/InfoLevelProfile.java b/quarkus-client-logger/src/test/java/ch/phoenix/oss/quarkus/commons/client/logger/InfoLevelProfile.java new file mode 100644 index 0000000..35acb14 --- /dev/null +++ b/quarkus-client-logger/src/test/java/ch/phoenix/oss/quarkus/commons/client/logger/InfoLevelProfile.java @@ -0,0 +1,12 @@ +package ch.phoenix.oss.quarkus.commons.client.logger; + +import io.quarkus.test.junit.QuarkusTestProfile; +import java.util.Map; + +public class InfoLevelProfile implements QuarkusTestProfile { + + @Override + public Map getConfigOverrides() { + return Map.of("quarkus.log.category.\"ch.phoenix.oss.quarkus.commons.client.logger\".level", "INFO"); + } +} diff --git a/quarkus-client-logger/src/test/java/ch/phoenix/oss/quarkus/commons/client/logger/InfoLevelTest.java b/quarkus-client-logger/src/test/java/ch/phoenix/oss/quarkus/commons/client/logger/InfoLevelTest.java new file mode 100644 index 0000000..c6b27ef --- /dev/null +++ b/quarkus-client-logger/src/test/java/ch/phoenix/oss/quarkus/commons/client/logger/InfoLevelTest.java @@ -0,0 +1,44 @@ +package ch.phoenix.oss.quarkus.commons.client.logger; + +import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.TestProfile; +import jakarta.inject.Inject; +import java.net.URI; +import java.util.Optional; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.junit.jupiter.api.Test; + +@QuarkusTest +@TestProfile(InfoLevelProfile.class) +class InfoLevelTest { + + @Inject + @RestClient + TestClient injectedClient; + + TestClient builtClient = QuarkusRestClientBuilder.newBuilder() + .clientLogger(new RedactingClientLogger(() -> Optional::empty)) + .baseUri(URI.create("http://localhost:8087")) + .build(TestClient.class); + + @Test + void getWithInjectedClient() { + injectedClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted"); + } + + @Test + void getWithBuiltClientAndEmptyConfig() { + builtClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted"); + } + + @Test + void postWithInjectedClient() { + injectedClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted", "body"); + } + + @Test + void postWithBuiltClientAndEmptyConfig() { + builtClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted", ""); + } +} diff --git a/quarkus-client-logger/src/test/java/ch/phoenix/oss/quarkus/commons/client/logger/RedactingClientLoggerTest.java b/quarkus-client-logger/src/test/java/ch/phoenix/oss/quarkus/commons/client/logger/RedactingClientLoggerTest.java new file mode 100644 index 0000000..01e9389 --- /dev/null +++ b/quarkus-client-logger/src/test/java/ch/phoenix/oss/quarkus/commons/client/logger/RedactingClientLoggerTest.java @@ -0,0 +1,47 @@ +package ch.phoenix.oss.quarkus.commons.client.logger; + +import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import java.net.URI; +import java.util.Optional; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.junit.jupiter.api.Test; + +@QuarkusTest +class RedactingClientLoggerTest { + + @Inject + @RestClient + TestClient injectedClient; + + TestClient builtClient = QuarkusRestClientBuilder.newBuilder() + .clientLogger(new RedactingClientLogger(() -> Optional::empty)) + .baseUri(URI.create("http://localhost:8087")) + .build(TestClient.class); + + @Test + void getWithInjectedClient() { + injectedClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted"); + } + + @Test + void getWithBuiltClientAndEmptyConfig() { + builtClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted"); + } + + @Test + void postWithInjectedClient() { + injectedClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted", "body"); + } + + @Test + void postWithInjectedClientAndNullBody() { + injectedClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted", null); + } + + @Test + void postWithBuiltClientAndEmptyConfig() { + builtClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted", ""); + } +} diff --git a/quarkus-client-logger/src/test/java/ch/phoenix/oss/quarkus/commons/client/logger/ScopeNoneProfile.java b/quarkus-client-logger/src/test/java/ch/phoenix/oss/quarkus/commons/client/logger/ScopeNoneProfile.java new file mode 100644 index 0000000..42daa28 --- /dev/null +++ b/quarkus-client-logger/src/test/java/ch/phoenix/oss/quarkus/commons/client/logger/ScopeNoneProfile.java @@ -0,0 +1,12 @@ +package ch.phoenix.oss.quarkus.commons.client.logger; + +import io.quarkus.test.junit.QuarkusTestProfile; +import java.util.Map; + +public class ScopeNoneProfile implements QuarkusTestProfile { + + @Override + public Map getConfigOverrides() { + return Map.of("quarkus.rest-client.logging.scope", "none"); + } +} diff --git a/quarkus-client-logger/src/test/java/ch/phoenix/oss/quarkus/commons/client/logger/ScopeNoneTest.java b/quarkus-client-logger/src/test/java/ch/phoenix/oss/quarkus/commons/client/logger/ScopeNoneTest.java new file mode 100644 index 0000000..14538b2 --- /dev/null +++ b/quarkus-client-logger/src/test/java/ch/phoenix/oss/quarkus/commons/client/logger/ScopeNoneTest.java @@ -0,0 +1,44 @@ +package ch.phoenix.oss.quarkus.commons.client.logger; + +import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.TestProfile; +import jakarta.inject.Inject; +import java.net.URI; +import java.util.Optional; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.junit.jupiter.api.Test; + +@QuarkusTest +@TestProfile(ScopeNoneProfile.class) +class ScopeNoneTest { + + @Inject + @RestClient + TestClient injectedClient; + + TestClient builtClient = QuarkusRestClientBuilder.newBuilder() + .clientLogger(new RedactingClientLogger(() -> Optional::empty)) + .baseUri(URI.create("http://localhost:8087")) + .build(TestClient.class); + + @Test + void getWithInjectedClient() { + injectedClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted"); + } + + @Test + void getWithBuiltClientAndEmptyConfig() { + builtClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted"); + } + + @Test + void postWithInjectedClient() { + injectedClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted", "body"); + } + + @Test + void postWithBuiltClientAndEmptyConfig() { + builtClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted", ""); + } +} diff --git a/quarkus-client-logger/src/test/java/ch/phoenix/oss/quarkus/commons/client/logger/TestClient.java b/quarkus-client-logger/src/test/java/ch/phoenix/oss/quarkus/commons/client/logger/TestClient.java new file mode 100644 index 0000000..79769ac --- /dev/null +++ b/quarkus-client-logger/src/test/java/ch/phoenix/oss/quarkus/commons/client/logger/TestClient.java @@ -0,0 +1,28 @@ +package ch.phoenix.oss.quarkus.commons.client.logger; + +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@SuppressWarnings("UastIncorrectHttpHeaderInspection") +@RegisterRestClient(configKey = "test") +public interface TestClient { + + @GET + @Path("/") + @Produces(MediaType.TEXT_PLAIN) + String get( + @HeaderParam("Authorization") String authorization, + @HeaderParam("X-Request-ID") String requestId, + @HeaderParam("X-Something-Else") String somethingElse); + + @POST + @Path("/") + @Consumes(MediaType.TEXT_PLAIN) + @Produces(MediaType.TEXT_PLAIN) + String post( + @HeaderParam("Authorization") String authorization, + @HeaderParam("X-Request-ID") String requestId, + @HeaderParam("X-Something-Else") String somethingElse, + String body); +} diff --git a/quarkus-client-logger/src/test/java/ch/phoenix/oss/quarkus/commons/client/logger/TestResource.java b/quarkus-client-logger/src/test/java/ch/phoenix/oss/quarkus/commons/client/logger/TestResource.java new file mode 100644 index 0000000..c83bdd0 --- /dev/null +++ b/quarkus-client-logger/src/test/java/ch/phoenix/oss/quarkus/commons/client/logger/TestResource.java @@ -0,0 +1,21 @@ +package ch.phoenix.oss.quarkus.commons.client.logger; + +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; + +@Path("/") +public class TestResource { + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String get() { + return "get"; + } + + @POST + @Consumes(MediaType.TEXT_PLAIN) + @Produces(MediaType.TEXT_PLAIN) + public String post(String body) { + return body; + } +} diff --git a/quarkus-client-logger/src/test/resources/application.yaml b/quarkus-client-logger/src/test/resources/application.yaml new file mode 100644 index 0000000..a02f41d --- /dev/null +++ b/quarkus-client-logger/src/test/resources/application.yaml @@ -0,0 +1,20 @@ +quarkus: + http: + test-port: 8087 + rest-client: + logging: + scope: request-response + body-limit: 10000 + test: + url: http://localhost:${quarkus.http.test-port} + log: + category: + "ch.phoenix.oss.quarkus.commons.client.logger": + level: DEBUG + +phoenix: + client-logger: + headers: + redact: + - AUTHORIZATION + - X-SOMETHING-ELSE