From b9b4e3fdd8f40a47c896e41699280aeb39b4941a Mon Sep 17 00:00:00 2001 From: Sergey Skrobotov Date: Thu, 23 Feb 2023 16:11:05 -0800 Subject: [PATCH] Adding a uniform configuration for all json/yaml mapper use cases: part 1 --- .../textsecuregcm/captcha/HCaptchaClient.java | 4 +- .../currency/CoinMarketCapClient.java | 13 ++- .../textsecuregcm/currency/FixerClient.java | 10 +- .../limits/StaticRateLimiter.java | 7 +- .../push/PushLatencyManager.java | 6 +- .../textsecuregcm/storage/Accounts.java | 17 ++-- .../storage/AccountsManager.java | 2 +- .../storage/DynamicConfigurationManager.java | 17 ++-- .../storage/ProfilesManager.java | 4 +- .../SerializedExpireableJsonDynamoStore.java | 4 +- .../storage/VerificationCodeStore.java | 6 +- .../subscriptions/BraintreeManager.java | 6 +- .../textsecuregcm/util/SystemMapper.java | 49 ++++++--- ...blementRefreshRequirementProviderTest.java | 6 +- .../controllers/AccountControllerTest.java | 2 +- .../controllers/AccountControllerV2Test.java | 2 +- .../controllers/ChallengeControllerTest.java | 2 +- .../controllers/MessageControllerTest.java | 31 +++--- .../controllers/ProfileControllerTest.java | 4 +- .../ProvisioningControllerTest.java | 7 +- .../RegistrationControllerTest.java | 2 +- .../SecureBackupControllerTest.java | 2 +- .../SubscriptionControllerTest.java | 11 +-- .../VerificationControllerTest.java | 2 +- .../entities/AnswerChallengeRequestTest.java | 12 ++- .../entities/IncomingMessageListTest.java | 11 ++- .../textsecuregcm/limits/LeakyBucketTest.java | 3 +- .../limits/RateLimitedByIpTest.java | 2 +- .../textsecuregcm/storage/AccountsTest.java | 4 +- .../tests/controllers/ArtControllerTest.java | 2 +- .../controllers/AttachmentControllerTest.java | 2 +- .../CertificateControllerTest.java | 4 +- .../SecureStorageControllerTest.java | 2 +- .../controllers/StickerControllerTest.java | 2 +- .../tests/util/AccountsHelper.java | 2 +- .../textsecuregcm/tests/util/JsonHelpers.java | 4 +- .../textsecuregcm/util/SystemMapperTest.java | 99 +++++++++++++++++++ .../LoggingUnhandledExceptionMapperTest.java | 6 +- 38 files changed, 250 insertions(+), 121 deletions(-) create mode 100644 service/src/test/java/org/whispersystems/textsecuregcm/util/SystemMapperTest.java diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/captcha/HCaptchaClient.java b/service/src/main/java/org/whispersystems/textsecuregcm/captcha/HCaptchaClient.java index 3f2583ff7..46435e77d 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/captcha/HCaptchaClient.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/captcha/HCaptchaClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 Signal Messenger, LLC + * Copyright 2021 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -81,7 +81,7 @@ public class HCaptchaClient implements CaptchaClient { throw new IOException("hCaptcha http failure : " + response.statusCode()); } - final HCaptchaResponse hCaptchaResponse = SystemMapper.getMapper() + final HCaptchaResponse hCaptchaResponse = SystemMapper.jsonMapper() .readValue(response.body(), HCaptchaResponse.class); logger.debug("received hCaptcha response: {}", hCaptchaResponse); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/currency/CoinMarketCapClient.java b/service/src/main/java/org/whispersystems/textsecuregcm/currency/CoinMarketCapClient.java index 0b1fd4e3c..9606e4d6a 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/currency/CoinMarketCapClient.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/currency/CoinMarketCapClient.java @@ -1,11 +1,13 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + package org.whispersystems.textsecuregcm.currency; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import com.google.common.annotations.VisibleForTesting; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.whispersystems.textsecuregcm.util.SystemMapper; import java.io.IOException; import java.math.BigDecimal; import java.net.URI; @@ -13,6 +15,9 @@ import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.whispersystems.textsecuregcm.util.SystemMapper; public class CoinMarketCapClient { @@ -64,7 +69,7 @@ public class CoinMarketCapClient { @VisibleForTesting static CoinMarketCapResponse parseResponse(final String responseJson) throws JsonProcessingException { - return SystemMapper.getMapper().readValue(responseJson, CoinMarketCapResponse.class); + return SystemMapper.jsonMapper().readValue(responseJson, CoinMarketCapResponse.class); } @VisibleForTesting diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/currency/FixerClient.java b/service/src/main/java/org/whispersystems/textsecuregcm/currency/FixerClient.java index 185b46aad..ffc4fd318 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/currency/FixerClient.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/currency/FixerClient.java @@ -1,8 +1,11 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + package org.whispersystems.textsecuregcm.currency; import com.fasterxml.jackson.annotation.JsonProperty; -import org.whispersystems.textsecuregcm.util.SystemMapper; - import java.io.IOException; import java.math.BigDecimal; import java.net.URI; @@ -10,6 +13,7 @@ import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.Map; +import org.whispersystems.textsecuregcm.util.SystemMapper; public class FixerClient { @@ -35,7 +39,7 @@ public class FixerClient { throw new FixerException("Bad response: " + response.statusCode() + " " + response.toString()); } - FixerResponse parsedResponse = SystemMapper.getMapper().readValue(response.body(), FixerResponse.class); + FixerResponse parsedResponse = SystemMapper.jsonMapper().readValue(response.body(), FixerResponse.class); if (parsedResponse.success) return parsedResponse.rates; else throw new FixerException("Got failed response!"); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/limits/StaticRateLimiter.java b/service/src/main/java/org/whispersystems/textsecuregcm/limits/StaticRateLimiter.java index d0e7ef970..78fba93bb 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/limits/StaticRateLimiter.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/limits/StaticRateLimiter.java @@ -12,7 +12,6 @@ import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.SharedMetricRegistries; import com.codahale.metrics.Timer; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.time.Duration; import org.slf4j.Logger; @@ -26,8 +25,6 @@ public class StaticRateLimiter implements RateLimiter { private static final Logger logger = LoggerFactory.getLogger(StaticRateLimiter.class); - private static final ObjectMapper MAPPER = SystemMapper.getMapper(); - protected final String name; private final RateLimiterConfig config; @@ -81,7 +78,7 @@ public class StaticRateLimiter implements RateLimiter { private void setBucket(final String key, final LeakyBucket bucket) { try { - final String serialized = bucket.serialize(MAPPER); + final String serialized = bucket.serialize(SystemMapper.jsonMapper()); cacheCluster.useCluster(connection -> connection.sync().setex( getBucketName(key), (int) Math.ceil((config.bucketSize() / config.leakRatePerMillis()) / 1000), @@ -96,7 +93,7 @@ public class StaticRateLimiter implements RateLimiter { final String serialized = cacheCluster.withCluster(connection -> connection.sync().get(getBucketName(key))); if (serialized != null) { - return LeakyBucket.fromSerialized(MAPPER, serialized); + return LeakyBucket.fromSerialized(SystemMapper.jsonMapper(), serialized); } } catch (final IOException e) { logger.warn("Deserialization error", e); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/push/PushLatencyManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/push/PushLatencyManager.java index 6b7809ce4..c8525135a 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/push/PushLatencyManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/push/PushLatencyManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2022 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -106,7 +106,7 @@ public class PushLatencyManager { void recordPushSent(final UUID accountUuid, final long deviceId, final boolean isVoip) { try { - final String recordJson = SystemMapper.getMapper().writeValueAsString( + final String recordJson = SystemMapper.jsonMapper().writeValueAsString( new PushRecord(Instant.now(clock), isVoip ? PushType.VOIP : PushType.STANDARD)); redisCluster.useCluster(connection -> @@ -159,7 +159,7 @@ public class PushLatencyManager { .thenApply(recordJson -> { if (StringUtils.isNotEmpty(recordJson)) { try { - return SystemMapper.getMapper().readValue(recordJson, PushRecord.class); + return SystemMapper.jsonMapper().readValue(recordJson, PushRecord.class); } catch (JsonProcessingException e) { return null; } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Accounts.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Accounts.java index a37e5a8c9..e26d62076 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Accounts.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Accounts.java @@ -14,9 +14,6 @@ import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.Timer; import java.io.IOException; import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.time.Clock; import java.time.Duration; import java.util.ArrayList; @@ -271,7 +268,7 @@ public class Accounts extends AbstractDynamoDbStore { "#version", ATTR_VERSION)) .expressionAttributeValues(Map.of( ":number", numberAttr, - ":data", AttributeValues.fromByteArray(SystemMapper.getMapper().writeValueAsBytes(account)), + ":data", AttributeValues.fromByteArray(SystemMapper.jsonMapper().writeValueAsBytes(account)), ":cds", AttributeValues.fromBool(account.shouldBeVisibleInDirectory()), ":pni", pniAttr, ":version", AttributeValues.fromInt(account.getVersion()), @@ -342,7 +339,7 @@ public class Accounts extends AbstractDynamoDbStore { .conditionExpression("#version = :version") .expressionAttributeNames(Map.of("#data", ATTR_ACCOUNT_DATA, "#version", ATTR_VERSION)) .expressionAttributeValues(Map.of( - ":data", AttributeValues.fromByteArray(SystemMapper.getMapper().writeValueAsBytes(account)), + ":data", AttributeValues.fromByteArray(SystemMapper.jsonMapper().writeValueAsBytes(account)), ":version", AttributeValues.fromInt(account.getVersion()), ":version_increment", AttributeValues.fromInt(1))) .build()) @@ -423,7 +420,7 @@ public class Accounts extends AbstractDynamoDbStore { "#username_hash", ATTR_USERNAME_HASH, "#version", ATTR_VERSION)) .expressionAttributeValues(Map.of( - ":data", AttributeValues.fromByteArray(SystemMapper.getMapper().writeValueAsBytes(account)), + ":data", AttributeValues.fromByteArray(SystemMapper.jsonMapper().writeValueAsBytes(account)), ":username_hash", AttributeValues.fromByteArray(usernameHash), ":version", AttributeValues.fromInt(account.getVersion()), ":version_increment", AttributeValues.fromInt(1))) @@ -478,7 +475,7 @@ public class Accounts extends AbstractDynamoDbStore { "#username_hash", ATTR_USERNAME_HASH, "#version", ATTR_VERSION)) .expressionAttributeValues(Map.of( - ":data", AttributeValues.fromByteArray(SystemMapper.getMapper().writeValueAsBytes(account)), + ":data", AttributeValues.fromByteArray(SystemMapper.jsonMapper().writeValueAsBytes(account)), ":version", AttributeValues.fromInt(account.getVersion()), ":version_increment", AttributeValues.fromInt(1))) .build()) @@ -523,7 +520,7 @@ public class Accounts extends AbstractDynamoDbStore { "#cds", ATTR_CANONICALLY_DISCOVERABLE, "#version", ATTR_VERSION)); final Map attrValues = new HashMap<>(Map.of( - ":data", AttributeValues.fromByteArray(SystemMapper.getMapper().writeValueAsBytes(account)), + ":data", AttributeValues.fromByteArray(SystemMapper.jsonMapper().writeValueAsBytes(account)), ":cds", AttributeValues.fromBool(account.shouldBeVisibleInDirectory()), ":version", AttributeValues.fromInt(account.getVersion()), ":version_increment", AttributeValues.fromInt(1))); @@ -720,7 +717,7 @@ public class Accounts extends AbstractDynamoDbStore { KEY_ACCOUNT_UUID, uuidAttr, ATTR_ACCOUNT_E164, numberAttr, ATTR_PNI_UUID, pniUuidAttr, - ATTR_ACCOUNT_DATA, AttributeValues.fromByteArray(SystemMapper.getMapper().writeValueAsBytes(account)), + ATTR_ACCOUNT_DATA, AttributeValues.fromByteArray(SystemMapper.jsonMapper().writeValueAsBytes(account)), ATTR_VERSION, AttributeValues.fromInt(account.getVersion()), ATTR_CANONICALLY_DISCOVERABLE, AttributeValues.fromBool(account.shouldBeVisibleInDirectory()))); @@ -842,7 +839,7 @@ public class Accounts extends AbstractDynamoDbStore { throw new RuntimeException("item missing values"); } try { - final Account account = SystemMapper.getMapper().readValue(item.get(ATTR_ACCOUNT_DATA).b().asByteArray(), Account.class); + final Account account = SystemMapper.jsonMapper().readValue(item.get(ATTR_ACCOUNT_DATA).b().asByteArray(), Account.class); final UUID accountIdentifier = UUIDUtil.fromByteBuffer(item.get(KEY_ACCOUNT_UUID).b().asByteBuffer()); final UUID phoneNumberIdentifierFromAttribute = AttributeValues.getUUID(item, ATTR_PNI_UUID, null); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java index ea1c3def3..bcad7081a 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java @@ -96,7 +96,7 @@ public class AccountsManager { private final RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager; private final Clock clock; - private static final ObjectMapper mapper = SystemMapper.getMapper(); + private static final ObjectMapper mapper = SystemMapper.jsonMapper(); // An account that's used at least daily will get reset in the cache at least once per day when its "last seen" // timestamp updates; expiring entries after two days will help clear out "zombie" cache entries that are read diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/DynamicConfigurationManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/DynamicConfigurationManager.java index b70190600..e9f1b1f0b 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/DynamicConfigurationManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/DynamicConfigurationManager.java @@ -1,12 +1,13 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + package org.whispersystems.textsecuregcm.storage; import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.google.common.annotations.VisibleForTesting; import io.micrometer.core.instrument.Metrics; import java.time.Duration; @@ -18,6 +19,7 @@ import javax.validation.Validation; import javax.validation.Validator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.whispersystems.textsecuregcm.util.SystemMapper; import org.whispersystems.textsecuregcm.util.Util; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.services.appconfigdata.AppConfigDataClient; @@ -39,11 +41,6 @@ public class DynamicConfigurationManager { private String configurationToken = null; private boolean initialized = false; - - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(new YAMLFactory()) - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .registerModule(new JavaTimeModule()); - private static final Validator VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator(); private static final String ERROR_COUNTER_NAME = name(DynamicConfigurationManager.class, "error"); @@ -143,7 +140,7 @@ public class DynamicConfigurationManager { @VisibleForTesting public static Optional parseConfiguration(final String configurationYaml, final Class configurationClass) throws JsonProcessingException { - final T configuration = OBJECT_MAPPER.readValue(configurationYaml, configurationClass); + final T configuration = SystemMapper.yamlMapper().readValue(configurationYaml, configurationClass); final Set> violations = VALIDATOR.validate(configuration); final Optional maybeDynamicConfiguration; diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/ProfilesManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/ProfilesManager.java index cf993c084..915baf7a9 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/ProfilesManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/ProfilesManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2020 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -30,7 +30,7 @@ public class ProfilesManager { final FaultTolerantRedisCluster cacheCluster) { this.profiles = profiles; this.cacheCluster = cacheCluster; - this.mapper = SystemMapper.getMapper(); + this.mapper = SystemMapper.jsonMapper(); } public void set(UUID uuid, VersionedProfile versionedProfile) { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/SerializedExpireableJsonDynamoStore.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/SerializedExpireableJsonDynamoStore.java index 451ca5385..adab40b3f 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/SerializedExpireableJsonDynamoStore.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/SerializedExpireableJsonDynamoStore.java @@ -84,7 +84,7 @@ public abstract class SerializedExpireableJsonDynamoStore { final Map attributeValueMap = new HashMap<>(Map.of( KEY_KEY, AttributeValues.fromString(key), ATTR_SERIALIZED_VALUE, - AttributeValues.fromString(SystemMapper.getMapper().writeValueAsString(v)))); + AttributeValues.fromString(SystemMapper.jsonMapper().writeValueAsString(v)))); if (v instanceof Expireable ev) { attributeValueMap.put(ATTR_TTL, AttributeValues.fromLong(getExpirationTimestamp(ev))); } @@ -117,7 +117,7 @@ public abstract class SerializedExpireableJsonDynamoStore { try { return response.hasItem() ? filterMaybeExpiredValue( - SystemMapper.getMapper() + SystemMapper.jsonMapper() .readValue(response.item().get(ATTR_SERIALIZED_VALUE).s(), deserializationTargetClass)) : Optional.empty(); } catch (final JsonProcessingException e) { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/VerificationCodeStore.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/VerificationCodeStore.java index 9f80d5f83..7d38b5060 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/VerificationCodeStore.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/VerificationCodeStore.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -58,7 +58,7 @@ public class VerificationCodeStore { .tableName(tableName) .item(Map.of( KEY_E164, AttributeValues.fromString(number), - ATTR_STORED_CODE, AttributeValues.fromString(SystemMapper.getMapper().writeValueAsString(verificationCode)), + ATTR_STORED_CODE, AttributeValues.fromString(SystemMapper.jsonMapper().writeValueAsString(verificationCode)), ATTR_TTL, AttributeValues.fromLong(getExpirationTimestamp(verificationCode)))) .build()); } catch (final JsonProcessingException e) { @@ -84,7 +84,7 @@ public class VerificationCodeStore { try { return response.hasItem() ? filterMaybeExpiredCode( - SystemMapper.getMapper().readValue(response.item().get(ATTR_STORED_CODE).s(), StoredVerificationCode.class)) + SystemMapper.jsonMapper().readValue(response.item().get(ATTR_STORED_CODE).s(), StoredVerificationCode.class)) : Optional.empty(); } catch (final JsonProcessingException e) { log.error("Failed to parse stored verification code", e); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/BraintreeManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/BraintreeManager.java index 1ba3738a2..e57b01a49 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/BraintreeManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/BraintreeManager.java @@ -19,7 +19,6 @@ import com.braintreegateway.TransactionSearchRequest; import com.braintreegateway.exceptions.BraintreeException; import com.braintreegateway.exceptions.NotFoundException; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import java.math.BigDecimal; import java.time.Instant; import java.util.Comparator; @@ -40,6 +39,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration; import org.whispersystems.textsecuregcm.http.FaultTolerantHttpClient; +import org.whispersystems.textsecuregcm.util.SystemMapper; public class BraintreeManager implements SubscriptionProcessorManager { @@ -374,7 +374,7 @@ public class BraintreeManager implements SubscriptionProcessorManager { private long getLevelForPlan(final Plan plan) { final BraintreePlanMetadata metadata; try { - metadata = new ObjectMapper().readValue(plan.getDescription(), BraintreePlanMetadata.class); + metadata = SystemMapper.jsonMapper().readValue(plan.getDescription(), BraintreePlanMetadata.class); } catch (JsonProcessingException e) { throw new RuntimeException(e); @@ -477,7 +477,7 @@ public class BraintreeManager implements SubscriptionProcessorManager { final BraintreePlanMetadata metadata; try { - metadata = new ObjectMapper().readValue(plan.getDescription(), BraintreePlanMetadata.class); + metadata = SystemMapper.jsonMapper().readValue(plan.getDescription(), BraintreePlanMetadata.class); } catch (JsonProcessingException e) { throw new RuntimeException(e); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/util/SystemMapper.java b/service/src/main/java/org/whispersystems/textsecuregcm/util/SystemMapper.java index 7c3e6b667..52d61ced5 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/util/SystemMapper.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/util/SystemMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2020 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -7,28 +7,55 @@ package org.whispersystems.textsecuregcm.util; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.vdurmont.semver4j.Semver; +import java.io.IOException; import javax.annotation.Nonnull; public class SystemMapper { - private static final ObjectMapper MAPPER = build(); + private static final ObjectMapper JSON_MAPPER = configureMapper(new ObjectMapper()); + + private static final ObjectMapper YAML_MAPPER = configureMapper(new YAMLMapper()); @Nonnull - public static ObjectMapper getMapper() { - return MAPPER; + public static ObjectMapper jsonMapper() { + return JSON_MAPPER; } @Nonnull - private static ObjectMapper build() { - final ObjectMapper mapper = new ObjectMapper(); - mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); - mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); - mapper.registerModule(new JavaTimeModule()); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - return mapper; + public static ObjectMapper yamlMapper() { + return YAML_MAPPER; + } + + public static ObjectMapper configureMapper(final ObjectMapper mapper) { + return mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE) + .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) + .registerModules( + applicationModule(), + new JavaTimeModule(), + new Jdk8Module()); + } + + private static Module applicationModule() { + return new SimpleModule() + .addDeserializer(Semver.class, new JsonDeserializer<>() { + @Override + public Semver deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException { + final String strValue = p.readValueAs(String.class); + return strValue != null ? new Semver(strValue) : null; + } + }); } } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/auth/AuthEnablementRefreshRequirementProviderTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/auth/AuthEnablementRefreshRequirementProviderTest.java index 68c6597b7..617eea77e 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/auth/AuthEnablementRefreshRequirementProviderTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/auth/AuthEnablementRefreshRequirementProviderTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2020 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -17,7 +17,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.protobuf.InvalidProtocolBufferException; @@ -76,6 +75,7 @@ import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.tests.util.DevicesHelper; +import org.whispersystems.textsecuregcm.util.SystemMapper; import org.whispersystems.websocket.WebSocketResourceProvider; import org.whispersystems.websocket.auth.WebsocketAuthValueFactoryProvider; import org.whispersystems.websocket.logging.WebsocketRequestLog; @@ -326,7 +326,7 @@ class AuthEnablementRefreshRequirementProviderTest { resourceConfig.register(new TestResource()); resourceConfig.register(new WebSocketSessionContextValueFactoryProvider.Binder()); resourceConfig.register(new WebsocketAuthValueFactoryProvider.Binder<>(TestPrincipal.class)); - resourceConfig.register(new JacksonMessageBodyProvider(new ObjectMapper())); + resourceConfig.register(new JacksonMessageBodyProvider(SystemMapper.jsonMapper())); ApplicationHandler applicationHandler = new ApplicationHandler(resourceConfig); WebsocketRequestLog requestLog = mock(WebsocketRequestLog.class); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerTest.java index 3fa51c691..54185bafe 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerTest.java @@ -213,7 +213,7 @@ class AccountControllerTest { .addProvider(new ImpossiblePhoneNumberExceptionMapper()) .addProvider(new NonNormalizedPhoneNumberExceptionMapper()) .addProvider(new RateLimitByIpFilter(rateLimiters)) - .setMapper(SystemMapper.getMapper()) + .setMapper(SystemMapper.jsonMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource(new AccountController(pendingAccountsManager, accountsManager, diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerV2Test.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerV2Test.java index b40718edf..b2c458413 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerV2Test.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerV2Test.java @@ -105,7 +105,7 @@ class AccountControllerV2Test { .addProvider(new RateLimitExceededExceptionMapper()) .addProvider(new ImpossiblePhoneNumberExceptionMapper()) .addProvider(new NonNormalizedPhoneNumberExceptionMapper()) - .setMapper(SystemMapper.getMapper()) + .setMapper(SystemMapper.jsonMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource( new AccountControllerV2(accountsManager, changeNumberManager, diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ChallengeControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ChallengeControllerTest.java index 5cb4244c4..260099751 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ChallengeControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ChallengeControllerTest.java @@ -47,7 +47,7 @@ class ChallengeControllerTest { .addProvider(AuthHelper.getAuthFilter()) .addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>( Set.of(AuthenticatedAccount.class, DisabledPermittedAuthenticatedAccount.class))) - .setMapper(SystemMapper.getMapper()) + .setMapper(SystemMapper.jsonMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource(new RateLimitExceededExceptionMapper()) .addResource(challengeController) diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/MessageControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/MessageControllerTest.java index bd14391ab..dbe4aa93d 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/MessageControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/MessageControllerTest.java @@ -59,7 +59,6 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.glassfish.jersey.server.ServerProperties; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; -import org.hamcrest.CoreMatchers; import org.hamcrest.Matcher; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -221,7 +220,7 @@ class MessageControllerTest { .target(String.format("/v1/messages/%s", SINGLE_DEVICE_UUID)) .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.DISABLED_UUID, AuthHelper.DISABLED_PASSWORD)) - .put(Entity.entity(SystemMapper.getMapper().readValue(jsonFixture("fixtures/current_message_single_device.json"), + .put(Entity.entity(SystemMapper.jsonMapper().readValue(jsonFixture("fixtures/current_message_single_device.json"), IncomingMessageList.class), MediaType.APPLICATION_JSON_TYPE)); @@ -235,7 +234,7 @@ class MessageControllerTest { .target(String.format("/v1/messages/%s", SINGLE_DEVICE_UUID)) .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) - .put(Entity.entity(SystemMapper.getMapper().readValue(jsonFixture("fixtures/current_message_single_device.json"), + .put(Entity.entity(SystemMapper.jsonMapper().readValue(jsonFixture("fixtures/current_message_single_device.json"), IncomingMessageList.class), MediaType.APPLICATION_JSON_TYPE)); @@ -256,7 +255,7 @@ class MessageControllerTest { .target(String.format("/v1/messages/%s", SINGLE_DEVICE_UUID)) .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) - .put(Entity.entity(SystemMapper.getMapper().readValue(jsonFixture("fixtures/current_message_single_device_not_urgent.json"), + .put(Entity.entity(SystemMapper.jsonMapper().readValue(jsonFixture("fixtures/current_message_single_device_not_urgent.json"), IncomingMessageList.class), MediaType.APPLICATION_JSON_TYPE)); @@ -277,7 +276,7 @@ class MessageControllerTest { .target(String.format("/v1/messages/%s", SINGLE_DEVICE_PNI)) .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) - .put(Entity.entity(SystemMapper.getMapper().readValue(jsonFixture("fixtures/current_message_single_device.json"), + .put(Entity.entity(SystemMapper.jsonMapper().readValue(jsonFixture("fixtures/current_message_single_device.json"), IncomingMessageList.class), MediaType.APPLICATION_JSON_TYPE)); @@ -297,7 +296,7 @@ class MessageControllerTest { .target(String.format("/v1/messages/%s", SINGLE_DEVICE_UUID)) .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) - .put(Entity.entity(SystemMapper.getMapper().readValue(jsonFixture("fixtures/current_message_null_message_in_list.json"), IncomingMessageList.class), + .put(Entity.entity(SystemMapper.jsonMapper().readValue(jsonFixture("fixtures/current_message_null_message_in_list.json"), IncomingMessageList.class), MediaType.APPLICATION_JSON_TYPE)); assertThat("Bad request", response.getStatus(), is(equalTo(422))); @@ -310,7 +309,7 @@ class MessageControllerTest { .target(String.format("/v1/messages/%s", SINGLE_DEVICE_UUID)) .request() .header(OptionalAccess.UNIDENTIFIED, Base64.getEncoder().encodeToString(UNIDENTIFIED_ACCESS_BYTES)) - .put(Entity.entity(SystemMapper.getMapper().readValue(jsonFixture("fixtures/current_message_single_device.json"), + .put(Entity.entity(SystemMapper.jsonMapper().readValue(jsonFixture("fixtures/current_message_single_device.json"), IncomingMessageList.class), MediaType.APPLICATION_JSON_TYPE)); @@ -329,7 +328,7 @@ class MessageControllerTest { resources.getJerseyTest() .target(String.format("/v1/messages/%s", SINGLE_DEVICE_UUID)) .request() - .put(Entity.entity(SystemMapper.getMapper().readValue(jsonFixture("fixtures/current_message_single_device.json"), + .put(Entity.entity(SystemMapper.jsonMapper().readValue(jsonFixture("fixtures/current_message_single_device.json"), IncomingMessageList.class), MediaType.APPLICATION_JSON_TYPE)); @@ -343,7 +342,7 @@ class MessageControllerTest { .target(String.format("/v1/messages/%s", MULTI_DEVICE_UUID)) .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) - .put(Entity.entity(SystemMapper.getMapper().readValue(jsonFixture("fixtures/current_message_single_device.json"), + .put(Entity.entity(SystemMapper.jsonMapper().readValue(jsonFixture("fixtures/current_message_single_device.json"), IncomingMessageList.class), MediaType.APPLICATION_JSON_TYPE)); @@ -363,7 +362,7 @@ class MessageControllerTest { .target(String.format("/v1/messages/%s", MULTI_DEVICE_UUID)) .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) - .put(Entity.entity(SystemMapper.getMapper().readValue(jsonFixture("fixtures/current_message_extra_device.json"), + .put(Entity.entity(SystemMapper.jsonMapper().readValue(jsonFixture("fixtures/current_message_extra_device.json"), IncomingMessageList.class), MediaType.APPLICATION_JSON_TYPE)); @@ -383,7 +382,7 @@ class MessageControllerTest { .target(String.format("/v1/messages/%s", MULTI_DEVICE_UUID)) .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) - .put(Entity.entity(SystemMapper.getMapper().readValue(jsonFixture("fixtures/current_message_multi_device.json"), + .put(Entity.entity(SystemMapper.jsonMapper().readValue(jsonFixture("fixtures/current_message_multi_device.json"), IncomingMessageList.class), MediaType.APPLICATION_JSON_TYPE)); @@ -403,7 +402,7 @@ class MessageControllerTest { .target(String.format("/v1/messages/%s", MULTI_DEVICE_UUID)) .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) - .put(Entity.entity(SystemMapper.getMapper().readValue(jsonFixture("fixtures/current_message_multi_device_not_urgent.json"), + .put(Entity.entity(SystemMapper.jsonMapper().readValue(jsonFixture("fixtures/current_message_multi_device_not_urgent.json"), IncomingMessageList.class), MediaType.APPLICATION_JSON_TYPE)); @@ -423,7 +422,7 @@ class MessageControllerTest { .target(String.format("/v1/messages/%s", MULTI_DEVICE_PNI)) .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) - .put(Entity.entity(SystemMapper.getMapper().readValue(jsonFixture("fixtures/current_message_multi_device_pni.json"), + .put(Entity.entity(SystemMapper.jsonMapper().readValue(jsonFixture("fixtures/current_message_multi_device_pni.json"), IncomingMessageList.class), MediaType.APPLICATION_JSON_TYPE)); @@ -438,7 +437,7 @@ class MessageControllerTest { resources.getJerseyTest().target(String.format("/v1/messages/%s", MULTI_DEVICE_UUID)) .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) - .put(Entity.entity(SystemMapper.getMapper().readValue(jsonFixture("fixtures/current_message_registration_id.json"), + .put(Entity.entity(SystemMapper.jsonMapper().readValue(jsonFixture("fixtures/current_message_registration_id.json"), IncomingMessageList.class), MediaType.APPLICATION_JSON_TYPE)); @@ -837,7 +836,7 @@ class MessageControllerTest { .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) .header(HttpHeaders.USER_AGENT, "Test-UA") - .put(Entity.entity(SystemMapper.getMapper().readValue(jsonFixture(payloadFilename), IncomingMessageList.class), + .put(Entity.entity(SystemMapper.jsonMapper().readValue(jsonFixture(payloadFilename), IncomingMessageList.class), MediaType.APPLICATION_JSON_TYPE)); if (expectOk) { @@ -1031,7 +1030,7 @@ class MessageControllerTest { String accessBytes = Base64.getEncoder().encodeToString(UNIDENTIFIED_ACCESS_BYTES); String json = jsonFixture("fixtures/current_message_single_device.json"); UUID unknownUUID = UUID.randomUUID(); - IncomingMessageList list = SystemMapper.getMapper().readValue(json, IncomingMessageList.class); + IncomingMessageList list = SystemMapper.jsonMapper().readValue(json, IncomingMessageList.class); Response response = resources.getJerseyTest() .target(String.format("/v1/messages/%s", unknownUUID)) diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ProfileControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ProfileControllerTest.java index 2a2ede4db..14374ce1f 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ProfileControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ProfileControllerTest.java @@ -147,7 +147,7 @@ class ProfileControllerTest { .addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>( ImmutableSet.of(AuthenticatedAccount.class, DisabledPermittedAuthenticatedAccount.class))) .addProvider(new RateLimitExceededExceptionMapper()) - .setMapper(SystemMapper.getMapper()) + .setMapper(SystemMapper.jsonMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource(new ProfileController( clock, @@ -1392,7 +1392,7 @@ class ProfileControllerTest { // `null` properties should be omitted from the response assertThat(responseJson).doesNotContain("null"); - BatchIdentityCheckResponse identityCheckResponse = SystemMapper.getMapper() + BatchIdentityCheckResponse identityCheckResponse = SystemMapper.jsonMapper() .readValue(responseJson, BatchIdentityCheckResponse.class); assertThat(identityCheckResponse).isNotNull(); assertThat(identityCheckResponse.elements()).isNotNull().hasSize(2); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ProvisioningControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ProvisioningControllerTest.java index 4384c1d16..bfd0d67e5 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ProvisioningControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ProvisioningControllerTest.java @@ -1,3 +1,8 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + package org.whispersystems.textsecuregcm.controllers; import static org.junit.jupiter.api.Assertions.assertArrayEquals; @@ -50,7 +55,7 @@ class ProvisioningControllerTest { .addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>( ImmutableSet.of(AuthenticatedAccount.class, DisabledPermittedAuthenticatedAccount.class))) .addProvider(new RateLimitExceededExceptionMapper()) - .setMapper(SystemMapper.getMapper()) + .setMapper(SystemMapper.jsonMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource(new ProvisioningController(rateLimiters, provisioningManager)) .build(); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/RegistrationControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/RegistrationControllerTest.java index 1a58caa53..f3b05dc84 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/RegistrationControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/RegistrationControllerTest.java @@ -82,7 +82,7 @@ class RegistrationControllerTest { .addProvider(new RateLimitExceededExceptionMapper()) .addProvider(new ImpossiblePhoneNumberExceptionMapper()) .addProvider(new NonNormalizedPhoneNumberExceptionMapper()) - .setMapper(SystemMapper.getMapper()) + .setMapper(SystemMapper.jsonMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource( new RegistrationController(accountsManager, diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SecureBackupControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SecureBackupControllerTest.java index 46bd69cd3..beb7a64e3 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SecureBackupControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SecureBackupControllerTest.java @@ -68,7 +68,7 @@ class SecureBackupControllerTest { private static final ResourceExtension RESOURCES = ResourceExtension.builder() .addProvider(AuthHelper.getAuthFilter()) - .setMapper(SystemMapper.getMapper()) + .setMapper(SystemMapper.jsonMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource(CONTROLLER) .build(); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SubscriptionControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SubscriptionControllerTest.java index d51a2580b..ae6369c3d 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SubscriptionControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SubscriptionControllerTest.java @@ -19,8 +19,7 @@ import static org.whispersystems.textsecuregcm.util.AttributeValues.b; import static org.whispersystems.textsecuregcm.util.AttributeValues.n; import static org.whispersystems.textsecuregcm.util.AttributeValues.s; -import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.databind.ObjectMapper; import com.stripe.exception.ApiException; import com.stripe.model.PaymentIntent; import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider; @@ -80,11 +79,7 @@ class SubscriptionControllerTest { private static final Clock CLOCK = mock(Clock.class); - private static final YAMLMapper YAML_MAPPER = new YAMLMapper(); - - static { - YAML_MAPPER.registerModule(new JavaTimeModule()); - } + private static final ObjectMapper YAML_MAPPER = SystemMapper.yamlMapper(); private static final SubscriptionConfiguration SUBSCRIPTION_CONFIG = ConfigHelper.getSubscriptionConfig(); private static final OneTimeDonationConfiguration ONETIME_CONFIG = ConfigHelper.getOneTimeConfig(); @@ -119,7 +114,7 @@ class SubscriptionControllerTest { .addProvider(CompletionExceptionMapper.class) .addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>(Set.of( AuthenticatedAccount.class, DisabledPermittedAuthenticatedAccount.class))) - .setMapper(SystemMapper.getMapper()) + .setMapper(SystemMapper.jsonMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource(SUBSCRIPTION_CONTROLLER) .build(); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/VerificationControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/VerificationControllerTest.java index 16a1e6574..e9e3f994f 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/VerificationControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/VerificationControllerTest.java @@ -92,7 +92,7 @@ class VerificationControllerTest { .addProvider(new ImpossiblePhoneNumberExceptionMapper()) .addProvider(new NonNormalizedPhoneNumberExceptionMapper()) .addProvider(new RegistrationServiceSenderExceptionMapper()) - .setMapper(SystemMapper.getMapper()) + .setMapper(SystemMapper.jsonMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource( new VerificationController(registrationServiceClient, verificationSessionManager, pushNotificationManager, diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/entities/AnswerChallengeRequestTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/entities/AnswerChallengeRequestTest.java index 9ff2d12eb..8572833d9 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/entities/AnswerChallengeRequestTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/entities/AnswerChallengeRequestTest.java @@ -5,13 +5,15 @@ package org.whispersystems.textsecuregcm.entities; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.exc.InvalidTypeIdException; import org.junit.jupiter.api.Test; import org.whispersystems.textsecuregcm.util.SystemMapper; -import static org.junit.jupiter.api.Assertions.*; - class AnswerChallengeRequestTest { @Test @@ -25,7 +27,7 @@ class AnswerChallengeRequestTest { """; final AnswerChallengeRequest answerChallengeRequest = - SystemMapper.getMapper().readValue(pushChallengeJson, AnswerChallengeRequest.class); + SystemMapper.jsonMapper().readValue(pushChallengeJson, AnswerChallengeRequest.class); assertTrue(answerChallengeRequest instanceof AnswerPushChallengeRequest); assertEquals("Hello I am a push challenge token", @@ -42,7 +44,7 @@ class AnswerChallengeRequestTest { """; final AnswerChallengeRequest answerChallengeRequest = - SystemMapper.getMapper().readValue(recaptchaChallengeJson, AnswerChallengeRequest.class); + SystemMapper.jsonMapper().readValue(recaptchaChallengeJson, AnswerChallengeRequest.class); assertTrue(answerChallengeRequest instanceof AnswerRecaptchaChallengeRequest); @@ -63,7 +65,7 @@ class AnswerChallengeRequestTest { """; assertThrows(InvalidTypeIdException.class, - () -> SystemMapper.getMapper().readValue(unrecognizedTypeJson, AnswerChallengeRequest.class)); + () -> SystemMapper.jsonMapper().readValue(unrecognizedTypeJson, AnswerChallengeRequest.class)); } } } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/entities/IncomingMessageListTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/entities/IncomingMessageListTest.java index 28ca9abf6..d455f1e66 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/entities/IncomingMessageListTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/entities/IncomingMessageListTest.java @@ -1,16 +1,17 @@ /* - * Copyright 2013-2022 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ package org.whispersystems.textsecuregcm.entities; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + import com.fasterxml.jackson.core.JsonProcessingException; import org.junit.jupiter.api.Test; import org.whispersystems.textsecuregcm.util.SystemMapper; -import static org.junit.jupiter.api.Assertions.*; - class IncomingMessageListTest { @Test @@ -26,7 +27,7 @@ class IncomingMessageListTest { """; final IncomingMessageList incomingMessageList = - SystemMapper.getMapper().readValue(incomingMessageListJson, IncomingMessageList.class); + SystemMapper.jsonMapper().readValue(incomingMessageListJson, IncomingMessageList.class); assertTrue(incomingMessageList.online()); assertFalse(incomingMessageList.urgent()); @@ -42,7 +43,7 @@ class IncomingMessageListTest { """; final IncomingMessageList incomingMessageList = - SystemMapper.getMapper().readValue(incomingMessageListJson, IncomingMessageList.class); + SystemMapper.jsonMapper().readValue(incomingMessageListJson, IncomingMessageList.class); assertTrue(incomingMessageList.online()); assertTrue(incomingMessageList.urgent()); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/limits/LeakyBucketTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/limits/LeakyBucketTest.java index 89cabf541..76ae6bf1b 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/limits/LeakyBucketTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/limits/LeakyBucketTest.java @@ -15,6 +15,7 @@ import java.io.IOException; import java.time.Duration; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; +import org.whispersystems.textsecuregcm.util.SystemMapper; class LeakyBucketTest { @@ -35,7 +36,7 @@ class LeakyBucketTest { @Test void testLapseRate() throws IOException { - ObjectMapper mapper = new ObjectMapper(); + ObjectMapper mapper = SystemMapper.jsonMapper(); String serialized = "{\"bucketSize\":2,\"leakRatePerMillis\":8.333333333333334E-6,\"spaceRemaining\":0,\"lastUpdateTimeMillis\":" + (System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(2)) + "}"; LeakyBucket leakyBucket = LeakyBucket.fromSerialized(mapper, serialized); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/limits/RateLimitedByIpTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/limits/RateLimitedByIpTest.java index 8c972f04d..94c3dc767 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/limits/RateLimitedByIpTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/limits/RateLimitedByIpTest.java @@ -61,7 +61,7 @@ public class RateLimitedByIpTest { Mockito.when(rl.forDescriptor(Mockito.eq(RateLimiters.For.BACKUP_AUTH_CHECK))).thenReturn(RATE_LIMITER)); private static final ResourceExtension RESOURCES = ResourceExtension.builder() - .setMapper(SystemMapper.getMapper()) + .setMapper(SystemMapper.jsonMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource(new Controller()) .addProvider(new RateLimitByIpFilter(RATE_LIMITERS)) diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsTest.java index 1f3b09039..a8f51c5db 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2022 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -271,7 +271,7 @@ class AccountsTest { .item(Map.of( Accounts.KEY_ACCOUNT_UUID, AttributeValues.fromUUID(uuid), Accounts.ATTR_ACCOUNT_E164, AttributeValues.fromString(account.getNumber()), - Accounts.ATTR_ACCOUNT_DATA, AttributeValues.fromByteArray(SystemMapper.getMapper().writeValueAsBytes(account)), + Accounts.ATTR_ACCOUNT_DATA, AttributeValues.fromByteArray(SystemMapper.jsonMapper().writeValueAsBytes(account)), Accounts.ATTR_VERSION, AttributeValues.fromInt(account.getVersion()), Accounts.ATTR_CANONICALLY_DISCOVERABLE, AttributeValues.fromBool(account.shouldBeVisibleInDirectory()))) .build()) diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ArtControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ArtControllerTest.java index c146ce8cc..d4a9aadeb 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ArtControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ArtControllerTest.java @@ -46,7 +46,7 @@ class ArtControllerTest { .addProvider(AuthHelper.getAuthFilter()) .addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>( ImmutableSet.of(AuthenticatedAccount.class, DisabledPermittedAuthenticatedAccount.class))) - .setMapper(SystemMapper.getMapper()) + .setMapper(SystemMapper.jsonMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource(new ArtController(rateLimiters, artCredentialsGenerator)) .build(); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AttachmentControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AttachmentControllerTest.java index 037eb6a8d..522f600ce 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AttachmentControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AttachmentControllerTest.java @@ -77,7 +77,7 @@ class AttachmentControllerTest { .addProvider(AuthHelper.getAuthFilter()) .addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>( ImmutableSet.of(AuthenticatedAccount.class, DisabledPermittedAuthenticatedAccount.class))) - .setMapper(SystemMapper.getMapper()) + .setMapper(SystemMapper.jsonMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource(new AttachmentControllerV2(RATE_LIMITERS, "accessKey", "accessSecret", "us-east-1", "attachmentv2-bucket")) .addResource(new AttachmentControllerV3(RATE_LIMITERS, "some-cdn.signal.org", "signal@example.com", 1000, "/attach-here", RSA_PRIVATE_KEY_PEM)) diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java index 9f8aa8c4d..7af469a28 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -84,7 +84,7 @@ class CertificateControllerTest { .addProvider(AuthHelper.getAuthFilter()) .addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>( ImmutableSet.of(AuthenticatedAccount.class, DisabledPermittedAuthenticatedAccount.class))) - .setMapper(SystemMapper.getMapper()) + .setMapper(SystemMapper.jsonMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource(new CertificateController(certificateGenerator, serverZkAuthOperations, clock)) .build(); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/SecureStorageControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/SecureStorageControllerTest.java index a4883c82d..45238727a 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/SecureStorageControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/SecureStorageControllerTest.java @@ -40,7 +40,7 @@ class SecureStorageControllerTest { .addProvider(AuthHelper.getAuthFilter()) .addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>( ImmutableSet.of(AuthenticatedAccount.class, DisabledPermittedAuthenticatedAccount.class))) - .setMapper(SystemMapper.getMapper()) + .setMapper(SystemMapper.jsonMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource(new SecureStorageController(STORAGE_CREDENTIAL_GENERATOR)) .build(); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/StickerControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/StickerControllerTest.java index 9525ecc1d..ff21cb7ce 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/StickerControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/StickerControllerTest.java @@ -41,7 +41,7 @@ class StickerControllerTest { .addProvider(AuthHelper.getAuthFilter()) .addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>( ImmutableSet.of(AuthenticatedAccount.class, DisabledPermittedAuthenticatedAccount.class))) - .setMapper(SystemMapper.getMapper()) + .setMapper(SystemMapper.jsonMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource(new StickerController(rateLimiters, "foo", "bar", "us-east-1", "mybucket")) .build(); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AccountsHelper.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AccountsHelper.java index 94fe45301..4151059d1 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AccountsHelper.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AccountsHelper.java @@ -137,7 +137,7 @@ public class AccountsHelper { } } else { - final ObjectMapper mapper = SystemMapper.getMapper(); + final ObjectMapper mapper = SystemMapper.jsonMapper(); updatedAccount = mapper.readValue(mapper.writeValueAsBytes(account), Account.class); updatedAccount.setNumber(account.getNumber(), account.getPhoneNumberIdentifier()); account.markStale(); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/JsonHelpers.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/JsonHelpers.java index 3205f2a6f..fc25c010d 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/JsonHelpers.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/JsonHelpers.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2020 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -15,7 +15,7 @@ import org.whispersystems.textsecuregcm.util.SystemMapper; public class JsonHelpers { - private static final ObjectMapper objectMapper = SystemMapper.getMapper(); + private static final ObjectMapper objectMapper = SystemMapper.jsonMapper(); public static String asJson(Object object) throws JsonProcessingException { return objectMapper.writeValueAsString(object); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/util/SystemMapperTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/util/SystemMapperTest.java new file mode 100644 index 000000000..93b6501cd --- /dev/null +++ b/service/src/test/java/org/whispersystems/textsecuregcm/util/SystemMapperTest.java @@ -0,0 +1,99 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Optional; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +class SystemMapperTest { + + private static final ObjectMapper MAPPER = SystemMapper.configureMapper(new ObjectMapper()); + + private static final String JSON_NO_FIELD = """ + {} + """.trim(); + + private static final String JSON_NULL_FIELD = """ + {"name":null} + """.trim(); + + private static final String JSON_WITH_FIELD = """ + {"name":"value"} + """.trim(); + + interface Data { + Optional name(); + } + + @JsonInclude(JsonInclude.Include.NON_ABSENT) + public record DataRecord(Optional name) implements Data { + } + + public static class DataClass implements Data { + + @JsonProperty + private Optional name = Optional.empty(); + + public DataClass() { + } + + public DataClass(final Optional name) { + this.name = name; + } + + @Override + public Optional name() { + return name; + } + } + + @JsonInclude(JsonInclude.Include.NON_ABSENT) + public static class DataClass2 extends DataClass { + + public DataClass2(final Optional name) { + super(name); + } + } + + + @ParameterizedTest + @ValueSource(classes = {DataClass.class, DataRecord.class}) + public void testOptionalField(final Class clazz) throws Exception { + assertTrue(MAPPER.readValue(JSON_NO_FIELD, clazz).name().isEmpty()); + assertTrue(MAPPER.readValue(JSON_NULL_FIELD, clazz).name().isEmpty()); + assertEquals("value", MAPPER.readValue(JSON_WITH_FIELD, clazz).name().orElseThrow()); + } + + @ParameterizedTest + @MethodSource("provideStringsForIsBlank") + public void testSerialization(final Data data, final String expectedJson) throws Exception { + assertEquals(expectedJson, MAPPER.writeValueAsString(data)); + } + + private static Stream provideStringsForIsBlank() { + return Stream.of( + Arguments.of(new DataClass(Optional.of("value")), JSON_WITH_FIELD), + Arguments.of(new DataClass(Optional.empty()), JSON_NULL_FIELD), + Arguments.of(new DataClass(null), JSON_NULL_FIELD), + Arguments.of(new DataClass2(Optional.of("value")), JSON_WITH_FIELD), + Arguments.of(new DataClass2(Optional.of("value")), JSON_WITH_FIELD), + Arguments.of(new DataClass2(Optional.empty()), JSON_NO_FIELD), + Arguments.of(new DataRecord(Optional.of("value")), JSON_WITH_FIELD), + Arguments.of(new DataRecord(Optional.empty()), JSON_NO_FIELD), + Arguments.of(new DataRecord(null), JSON_NO_FIELD) + ); + } +} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/util/logging/LoggingUnhandledExceptionMapperTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/util/logging/LoggingUnhandledExceptionMapperTest.java index 37cf4a4f5..4d92e2a30 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/util/logging/LoggingUnhandledExceptionMapperTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/util/logging/LoggingUnhandledExceptionMapperTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -15,7 +15,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; -import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.net.HttpHeaders; import io.dropwizard.jersey.DropwizardResourceConfig; import io.dropwizard.jersey.jackson.JacksonMessageBodyProvider; @@ -43,6 +42,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.slf4j.Logger; +import org.whispersystems.textsecuregcm.util.SystemMapper; import org.whispersystems.websocket.WebSocketResourceProvider; import org.whispersystems.websocket.auth.WebsocketAuthValueFactoryProvider; import org.whispersystems.websocket.logging.WebsocketRequestLog; @@ -129,7 +129,7 @@ class LoggingUnhandledExceptionMapperTest { resourceConfig.register(new TestController()); resourceConfig.register(new WebSocketSessionContextValueFactoryProvider.Binder()); resourceConfig.register(new WebsocketAuthValueFactoryProvider.Binder<>(TestPrincipal.class)); - resourceConfig.register(new JacksonMessageBodyProvider(new ObjectMapper())); + resourceConfig.register(new JacksonMessageBodyProvider(SystemMapper.jsonMapper())); ApplicationHandler applicationHandler = new ApplicationHandler(resourceConfig); WebsocketRequestLog requestLog = mock(WebsocketRequestLog.class);