diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/grpc/RequestAttributesInterceptor.java b/service/src/main/java/org/whispersystems/textsecuregcm/grpc/RequestAttributesInterceptor.java index 06c6f957a..96b8ef9f8 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/grpc/RequestAttributesInterceptor.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/grpc/RequestAttributesInterceptor.java @@ -59,6 +59,15 @@ public class RequestAttributesInterceptor implements ServerInterceptor { } } + { + final Optional maybeRawUserAgent = + grpcClientConnectionManager.getRawUserAgent(localAddress); + + if (maybeRawUserAgent.isPresent()) { + context = context.withValue(RequestAttributesUtil.RAW_USER_AGENT_CONTEXT_KEY, maybeRawUserAgent.get()); + } + } + { final Optional maybeUserAgent = grpcClientConnectionManager.getUserAgent(localAddress); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/grpc/RequestAttributesUtil.java b/service/src/main/java/org/whispersystems/textsecuregcm/grpc/RequestAttributesUtil.java index f55f5d687..76d3109a1 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/grpc/RequestAttributesUtil.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/grpc/RequestAttributesUtil.java @@ -13,7 +13,8 @@ public class RequestAttributesUtil { static final Context.Key> ACCEPT_LANGUAGE_CONTEXT_KEY = Context.key("accept-language"); static final Context.Key REMOTE_ADDRESS_CONTEXT_KEY = Context.key("remote-address"); - static final Context.Key USER_AGENT_CONTEXT_KEY = Context.key("user-agent"); + static final Context.Key RAW_USER_AGENT_CONTEXT_KEY = Context.key("unparsed-user-agent"); + static final Context.Key USER_AGENT_CONTEXT_KEY = Context.key("parsed-user-agent"); private static final List AVAILABLE_LOCALES = Arrays.asList(Locale.getAvailableLocales()); @@ -51,9 +52,18 @@ public class RequestAttributesUtil { /** * Returns the parsed user-agent of the remote client in the current gRPC request context. * - * @return the parsed user-agent of the remote client; may be null if unparseable or not specified + * @return the parsed user-agent of the remote client; may be empty if unparseable or not specified */ public static Optional getUserAgent() { return Optional.ofNullable(USER_AGENT_CONTEXT_KEY.get()); } + + /** + * Returns the unparsed user-agent of the remote client in the current gRPC request context. + * + * @return the unparsed user-agent of the remote client; may be empty if not specified + */ + public static Optional getRawUserAgent() { + return Optional.ofNullable(RAW_USER_AGENT_CONTEXT_KEY.get()); + } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/grpc/net/GrpcClientConnectionManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/grpc/net/GrpcClientConnectionManager.java index fbca3f930..69b895d69 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/grpc/net/GrpcClientConnectionManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/grpc/net/GrpcClientConnectionManager.java @@ -106,7 +106,21 @@ public class GrpcClientConnectionManager implements DisconnectionRequestListener } /** - * Returns the parsed user agent provided by the client the opened the connection associated with the given local + * Returns the unparsed user agent provided by the client that opened the connection associated with the given local + * address. This method may return an empty value if no active local connection is associated with the given local + * address. + * + * @param localAddress the local address for which to find a User-Agent string + * + * @return the user agent string associated with the given local address + */ + public Optional getRawUserAgent(final LocalAddress localAddress) { + return Optional.ofNullable(remoteChannelsByLocalAddress.get(localAddress)) + .map(remoteChannel -> remoteChannel.attr(RAW_USER_AGENT_ATTRIBUTE_KEY).get()); + } + + /** + * Returns the parsed user agent provided by the client that opened the connection associated with the given local * address. This method may return an empty value if no active local connection is associated with the given local * address or if the client's user-agent string was not recognized. * 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 0fa5e5044..05046ec82 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/RequestAttributesServiceImpl.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/RequestAttributesServiceImpl.java @@ -33,6 +33,8 @@ public class RequestAttributesServiceImpl extends RequestAttributesGrpc.RequestA .setAdditionalSpecifiers(userAgent.getAdditionalSpecifiers().orElse("")) .build())); + RequestAttributesUtil.getRawUserAgent().ifPresent(responseBuilder::setRawUserAgent); + responseObserver.onNext(responseBuilder.build()); responseObserver.onCompleted(); } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/RequestAttributesUtilTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/RequestAttributesUtilTest.java index 780469fc9..3bbcb22be 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/RequestAttributesUtilTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/RequestAttributesUtilTest.java @@ -152,6 +152,21 @@ class RequestAttributesUtilTest { assertEquals("Linux", response.getUserAgent().getAdditionalSpecifiers()); } + @Test + void getRawUserAgent() { + when(grpcClientConnectionManager.getRawUserAgent(any())) + .thenReturn(Optional.empty()); + + assertTrue(getRequestAttributes().getRawUserAgent().isBlank()); + + final String userAgentString = "Signal-Desktop/1.2.3 Linux"; + + when(grpcClientConnectionManager.getRawUserAgent(any())) + .thenReturn(Optional.of(userAgentString)); + + assertEquals(userAgentString, getRequestAttributes().getRawUserAgent()); + } + private GetRequestAttributesResponse getRequestAttributes() { return RequestAttributesGrpc.newBlockingStub(managedChannel) .getRequestAttributes(GetRequestAttributesRequest.newBuilder().build()); diff --git a/service/src/test/proto/request_attributes_service.proto b/service/src/test/proto/request_attributes_service.proto index f7df61d64..e52e9873e 100644 --- a/service/src/test/proto/request_attributes_service.proto +++ b/service/src/test/proto/request_attributes_service.proto @@ -23,7 +23,8 @@ message GetRequestAttributesResponse { repeated string acceptable_languages = 1; repeated string available_accepted_locales = 2; string remote_address = 3; - UserAgent user_agent = 4; + string raw_user_agent = 4; + UserAgent user_agent = 5; } message UserAgent {