diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index dbcd76602..4a4b66ae3 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -669,8 +669,7 @@ public class WhisperServerService extends Application commonControllers = Lists.newArrayList( - new AccountController(accountsManager, rateLimiters, turnTokenGenerator, registrationRecoveryPasswordsManager, + new AccountController(accountsManager, rateLimiters, registrationRecoveryPasswordsManager, usernameHashZkProofVerifier), new AccountControllerV2(accountsManager, changeNumberManager, phoneVerificationTokenManager, registrationLockVerificationManager, rateLimiters), diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/auth/TurnTokenGenerator.java b/service/src/main/java/org/whispersystems/textsecuregcm/auth/TurnTokenGenerator.java index ba627c0d5..a004f429c 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/auth/TurnTokenGenerator.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/auth/TurnTokenGenerator.java @@ -27,8 +27,6 @@ import org.whispersystems.textsecuregcm.util.WeightedRandomSelect; public class TurnTokenGenerator { - private final DynamicConfigurationManager dynamicConfigurationManager; - private final byte[] turnSecret; private static final String ALGORITHM = "HmacSHA1"; @@ -37,17 +35,10 @@ public class TurnTokenGenerator { private static final String WithIpsProtocol = "01"; - public TurnTokenGenerator(final DynamicConfigurationManager dynamicConfigurationManager, - final byte[] turnSecret) { - this.dynamicConfigurationManager = dynamicConfigurationManager; + public TurnTokenGenerator(final byte[] turnSecret) { this.turnSecret = turnSecret; } - @Deprecated - public TurnToken generate(final UUID aci) { - return generateToken(null, null, urls(aci)); - } - public TurnToken generateWithTurnServerOptions(TurnServerOptions options) { return generateToken(options.hostname(), options.urlsWithIps(), options.urlsWithHostname()); } @@ -71,23 +62,4 @@ public class TurnTokenGenerator { throw new AssertionError(e); } } - - private List urls(final UUID aci) { - final DynamicTurnConfiguration turnConfig = dynamicConfigurationManager.getConfiguration().getTurnConfiguration(); - - // Check if number is enrolled to test out specific turn servers - final Optional enrolled = turnConfig.getUriConfigs().stream() - .filter(config -> config.getEnrolledAcis().contains(aci)) - .findFirst(); - - if (enrolled.isPresent()) { - return enrolled.get().getUris(); - } - - // Otherwise, select from turn server sets by weighted choice - return WeightedRandomSelect.select(turnConfig - .getUriConfigs() - .stream() - .map(c -> new Pair<>(c.getUris(), c.getWeight())).toList()); - } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java index 1b724a6c2..ffe7dad20 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java @@ -35,11 +35,8 @@ import java.util.UUID; import java.util.concurrent.CompletableFuture; import javax.annotation.Nullable; import org.signal.libsignal.usernames.BaseUsernameException; -import org.whispersystems.textsecuregcm.auth.AccountAndAuthenticatedDeviceHolder; import org.whispersystems.textsecuregcm.auth.AuthenticatedDevice; import org.whispersystems.textsecuregcm.auth.SaltedTokenHash; -import org.whispersystems.textsecuregcm.auth.TurnToken; -import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator; import org.whispersystems.textsecuregcm.entities.AccountAttributes; import org.whispersystems.textsecuregcm.entities.AccountIdentifierResponse; import org.whispersystems.textsecuregcm.entities.AccountIdentityResponse; @@ -61,7 +58,6 @@ import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.Device; -import org.whispersystems.textsecuregcm.storage.DeviceCapability; import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager; import org.whispersystems.textsecuregcm.storage.UsernameHashNotAvailableException; import org.whispersystems.textsecuregcm.storage.UsernameReservationNotFoundException; @@ -82,33 +78,20 @@ public class AccountController { private final AccountsManager accounts; private final RateLimiters rateLimiters; - private final TurnTokenGenerator turnTokenGenerator; private final RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager; private final UsernameHashZkProofVerifier usernameHashZkProofVerifier; public AccountController( AccountsManager accounts, RateLimiters rateLimiters, - TurnTokenGenerator turnTokenGenerator, RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager, UsernameHashZkProofVerifier usernameHashZkProofVerifier) { this.accounts = accounts; this.rateLimiters = rateLimiters; - this.turnTokenGenerator = turnTokenGenerator; this.registrationRecoveryPasswordsManager = registrationRecoveryPasswordsManager; this.usernameHashZkProofVerifier = usernameHashZkProofVerifier; } - // may be removed after 2024-07-16 - @Deprecated(forRemoval = true) - @GET - @Path("/turn/") - @Produces(MediaType.APPLICATION_JSON) - public TurnToken getTurnToken(@ReadOnly @Auth AuthenticatedDevice auth) throws RateLimitExceededException { - rateLimiters.getTurnLimiter().validate(auth.getAccount().getUuid()); - return turnTokenGenerator.generate(auth.getAccount().getUuid()); - } - @PUT @Path("/gcm/") @Consumes(MediaType.APPLICATION_JSON) diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/grpc/CallingGrpcService.java b/service/src/main/java/org/whispersystems/textsecuregcm/grpc/CallingGrpcService.java deleted file mode 100644 index 7acab7547..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/grpc/CallingGrpcService.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2023 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.grpc; - -import org.signal.chat.calling.GetTurnCredentialsRequest; -import org.signal.chat.calling.GetTurnCredentialsResponse; -import org.signal.chat.calling.ReactorCallingGrpc; -import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator; -import org.whispersystems.textsecuregcm.auth.grpc.AuthenticatedDevice; -import org.whispersystems.textsecuregcm.auth.grpc.AuthenticationUtil; -import org.whispersystems.textsecuregcm.limits.RateLimiters; -import reactor.core.publisher.Mono; - -public class CallingGrpcService extends ReactorCallingGrpc.CallingImplBase { - - private final TurnTokenGenerator turnTokenGenerator; - private final RateLimiters rateLimiters; - - public CallingGrpcService(final TurnTokenGenerator turnTokenGenerator, final RateLimiters rateLimiters) { - this.turnTokenGenerator = turnTokenGenerator; - this.rateLimiters = rateLimiters; - } - - @Override - public Mono getTurnCredentials(final GetTurnCredentialsRequest request) { - final AuthenticatedDevice authenticatedDevice = AuthenticationUtil.requireAuthenticatedDevice(); - - return rateLimiters.getTurnLimiter().validateReactive(authenticatedDevice.accountIdentifier()) - .then(Mono.fromSupplier(() -> turnTokenGenerator.generate(authenticatedDevice.accountIdentifier()))) - .map(turnToken -> GetTurnCredentialsResponse.newBuilder() - .setUsername(turnToken.username()) - .setPassword(turnToken.password()) - .addAllUrls(turnToken.urls()) - .build()); - } -} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/auth/TurnTokenGeneratorTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/auth/TurnTokenGeneratorTest.java deleted file mode 100644 index 543261bd8..000000000 --- a/service/src/test/java/org/whispersystems/textsecuregcm/auth/TurnTokenGeneratorTest.java +++ /dev/null @@ -1,137 +0,0 @@ -package org.whispersystems.textsecuregcm.auth; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import com.fasterxml.jackson.core.JsonProcessingException; -import java.nio.charset.StandardCharsets; -import java.util.Map; -import java.util.UUID; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.junit.jupiter.api.Test; -import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; -import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; - -public class TurnTokenGeneratorTest { - @Test - public void testAlwaysSelectFirst() throws JsonProcessingException { - final String configString = """ - captcha: - scoreFloor: 1.0 - turn: - uriConfigs: - - uris: - - always1.org - - always2.org - - uris: - - never.org - weight: 0 - """; - DynamicConfiguration config = DynamicConfigurationManager - .parseConfiguration(configString, DynamicConfiguration.class) - .orElseThrow(); - - @SuppressWarnings("unchecked") - DynamicConfigurationManager mockDynamicConfigManager = mock( - DynamicConfigurationManager.class); - - when(mockDynamicConfigManager.getConfiguration()).thenReturn(config); - - final TurnTokenGenerator turnTokenGenerator = - new TurnTokenGenerator(mockDynamicConfigManager, "bloop".getBytes(StandardCharsets.UTF_8)); - - final long COUNT = 1000; - - final Map urlCounts = Stream - .generate(() -> turnTokenGenerator.generate(UUID.randomUUID())) - .limit(COUNT) - .flatMap(token -> token.urls().stream()) - .collect(Collectors.groupingBy(i -> i, Collectors.counting())); - - assertThat(urlCounts.get("always1.org")).isEqualTo(COUNT); - assertThat(urlCounts.get("always2.org")).isEqualTo(COUNT); - assertThat(urlCounts).doesNotContainKey("never.org"); - } - - @Test - public void testProbabilisticUrls() throws JsonProcessingException { - final String configString = """ - captcha: - scoreFloor: 1.0 - turn: - uriConfigs: - - uris: - - always.org - - sometimes1.org - weight: 5 - - uris: - - always.org - - sometimes2.org - weight: 5 - """; - DynamicConfiguration config = DynamicConfigurationManager - .parseConfiguration(configString, DynamicConfiguration.class) - .orElseThrow(); - - @SuppressWarnings("unchecked") - DynamicConfigurationManager mockDynamicConfigManager = mock( - DynamicConfigurationManager.class); - - when(mockDynamicConfigManager.getConfiguration()).thenReturn(config); - - final TurnTokenGenerator turnTokenGenerator = - new TurnTokenGenerator(mockDynamicConfigManager, "bloop".getBytes(StandardCharsets.UTF_8)); - - final long COUNT = 1000; - - final Map urlCounts = Stream - .generate(() -> turnTokenGenerator.generate(UUID.randomUUID())) - .limit(COUNT) - .flatMap(token -> token.urls().stream()) - .collect(Collectors.groupingBy(i -> i, Collectors.counting())); - - assertThat(urlCounts.get("always.org")).isEqualTo(COUNT); - assertThat(urlCounts.get("sometimes1.org")).isGreaterThan(0); - assertThat(urlCounts.get("sometimes2.org")).isGreaterThan(0); - } - - @Test - public void testExplicitEnrollment() throws JsonProcessingException { - final String configString = """ - captcha: - scoreFloor: 1.0 - turn: - secret: bloop - uriConfigs: - - uris: - - enrolled.org - weight: 0 - enrolledAcis: - - 732506d7-d04f-43a4-b1d7-8a3a91ebe8a6 - - uris: - - unenrolled.org - weight: 1 - """; - DynamicConfiguration config = DynamicConfigurationManager - .parseConfiguration(configString, DynamicConfiguration.class) - .orElseThrow(); - - @SuppressWarnings("unchecked") - DynamicConfigurationManager mockDynamicConfigManager = mock( - DynamicConfigurationManager.class); - - when(mockDynamicConfigManager.getConfiguration()).thenReturn(config); - - final TurnTokenGenerator turnTokenGenerator = - new TurnTokenGenerator(mockDynamicConfigManager, "bloop".getBytes(StandardCharsets.UTF_8)); - - TurnToken token = turnTokenGenerator.generate(UUID.fromString("732506d7-d04f-43a4-b1d7-8a3a91ebe8a6")); - assertThat(token.urls().get(0)).isEqualTo("enrolled.org"); - token = turnTokenGenerator.generate(UUID.randomUUID()); - assertThat(token.urls().get(0)).isEqualTo("unenrolled.org"); - - } - -} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerTest.java index 43e4c7538..6ffe5a8a3 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerTest.java @@ -159,7 +159,6 @@ class AccountControllerTest { .addResource(new AccountController( accountsManager, rateLimiters, - turnTokenGenerator, registrationRecoveryPasswordsManager, usernameZkProofVerifier )) diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/CallRoutingControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/CallRoutingControllerTest.java index cbc1c234c..e90db20b9 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/CallRoutingControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/CallRoutingControllerTest.java @@ -34,12 +34,10 @@ import org.whispersystems.textsecuregcm.auth.TurnToken; import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator; import org.whispersystems.textsecuregcm.calls.routing.TurnCallRouter; import org.whispersystems.textsecuregcm.calls.routing.TurnServerOptions; -import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager; import org.whispersystems.textsecuregcm.limits.RateLimiter; import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper; -import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; import org.whispersystems.textsecuregcm.tests.util.AuthHelper; import org.whispersystems.textsecuregcm.util.SystemMapper; import org.whispersystems.textsecuregcm.util.TestRemoteAddressFilterProvider; @@ -52,12 +50,10 @@ class CallRoutingControllerTest { private static final RateLimiters rateLimiters = mock(RateLimiters.class); private static final RateLimiter getCallEndpointLimiter = mock(RateLimiter.class); - private static final DynamicConfigurationManager dynamicConfigurationManager = mock( - DynamicConfigurationManager.class); private static final ExperimentEnrollmentManager experimentEnrollmentManager = mock( ExperimentEnrollmentManager.class); - private static final TurnTokenGenerator turnTokenGenerator = new TurnTokenGenerator(dynamicConfigurationManager, - "bloop".getBytes(StandardCharsets.UTF_8)); + private static final TurnTokenGenerator turnTokenGenerator = + new TurnTokenGenerator("bloop".getBytes(StandardCharsets.UTF_8)); private static final CloudflareTurnCredentialsManager cloudflareTurnCredentialsManager = mock( CloudflareTurnCredentialsManager.class); @@ -81,8 +77,7 @@ class CallRoutingControllerTest { @AfterEach void tearDown() { - reset(experimentEnrollmentManager, dynamicConfigurationManager, rateLimiters, getCallEndpointLimiter, - turnCallRouter); + reset(experimentEnrollmentManager, rateLimiters, getCallEndpointLimiter, turnCallRouter); } @Test diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/CallRoutingControllerV2Test.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/CallRoutingControllerV2Test.java index 8e359401a..261bea77a 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/CallRoutingControllerV2Test.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/CallRoutingControllerV2Test.java @@ -63,12 +63,9 @@ class CallRoutingControllerV2Test { private static final RateLimiters rateLimiters = mock(RateLimiters.class); private static final RateLimiter getCallEndpointLimiter = mock(RateLimiter.class); - private static final DynamicConfigurationManager dynamicConfigurationManager = mock( - DynamicConfigurationManager.class); private static final ExperimentEnrollmentManager experimentEnrollmentManager = mock( ExperimentEnrollmentManager.class); - private static final TurnTokenGenerator turnTokenGenerator = new TurnTokenGenerator(dynamicConfigurationManager, - "bloop".getBytes(StandardCharsets.UTF_8)); + private static final TurnTokenGenerator turnTokenGenerator = new TurnTokenGenerator("bloop".getBytes(StandardCharsets.UTF_8)); private static final CloudflareTurnCredentialsManager cloudflareTurnCredentialsManager = mock( CloudflareTurnCredentialsManager.class); private static final TurnCallRouter turnCallRouter = mock(TurnCallRouter.class); @@ -91,8 +88,7 @@ class CallRoutingControllerV2Test { @AfterEach void tearDown() { - reset(experimentEnrollmentManager, dynamicConfigurationManager, rateLimiters, getCallEndpointLimiter, - turnCallRouter); + reset(experimentEnrollmentManager, rateLimiters, getCallEndpointLimiter, turnCallRouter); } void initializeMocksWith(Optional signalTurn, Optional cloudflare) { diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/CallingGrpcServiceTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/CallingGrpcServiceTest.java deleted file mode 100644 index 098fab59d..000000000 --- a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/CallingGrpcServiceTest.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; -import static org.whispersystems.textsecuregcm.grpc.GrpcTestUtils.assertRateLimitExceeded; - -import java.time.Duration; -import java.util.List; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.signal.chat.calling.CallingGrpc; -import org.signal.chat.calling.GetTurnCredentialsRequest; -import org.signal.chat.calling.GetTurnCredentialsResponse; -import org.whispersystems.textsecuregcm.auth.TurnToken; -import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator; -import org.whispersystems.textsecuregcm.limits.RateLimiter; -import org.whispersystems.textsecuregcm.limits.RateLimiters; -import org.whispersystems.textsecuregcm.util.MockUtils; - -class CallingGrpcServiceTest extends SimpleBaseGrpcTest { - - @Mock - private TurnTokenGenerator turnTokenGenerator; - - @Mock - private RateLimiter turnCredentialRateLimiter; - - - @Override - protected CallingGrpcService createServiceBeforeEachTest() { - final RateLimiters rateLimiters = mock(RateLimiters.class); - when(rateLimiters.getTurnLimiter()).thenReturn(turnCredentialRateLimiter); - return new CallingGrpcService(turnTokenGenerator, rateLimiters); - } - - @Test - void getTurnCredentials() { - final String username = "test-username"; - final String password = "test-password"; - final List urls = List.of("first", "second"); - final String hostname = "hostname"; - - MockUtils.updateRateLimiterResponseToAllow(turnCredentialRateLimiter, AUTHENTICATED_ACI); - when(turnTokenGenerator.generate(any())).thenReturn(new TurnToken(username, password, urls, null, hostname)); - - final GetTurnCredentialsResponse response = authenticatedServiceStub().getTurnCredentials(GetTurnCredentialsRequest.newBuilder().build()); - - final GetTurnCredentialsResponse expectedResponse = GetTurnCredentialsResponse.newBuilder() - .setUsername(username) - .setPassword(password) - .addAllUrls(urls) - .build(); - - assertEquals(expectedResponse, response); - } - - @Test - void getTurnCredentialsRateLimited() { - final Duration retryAfter = MockUtils.updateRateLimiterResponseToFail( - turnCredentialRateLimiter, AUTHENTICATED_ACI, Duration.ofMinutes(19)); - assertRateLimitExceeded(retryAfter, () -> authenticatedServiceStub().getTurnCredentials(GetTurnCredentialsRequest.newBuilder().build())); - verify(turnTokenGenerator, never()).generate(any()); - verifyNoInteractions(turnTokenGenerator); - } -}