From 68814813c3455d57dc6910781b3010c550eb3330 Mon Sep 17 00:00:00 2001 From: Chris Eager Date: Tue, 1 Oct 2024 11:42:57 -0500 Subject: [PATCH] Add timestamp header to all responses --- .../textsecuregcm/WhisperServerService.java | 8 +++-- .../filters/TimestampResponseFilter.java | 27 +++++++++++--- .../WhisperServerServiceTest.java | 35 +++++++++++++++++-- .../filters/TimestampResponseFilterTest.java | 34 ++++++++++++------ 4 files changed, 84 insertions(+), 20 deletions(-) diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index c6b8a9090..75391540d 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -113,7 +113,6 @@ import org.whispersystems.textsecuregcm.controllers.ArtController; import org.whispersystems.textsecuregcm.controllers.AttachmentControllerV2; import org.whispersystems.textsecuregcm.controllers.AttachmentControllerV3; import org.whispersystems.textsecuregcm.controllers.AttachmentControllerV4; -import org.whispersystems.textsecuregcm.controllers.OneTimeDonationController; import org.whispersystems.textsecuregcm.controllers.CallLinkController; import org.whispersystems.textsecuregcm.controllers.CallRoutingController; import org.whispersystems.textsecuregcm.controllers.CertificateController; @@ -125,6 +124,7 @@ import org.whispersystems.textsecuregcm.controllers.KeepAliveController; import org.whispersystems.textsecuregcm.controllers.KeyTransparencyController; import org.whispersystems.textsecuregcm.controllers.KeysController; import org.whispersystems.textsecuregcm.controllers.MessageController; +import org.whispersystems.textsecuregcm.controllers.OneTimeDonationController; import org.whispersystems.textsecuregcm.controllers.PaymentsController; import org.whispersystems.textsecuregcm.controllers.ProfileController; import org.whispersystems.textsecuregcm.controllers.ProvisioningController; @@ -192,12 +192,12 @@ import org.whispersystems.textsecuregcm.metrics.TrafficSource; import org.whispersystems.textsecuregcm.providers.MultiRecipientMessageProvider; import org.whispersystems.textsecuregcm.providers.RedisClusterHealthCheck; import org.whispersystems.textsecuregcm.push.APNSender; -import org.whispersystems.textsecuregcm.push.PushNotificationScheduler; import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.FcmSender; import org.whispersystems.textsecuregcm.push.MessageSender; import org.whispersystems.textsecuregcm.push.ProvisioningManager; import org.whispersystems.textsecuregcm.push.PushNotificationManager; +import org.whispersystems.textsecuregcm.push.PushNotificationScheduler; import org.whispersystems.textsecuregcm.push.ReceiptSender; import org.whispersystems.textsecuregcm.redis.ConnectionEventLogger; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster; @@ -959,6 +959,7 @@ public class WhisperServerService extends Application filters = new ArrayList<>(); filters.add(remoteDeprecationFilter); filters.add(new RemoteAddressFilter()); + filters.add(new TimestampResponseFilter()); for (Filter filter : filters) { environment.servlets() @@ -1013,7 +1014,7 @@ public class WhisperServerService extends Application spamFilters = ServiceLoader.load(SpamFilter.class) .stream() @@ -1146,6 +1147,7 @@ public class WhisperServerService extends Application= start); final WebSocketResponseMessage keepAlive = testWebsocketListener.doGet("/v1/keepalive").join(); - assertEquals(200, keepAlive.getStatus()); + final long keepAliveTimestamp = Long.parseLong( + keepAlive.getHeaders().get(HeaderUtils.TIMESTAMP_HEADER.toLowerCase())); + assertTrue(keepAliveTimestamp >= start); + + final WebSocketResponseMessage whoami = testWebsocketListener.doGet("/v1/accounts/whoami").join(); + assertEquals(401, whoami.getStatus()); + final long whoamiTimestamp = Long.parseLong(whoami.getHeaders().get(HeaderUtils.TIMESTAMP_HEADER.toLowerCase())); + assertTrue(whoamiTimestamp >= start); + } + + @Test + void rest() throws Exception { + // test unauthenticated rest + final long start = System.currentTimeMillis(); + + final Response whoami = EXTENSION.client().target( + "http://localhost:%d/v1/accounts/whoami".formatted(EXTENSION.getLocalPort())).request().get(); + + assertEquals(401, whoami.getStatus()); + final List timestampValues = whoami.getHeaders().get(HeaderUtils.TIMESTAMP_HEADER.toLowerCase()); + assertEquals(1, timestampValues.size()); + + final long whoamiTimestamp = Long.parseLong(timestampValues.getFirst().toString()); + assertTrue(whoamiTimestamp >= start); } @Test @@ -140,7 +170,6 @@ class WhisperServerServiceTest { .key(Map.of(numbers.hashKeyName(), numberAV)) .build()); } - } } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/filters/TimestampResponseFilterTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/filters/TimestampResponseFilterTest.java index 100931eac..33f9f276a 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/filters/TimestampResponseFilterTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/filters/TimestampResponseFilterTest.java @@ -6,28 +6,42 @@ package org.whispersystems.textsecuregcm.filters; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.matches; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import javax.servlet.FilterChain; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerResponseContext; import javax.ws.rs.core.MultivaluedMap; -import org.glassfish.jersey.message.internal.HeaderUtils; import org.junit.jupiter.api.Test; +import org.whispersystems.textsecuregcm.util.HeaderUtils; class TimestampResponseFilterTest { - @Test - void testFilter() { - final ContainerRequestContext requestContext = mock(ContainerRequestContext.class); - final ContainerResponseContext responseContext = mock(ContainerResponseContext.class); + @Test + void testJerseyFilter() { + final ContainerRequestContext requestContext = mock(ContainerRequestContext.class); + final ContainerResponseContext responseContext = mock(ContainerResponseContext.class); + final MultivaluedMap headers = org.glassfish.jersey.message.internal.HeaderUtils.createOutbound(); + when(responseContext.getHeaders()).thenReturn(headers); - final MultivaluedMap headers = HeaderUtils.createOutbound(); + new TimestampResponseFilter().filter(requestContext, responseContext); - when(responseContext.getHeaders()).thenReturn(headers); + assertTrue(headers.containsKey(org.whispersystems.textsecuregcm.util.HeaderUtils.TIMESTAMP_HEADER)); + } - new TimestampResponseFilter().filter(requestContext, responseContext); + @Test + void testServletFilter() throws Exception { + final HttpServletRequest request = mock(HttpServletRequest.class); + final HttpServletResponse response = mock(HttpServletResponse.class); - assertTrue(headers.containsKey(org.whispersystems.textsecuregcm.util.HeaderUtils.TIMESTAMP_HEADER)); - } + new TimestampResponseFilter().doFilter(request, response, mock(FilterChain.class)); + + verify(response).setHeader(eq(HeaderUtils.TIMESTAMP_HEADER), matches("\\d{10,}")); + } }