diff --git a/src/main/java/com/box/sdk/BoxAPIConnection.java b/src/main/java/com/box/sdk/BoxAPIConnection.java index 1dda78f73..c59da7279 100644 --- a/src/main/java/com/box/sdk/BoxAPIConnection.java +++ b/src/main/java/com/box/sdk/BoxAPIConnection.java @@ -31,6 +31,7 @@ import okhttp3.Authenticator; import okhttp3.Call; import okhttp3.Credentials; +import okhttp3.Headers; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; @@ -1265,7 +1266,8 @@ private Response executeOnClient(OkHttpClient httpClient, Request request) { try { return createNewCall(httpClient, request).execute(); } catch (IOException e) { - throw new BoxAPIException("Couldn't connect to the Box API due to a network error. Request\n" + request, e); + throw new BoxAPIException("Couldn't connect to the Box API due to a network error. Request\n" + + toSanitizedRequest(request), e); } } @@ -1298,4 +1300,12 @@ protected enum ResourceLinkType { */ SharedLink } + + private Request toSanitizedRequest(Request originalRequest) { + Headers sanitizedHeaders = BoxSensitiveDataSanitizer.sanitizeHeaders(originalRequest.headers()); + + return originalRequest.newBuilder() + .headers(sanitizedHeaders) + .build(); + } } diff --git a/src/main/java/com/box/sdk/BoxSensitiveDataSanitizer.java b/src/main/java/com/box/sdk/BoxSensitiveDataSanitizer.java new file mode 100644 index 000000000..8b586bb90 --- /dev/null +++ b/src/main/java/com/box/sdk/BoxSensitiveDataSanitizer.java @@ -0,0 +1,50 @@ +package com.box.sdk; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import okhttp3.Headers; +import org.jetbrains.annotations.NotNull; + +/** + * Class used to sanitize sensitive data from payload. + */ +public final class BoxSensitiveDataSanitizer { + private static final Set SENSITIVE_KEYS = new HashSet<>(Arrays.asList("authorization", "access_token", + "refresh_token", "subject_token", "token", "client_id", "client_secret", "code", "shared_link", "download_url", + "jwt_private_key", "jwt_private_key_passphrase", "password")); + + private BoxSensitiveDataSanitizer() { + } + + /** + * Add key that should be sanitized + * + * @param key key to be sanitized + */ + public static void addKeyToSanitize(String key) { + SENSITIVE_KEYS.add(key); + } + + @NotNull + static Headers sanitizeHeaders(Headers originalHeaders) { + Headers.Builder sanitizedHeadersBuilder = originalHeaders.newBuilder(); + + for (String originalHeaderName : originalHeaders.names()) { + if (isSensitiveKey(originalHeaderName)) { + sanitizedHeadersBuilder.set(originalHeaderName, "[REDACTED]"); + } else { + String headerValue = originalHeaders.get(originalHeaderName); + if (headerValue != null) { + sanitizedHeadersBuilder.set(originalHeaderName, headerValue); + } + } + } + + return sanitizedHeadersBuilder.build(); + } + + private static boolean isSensitiveKey(@NotNull String key) { + return SENSITIVE_KEYS.contains(key.toLowerCase()); + } +} diff --git a/src/test/java/com/box/sdk/BoxSensitiveDataSanitizerTest.java b/src/test/java/com/box/sdk/BoxSensitiveDataSanitizerTest.java new file mode 100644 index 000000000..998c39845 --- /dev/null +++ b/src/test/java/com/box/sdk/BoxSensitiveDataSanitizerTest.java @@ -0,0 +1,88 @@ +package com.box.sdk; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import java.util.HashMap; +import java.util.Map; +import okhttp3.Headers; +import org.junit.Test; + +public class BoxSensitiveDataSanitizerTest { + @Test + public void removeSensitiveDataFromHeaders() { + Map headersMap = new HashMap<>(); + headersMap.put("authorization", "token"); + headersMap.put("user-agent", "java-sdk"); + + Headers headers = Headers.of(headersMap); + Headers sanitizedHeaders = BoxSensitiveDataSanitizer.sanitizeHeaders(headers); + + assertThat(sanitizedHeaders.size(), is(2)); + assertThat(sanitizedHeaders.get("authorization"), is("[REDACTED]")); + assertThat(sanitizedHeaders.get("user-agent"), is("java-sdk")); + } + + @Test + public void removeAllHeadersWhenOnlySensitiveData() { + Map headersMap = new HashMap<>(); + headersMap.put("authorization", "token"); + headersMap.put("password", "123"); + + Headers headers = Headers.of(headersMap); + Headers sanitizedHeaders = BoxSensitiveDataSanitizer.sanitizeHeaders(headers); + + assertThat(sanitizedHeaders.size(), is(2)); + assertThat(sanitizedHeaders.get("authorization"), is("[REDACTED]")); + assertThat(sanitizedHeaders.get("password"), is("[REDACTED]")); + } + + @Test + public void removeSensitiveDataFromHeadersWhenUppercase() { + Map headersMap = new HashMap<>(); + headersMap.put("Authorization", "token"); + headersMap.put("user-agent", "java-sdk"); + + Headers headers = Headers.of(headersMap); + Headers sanitizedHeaders = BoxSensitiveDataSanitizer.sanitizeHeaders(headers); + + assertThat(sanitizedHeaders.size(), is(2)); + assertThat(sanitizedHeaders.get("Authorization"), is("[REDACTED]")); + assertThat(sanitizedHeaders.get("user-agent"), is("java-sdk")); + } + + @Test + public void headersNotRemovedWhenNoSensitiveData() { + Map headersMap = new HashMap<>(); + headersMap.put("accept", "application/json"); + headersMap.put("user-agent", "java-sdk"); + + Headers headers = Headers.of(headersMap); + Headers sanitizedHeaders = BoxSensitiveDataSanitizer.sanitizeHeaders(headers); + + assertThat(sanitizedHeaders.size(), is(2)); + assertThat(sanitizedHeaders.get("accept"), is("application/json")); + assertThat(sanitizedHeaders.get("user-agent"), is("java-sdk")); + } + + @Test + public void returnEmptyHeadersWhenEmptyHeadersPassed() { + Headers headers = Headers.of(new HashMap<>()); + Headers sanitizedHeaders = BoxSensitiveDataSanitizer.sanitizeHeaders(headers); + + assertThat(sanitizedHeaders.size(), is(0)); + } + + @Test + public void sanitizeAddedKeys() { + Map headersMap = new HashMap<>(); + headersMap.put("x-auth", "token"); + + Headers headers = Headers.of(headersMap); + BoxSensitiveDataSanitizer.addKeyToSanitize("x-auth"); + Headers sanitizedHeaders = BoxSensitiveDataSanitizer.sanitizeHeaders(headers); + + assertThat(sanitizedHeaders.size(), is(1)); + assertThat(sanitizedHeaders.get("x-auth"), is("[REDACTED]")); + } +}