Make raw User-Agent strings available to gRPC services

This commit is contained in:
Jon Chambers 2025-03-31 15:40:50 -04:00 committed by Jon Chambers
parent c2e3ab832c
commit d4031893cc
6 changed files with 55 additions and 4 deletions

View File

@ -59,6 +59,15 @@ public class RequestAttributesInterceptor implements ServerInterceptor {
}
}
{
final Optional<String> maybeRawUserAgent =
grpcClientConnectionManager.getRawUserAgent(localAddress);
if (maybeRawUserAgent.isPresent()) {
context = context.withValue(RequestAttributesUtil.RAW_USER_AGENT_CONTEXT_KEY, maybeRawUserAgent.get());
}
}
{
final Optional<UserAgent> maybeUserAgent = grpcClientConnectionManager.getUserAgent(localAddress);

View File

@ -13,7 +13,8 @@ public class RequestAttributesUtil {
static final Context.Key<List<Locale.LanguageRange>> ACCEPT_LANGUAGE_CONTEXT_KEY = Context.key("accept-language");
static final Context.Key<InetAddress> REMOTE_ADDRESS_CONTEXT_KEY = Context.key("remote-address");
static final Context.Key<UserAgent> USER_AGENT_CONTEXT_KEY = Context.key("user-agent");
static final Context.Key<String> RAW_USER_AGENT_CONTEXT_KEY = Context.key("unparsed-user-agent");
static final Context.Key<UserAgent> USER_AGENT_CONTEXT_KEY = Context.key("parsed-user-agent");
private static final List<Locale> 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<UserAgent> 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<String> getRawUserAgent() {
return Optional.ofNullable(RAW_USER_AGENT_CONTEXT_KEY.get());
}
}

View File

@ -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<String> 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.
*

View File

@ -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();
}

View File

@ -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());

View File

@ -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 {