From f9fabbedce68b614c2bdc68ccbc4ba44d91de5cc Mon Sep 17 00:00:00 2001 From: Jon Chambers Date: Tue, 19 Sep 2023 13:09:31 -0400 Subject: [PATCH] Convert `SubscriptionController` request/response entities to records --- .../controllers/SubscriptionController.java | 356 ++++-------------- .../SubscriptionControllerTest.java | 8 +- 2 files changed, 74 insertions(+), 290 deletions(-) diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java index 048859816..7e6966c09 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java @@ -5,12 +5,8 @@ package org.whispersystems.textsecuregcm.controllers; -import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name; - -import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.annotations.VisibleForTesting; import com.stripe.exception.StripeException; import io.dropwizard.auth.Auth; @@ -84,6 +80,7 @@ import org.whispersystems.textsecuregcm.configuration.SubscriptionConfiguration; import org.whispersystems.textsecuregcm.configuration.SubscriptionLevelConfiguration; import org.whispersystems.textsecuregcm.entities.Badge; import org.whispersystems.textsecuregcm.entities.PurchasableBadge; +import org.whispersystems.textsecuregcm.metrics.MetricsUtil; import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil; import org.whispersystems.textsecuregcm.storage.IssuedReceiptsManager; import org.whispersystems.textsecuregcm.storage.SubscriptionManager; @@ -116,9 +113,9 @@ public class SubscriptionController { private final LevelTranslator levelTranslator; private final Map currencyConfiguration; - private static final String INVALID_ACCEPT_LANGUAGE_COUNTER_NAME = name(SubscriptionController.class, + private static final String INVALID_ACCEPT_LANGUAGE_COUNTER_NAME = MetricsUtil.name(SubscriptionController.class, "invalidAcceptLanguage"); - private static final String RECEIPT_ISSUED_COUNTER_NAME = name(SubscriptionController.class, "receiptIssued"); + private static final String RECEIPT_ISSUED_COUNTER_NAME = MetricsUtil.name(SubscriptionController.class, "receiptIssued"); private static final String PROCESSOR_TAG_NAME = "processor"; private static final String TYPE_TAG_NAME = "type"; @@ -433,61 +430,19 @@ public class SubscriptionController { new ClientErrorException(Status.CONFLICT))) .thenApply(customer -> Response.ok().build()); } - public static class SetSubscriptionLevelSuccessResponse { - private final long level; - - @JsonCreator - public SetSubscriptionLevelSuccessResponse( - @JsonProperty("level") long level) { - this.level = level; - } - - public long getLevel() { - return level; - } + public record SetSubscriptionLevelSuccessResponse(long level) { } - public static class SetSubscriptionLevelErrorResponse { + public record SetSubscriptionLevelErrorResponse(List errors) { - public static class Error { + public record Error(SetSubscriptionLevelErrorResponse.Error.Type type, String message) { public enum Type { UNSUPPORTED_LEVEL, UNSUPPORTED_CURRENCY, PAYMENT_REQUIRES_ACTION, } - - private final Type type; - private final String message; - - @JsonCreator - public Error( - @JsonProperty("type") Type type, - @JsonProperty("message") String message) { - this.type = type; - this.message = message; - } - - public Type getType() { - return type; - } - - public String getMessage() { - return message; - } - } - - private final List errors; - - @JsonCreator - public SetSubscriptionLevelErrorResponse( - @JsonProperty("errors") List errors) { - this.errors = errors; - } - - public List getErrors() { - return errors; } } @@ -614,34 +569,11 @@ public class SubscriptionController { }); } - public static class GetBoostBadgesResponse { - public static class Level { - private final PurchasableBadge badge; - - @JsonCreator - public Level( - @JsonProperty("badge") PurchasableBadge badge) { - this.badge = badge; - } - - public PurchasableBadge getBadge() { - return badge; + public record GetBoostBadgesResponse(Map levels) { + public record Level(PurchasableBadge badge) { } } - private final Map levels; - - @JsonCreator - public GetBoostBadgesResponse( - @JsonProperty("levels") Map levels) { - this.levels = Objects.requireNonNull(levels); - } - - public Map getLevels() { - return levels; - } - } - @GET @Path("/boost/badges") @Produces(MediaType.APPLICATION_JSON) @@ -686,19 +618,7 @@ public class SubscriptionController { } - public static class CreateBoostResponse { - - private final String clientSecret; - - @JsonCreator - public CreateBoostResponse( - @JsonProperty("clientSecret") String clientSecret) { - this.clientSecret = clientSecret; - } - - public String getClientSecret() { - return clientSecret; - } + public record CreateBoostResponse(String clientSecret) { } /** @@ -830,19 +750,7 @@ public class SubscriptionController { public SubscriptionProcessor processor = SubscriptionProcessor.STRIPE; } - public static class CreateBoostReceiptCredentialsResponse { - - private final byte[] receiptCredentialResponse; - - @JsonCreator - public CreateBoostReceiptCredentialsResponse( - @JsonProperty("receiptCredentialResponse") byte[] receiptCredentialResponse) { - this.receiptCredentialResponse = receiptCredentialResponse; - } - - public byte[] getReceiptCredentialResponse() { - return receiptCredentialResponse; - } + public record CreateBoostReceiptCredentialsResponse(byte[] receiptCredentialResponse) { } @POST @@ -921,100 +829,17 @@ public class SubscriptionController { }); } - public static class GetSubscriptionInformationResponse { + public record GetSubscriptionInformationResponse( + SubscriptionController.GetSubscriptionInformationResponse.Subscription subscription, + @JsonInclude(Include.NON_NULL) ChargeFailure chargeFailure) { - public static class Subscription { + public record Subscription(long level, Instant billingCycleAnchor, Instant endOfCurrentPeriod, boolean active, + boolean cancelAtPeriodEnd, String currency, BigDecimal amount, String status, + SubscriptionProcessor processor) { - private final long level; - private final Instant billingCycleAnchor; - private final Instant endOfCurrentPeriod; - private final boolean active; - private final boolean cancelAtPeriodEnd; - private final String currency; - private final BigDecimal amount; - private final String status; - private final SubscriptionProcessor processor; - - @JsonCreator - public Subscription( - @JsonProperty("level") long level, - @JsonProperty("billingCycleAnchor") Instant billingCycleAnchor, - @JsonProperty("endOfCurrentPeriod") Instant endOfCurrentPeriod, - @JsonProperty("active") boolean active, - @JsonProperty("cancelAtPeriodEnd") boolean cancelAtPeriodEnd, - @JsonProperty("currency") String currency, - @JsonProperty("amount") BigDecimal amount, - @JsonProperty("status") String status, - @JsonProperty("processor") SubscriptionProcessor processor) { - this.level = level; - this.billingCycleAnchor = billingCycleAnchor; - this.endOfCurrentPeriod = endOfCurrentPeriod; - this.active = active; - this.cancelAtPeriodEnd = cancelAtPeriodEnd; - this.currency = currency; - this.amount = amount; - this.status = status; - this.processor = processor; - } - - public long getLevel() { - return level; - } - - public Instant getBillingCycleAnchor() { - return billingCycleAnchor; - } - - public Instant getEndOfCurrentPeriod() { - return endOfCurrentPeriod; - } - - public boolean isActive() { - return active; - } - - public boolean isCancelAtPeriodEnd() { - return cancelAtPeriodEnd; - } - - public String getCurrency() { - return currency; - } - - public BigDecimal getAmount() { - return amount; - } - - public String getStatus() { - return status; - } - - public SubscriptionProcessor getProcessor() { - return processor; } } - private final Subscription subscription; - private final ChargeFailure chargeFailure; - - @JsonCreator - public GetSubscriptionInformationResponse( - @JsonProperty("subscription") Subscription subscription, - @JsonProperty("chargeFailure") ChargeFailure chargeFailure) { - this.subscription = subscription; - this.chargeFailure = chargeFailure; - } - - public Subscription getSubscription() { - return subscription; - } - - @JsonInclude(Include.NON_NULL) - public ChargeFailure getChargeFailure() { - return chargeFailure; - } - } - @GET @Path("/{subscriberId}") @Produces(MediaType.APPLICATION_JSON) @@ -1049,36 +874,10 @@ public class SubscriptionController { }); } - public static class GetReceiptCredentialsRequest { - - private final byte[] receiptCredentialRequest; - - @JsonCreator - public GetReceiptCredentialsRequest( - @JsonProperty("receiptCredentialRequest") byte[] receiptCredentialRequest) { - this.receiptCredentialRequest = receiptCredentialRequest; - } - - @NotEmpty - public byte[] getReceiptCredentialRequest() { - return receiptCredentialRequest; - } + public record GetReceiptCredentialsRequest(@NotEmpty byte[] receiptCredentialRequest) { } - public static class GetReceiptCredentialsResponse { - - private final byte[] receiptCredentialResponse; - - @JsonCreator - public GetReceiptCredentialsResponse( - @JsonProperty("receiptCredentialResponse") byte[] receiptCredentialResponse) { - this.receiptCredentialResponse = receiptCredentialResponse; - } - - @NotEmpty - public byte[] getReceiptCredentialResponse() { - return receiptCredentialResponse; - } + public record GetReceiptCredentialsResponse(@NotEmpty byte[] receiptCredentialResponse) { } @POST @@ -1099,7 +898,7 @@ public class SubscriptionController { } ReceiptCredentialRequest receiptCredentialRequest; try { - receiptCredentialRequest = new ReceiptCredentialRequest(request.getReceiptCredentialRequest()); + receiptCredentialRequest = new ReceiptCredentialRequest(request.receiptCredentialRequest()); } catch (InvalidInputException e) { throw new BadRequestException("invalid receipt credential request", e); } @@ -1182,74 +981,59 @@ public class SubscriptionController { } } - private static class RequestData { + private record RequestData(@Nonnull byte[] subscriberBytes, + @Nonnull byte[] subscriberUser, + @Nonnull byte[] subscriberKey, + @Nonnull byte[] hmac, + @Nonnull Instant now) { - public final byte[] subscriberBytes; - public final byte[] subscriberUser; - public final byte[] subscriberKey; - public final byte[] hmac; - public final Instant now; - - private RequestData( - @Nonnull byte[] subscriberBytes, - @Nonnull byte[] subscriberUser, - @Nonnull byte[] subscriberKey, - @Nonnull byte[] hmac, - @Nonnull Instant now) { - this.subscriberBytes = Objects.requireNonNull(subscriberBytes); - this.subscriberUser = Objects.requireNonNull(subscriberUser); - this.subscriberKey = Objects.requireNonNull(subscriberKey); - this.hmac = Objects.requireNonNull(hmac); - this.now = Objects.requireNonNull(now); - } - - public static RequestData process( - Optional authenticatedAccount, - String subscriberId, - Clock clock) { - Instant now = clock.instant(); - if (authenticatedAccount.isPresent()) { - throw new ForbiddenException("must not use authenticated connection for subscriber operations"); - } - byte[] subscriberBytes = convertSubscriberIdStringToBytes(subscriberId); - byte[] subscriberUser = getUser(subscriberBytes); - byte[] subscriberKey = getKey(subscriberBytes); - byte[] hmac = computeHmac(subscriberUser, subscriberKey); - return new RequestData(subscriberBytes, subscriberUser, subscriberKey, hmac, now); - } - - private static byte[] convertSubscriberIdStringToBytes(String subscriberId) { - try { - byte[] bytes = Base64.getUrlDecoder().decode(subscriberId); - if (bytes.length != 32) { - throw new NotFoundException(); + public static RequestData process( + Optional authenticatedAccount, + String subscriberId, + Clock clock) { + Instant now = clock.instant(); + if (authenticatedAccount.isPresent()) { + throw new ForbiddenException("must not use authenticated connection for subscriber operations"); + } + byte[] subscriberBytes = convertSubscriberIdStringToBytes(subscriberId); + byte[] subscriberUser = getUser(subscriberBytes); + byte[] subscriberKey = getKey(subscriberBytes); + byte[] hmac = computeHmac(subscriberUser, subscriberKey); + return new RequestData(subscriberBytes, subscriberUser, subscriberKey, hmac, now); + } + + private static byte[] convertSubscriberIdStringToBytes(String subscriberId) { + try { + byte[] bytes = Base64.getUrlDecoder().decode(subscriberId); + if (bytes.length != 32) { + throw new NotFoundException(); + } + return bytes; + } catch (IllegalArgumentException e) { + throw new NotFoundException(e); + } + } + + private static byte[] getUser(byte[] subscriberBytes) { + byte[] user = new byte[16]; + System.arraycopy(subscriberBytes, 0, user, 0, user.length); + return user; + } + + private static byte[] getKey(byte[] subscriberBytes) { + byte[] key = new byte[16]; + System.arraycopy(subscriberBytes, 16, key, 0, key.length); + return key; + } + + private static byte[] computeHmac(byte[] subscriberUser, byte[] subscriberKey) { + try { + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(subscriberKey, "HmacSHA256")); + return mac.doFinal(subscriberUser); + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + throw new InternalServerErrorException(e); } - return bytes; - } catch (IllegalArgumentException e) { - throw new NotFoundException(e); } } - - private static byte[] getUser(byte[] subscriberBytes) { - byte[] user = new byte[16]; - System.arraycopy(subscriberBytes, 0, user, 0, user.length); - return user; - } - - private static byte[] getKey(byte[] subscriberBytes) { - byte[] key = new byte[16]; - System.arraycopy(subscriberBytes, 16, key, 0, key.length); - return key; - } - - private static byte[] computeHmac(byte[] subscriberUser, byte[] subscriberKey) { - try { - Mac mac = Mac.getInstance("HmacSHA256"); - mac.init(new SecretKeySpec(subscriberKey, "HmacSHA256")); - return mac.doFinal(subscriberUser); - } catch (NoSuchAlgorithmException | InvalidKeyException e) { - throw new InternalServerErrorException(e); - } - } - } } 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 298229f8f..c7a171041 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SubscriptionControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SubscriptionControllerTest.java @@ -342,9 +342,9 @@ class SubscriptionControllerTest { assertThat(response.readEntity(SubscriptionController.SetSubscriptionLevelErrorResponse.class)) .satisfies(errorResponse -> { - assertThat(errorResponse.getErrors()) + assertThat(errorResponse.errors()) .anySatisfy(error -> { - assertThat(error.getType()).isEqualTo( + assertThat(error.type()).isEqualTo( SubscriptionController.SetSubscriptionLevelErrorResponse.Error.Type.PAYMENT_REQUIRES_ACTION); }); }); @@ -559,7 +559,7 @@ class SubscriptionControllerTest { assertThat(response.getStatus()).isEqualTo(200); assertThat(response.readEntity(SubscriptionController.SetSubscriptionLevelSuccessResponse.class)) - .extracting(SubscriptionController.SetSubscriptionLevelSuccessResponse::getLevel) + .extracting(SubscriptionController.SetSubscriptionLevelSuccessResponse::level) .isEqualTo(level); } @@ -628,7 +628,7 @@ class SubscriptionControllerTest { assertThat(response.getStatus()).isEqualTo(200); assertThat(response.readEntity(SubscriptionController.SetSubscriptionLevelSuccessResponse.class)) - .extracting(SubscriptionController.SetSubscriptionLevelSuccessResponse::getLevel) + .extracting(SubscriptionController.SetSubscriptionLevelSuccessResponse::level) .isEqualTo(requestLevel); }