diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c4922260..4ab06730a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ # Changelog +## [Unreleased] +### Changed +* `toString()` of credential-bearing structures (`B2AccountAuthorization`, `B2FileSseForRequest`, `B2WebhookConfiguration`, `B2WebhookCustomHeader`, and `B2HeadersImpl`) omits sensitive values; `B2WebhookCustomHeader` omits all header values. +* Event notification rule ordering compares webhook target settings (url, custom headers, signing secret, maxEventsPerBatch) directly instead of through `toString()`. + ## [6.5.0] - 2025-12-15 * Add `readBucketLifecycleRules` and `writeBucketLifecycleRules` capabilities. ## [6.4.0] - 2025-05-14 diff --git a/core/src/main/java/com/backblaze/b2/client/contentSources/B2HeadersImpl.java b/core/src/main/java/com/backblaze/b2/client/contentSources/B2HeadersImpl.java index 4267bc5fd..5dcf9ba41 100644 --- a/core/src/main/java/com/backblaze/b2/client/contentSources/B2HeadersImpl.java +++ b/core/src/main/java/com/backblaze/b2/client/contentSources/B2HeadersImpl.java @@ -15,6 +15,8 @@ * B2HeadersImpl implements the B2Headers interface. */ public class B2HeadersImpl implements B2Headers { + private static final String REDACTED_VALUE = "[redacted]"; + private Map pairs; private B2HeadersImpl(Map pairs) { @@ -72,8 +74,20 @@ public B2HeadersImpl build() { @Override public String toString() { + final Map pairsForDisplay = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + pairs.forEach((name, value) -> pairsForDisplay.put(name, getDisplayValue(name, value))); + return "B2HeadersImpl{" + - "pairs=" + pairs + + "pairs=" + pairsForDisplay + '}'; } + + private static boolean isSensitiveHeader(String name) { + return B2Headers.AUTHORIZATION.equalsIgnoreCase(name) || + B2Headers.SERVER_SIDE_ENCRYPTION_CUSTOMER_KEY.equalsIgnoreCase(name); + } + + private static String getDisplayValue(String name, String value) { + return value != null && isSensitiveHeader(name) ? REDACTED_VALUE : value; + } } diff --git a/core/src/main/java/com/backblaze/b2/client/structures/B2AccountAuthorization.java b/core/src/main/java/com/backblaze/b2/client/structures/B2AccountAuthorization.java index 43b6e5530..0cb52715d 100644 --- a/core/src/main/java/com/backblaze/b2/client/structures/B2AccountAuthorization.java +++ b/core/src/main/java/com/backblaze/b2/client/structures/B2AccountAuthorization.java @@ -9,6 +9,8 @@ import java.util.Objects; public class B2AccountAuthorization { + private static final String REDACTED_VALUE = "[redacted]"; + @B2Json.required private final String accountId; @B2Json.required @@ -98,7 +100,7 @@ public boolean equals(Object o) { public String toString() { return "B2AccountAuthorization{" + "accountId='" + accountId + '\'' + - ", authorizationToken='" + authorizationToken + '\'' + + ", authorizationToken='" + (authorizationToken == null ? null : REDACTED_VALUE) + '\'' + ", apiUrl='" + apiUrl + '\'' + ", downloadUrl='" + downloadUrl + '\'' + ", recommendedPartSize=" + recommendedPartSize + diff --git a/core/src/main/java/com/backblaze/b2/client/structures/B2EventNotificationRule.java b/core/src/main/java/com/backblaze/b2/client/structures/B2EventNotificationRule.java index a46734c3a..7f6a0290e 100644 --- a/core/src/main/java/com/backblaze/b2/client/structures/B2EventNotificationRule.java +++ b/core/src/main/java/com/backblaze/b2/client/structures/B2EventNotificationRule.java @@ -7,6 +7,7 @@ import com.backblaze.b2.json.B2Json; import java.util.Comparator; +import java.util.Iterator; import java.util.Objects; import java.util.TreeSet; @@ -17,7 +18,7 @@ public class B2EventNotificationRule implements Comparable COMPARATOR = Comparator.comparing(B2EventNotificationRule::getName) .thenComparing(rule -> String.join(",", rule.getEventTypes())) .thenComparing(B2EventNotificationRule::getObjectNamePrefix) - .thenComparing(rule -> rule.getTargetConfiguration().toString()) + .thenComparing(B2EventNotificationRule::getTargetConfiguration, B2EventNotificationRule::compareTargetConfigurations) .thenComparing(B2EventNotificationRule::isEnabled) .thenComparing(B2EventNotificationRule::isSuspended) .thenComparing(B2EventNotificationRule::getSuspensionReason); @@ -172,4 +173,98 @@ public String toString() { public int compareTo(B2EventNotificationRule r) { return COMPARATOR.compare(this, r); } + + private static int compareTargetConfigurations(B2EventNotificationTargetConfiguration first, + B2EventNotificationTargetConfiguration second) { + if (first == second) { + return 0; + } + if (first == null) { + return -1; + } + if (second == null) { + return 1; + } + if (first instanceof B2WebhookConfiguration && second instanceof B2WebhookConfiguration) { + return compareWebhookConfigurations((B2WebhookConfiguration) first, (B2WebhookConfiguration) second); + } + + final int classComparison = first.getClass().getName().compareTo(second.getClass().getName()); + if (classComparison != 0) { + return classComparison; + } + if (first.equals(second)) { + return 0; + } + return first.toString().compareTo(second.toString()); + } + + private static int compareWebhookConfigurations(B2WebhookConfiguration first, + B2WebhookConfiguration second) { + int comparison = first.getUrl().compareTo(second.getUrl()); + if (comparison != 0) { + return comparison; + } + + comparison = compareCustomHeaders(first.getCustomHeaders(), second.getCustomHeaders()); + if (comparison != 0) { + return comparison; + } + + comparison = compareNullableStrings(first.getHmacSha256SigningSecret(), second.getHmacSha256SigningSecret()); + if (comparison != 0) { + return comparison; + } + + return compareNullableIntegers(first.getMaxEventsPerBatch(), second.getMaxEventsPerBatch()); + } + + private static int compareCustomHeaders(TreeSet first, + TreeSet second) { + if (first == second) { + return 0; + } + if (first == null) { + return -1; + } + if (second == null) { + return 1; + } + + final Iterator firstIterator = first.iterator(); + final Iterator secondIterator = second.iterator(); + while (firstIterator.hasNext() && secondIterator.hasNext()) { + final int comparison = firstIterator.next().compareTo(secondIterator.next()); + if (comparison != 0) { + return comparison; + } + } + return Boolean.compare(firstIterator.hasNext(), secondIterator.hasNext()); + } + + private static int compareNullableStrings(String first, String second) { + if (first == second) { + return 0; + } + if (first == null) { + return -1; + } + if (second == null) { + return 1; + } + return first.compareTo(second); + } + + private static int compareNullableIntegers(Integer first, Integer second) { + if (first == second) { + return 0; + } + if (first == null) { + return -1; + } + if (second == null) { + return 1; + } + return first.compareTo(second); + } } diff --git a/core/src/main/java/com/backblaze/b2/client/structures/B2FileSseForRequest.java b/core/src/main/java/com/backblaze/b2/client/structures/B2FileSseForRequest.java index b0ca5454d..97bb39b1a 100644 --- a/core/src/main/java/com/backblaze/b2/client/structures/B2FileSseForRequest.java +++ b/core/src/main/java/com/backblaze/b2/client/structures/B2FileSseForRequest.java @@ -15,6 +15,8 @@ import static com.backblaze.b2.client.structures.B2ServerSideEncryptionMode.SSE_C; public class B2FileSseForRequest { + private static final String REDACTED_VALUE = "[redacted]"; + /** * The SSE mode, e.g. SSE-B2 or SSE-C */ @@ -124,7 +126,7 @@ public String toString() { return "B2FileSseForRequest{" + "mode='" + mode + "', " + "algorithm=" + algorithm + ", " + - "customerKey=" + customerKey + ", " + + "customerKey=" + (customerKey == null ? null : REDACTED_VALUE) + ", " + "customerKeyMd5=" + customerKeyMd5 + '}'; } diff --git a/core/src/main/java/com/backblaze/b2/client/structures/B2WebhookConfiguration.java b/core/src/main/java/com/backblaze/b2/client/structures/B2WebhookConfiguration.java index 55d862849..66bdc094d 100644 --- a/core/src/main/java/com/backblaze/b2/client/structures/B2WebhookConfiguration.java +++ b/core/src/main/java/com/backblaze/b2/client/structures/B2WebhookConfiguration.java @@ -14,6 +14,8 @@ * Webhook destination for B2EventNotificationRule */ public class B2WebhookConfiguration extends B2EventNotificationTargetConfiguration { + private static final String REDACTED_VALUE = "[redacted]"; + /** * The URL endpoint for the webhook, including the protocol, which * must be "https://". @@ -105,7 +107,7 @@ public String toString() { return "B2WebhookConfiguration{" + "url='" + url + '\'' + ", customHeaders=" + customHeaders + - ", hmacSha256SigningSecret='" + hmacSha256SigningSecret + '\'' + + ", hmacSha256SigningSecret='" + (hmacSha256SigningSecret == null ? null : REDACTED_VALUE) + '\'' + '}'; } } diff --git a/core/src/main/java/com/backblaze/b2/client/structures/B2WebhookCustomHeader.java b/core/src/main/java/com/backblaze/b2/client/structures/B2WebhookCustomHeader.java index 2fec63259..90fb405f1 100644 --- a/core/src/main/java/com/backblaze/b2/client/structures/B2WebhookCustomHeader.java +++ b/core/src/main/java/com/backblaze/b2/client/structures/B2WebhookCustomHeader.java @@ -16,6 +16,8 @@ */ public class B2WebhookCustomHeader implements Comparable { + private static final String REDACTED_VALUE = "[redacted]"; + private static final Comparator COMPARATOR = Comparator.comparing(B2WebhookCustomHeader::getName, String.CASE_INSENSITIVE_ORDER) .thenComparing(B2WebhookCustomHeader::getValue); @@ -71,7 +73,7 @@ public int hashCode() { public String toString() { return "B2CustomWebhookHeader{" + "name='" + name + '\'' + - ", value='" + value + '\'' + + ", value='" + (value == null ? null : REDACTED_VALUE) + '\'' + '}'; } diff --git a/core/src/test/java/com/backblaze/b2/client/B2AccountAuthorizationCacheTest.java b/core/src/test/java/com/backblaze/b2/client/B2AccountAuthorizationCacheTest.java index 9696abd9e..bd41185cc 100644 --- a/core/src/test/java/com/backblaze/b2/client/B2AccountAuthorizationCacheTest.java +++ b/core/src/test/java/com/backblaze/b2/client/B2AccountAuthorizationCacheTest.java @@ -136,7 +136,7 @@ public void test_forCoverage() { //noinspection ResultOfMethodCallIgnored a.hashCode(); - assertEquals("B2AccountAuthorization{accountId='1', authorizationToken='accountToken1', apiUrl='apiUrl1', downloadUrl='downloadUrl1', recommendedPartSize=1000, absoluteMinimumPartSize=100, allowed=B2Allowed(capabilities=[listFiles], bucketId=null, bucketName=null, namePrefix=), s3ApiUrl=s3ApiUrl1}", a.toString()); + assertEquals("B2AccountAuthorization{accountId='1', authorizationToken='[redacted]', apiUrl='apiUrl1', downloadUrl='downloadUrl1', recommendedPartSize=1000, absoluteMinimumPartSize=100, allowed=B2Allowed(capabilities=[listFiles], bucketId=null, bucketName=null, namePrefix=), s3ApiUrl=s3ApiUrl1}", a.toString()); } } diff --git a/core/src/test/java/com/backblaze/b2/client/contentSources/B2HeadersImplTest.java b/core/src/test/java/com/backblaze/b2/client/contentSources/B2HeadersImplTest.java index e165f23bd..5189946ac 100644 --- a/core/src/test/java/com/backblaze/b2/client/contentSources/B2HeadersImplTest.java +++ b/core/src/test/java/com/backblaze/b2/client/contentSources/B2HeadersImplTest.java @@ -15,6 +15,7 @@ import java.util.Map; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -243,4 +244,39 @@ public void testBuilderThatCopies() { .build(); } } + + @Test + public void testToStringRedactsSensitiveHeaderValuesCaseInsensitively() { + final B2Headers headers = B2HeadersImpl + .builder() + .set("aUtHoRiZaTiOn", "SECRET-AUTHORIZATION") + .set("x-Bz-SeRvEr-SiDe-EnCrYpTiOn-CuStOmEr-KeY", "SECRET-SSE-C-KEY") + .set(B2Headers.SERVER_SIDE_ENCRYPTION_CUSTOMER_KEY_MD5, "customer-key-md5") + .set("X-Normal-Header", "normal-value") + .build(); + + final String value = headers.toString(); + + assertFalse(value.contains("SECRET-AUTHORIZATION")); + assertFalse(value.contains("SECRET-SSE-C-KEY")); + assertTrue(value.contains("aUtHoRiZaTiOn=[redacted]")); + assertTrue(value.contains("x-Bz-SeRvEr-SiDe-EnCrYpTiOn-CuStOmEr-KeY=[redacted]")); + assertTrue(value.contains(B2Headers.SERVER_SIDE_ENCRYPTION_CUSTOMER_KEY_MD5 + "=customer-key-md5")); + assertTrue(value.contains("X-Normal-Header=normal-value")); + assertEquals("SECRET-AUTHORIZATION", headers.getValueOrNull(B2Headers.AUTHORIZATION)); + assertEquals("SECRET-SSE-C-KEY", headers.getValueOrNull(B2Headers.SERVER_SIDE_ENCRYPTION_CUSTOMER_KEY)); + } + + @Test + public void testToStringShowsNullSensitiveHeaderValue() { + final B2Headers headers = B2HeadersImpl + .builder() + .set("aUtHoRiZaTiOn", null) + .build(); + + final String value = headers.toString(); + + assertTrue(value.contains("aUtHoRiZaTiOn=null")); + assertFalse(value.contains("aUtHoRiZaTiOn=[redacted]")); + } } diff --git a/core/src/test/java/com/backblaze/b2/client/structures/B2AccountAuthorizationTest.java b/core/src/test/java/com/backblaze/b2/client/structures/B2AccountAuthorizationTest.java new file mode 100644 index 000000000..3a864040c --- /dev/null +++ b/core/src/test/java/com/backblaze/b2/client/structures/B2AccountAuthorizationTest.java @@ -0,0 +1,77 @@ +/* + * Copyright 2026, Backblaze Inc. All Rights Reserved. + * License https://www.backblaze.com/using_b2_code.html + */ +package com.backblaze.b2.client.structures; + +import com.backblaze.b2.json.B2Json; +import com.backblaze.b2.util.B2Collections; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class B2AccountAuthorizationTest { + + @Test + public void testToStringRedactsAuthorizationToken() { + final B2AccountAuthorization authorization = new B2AccountAuthorization( + "account-id", + "SECRET-ACCOUNT-TOKEN", + "https://api.example.com", + "https://download.example.com", + 100, + 50, + new B2Allowed(B2Collections.listOf(B2Capabilities.LIST_FILES), "bucket-id", "bucket-name", "prefix"), + "https://s3.example.com"); + + final String value = authorization.toString(); + + assertFalse(value.contains("SECRET-ACCOUNT-TOKEN")); + assertTrue(value.contains("authorizationToken='[redacted]'")); + assertTrue(value.contains("accountId='account-id'")); + assertTrue(value.contains("apiUrl='https://api.example.com'")); + assertTrue(value.contains("downloadUrl='https://download.example.com'")); + assertTrue(value.contains("recommendedPartSize=100")); + assertTrue(value.contains("absoluteMinimumPartSize=50")); + assertTrue(value.contains("allowed=")); + assertTrue(value.contains("s3ApiUrl=https://s3.example.com")); + } + + @Test + public void testJsonRoundTripKeepsAuthorizationToken() { + final B2AccountAuthorization authorization = new B2AccountAuthorization( + "account-id", + "SECRET-ACCOUNT-TOKEN", + "https://api.example.com", + "https://download.example.com", + 100, + 50, + new B2Allowed(B2Collections.listOf(B2Capabilities.LIST_FILES), "bucket-id", "bucket-name", "prefix"), + "https://s3.example.com"); + + final String json = B2Json.toJsonOrThrowRuntime(authorization); + + assertTrue(json.contains("\"authorizationToken\": \"SECRET-ACCOUNT-TOKEN\"")); + assertEquals(authorization, B2Json.fromJsonOrThrowRuntime(json, B2AccountAuthorization.class)); + } + + @Test + public void testToStringShowsNullAuthorizationToken() { + final B2AccountAuthorization authorization = new B2AccountAuthorization( + "account-id", + null, + "https://api.example.com", + "https://download.example.com", + 100, + 50, + new B2Allowed(B2Collections.listOf(B2Capabilities.LIST_FILES), "bucket-id", "bucket-name", "prefix"), + "https://s3.example.com"); + + final String value = authorization.toString(); + + assertTrue(value.contains("authorizationToken='null'")); + assertFalse(value.contains("authorizationToken='[redacted]'")); + } +} diff --git a/core/src/test/java/com/backblaze/b2/client/structures/B2CustomWebhookHeaderTest.java b/core/src/test/java/com/backblaze/b2/client/structures/B2CustomWebhookHeaderTest.java index 4b31ff97b..87a8ff31f 100644 --- a/core/src/test/java/com/backblaze/b2/client/structures/B2CustomWebhookHeaderTest.java +++ b/core/src/test/java/com/backblaze/b2/client/structures/B2CustomWebhookHeaderTest.java @@ -10,6 +10,8 @@ import org.junit.Test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; public class B2CustomWebhookHeaderTest extends B2BaseTest { @@ -31,4 +33,23 @@ public void testToJsonAndBack() { assertEquals(defaultConfig, converted); assertEquals(jsonString, convertedJson); } + + @Test + public void testToStringRedactsValue() { + final B2WebhookCustomHeader header = new B2WebhookCustomHeader("X-Webhook-Authorization", "SECRET-WEBHOOK-HEADER"); + + final String value = header.toString(); + + assertFalse(value.contains("SECRET-WEBHOOK-HEADER")); + assertTrue(value.contains("name='X-Webhook-Authorization'")); + assertTrue(value.contains("value='[redacted]'")); + } + + @Test + public void testToStringShowsNullValue() { + final String value = new B2WebhookCustomHeader("X-Webhook-Authorization", null).toString(); + + assertTrue(value.contains("value='null'")); + assertFalse(value.contains("value='[redacted]'")); + } } diff --git a/core/src/test/java/com/backblaze/b2/client/structures/B2EventNotificationRuleTest.java b/core/src/test/java/com/backblaze/b2/client/structures/B2EventNotificationRuleTest.java index d0601bc13..667524736 100644 --- a/core/src/test/java/com/backblaze/b2/client/structures/B2EventNotificationRuleTest.java +++ b/core/src/test/java/com/backblaze/b2/client/structures/B2EventNotificationRuleTest.java @@ -12,6 +12,8 @@ import static com.backblaze.b2.util.B2Collections.listOf; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class B2EventNotificationRuleTest extends B2BaseTest { @@ -77,4 +79,98 @@ public void testToJsonAndBack() { assertEquals(defaultConfig, converted); assertEquals(jsonString, convertedJson); } + + @Test + public void testCompareToDistinguishesSigningSecrets() { + final B2EventNotificationRule first = makeWebhookRule("signing-secret-one", "header-value", 20); + final B2EventNotificationRule second = makeWebhookRule("signing-secret-two", "header-value", 20); + + assertFalse(first.equals(second)); + assertTrue(first.compareTo(second) != 0); + + final TreeSet rules = new TreeSet<>(listOf(first, second)); + assertEquals(2, rules.size()); + } + + @Test + public void testCompareToDistinguishesCustomHeaderValues() { + final B2EventNotificationRule first = makeWebhookRule("signing-secret", "header-value-one", 20); + final B2EventNotificationRule second = makeWebhookRule("signing-secret", "header-value-two", 20); + + assertFalse(first.equals(second)); + assertTrue(first.compareTo(second) != 0); + + final TreeSet rules = new TreeSet<>(listOf(first, second)); + assertEquals(2, rules.size()); + } + + @Test + public void testCompareToDistinguishesMaxEventsPerBatch() { + final B2EventNotificationRule first = makeWebhookRule("signing-secret", "header-value", 1); + final B2EventNotificationRule second = makeWebhookRule("signing-secret", "header-value", 2); + + assertFalse(first.equals(second)); + assertTrue(first.compareTo(second) != 0); + } + + @Test + public void testCompareToIsConsistentWithEquals() { + final B2EventNotificationRule first = makeWebhookRule("signing-secret", "header-value", 20); + final B2EventNotificationRule second = makeWebhookRule("signing-secret", "header-value", 20); + + assertEquals(first, second); + assertEquals(0, first.compareTo(second)); + } + + @Test + public void testCompareToOrdersUnknownTargetConfigurationsWithoutThrowing() { + final B2EventNotificationRule first = new B2EventNotificationRule( + "myRule", + new TreeSet<>(listOf("b2:ObjectCreated:Upload")), + "", + new B2EventNotificationTargetConfiguration(), + true, + false, + ""); + final B2EventNotificationRule second = new B2EventNotificationRule( + "myRule", + new TreeSet<>(listOf("b2:ObjectCreated:Upload")), + "", + new B2EventNotificationTargetConfiguration(), + true, + false, + ""); + + try { + first.compareTo(second); + } catch (IllegalArgumentException e) { + fail("compareTo must support unknown target configurations: " + e.getMessage()); + } + + final TreeSet rules = new TreeSet<>(); + try { + rules.add(first); + rules.add(second); + } catch (IllegalArgumentException e) { + fail("TreeSet must support unknown target configurations: " + e.getMessage()); + } + assertEquals(2, rules.size()); + } + + private B2EventNotificationRule makeWebhookRule(String signingSecret, + String customHeaderValue, + int maxEventsPerBatch) { + return new B2EventNotificationRule( + "myRule", + new TreeSet<>(listOf("b2:ObjectCreated:Upload")), + "", + new B2WebhookConfiguration( + "https://www.example.com", + new TreeSet<>(listOf(new B2WebhookCustomHeader("X-Webhook-Authorization", customHeaderValue))), + signingSecret, + maxEventsPerBatch), + true, + false, + ""); + } } diff --git a/core/src/test/java/com/backblaze/b2/client/structures/B2FileSseForRequestTest.java b/core/src/test/java/com/backblaze/b2/client/structures/B2FileSseForRequestTest.java index 2fb44102a..b206691fd 100644 --- a/core/src/test/java/com/backblaze/b2/client/structures/B2FileSseForRequestTest.java +++ b/core/src/test/java/com/backblaze/b2/client/structures/B2FileSseForRequestTest.java @@ -9,6 +9,8 @@ import java.util.Base64; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; /* * Copyright 2020, Backblaze, Inc. All rights reserved. @@ -48,6 +50,30 @@ public void testFullSseCustomerManagedKeyConfig() { final String convertedJson = B2Json.toJsonOrThrowRuntime(defaultConfig); assertEquals(defaultConfig, converted); assertEquals(jsonString, convertedJson); + assertTrue(convertedJson.contains(key)); + } + + @Test + public void testToStringRedactsCustomerKey() { + final B2FileSseForRequest encryption = B2FileSseForRequest.createSseCAes256( + "SECRET-SSE-C-CUSTOMER-KEY", + "customer-key-md5"); + + final String value = encryption.toString(); + + assertFalse(value.contains("SECRET-SSE-C-CUSTOMER-KEY")); + assertTrue(value.contains("customerKey=[redacted]")); + assertTrue(value.contains("mode='SSE-C'")); + assertTrue(value.contains("algorithm=AES256")); + assertTrue(value.contains("customerKeyMd5=customer-key-md5")); + } + + @Test + public void testToStringShowsNullCustomerKey() { + final String value = B2FileSseForRequest.createSseB2Aes256().toString(); + + assertTrue(value.contains("customerKey=null")); + assertFalse(value.contains("customerKey=[redacted]")); } @Test @@ -138,4 +164,4 @@ public void testNull() { "}"; B2Json.fromJsonOrThrowRuntime(jsonString, B2FileSseForRequest.class); } -} \ No newline at end of file +} diff --git a/core/src/test/java/com/backblaze/b2/client/structures/B2WebhookConfigurationTest.java b/core/src/test/java/com/backblaze/b2/client/structures/B2WebhookConfigurationTest.java index f59ed31b5..22ed4478d 100644 --- a/core/src/test/java/com/backblaze/b2/client/structures/B2WebhookConfigurationTest.java +++ b/core/src/test/java/com/backblaze/b2/client/structures/B2WebhookConfigurationTest.java @@ -13,6 +13,8 @@ import static com.backblaze.b2.util.B2Collections.listOf; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class B2WebhookConfigurationTest extends B2BaseTest { @@ -137,4 +139,33 @@ public void testToJsonAndBack() { assertEquals(defaultConfig, converted); assertEquals(jsonString, convertedJson); } + + @Test + public void testToStringRedactsSecrets() { + final B2WebhookConfiguration configuration = new B2WebhookConfiguration( + "https://www.example.com", + new TreeSet<>(listOf(new B2WebhookCustomHeader("X-Webhook-Authorization", "SECRET-WEBHOOK-HEADER"))), + "SECRET-WEBHOOK-SIGNING-SECRET", + null); + + final String value = configuration.toString(); + final String json = B2Json.toJsonOrThrowRuntime(configuration); + + assertFalse(value.contains("SECRET-WEBHOOK-SIGNING-SECRET")); + assertFalse(value.contains("SECRET-WEBHOOK-HEADER")); + assertTrue(value.contains("url='https://www.example.com'")); + assertTrue(value.contains("X-Webhook-Authorization")); + assertTrue(value.contains("hmacSha256SigningSecret='[redacted]'")); + assertTrue(value.contains("value='[redacted]'")); + assertTrue(json.contains("\"hmacSha256SigningSecret\": \"SECRET-WEBHOOK-SIGNING-SECRET\"")); + assertTrue(json.contains("\"value\": \"SECRET-WEBHOOK-HEADER\"")); + } + + @Test + public void testToStringShowsNullSigningSecret() { + final String value = new B2WebhookConfiguration("https://www.example.com").toString(); + + assertTrue(value.contains("hmacSha256SigningSecret='null'")); + assertFalse(value.contains("hmacSha256SigningSecret='[redacted]'")); + } }