Add ExternalRequestFilter
This commit is contained in:
parent
63c8b275d1
commit
5b97bc04e0
|
@ -472,3 +472,11 @@ callingTurnManualTable:
|
|||
noiseTunnel:
|
||||
port: 8443
|
||||
recognizedProxySecret: secret://noiseTunnel.recognizedProxySecret
|
||||
|
||||
externalRequestFilter:
|
||||
grpcMethods:
|
||||
- com.example.grpc.ExampleService/exampleMethod
|
||||
paths:
|
||||
- /example
|
||||
permittedInternalRanges:
|
||||
- 127.0.0.0/8
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.whispersystems.textsecuregcm.configuration.DirectoryV2Configuration;
|
|||
import org.whispersystems.textsecuregcm.configuration.DogstatsdConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.DynamoDbClientConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.DynamoDbTables;
|
||||
import org.whispersystems.textsecuregcm.configuration.ExternalRequestFilterConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.FcmConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.GcpAttachmentsConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.GenericZkConfig;
|
||||
|
@ -339,6 +340,11 @@ public class WhisperServerConfiguration extends Configuration {
|
|||
@JsonProperty
|
||||
private NoiseWebSocketTunnelConfiguration noiseTunnel;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private ExternalRequestFilterConfiguration externalRequestFilter;
|
||||
|
||||
public TlsKeyStoreConfiguration getTlsKeyStoreConfiguration() {
|
||||
return tlsKeyStore;
|
||||
}
|
||||
|
@ -565,4 +571,8 @@ public class WhisperServerConfiguration extends Configuration {
|
|||
public NoiseWebSocketTunnelConfiguration getNoiseWebSocketTunnelConfiguration() {
|
||||
return noiseTunnel;
|
||||
}
|
||||
|
||||
public ExternalRequestFilterConfiguration getExternalRequestFilterConfiguration() {
|
||||
return externalRequestFilter;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,6 +123,7 @@ import org.whispersystems.textsecuregcm.currency.CoinMarketCapClient;
|
|||
import org.whispersystems.textsecuregcm.currency.CurrencyConversionManager;
|
||||
import org.whispersystems.textsecuregcm.currency.FixerClient;
|
||||
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
|
||||
import org.whispersystems.textsecuregcm.filters.ExternalRequestFilter;
|
||||
import org.whispersystems.textsecuregcm.filters.RemoteAddressFilter;
|
||||
import org.whispersystems.textsecuregcm.filters.RemoteDeprecationFilter;
|
||||
import org.whispersystems.textsecuregcm.filters.RequestStatisticsFilter;
|
||||
|
@ -778,6 +779,9 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
|||
// depends on the user-agent context so it has to come first here!
|
||||
// http://grpc.github.io/grpc-java/javadoc/io/grpc/ServerBuilder.html#intercept-io.grpc.ServerInterceptor-
|
||||
serverBuilder
|
||||
.intercept(
|
||||
new ExternalRequestFilter(config.getExternalRequestFilterConfiguration().permittedInternalRanges(),
|
||||
config.getExternalRequestFilterConfiguration().grpcMethods()))
|
||||
// TODO: specialize metrics with user-agent platform
|
||||
.intercept(metricCollectingServerInterceptor)
|
||||
.intercept(errorMappingInterceptor)
|
||||
|
@ -827,6 +831,14 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
|||
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*");
|
||||
}
|
||||
|
||||
if (!config.getExternalRequestFilterConfiguration().paths().isEmpty()) {
|
||||
environment.servlets().addFilter(ExternalRequestFilter.class.getSimpleName(),
|
||||
new ExternalRequestFilter(config.getExternalRequestFilterConfiguration().permittedInternalRanges(),
|
||||
config.getExternalRequestFilterConfiguration().grpcMethods()))
|
||||
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true,
|
||||
config.getExternalRequestFilterConfiguration().paths().toArray(new String[]{}));
|
||||
}
|
||||
|
||||
final AuthFilter<BasicCredentials, AuthenticatedAccount> accountAuthFilter =
|
||||
new BasicCredentialAuthFilter.Builder<AuthenticatedAccount>()
|
||||
.setAuthenticator(accountAuthenticator)
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import java.util.Set;
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import org.whispersystems.textsecuregcm.util.InetAddressRange;
|
||||
|
||||
public record ExternalRequestFilterConfiguration(@Valid @NotNull Set<@NotNull String> paths,
|
||||
@Valid @NotNull Set<@NotNull InetAddressRange> permittedInternalRanges,
|
||||
@Valid @NotNull Set<@NotNull String> grpcMethods) {
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.filters;
|
||||
|
||||
import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
|
||||
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.MethodDescriptor;
|
||||
import io.grpc.ServerCall;
|
||||
import io.grpc.ServerCallHandler;
|
||||
import io.grpc.ServerInterceptor;
|
||||
import io.grpc.Status;
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Set;
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.grpc.RequestAttributesUtil;
|
||||
import org.whispersystems.textsecuregcm.util.InetAddressRange;
|
||||
|
||||
public class ExternalRequestFilter implements Filter, ServerInterceptor {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ExternalRequestFilter.class);
|
||||
|
||||
private static final String REQUESTS_COUNTER_NAME = name(ExternalRequestFilter.class, "requests");
|
||||
private static final String PROTOCOL_TAG_NAME = "protocol";
|
||||
private static final String BLOCKED_TAG_NAME = "blocked";
|
||||
|
||||
private final Set<InetAddressRange> permittedInternalAddressRanges;
|
||||
private final Set<String> filteredGrpcMethodNames;
|
||||
|
||||
public ExternalRequestFilter(final Set<InetAddressRange> permittedInternalAddressRanges,
|
||||
final Set<String> filteredGrpcMethodNames) {
|
||||
this.permittedInternalAddressRanges = permittedInternalAddressRanges;
|
||||
this.filteredGrpcMethodNames = filteredGrpcMethodNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(final ServerCall<ReqT, RespT> call,
|
||||
final Metadata headers, final ServerCallHandler<ReqT, RespT> next) {
|
||||
|
||||
final MethodDescriptor<ReqT, RespT> methodDescriptor = call.getMethodDescriptor();
|
||||
final boolean shouldFilterMethod = filteredGrpcMethodNames.contains(methodDescriptor.getFullMethodName());
|
||||
|
||||
final InetAddress remoteAddress = RequestAttributesUtil.getRemoteAddress();
|
||||
final boolean blocked = shouldFilterMethod && shouldBlock(remoteAddress);
|
||||
|
||||
Metrics.counter(REQUESTS_COUNTER_NAME,
|
||||
PROTOCOL_TAG_NAME, "grpc",
|
||||
BLOCKED_TAG_NAME, String.valueOf(blocked))
|
||||
.increment();
|
||||
|
||||
if (blocked) {
|
||||
call.close(Status.NOT_FOUND, new Metadata());
|
||||
return new ServerCall.Listener<>() {};
|
||||
}
|
||||
|
||||
return next.startCall(call, headers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
|
||||
final InetAddress remoteInetAddress = InetAddress.getByName(
|
||||
(String) request.getAttribute(RemoteAddressFilter.REMOTE_ADDRESS_ATTRIBUTE_NAME));
|
||||
final boolean restricted = shouldBlock(remoteInetAddress);
|
||||
|
||||
Metrics.counter(REQUESTS_COUNTER_NAME,
|
||||
PROTOCOL_TAG_NAME, "http",
|
||||
BLOCKED_TAG_NAME, String.valueOf(restricted))
|
||||
.increment();
|
||||
|
||||
if (restricted) {
|
||||
if (response instanceof HttpServletResponse hsr) {
|
||||
hsr.setStatus(404);
|
||||
} else {
|
||||
logger.warn("response was an unexpected type: {}", response.getClass());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
public boolean shouldBlock(InetAddress remoteAddress) {
|
||||
return permittedInternalAddressRanges.stream()
|
||||
.noneMatch(range -> range.contains(remoteAddress));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Copyright 2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.util;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.net.InetAddresses;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* An InetAddressRange represents a contiguous range of IPv4 or IPv6 addresses.
|
||||
*/
|
||||
public class InetAddressRange {
|
||||
|
||||
private final InetAddress networkAddress;
|
||||
|
||||
private final byte[] networkAddressBytes;
|
||||
private final byte[] prefixMask;
|
||||
|
||||
public InetAddressRange(final String cidrBlock) {
|
||||
final String[] components = cidrBlock.split("/");
|
||||
|
||||
if (components.length != 2) {
|
||||
throw new IllegalArgumentException("Unexpected CIDR block notation: " + cidrBlock);
|
||||
}
|
||||
|
||||
final int prefixLength;
|
||||
|
||||
try {
|
||||
networkAddress = InetAddresses.forString(components[0]);
|
||||
prefixLength = Integer.parseInt(components[1]);
|
||||
|
||||
if (prefixLength > networkAddress.getAddress().length * 8) {
|
||||
throw new IllegalArgumentException("Prefix length cannot exceed length of address");
|
||||
}
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Bad prefix length: " + components[1]);
|
||||
}
|
||||
|
||||
networkAddressBytes = networkAddress.getAddress();
|
||||
prefixMask = generatePrefixMask(networkAddressBytes.length, prefixLength);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static byte[] generatePrefixMask(final int addressLengthBytes, final int prefixLengthBits) {
|
||||
final byte[] prefixMask = new byte[addressLengthBytes];
|
||||
|
||||
for (int i = 0; i < addressLengthBytes; i++) {
|
||||
final int bitsAvailable = Math.min(8, Math.max(0, prefixLengthBits - (i * 8)));
|
||||
prefixMask[i] = (byte) (0xff << (8 - bitsAvailable));
|
||||
}
|
||||
|
||||
return prefixMask;
|
||||
}
|
||||
|
||||
public boolean contains(final String name) {
|
||||
// InetAddresses.forString() throws "IllegalArgumentException" for anything that is not an IP address
|
||||
return contains(InetAddresses.forString(name));
|
||||
}
|
||||
|
||||
public boolean contains(final InetAddress inetAddress) {
|
||||
if (!networkAddress.getClass().equals(inetAddress.getClass())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final byte[] addressBytes = inetAddress.getAddress();
|
||||
|
||||
for (int i = 0; i < addressBytes.length; i++) {
|
||||
if (((addressBytes[i] ^ networkAddressBytes[i]) & prefixMask[i]) != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final InetAddressRange that = (InetAddressRange) o;
|
||||
|
||||
if (!networkAddress.equals(that.networkAddress)) {
|
||||
return false;
|
||||
}
|
||||
return Arrays.equals(prefixMask, that.prefixMask);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = networkAddress.hashCode();
|
||||
result = 31 * result + Arrays.hashCode(prefixMask);
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,245 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.filters;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import io.dropwizard.core.Application;
|
||||
import io.dropwizard.core.Configuration;
|
||||
import io.dropwizard.core.setup.Environment;
|
||||
import io.dropwizard.testing.DropwizardTestSupport;
|
||||
import io.dropwizard.testing.junit5.DropwizardAppExtension;
|
||||
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
|
||||
import io.grpc.ManagedChannel;
|
||||
import io.grpc.Server;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.inprocess.InProcessChannelBuilder;
|
||||
import io.grpc.inprocess.InProcessServerBuilder;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.client.Client;
|
||||
import javax.ws.rs.core.Response;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.signal.chat.rpc.EchoRequest;
|
||||
import org.signal.chat.rpc.EchoServiceGrpc;
|
||||
import org.whispersystems.textsecuregcm.grpc.EchoServiceImpl;
|
||||
import org.whispersystems.textsecuregcm.grpc.GrpcTestUtils;
|
||||
import org.whispersystems.textsecuregcm.grpc.MockRequestAttributesInterceptor;
|
||||
import org.whispersystems.textsecuregcm.util.InetAddressRange;
|
||||
|
||||
@ExtendWith(DropwizardExtensionsSupport.class)
|
||||
class ExternalRequestFilterTest {
|
||||
|
||||
@Nested
|
||||
class Allowed extends TestCase {
|
||||
|
||||
@Override
|
||||
DropwizardTestSupport<TestConfiguration> getTestSupport() {
|
||||
return new DropwizardTestSupport<>(TestApplication.class, getConfiguration());
|
||||
}
|
||||
|
||||
@Override
|
||||
int getExpectedHttpStatus() {
|
||||
return 200;
|
||||
}
|
||||
|
||||
@Override
|
||||
Status getExpectedGrpcStatus() {
|
||||
return Status.OK;
|
||||
}
|
||||
|
||||
@Override
|
||||
TestConfiguration getConfiguration() {
|
||||
return new TestConfiguration() {
|
||||
@Override
|
||||
public Set<InetAddressRange> getPermittedRanges() {
|
||||
return Set.of(new InetAddressRange("127.0.0.0/8"));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class Blocked extends TestCase {
|
||||
|
||||
@Override
|
||||
DropwizardTestSupport<TestConfiguration> getTestSupport() {
|
||||
return new DropwizardTestSupport<>(TestApplication.class, getConfiguration());
|
||||
}
|
||||
|
||||
@Override
|
||||
int getExpectedHttpStatus() {
|
||||
return 404;
|
||||
}
|
||||
|
||||
@Override
|
||||
Status getExpectedGrpcStatus() {
|
||||
return Status.NOT_FOUND;
|
||||
}
|
||||
|
||||
@Override
|
||||
TestConfiguration getConfiguration() {
|
||||
return new TestConfiguration() {
|
||||
@Override
|
||||
public Set<InetAddressRange> getPermittedRanges() {
|
||||
return Set.of(new InetAddressRange("10.0.0.0/8"));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
abstract static class TestCase {
|
||||
|
||||
abstract DropwizardTestSupport<TestConfiguration> getTestSupport();
|
||||
|
||||
abstract TestConfiguration getConfiguration();
|
||||
|
||||
abstract int getExpectedHttpStatus();
|
||||
|
||||
abstract Status getExpectedGrpcStatus();
|
||||
|
||||
private Server testServer;
|
||||
private ManagedChannel channel;
|
||||
|
||||
@Nested
|
||||
class Http {
|
||||
|
||||
private final DropwizardAppExtension<TestConfiguration> DROPWIZARD_APP_EXTENSION =
|
||||
new DropwizardAppExtension<>(getTestSupport());
|
||||
|
||||
@Test
|
||||
void testRestricted() {
|
||||
Client client = DROPWIZARD_APP_EXTENSION.client();
|
||||
|
||||
try (Response response = client.target(
|
||||
"http://localhost:%s/test/restricted".formatted(DROPWIZARD_APP_EXTENSION.getLocalPort()))
|
||||
.request()
|
||||
.get()) {
|
||||
|
||||
assertEquals(getExpectedHttpStatus(), response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOpen() {
|
||||
|
||||
Client client = DROPWIZARD_APP_EXTENSION.client();
|
||||
|
||||
try (Response response = client.target(
|
||||
"http://localhost:%s/test/open".formatted(DROPWIZARD_APP_EXTENSION.getLocalPort()))
|
||||
.request()
|
||||
.get()) {
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class Grpc {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
final MockRequestAttributesInterceptor mockRequestAttributesInterceptor = new MockRequestAttributesInterceptor();
|
||||
mockRequestAttributesInterceptor.setRemoteAddress(InetAddress.getByName("127.0.0.1"));
|
||||
|
||||
testServer = InProcessServerBuilder.forName("ExternalRequestFilterTest")
|
||||
.directExecutor()
|
||||
.addService(new EchoServiceImpl())
|
||||
.intercept(new ExternalRequestFilter(getConfiguration().getPermittedRanges(),
|
||||
Set.of("org.signal.chat.rpc.EchoService/echo2")))
|
||||
.intercept(mockRequestAttributesInterceptor)
|
||||
.build()
|
||||
.start();
|
||||
|
||||
channel = InProcessChannelBuilder.forName("ExternalRequestFilterTest")
|
||||
.directExecutor()
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBlocked() {
|
||||
final EchoServiceGrpc.EchoServiceBlockingStub client = EchoServiceGrpc.newBlockingStub(channel);
|
||||
|
||||
final String text = "0123456789";
|
||||
final EchoRequest req = EchoRequest.newBuilder().setPayload(ByteString.copyFromUtf8(text)).build();
|
||||
|
||||
final Status expectedGrpcStatus = getExpectedGrpcStatus();
|
||||
if (Status.Code.OK == expectedGrpcStatus.getCode()) {
|
||||
assertEquals(text, client.echo2(req).getPayload().toStringUtf8());
|
||||
} else {
|
||||
GrpcTestUtils.assertStatusException(expectedGrpcStatus, () -> client.echo2(req));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOpen() {
|
||||
final EchoServiceGrpc.EchoServiceBlockingStub client = EchoServiceGrpc.newBlockingStub(channel);
|
||||
|
||||
final String text = "0123456789";
|
||||
final EchoRequest req = EchoRequest.newBuilder().setPayload(ByteString.copyFromUtf8(text)).build();
|
||||
|
||||
assertEquals(text, client.echo(req).getPayload().toStringUtf8());
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws Exception {
|
||||
|
||||
testServer.shutdownNow()
|
||||
.awaitTermination(10, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@Path("/test")
|
||||
public static class Controller {
|
||||
|
||||
@GET
|
||||
@Path("/restricted")
|
||||
public Response restricted() {
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/open")
|
||||
public Response open() {
|
||||
return Response.ok().build();
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestApplication extends Application<TestConfiguration> {
|
||||
|
||||
@Override
|
||||
public void run(final TestConfiguration configuration, final Environment environment) throws Exception {
|
||||
|
||||
environment.jersey().register(new Controller());
|
||||
environment.servlets()
|
||||
.addFilter("ExternalRequestFilter",
|
||||
new ExternalRequestFilter(configuration.getPermittedRanges(),
|
||||
Collections.emptySet()))
|
||||
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/test/restricted");
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class TestConfiguration extends Configuration {
|
||||
|
||||
public abstract Set<InetAddressRange> getPermittedRanges();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -6,9 +6,9 @@
|
|||
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;
|
||||
import org.signal.chat.rpc.EchoServiceGrpc;
|
||||
|
||||
public class EchoServiceImpl extends EchoServiceGrpc.EchoServiceImplBase {
|
||||
@Override
|
||||
|
@ -16,4 +16,10 @@ public class EchoServiceImpl extends EchoServiceGrpc.EchoServiceImplBase {
|
|||
responseObserver.onNext(EchoResponse.newBuilder().setPayload(req.getPayload()).build());
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void echo2(EchoRequest req, StreamObserver<EchoResponse> responseObserver) {
|
||||
responseObserver.onNext(EchoResponse.newBuilder().setPayload(req.getPayload()).build());
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright 2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
class InetAddressRangeTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"192.168.0.1", "192.168.0.0/33", "$%#*(@!&^$/24", "192.168.0.0/fish", "signal.org"})
|
||||
void testBogusCidrBlock(final String cidrBlock) {
|
||||
assertThrows(IllegalArgumentException.class, () -> new InetAddressRange(cidrBlock));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("argumentsForTestGeneratePrefixMask")
|
||||
void testGeneratePrefixMask(final int addressLengthBytes, final int prefixLengthBits, final byte[] expectedMask) {
|
||||
assertArrayEquals(expectedMask, InetAddressRange.generatePrefixMask(addressLengthBytes, prefixLengthBits));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> argumentsForTestGeneratePrefixMask() {
|
||||
return Stream.of(
|
||||
Arguments.of(4, 32, new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}),
|
||||
Arguments.of(4, 24, new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, 0x00}),
|
||||
Arguments.of(4, 22, new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xfc, 0x00}),
|
||||
Arguments.of(4, 0, new byte[]{0x00, 0x00, 0x00, 0x00})
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("argumentsForTestContains")
|
||||
void testContains(final String cidrBlock, final String address, final boolean expectContains) {
|
||||
assertEquals(expectContains, new InetAddressRange(cidrBlock).contains(address));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> argumentsForTestContains() {
|
||||
return Stream.of(
|
||||
Arguments.of("192.168.0.0/24", "192.168.0.1", true),
|
||||
Arguments.of("192.168.0.0/24", "192.168.1.0", false),
|
||||
Arguments.of("192.168.0.1/32", "192.168.0.1", true),
|
||||
Arguments.of("192.168.0.1/32", "192.168.0.0", false),
|
||||
Arguments.of("2001:db8::/48", "2001:db8:0:0:0:0:0:0", true),
|
||||
Arguments.of("2001:db8::/48", "2001:db8:0:ffff:ffff:ffff:ffff:ffff", true),
|
||||
Arguments.of("2001:db8::/48", "2001:db6:0:ffff:ffff:ffff:ffff:ffff", false)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testContainsMismatchedAddressType() {
|
||||
assertFalse(new InetAddressRange("192.168.0.0/24").contains("2001:db8:0:0:0:0:0:0"));
|
||||
assertFalse(new InetAddressRange("2001:db8::/48").contains("192.168.0.1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeserialize() throws JsonProcessingException {
|
||||
final TypeReference<Map<String, InetAddressRange>> typeReference = new TypeReference<>() {};
|
||||
|
||||
assertEquals(Map.of("range", new InetAddressRange("192.168.0.0/24")),
|
||||
new ObjectMapper().readValue("{\"range\":\"192.168.0.0/24\"}", typeReference));
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ package org.signal.chat.rpc;
|
|||
// A simple service for testing gRPC interceptors
|
||||
service EchoService {
|
||||
rpc echo (EchoRequest) returns (EchoResponse) {}
|
||||
rpc echo2 (EchoRequest) returns (EchoResponse) {}
|
||||
}
|
||||
|
||||
message EchoRequest {
|
||||
|
|
Loading…
Reference in New Issue