mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-22 01:48:05 +01:00
initial grpc service code in chat
This commit is contained in:
committed by
GitHub
parent
cc3cab9c88
commit
8d995e456e
@@ -5,6 +5,8 @@
|
||||
|
||||
package org.whispersystems.textsecuregcm.filters;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -13,105 +15,165 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.common.net.HttpHeaders;
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.vdurmont.semver4j.Semver;
|
||||
import java.io.IOException;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.signal.chat.rpc.EchoServiceGrpc;
|
||||
import org.signal.chat.rpc.EchoRequest;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicRemoteDeprecationConfiguration;
|
||||
import org.whispersystems.textsecuregcm.grpc.EchoServiceImpl;
|
||||
import org.whispersystems.textsecuregcm.grpc.StatusConstants;
|
||||
import org.whispersystems.textsecuregcm.grpc.UserAgentInterceptor;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||
import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
|
||||
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.ManagedChannel;
|
||||
import io.grpc.Server;
|
||||
import io.grpc.ServerBuilder;
|
||||
import io.grpc.StatusRuntimeException;
|
||||
import io.grpc.inprocess.InProcessServerBuilder;
|
||||
import io.grpc.inprocess.InProcessChannelBuilder;
|
||||
import io.grpc.stub.MetadataUtils;
|
||||
|
||||
class RemoteDeprecationFilterTest {
|
||||
|
||||
@Test
|
||||
void testEmptyMap() throws IOException, ServletException {
|
||||
// We're happy as long as there's no exception
|
||||
final DynamicConfigurationManager dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
|
||||
final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class);
|
||||
final DynamicRemoteDeprecationConfiguration emptyConfiguration = new DynamicRemoteDeprecationConfiguration();
|
||||
@Test
|
||||
void testEmptyMap() throws IOException, ServletException {
|
||||
// We're happy as long as there's no exception
|
||||
final DynamicConfigurationManager dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
|
||||
final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class);
|
||||
final DynamicRemoteDeprecationConfiguration emptyConfiguration = new DynamicRemoteDeprecationConfiguration();
|
||||
|
||||
when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration);
|
||||
when(dynamicConfiguration.getRemoteDeprecationConfiguration()).thenReturn(emptyConfiguration);
|
||||
when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration);
|
||||
when(dynamicConfiguration.getRemoteDeprecationConfiguration()).thenReturn(emptyConfiguration);
|
||||
|
||||
final RemoteDeprecationFilter filter = new RemoteDeprecationFilter(dynamicConfigurationManager);
|
||||
final RemoteDeprecationFilter filter = new RemoteDeprecationFilter(dynamicConfigurationManager);
|
||||
|
||||
final HttpServletRequest servletRequest = mock(HttpServletRequest.class);
|
||||
final HttpServletResponse servletResponse = mock(HttpServletResponse.class);
|
||||
final FilterChain filterChain = mock(FilterChain.class);
|
||||
final HttpServletRequest servletRequest = mock(HttpServletRequest.class);
|
||||
final HttpServletResponse servletResponse = mock(HttpServletResponse.class);
|
||||
final FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
when(servletRequest.getHeader("UserAgent")).thenReturn("Signal-Android/4.68.3");
|
||||
when(servletRequest.getHeader("UserAgent")).thenReturn("Signal-Android/4.68.3");
|
||||
|
||||
filter.doFilter(servletRequest, servletResponse, filterChain);
|
||||
filter.doFilter(servletRequest, servletResponse, filterChain);
|
||||
|
||||
verify(filterChain).doFilter(servletRequest, servletResponse);
|
||||
verify(servletResponse, never()).sendError(anyInt());
|
||||
verify(filterChain).doFilter(servletRequest, servletResponse);
|
||||
verify(servletResponse, never()).sendError(anyInt());
|
||||
}
|
||||
|
||||
private RemoteDeprecationFilter filterConfiguredForTest() {
|
||||
final EnumMap<ClientPlatform, Semver> minimumVersionsByPlatform = new EnumMap<>(ClientPlatform.class);
|
||||
minimumVersionsByPlatform.put(ClientPlatform.ANDROID, new Semver("1.0.0"));
|
||||
minimumVersionsByPlatform.put(ClientPlatform.IOS, new Semver("1.0.0"));
|
||||
minimumVersionsByPlatform.put(ClientPlatform.DESKTOP, new Semver("1.0.0"));
|
||||
|
||||
final EnumMap<ClientPlatform, Semver> versionsPendingDeprecationByPlatform = new EnumMap<>(ClientPlatform.class);
|
||||
minimumVersionsByPlatform.put(ClientPlatform.ANDROID, new Semver("1.1.0"));
|
||||
minimumVersionsByPlatform.put(ClientPlatform.IOS, new Semver("1.1.0"));
|
||||
minimumVersionsByPlatform.put(ClientPlatform.DESKTOP, new Semver("1.1.0"));
|
||||
|
||||
final EnumMap<ClientPlatform, Set<Semver>> blockedVersionsByPlatform = new EnumMap<>(ClientPlatform.class);
|
||||
blockedVersionsByPlatform.put(ClientPlatform.DESKTOP, Set.of(new Semver("8.0.0-beta.2")));
|
||||
|
||||
final EnumMap<ClientPlatform, Set<Semver>> versionsPendingBlockByPlatform = new EnumMap<>(ClientPlatform.class);
|
||||
versionsPendingBlockByPlatform.put(ClientPlatform.DESKTOP, Set.of(new Semver("8.0.0-beta.3")));
|
||||
|
||||
final DynamicRemoteDeprecationConfiguration remoteDeprecationConfiguration = new DynamicRemoteDeprecationConfiguration();
|
||||
remoteDeprecationConfiguration.setMinimumVersions(minimumVersionsByPlatform);
|
||||
remoteDeprecationConfiguration.setVersionsPendingDeprecation(versionsPendingDeprecationByPlatform);
|
||||
remoteDeprecationConfiguration.setBlockedVersions(blockedVersionsByPlatform);
|
||||
remoteDeprecationConfiguration.setVersionsPendingBlock(versionsPendingBlockByPlatform);
|
||||
remoteDeprecationConfiguration.setUnrecognizedUserAgentAllowed(true);
|
||||
|
||||
final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class);
|
||||
final DynamicConfigurationManager dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
|
||||
|
||||
when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration);
|
||||
when(dynamicConfiguration.getRemoteDeprecationConfiguration()).thenReturn(remoteDeprecationConfiguration);
|
||||
|
||||
return new RemoteDeprecationFilter(dynamicConfigurationManager);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void testFilter(final String userAgent, final boolean expectDeprecation) throws IOException, ServletException {
|
||||
final HttpServletRequest servletRequest = mock(HttpServletRequest.class);
|
||||
final HttpServletResponse servletResponse = mock(HttpServletResponse.class);
|
||||
final FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
when(servletRequest.getHeader(HttpHeaders.USER_AGENT)).thenReturn(userAgent);
|
||||
|
||||
final RemoteDeprecationFilter filter = filterConfiguredForTest();
|
||||
filter.doFilter(servletRequest, servletResponse, filterChain);
|
||||
|
||||
if (expectDeprecation) {
|
||||
verify(filterChain, never()).doFilter(any(), any());
|
||||
verify(servletResponse).sendError(499);
|
||||
} else {
|
||||
verify(filterChain).doFilter(servletRequest, servletResponse);
|
||||
verify(servletResponse, never()).sendError(anyInt());
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource(delimiter = '|', value =
|
||||
{"Unrecognized UA | false",
|
||||
"Signal-Android/4.68.3 | false",
|
||||
"Signal-iOS/3.9.0 | false",
|
||||
"Signal-Desktop/1.2.3 | false",
|
||||
"Signal-Android/0.68.3 | true",
|
||||
"Signal-iOS/0.9.0 | true",
|
||||
"Signal-Desktop/0.2.3 | true",
|
||||
"Signal-Desktop/8.0.0-beta.2 | true",
|
||||
"Signal-Desktop/8.0.0-beta.1 | false",
|
||||
"Signal-iOS/8.0.0-beta.2 | false"})
|
||||
void testFilter(final String userAgent, final boolean expectDeprecation) throws IOException, ServletException {
|
||||
final EnumMap<ClientPlatform, Semver> minimumVersionsByPlatform = new EnumMap<>(ClientPlatform.class);
|
||||
minimumVersionsByPlatform.put(ClientPlatform.ANDROID, new Semver("1.0.0"));
|
||||
minimumVersionsByPlatform.put(ClientPlatform.IOS, new Semver("1.0.0"));
|
||||
minimumVersionsByPlatform.put(ClientPlatform.DESKTOP, new Semver("1.0.0"));
|
||||
@ParameterizedTest
|
||||
@MethodSource(value="testFilter")
|
||||
void testGrpcFilter(final String userAgent, final boolean expectDeprecation) throws Exception {
|
||||
final Server testServer = InProcessServerBuilder.forName("RemoteDeprecationFilterTest")
|
||||
.directExecutor()
|
||||
.addService(new EchoServiceImpl())
|
||||
.intercept(filterConfiguredForTest())
|
||||
.intercept(new UserAgentInterceptor())
|
||||
.build()
|
||||
.start();
|
||||
final ManagedChannel channel = InProcessChannelBuilder.forName("RemoteDeprecationFilterTest")
|
||||
.directExecutor()
|
||||
.userAgent(userAgent)
|
||||
.build();
|
||||
|
||||
final EnumMap<ClientPlatform, Semver> versionsPendingDeprecationByPlatform = new EnumMap<>(ClientPlatform.class);
|
||||
minimumVersionsByPlatform.put(ClientPlatform.ANDROID, new Semver("1.1.0"));
|
||||
minimumVersionsByPlatform.put(ClientPlatform.IOS, new Semver("1.1.0"));
|
||||
minimumVersionsByPlatform.put(ClientPlatform.DESKTOP, new Semver("1.1.0"));
|
||||
|
||||
final EnumMap<ClientPlatform, Set<Semver>> blockedVersionsByPlatform = new EnumMap<>(ClientPlatform.class);
|
||||
blockedVersionsByPlatform.put(ClientPlatform.DESKTOP, Set.of(new Semver("8.0.0-beta.2")));
|
||||
|
||||
final EnumMap<ClientPlatform, Set<Semver>> versionsPendingBlockByPlatform = new EnumMap<>(ClientPlatform.class);
|
||||
versionsPendingBlockByPlatform.put(ClientPlatform.DESKTOP, Set.of(new Semver("8.0.0-beta.3")));
|
||||
|
||||
final DynamicRemoteDeprecationConfiguration remoteDeprecationConfiguration = new DynamicRemoteDeprecationConfiguration();
|
||||
remoteDeprecationConfiguration.setMinimumVersions(minimumVersionsByPlatform);
|
||||
remoteDeprecationConfiguration.setVersionsPendingDeprecation(versionsPendingDeprecationByPlatform);
|
||||
remoteDeprecationConfiguration.setBlockedVersions(blockedVersionsByPlatform);
|
||||
remoteDeprecationConfiguration.setVersionsPendingBlock(versionsPendingBlockByPlatform);
|
||||
remoteDeprecationConfiguration.setUnrecognizedUserAgentAllowed(true);
|
||||
|
||||
final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class);
|
||||
final DynamicConfigurationManager dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
|
||||
|
||||
when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration);
|
||||
when(dynamicConfiguration.getRemoteDeprecationConfiguration()).thenReturn(remoteDeprecationConfiguration);
|
||||
|
||||
final HttpServletRequest servletRequest = mock(HttpServletRequest.class);
|
||||
final HttpServletResponse servletResponse = mock(HttpServletResponse.class);
|
||||
final FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
when(servletRequest.getHeader(HttpHeaders.USER_AGENT)).thenReturn(userAgent);
|
||||
|
||||
final RemoteDeprecationFilter filter = new RemoteDeprecationFilter(dynamicConfigurationManager);
|
||||
filter.doFilter(servletRequest, servletResponse, filterChain);
|
||||
try {
|
||||
final EchoServiceGrpc.EchoServiceBlockingStub client = EchoServiceGrpc.newBlockingStub(channel);
|
||||
|
||||
final EchoRequest req = EchoRequest.newBuilder().setPayload(ByteString.copyFromUtf8("cluck cluck, i'm a parrot")).build();
|
||||
if (expectDeprecation) {
|
||||
verify(filterChain, never()).doFilter(any(), any());
|
||||
verify(servletResponse).sendError(499);
|
||||
final StatusRuntimeException e = assertThrows(
|
||||
StatusRuntimeException.class,
|
||||
() -> client.echo(req));
|
||||
assertEquals(StatusConstants.UPGRADE_NEEDED_STATUS.toString(), e.getStatus().toString());
|
||||
} else {
|
||||
verify(filterChain).doFilter(servletRequest, servletResponse);
|
||||
verify(servletResponse, never()).sendError(anyInt());
|
||||
assertEquals("cluck cluck, i'm a parrot", client.echo(req).getPayload().toStringUtf8());
|
||||
}
|
||||
} finally {
|
||||
testServer.shutdownNow();
|
||||
testServer.awaitTermination();
|
||||
}
|
||||
}
|
||||
|
||||
private static Stream<Arguments> testFilter() {
|
||||
return Stream.of(
|
||||
Arguments.of("Unrecognized UA", false),
|
||||
Arguments.of("Signal-Android/4.68.3", false),
|
||||
Arguments.of("Signal-iOS/3.9.0", false),
|
||||
Arguments.of("Signal-Desktop/1.2.3", false),
|
||||
Arguments.of("Signal-Android/0.68.3", true),
|
||||
Arguments.of("Signal-iOS/0.9.0", true),
|
||||
Arguments.of("Signal-Desktop/0.2.3", true),
|
||||
Arguments.of("Signal-Desktop/8.0.0-beta.2", true),
|
||||
Arguments.of("Signal-Desktop/8.0.0-beta.1", false),
|
||||
Arguments.of("Signal-iOS/8.0.0-beta.2", false));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.grpc;
|
||||
|
||||
import io.grpc.stub.StreamObserver;
|
||||
import org.signal.chat.rpc.EchoServiceGrpc;
|
||||
import org.signal.chat.rpc.EchoRequest;
|
||||
import org.signal.chat.rpc.EchoResponse;
|
||||
|
||||
public class EchoServiceImpl extends EchoServiceGrpc.EchoServiceImplBase {
|
||||
@Override
|
||||
public void echo(EchoRequest req, StreamObserver<EchoResponse> responseObserver) {
|
||||
responseObserver.onNext(EchoResponse.newBuilder().setPayload(req.getPayload()).build());
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.grpc;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.vdurmont.semver4j.Semver;
|
||||
import io.grpc.ManagedChannel;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.Server;
|
||||
import io.grpc.inprocess.InProcessChannelBuilder;
|
||||
import io.grpc.inprocess.InProcessServerBuilder;
|
||||
import io.grpc.stub.MetadataUtils;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.signal.chat.rpc.EchoRequest;
|
||||
import org.signal.chat.rpc.EchoResponse;
|
||||
import org.signal.chat.rpc.EchoServiceGrpc;
|
||||
import org.whispersystems.textsecuregcm.grpc.EchoServiceImpl;
|
||||
import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
|
||||
import org.whispersystems.textsecuregcm.util.ua.UserAgent;
|
||||
import org.whispersystems.textsecuregcm.util.ua.UserAgentUtil;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class UserAgentInterceptorTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void testInterceptor(final String header, final ClientPlatform platform, final String version) throws Exception {
|
||||
|
||||
final AtomicReference<UserAgent> observedUserAgent = new AtomicReference<>(null);
|
||||
final EchoServiceImpl serviceImpl = new EchoServiceImpl() {
|
||||
@Override
|
||||
public void echo(EchoRequest req, StreamObserver<EchoResponse> responseObserver) {
|
||||
observedUserAgent.set(UserAgentUtil.userAgentFromGrpcContext());
|
||||
super.echo(req, responseObserver);
|
||||
}
|
||||
};
|
||||
|
||||
final Server testServer = InProcessServerBuilder.forName("RemoteDeprecationFilterTest")
|
||||
.directExecutor()
|
||||
.addService(serviceImpl)
|
||||
.intercept(new UserAgentInterceptor())
|
||||
.build()
|
||||
.start();
|
||||
|
||||
try {
|
||||
final ManagedChannel channel = InProcessChannelBuilder.forName("RemoteDeprecationFilterTest")
|
||||
.directExecutor()
|
||||
.userAgent(header)
|
||||
.build();
|
||||
|
||||
final EchoServiceGrpc.EchoServiceBlockingStub client = EchoServiceGrpc.newBlockingStub(channel);
|
||||
|
||||
final EchoRequest req = EchoRequest.newBuilder().setPayload(ByteString.copyFromUtf8("cluck cluck, i'm a parrot")).build();
|
||||
assertEquals("cluck cluck, i'm a parrot", client.echo(req).getPayload().toStringUtf8());
|
||||
if (platform == null) {
|
||||
assertNull(observedUserAgent.get());
|
||||
} else {
|
||||
assertEquals(platform, observedUserAgent.get().getPlatform());
|
||||
assertEquals(new Semver(version), observedUserAgent.get().getVersion());
|
||||
// can't assert on the additional specifiers because they include internal details of the grpc in-process channel itself
|
||||
}
|
||||
} finally {
|
||||
testServer.shutdownNow();
|
||||
testServer.awaitTermination();
|
||||
}
|
||||
}
|
||||
|
||||
private static Stream<Arguments> testInterceptor() {
|
||||
return Stream.of(
|
||||
Arguments.of(null, null, null),
|
||||
Arguments.of("", null, null),
|
||||
Arguments.of("Unrecognized UA", null, null),
|
||||
Arguments.of("Signal-Android/4.68.3", ClientPlatform.ANDROID, "4.68.3"),
|
||||
Arguments.of("Signal-iOS/3.9.0", ClientPlatform.IOS, "3.9.0"),
|
||||
Arguments.of("Signal-Desktop/1.2.3", ClientPlatform.DESKTOP, "1.2.3"),
|
||||
Arguments.of("Signal-Desktop/8.0.0-beta.2", ClientPlatform.DESKTOP, "8.0.0-beta.2"),
|
||||
Arguments.of("Signal-iOS/8.0.0-beta.2", ClientPlatform.IOS, "8.0.0-beta.2"));
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,10 @@ class UserAgentUtilTest {
|
||||
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"))),
|
||||
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",
|
||||
new UserAgent(ClientPlatform.ANDROID, new Semver("7.11.23-nightly-1982-06-28-07-07-07"), "Android/42 tonic/0.31")));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user