From f5e49b6db7f4da9c0312c1685288122b98e5dc6f Mon Sep 17 00:00:00 2001 From: Jon Chambers Date: Tue, 15 Apr 2025 12:38:00 -0400 Subject: [PATCH] Convert `UserAgent` to a record --- .../controllers/DeviceController.java | 2 +- .../OneTimeDonationController.java | 2 +- .../controllers/SubscriptionController.java | 2 +- .../filters/RemoteDeprecationFilter.java | 20 +++---- .../filters/RestDeprecationFilter.java | 6 +-- .../metrics/OpenWebSocketCounter.java | 2 +- .../metrics/UserAgentTagUtil.java | 13 +++-- .../LoggingUnhandledExceptionMapper.java | 2 +- .../textsecuregcm/util/ua/UserAgent.java | 54 +------------------ .../grpc/RequestAttributesServiceImpl.java | 7 +-- .../util/ua/UserAgentUtilTest.java | 8 +-- 11 files changed, 33 insertions(+), 85 deletions(-) diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java index f35831bfc..66260c9fa 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java @@ -402,7 +402,7 @@ public class DeviceController { private AtomicInteger getCounterForLinkedDeviceListeners(final String userAgent) { try { - return linkedDeviceListenersByPlatform.get(UserAgentUtil.parseUserAgentString(userAgent).getPlatform()); + return linkedDeviceListenersByPlatform.get(UserAgentUtil.parseUserAgentString(userAgent).platform()); } catch (final UnrecognizedUserAgentException ignored) { return linkedDeviceListenersForUnrecognizedPlatforms; } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/OneTimeDonationController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/OneTimeDonationController.java index a8c80c238..ab856f86e 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/OneTimeDonationController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/OneTimeDonationController.java @@ -428,7 +428,7 @@ public class OneTimeDonationController { @Nullable private static ClientPlatform getClientPlatform(@Nullable final String userAgentString) { try { - return UserAgentUtil.parseUserAgentString(userAgentString).getPlatform(); + return UserAgentUtil.parseUserAgentString(userAgentString).platform(); } catch (final UnrecognizedUserAgentException e) { return null; } 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 6d93d4d01..ab644223e 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java @@ -755,7 +755,7 @@ public class SubscriptionController { @Nullable private static ClientPlatform getClientPlatform(@Nullable final String userAgentString) { try { - return UserAgentUtil.parseUserAgentString(userAgentString).getPlatform(); + return UserAgentUtil.parseUserAgentString(userAgentString).platform(); } catch (final UnrecognizedUserAgentException e) { return null; } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/filters/RemoteDeprecationFilter.java b/service/src/main/java/org/whispersystems/textsecuregcm/filters/RemoteDeprecationFilter.java index 0d5f18836..b48709923 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/filters/RemoteDeprecationFilter.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/filters/RemoteDeprecationFilter.java @@ -108,28 +108,28 @@ public class RemoteDeprecationFilter implements Filter, ServerInterceptor { return true; } - if (blockedVersionsByPlatform.containsKey(userAgent.getPlatform())) { - if (blockedVersionsByPlatform.get(userAgent.getPlatform()).contains(userAgent.getVersion())) { + if (blockedVersionsByPlatform.containsKey(userAgent.platform())) { + if (blockedVersionsByPlatform.get(userAgent.platform()).contains(userAgent.version())) { recordDeprecation(userAgent, BLOCKED_CLIENT_REASON); shouldBlock = true; } } - if (minimumVersionsByPlatform.containsKey(userAgent.getPlatform())) { - if (userAgent.getVersion().isLowerThan(minimumVersionsByPlatform.get(userAgent.getPlatform()))) { + if (minimumVersionsByPlatform.containsKey(userAgent.platform())) { + if (userAgent.version().isLowerThan(minimumVersionsByPlatform.get(userAgent.platform()))) { recordDeprecation(userAgent, EXPIRED_CLIENT_REASON); shouldBlock = true; } } - if (versionsPendingBlockByPlatform.containsKey(userAgent.getPlatform())) { - if (versionsPendingBlockByPlatform.get(userAgent.getPlatform()).contains(userAgent.getVersion())) { + if (versionsPendingBlockByPlatform.containsKey(userAgent.platform())) { + if (versionsPendingBlockByPlatform.get(userAgent.platform()).contains(userAgent.version())) { recordPendingDeprecation(userAgent, BLOCKED_CLIENT_REASON); } } - if (versionsPendingDeprecationByPlatform.containsKey(userAgent.getPlatform())) { - if (userAgent.getVersion().isLowerThan(versionsPendingDeprecationByPlatform.get(userAgent.getPlatform()))) { + if (versionsPendingDeprecationByPlatform.containsKey(userAgent.platform())) { + if (userAgent.version().isLowerThan(versionsPendingDeprecationByPlatform.get(userAgent.platform()))) { recordPendingDeprecation(userAgent, EXPIRED_CLIENT_REASON); } } @@ -139,13 +139,13 @@ public class RemoteDeprecationFilter implements Filter, ServerInterceptor { private void recordDeprecation(final UserAgent userAgent, final String reason) { Metrics.counter(DEPRECATED_CLIENT_COUNTER_NAME, - PLATFORM_TAG, userAgent != null ? userAgent.getPlatform().name().toLowerCase() : "unrecognized", + PLATFORM_TAG, userAgent != null ? userAgent.platform().name().toLowerCase() : "unrecognized", REASON_TAG_NAME, reason).increment(); } private void recordPendingDeprecation(final UserAgent userAgent, final String reason) { Metrics.counter(PENDING_DEPRECATION_COUNTER_NAME, - PLATFORM_TAG, userAgent.getPlatform().name().toLowerCase(), + PLATFORM_TAG, userAgent.platform().name().toLowerCase(), REASON_TAG_NAME, reason).increment(); } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/filters/RestDeprecationFilter.java b/service/src/main/java/org/whispersystems/textsecuregcm/filters/RestDeprecationFilter.java index cf363960d..595d32219 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/filters/RestDeprecationFilter.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/filters/RestDeprecationFilter.java @@ -15,8 +15,6 @@ import jakarta.ws.rs.container.ContainerRequestFilter; import jakarta.ws.rs.core.SecurityContext; import java.io.IOException; import java.util.Map; -import java.util.Optional; -import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.auth.AuthenticatedDevice; @@ -70,8 +68,8 @@ public class RestDeprecationFilter implements ContainerRequestFilter { try { final UserAgent userAgent = UserAgentUtil.parseUserAgentString(userAgentString); - final ClientPlatform platform = userAgent.getPlatform(); - final Semver version = userAgent.getVersion(); + final ClientPlatform platform = userAgent.platform(); + final Semver version = userAgent.version(); if (!minimumRestFreeVersion.containsKey(platform)) { return; } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/metrics/OpenWebSocketCounter.java b/service/src/main/java/org/whispersystems/textsecuregcm/metrics/OpenWebSocketCounter.java index 340d65e5e..0273857a6 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/metrics/OpenWebSocketCounter.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/metrics/OpenWebSocketCounter.java @@ -67,7 +67,7 @@ public class OpenWebSocketCounter { try { final ClientPlatform clientPlatform = - UserAgentUtil.parseUserAgentString(context.getClient().getUserAgent()).getPlatform(); + UserAgentUtil.parseUserAgentString(context.getClient().getUserAgent()).platform(); calculatedOpenWebSocketCounter = openWebsocketsByClientPlatform.get(clientPlatform); calculatedDurationTimer = durationTimersByClientPlatform.get(clientPlatform); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/metrics/UserAgentTagUtil.java b/service/src/main/java/org/whispersystems/textsecuregcm/metrics/UserAgentTagUtil.java index 80cf8e811..51699bec5 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/metrics/UserAgentTagUtil.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/metrics/UserAgentTagUtil.java @@ -9,6 +9,7 @@ import io.micrometer.core.instrument.Tag; import java.util.List; import java.util.Optional; import java.util.UUID; +import org.apache.commons.lang3.StringUtils; import org.whispersystems.textsecuregcm.WhisperServerVersion; import org.whispersystems.textsecuregcm.storage.ClientReleaseManager; import org.whispersystems.textsecuregcm.util.ua.UnrecognizedUserAgentException; @@ -48,15 +49,15 @@ public class UserAgentTagUtil { } public static Tag getPlatformTag(@Nullable final UserAgent userAgent) { - return Tag.of(PLATFORM_TAG, userAgent != null ? userAgent.getPlatform().name().toLowerCase() : "unrecognized"); + return Tag.of(PLATFORM_TAG, userAgent != null ? userAgent.platform().name().toLowerCase() : "unrecognized"); } public static Optional getClientVersionTag(final String userAgentString, final ClientReleaseManager clientReleaseManager) { try { final UserAgent userAgent = UserAgentUtil.parseUserAgentString(userAgentString); - if (clientReleaseManager.isVersionActive(userAgent.getPlatform(), userAgent.getVersion())) { - return Optional.of(Tag.of(VERSION_TAG, userAgent.getVersion().toString())); + if (clientReleaseManager.isVersionActive(userAgent.platform(), userAgent.version())) { + return Optional.of(Tag.of(VERSION_TAG, userAgent.version().toString())); } } catch (final UnrecognizedUserAgentException ignored) { } @@ -70,10 +71,8 @@ public class UserAgentTagUtil { try { final UserAgent userAgent = UserAgentUtil.parseUserAgentString(userAgentString); - platform = userAgent.getPlatform().name().toLowerCase(); - libsignal = userAgent.getAdditionalSpecifiers() - .map(additionalSpecifiers -> additionalSpecifiers.contains("libsignal")) - .orElse(false); + platform = userAgent.platform().name().toLowerCase(); + libsignal = StringUtils.contains(userAgent.additionalSpecifiers(), "libsignal"); } catch (final UnrecognizedUserAgentException e) { platform = "unrecognized"; libsignal = false; diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/util/logging/LoggingUnhandledExceptionMapper.java b/service/src/main/java/org/whispersystems/textsecuregcm/util/logging/LoggingUnhandledExceptionMapper.java index 249b09aa4..153b00711 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/util/logging/LoggingUnhandledExceptionMapper.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/util/logging/LoggingUnhandledExceptionMapper.java @@ -46,7 +46,7 @@ public class LoggingUnhandledExceptionMapper extends LoggingExceptionMapper getAdditionalSpecifiers() { - return Optional.ofNullable(additionalSpecifiers); - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - final UserAgent userAgent = (UserAgent)o; - return platform == userAgent.platform && - version.equals(userAgent.version) && - Objects.equals(additionalSpecifiers, userAgent.additionalSpecifiers); - } - - @Override - public int hashCode() { - return Objects.hash(platform, version, additionalSpecifiers); - } - - @Override - public String toString() { - return "UserAgent{" + - "platform=" + platform + - ", version=" + version + - ", additionalSpecifiers='" + additionalSpecifiers + '\'' + - '}'; - } +public record UserAgent(ClientPlatform platform, Semver version, @Nullable String additionalSpecifiers) { } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/RequestAttributesServiceImpl.java b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/RequestAttributesServiceImpl.java index 05046ec82..f24931676 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/RequestAttributesServiceImpl.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/RequestAttributesServiceImpl.java @@ -1,6 +1,7 @@ package org.whispersystems.textsecuregcm.grpc; import io.grpc.stub.StreamObserver; +import org.apache.commons.lang3.StringUtils; import org.signal.chat.rpc.GetAuthenticatedDeviceRequest; import org.signal.chat.rpc.GetAuthenticatedDeviceResponse; import org.signal.chat.rpc.GetRequestAttributesRequest; @@ -28,9 +29,9 @@ public class RequestAttributesServiceImpl extends RequestAttributesGrpc.RequestA responseBuilder.setRemoteAddress(RequestAttributesUtil.getRemoteAddress().getHostAddress()); RequestAttributesUtil.getUserAgent().ifPresent(userAgent -> responseBuilder.setUserAgent(UserAgent.newBuilder() - .setPlatform(userAgent.getPlatform().toString()) - .setVersion(userAgent.getVersion().toString()) - .setAdditionalSpecifiers(userAgent.getAdditionalSpecifiers().orElse("")) + .setPlatform(userAgent.platform().toString()) + .setVersion(userAgent.version().toString()) + .setAdditionalSpecifiers(StringUtils.stripToEmpty(userAgent.additionalSpecifiers())) .build())); RequestAttributesUtil.getRawUserAgent().ifPresent(responseBuilder::setRawUserAgent); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/util/ua/UserAgentUtilTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/util/ua/UserAgentUtilTest.java index 796e97caa..dddc61a94 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/util/ua/UserAgentUtilTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/util/ua/UserAgentUtilTest.java @@ -42,18 +42,18 @@ class UserAgentUtilTest { Arguments.of("This is obviously not a reasonable User-Agent string.", null), Arguments.of("Signal-Android/4.68.3 Android/25", new UserAgent(ClientPlatform.ANDROID, new Semver("4.68.3"), "Android/25")), - Arguments.of("Signal-Android/4.68.3", new UserAgent(ClientPlatform.ANDROID, new Semver("4.68.3"))), + Arguments.of("Signal-Android/4.68.3", new UserAgent(ClientPlatform.ANDROID, new Semver("4.68.3"), null)), Arguments.of("Signal-Desktop/1.2.3 Linux", new UserAgent(ClientPlatform.DESKTOP, new Semver("1.2.3"), "Linux")), Arguments.of("Signal-Desktop/1.2.3 macOS", new UserAgent(ClientPlatform.DESKTOP, new Semver("1.2.3"), "macOS")), Arguments.of("Signal-Desktop/1.2.3 Windows", new UserAgent(ClientPlatform.DESKTOP, new Semver("1.2.3"), "Windows")), - Arguments.of("Signal-Desktop/1.2.3", new UserAgent(ClientPlatform.DESKTOP, new Semver("1.2.3"))), + Arguments.of("Signal-Desktop/1.2.3", new UserAgent(ClientPlatform.DESKTOP, new Semver("1.2.3"), null)), Arguments.of("Signal-Desktop/1.32.0-beta.3", - new UserAgent(ClientPlatform.DESKTOP, new Semver("1.32.0-beta.3"))), + new UserAgent(ClientPlatform.DESKTOP, new Semver("1.32.0-beta.3"), null)), Arguments.of("Signal-iOS/3.9.0 (iPhone; iOS 12.2; Scale/3.00)", new UserAgent(ClientPlatform.IOS, new Semver("3.9.0"), "(iPhone; iOS 12.2; Scale/3.00)")), Arguments.of("Signal-iOS/3.9.0 iOS/14.2", new UserAgent(ClientPlatform.IOS, new Semver("3.9.0"), "iOS/14.2")), - Arguments.of("Signal-iOS/3.9.0", new UserAgent(ClientPlatform.IOS, new Semver("3.9.0"))), + Arguments.of("Signal-iOS/3.9.0", new UserAgent(ClientPlatform.IOS, new Semver("3.9.0"), null)), Arguments.of("Signal-Android/7.11.23-nightly-1982-06-28-07-07-07 tonic/0.31", new UserAgent(ClientPlatform.ANDROID, new Semver("7.11.23-nightly-1982-06-28-07-07-07"), "tonic/0.31")), Arguments.of("Signal-Android/7.11.23-nightly-1982-06-28-07-07-07 Android/42 tonic/0.31",