Pass carrier data from lookup services to registration service

This commit is contained in:
Jon Chambers
2026-01-22 10:42:18 -05:00
committed by Jon Chambers
parent 5043175cb4
commit 9ffb588c6a
6 changed files with 65 additions and 7 deletions

View File

@@ -1118,7 +1118,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
config.getCdnConfiguration().bucket()),
new VerificationController(registrationServiceClient, new VerificationSessionManager(verificationSessions),
pushNotificationManager, registrationCaptchaManager, registrationRecoveryPasswordsManager,
phoneNumberIdentifiers, rateLimiters, accountsManager, registrationFraudChecker,
phoneNumberIdentifiers, rateLimiters, accountsManager, carrierDataProvider, registrationFraudChecker,
dynamicConfigurationManager, clock)
);
if (config.getSubscription() != null && config.getOneTimeDonations() != null) {

View File

@@ -0,0 +1,24 @@
/*
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration.dynamic;
import jakarta.validation.constraints.NotNull;
import java.time.Duration;
public record DynamicCarrierDataLookupConfiguration(boolean enabled, @NotNull Duration maxCacheAge) {
public static Duration DEFAULT_MAX_CACHE_AGE = Duration.ofDays(7);
public DynamicCarrierDataLookupConfiguration() {
this(false, DEFAULT_MAX_CACHE_AGE);
}
public DynamicCarrierDataLookupConfiguration {
if (maxCacheAge == null) {
maxCacheAge = DEFAULT_MAX_CACHE_AGE;
}
}
}

View File

@@ -68,6 +68,10 @@ public class DynamicConfiguration {
@Valid
private DynamicBackupConfiguration backup = new DynamicBackupConfiguration();
@JsonProperty
@Valid
private DynamicCarrierDataLookupConfiguration carrierDataLookup = new DynamicCarrierDataLookupConfiguration();
public Optional<DynamicExperimentEnrollmentConfiguration> getExperimentEnrollmentConfiguration(
final String experimentName) {
return Optional.ofNullable(experiments.get(experimentName));
@@ -121,4 +125,8 @@ public class DynamicConfiguration {
public DynamicBackupConfiguration getBackupConfiguration() {
return backup;
}
public DynamicCarrierDataLookupConfiguration getCarrierDataLookupConfiguration() {
return carrierDataLookup;
}
}

View File

@@ -94,6 +94,9 @@ import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import org.whispersystems.textsecuregcm.storage.PhoneNumberIdentifiers;
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager;
import org.whispersystems.textsecuregcm.storage.VerificationSessionManager;
import org.whispersystems.textsecuregcm.telephony.CarrierData;
import org.whispersystems.textsecuregcm.telephony.CarrierDataException;
import org.whispersystems.textsecuregcm.telephony.CarrierDataProvider;
import org.whispersystems.textsecuregcm.util.ExceptionUtils;
import org.whispersystems.textsecuregcm.util.ObsoletePhoneNumberFormatException;
import org.whispersystems.textsecuregcm.util.Pair;
@@ -129,6 +132,7 @@ public class VerificationController {
private final PhoneNumberIdentifiers phoneNumberIdentifiers;
private final RateLimiters rateLimiters;
private final AccountsManager accountsManager;
private final CarrierDataProvider carrierDataProvider;
private final RegistrationFraudChecker registrationFraudChecker;
private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager;
private final Clock clock;
@@ -141,6 +145,7 @@ public class VerificationController {
final PhoneNumberIdentifiers phoneNumberIdentifiers,
final RateLimiters rateLimiters,
final AccountsManager accountsManager,
final CarrierDataProvider carrierDataProvider,
final RegistrationFraudChecker registrationFraudChecker,
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager,
final Clock clock) {
@@ -152,6 +157,7 @@ public class VerificationController {
this.phoneNumberIdentifiers = phoneNumberIdentifiers;
this.rateLimiters = rateLimiters;
this.accountsManager = accountsManager;
this.carrierDataProvider = carrierDataProvider;
this.registrationFraudChecker = registrationFraudChecker;
this.dynamicConfigurationManager = dynamicConfigurationManager;
this.clock = clock;
@@ -187,14 +193,29 @@ public class VerificationController {
throw new ServerErrorException("could not parse already validated number", Response.Status.INTERNAL_SERVER_ERROR);
}
Optional<CarrierData> maybeCarrierData;
if (dynamicConfigurationManager.getConfiguration().getCarrierDataLookupConfiguration().enabled()) {
try {
maybeCarrierData = carrierDataProvider.lookupCarrierData(phoneNumber,
dynamicConfigurationManager.getConfiguration().getCarrierDataLookupConfiguration().maxCacheAge());
} catch (final IOException | CarrierDataException e) {
logger.warn("Failed to retrieve carrier data", e);
maybeCarrierData = Optional.empty();
}
} else {
maybeCarrierData = Optional.empty();
}
final RegistrationServiceSession registrationServiceSession;
try {
final String sourceHost = (String) requestContext.getProperty(RemoteAddressFilter.REMOTE_ADDRESS_ATTRIBUTE_NAME);
registrationServiceSession = registrationServiceClient.createRegistrationSession(phoneNumber, sourceHost,
registrationServiceSession = registrationServiceClient.createRegistrationSession(phoneNumber,
sourceHost,
accountsManager.getByE164(request.number()).isPresent(),
request.updateVerificationSessionRequest().mcc(),
request.updateVerificationSessionRequest().mnc(),
maybeCarrierData.flatMap(CarrierData::mcc).orElse(null),
maybeCarrierData.flatMap(CarrierData::mnc).orElse(null),
REGISTRATION_RPC_TIMEOUT).join();
} catch (final CancellationException e) {

View File

@@ -47,12 +47,12 @@ message CreateRegistrationSessionRequest {
string rate_limit_collation_key = 3;
/**
* The MCC for the given `e164` as reported by the client.
* The MCC for the given `e164` as reported by a number lookup service.
*/
string mcc = 4;
/**
* The MNC for the given `e164` as reported by the client.
* The MNC for the given `e164` as reported by a number lookup service.
*/
string mnc = 5;
}

View File

@@ -58,6 +58,7 @@ import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.ArgumentCaptor;
import org.whispersystems.textsecuregcm.captcha.AssessmentResult;
import org.whispersystems.textsecuregcm.captcha.RegistrationCaptchaManager;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicCarrierDataLookupConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicRegistrationConfiguration;
import org.whispersystems.textsecuregcm.entities.RegistrationServiceSession;
@@ -83,6 +84,7 @@ import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import org.whispersystems.textsecuregcm.storage.PhoneNumberIdentifiers;
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager;
import org.whispersystems.textsecuregcm.storage.VerificationSessionManager;
import org.whispersystems.textsecuregcm.telephony.CarrierDataProvider;
import org.whispersystems.textsecuregcm.util.SystemMapper;
import org.whispersystems.textsecuregcm.util.TestRemoteAddressFilterProvider;
@@ -106,6 +108,7 @@ class VerificationControllerTest {
private final PhoneNumberIdentifiers phoneNumberIdentifiers = mock(PhoneNumberIdentifiers.class);
private final RateLimiters rateLimiters = mock(RateLimiters.class);
private final AccountsManager accountsManager = mock(AccountsManager.class);
private final CarrierDataProvider carrierDataProvider = mock(CarrierDataProvider.class);
private final Clock clock = Clock.systemUTC();
private final RateLimiter captchaLimiter = mock(RateLimiter.class);
@@ -127,7 +130,7 @@ class VerificationControllerTest {
.addResource(
new VerificationController(registrationServiceClient, verificationSessionManager, pushNotificationManager,
registrationCaptchaManager, registrationRecoveryPasswordsManager, phoneNumberIdentifiers, rateLimiters, accountsManager,
RegistrationFraudChecker.noop(), dynamicConfigurationManager, clock))
carrierDataProvider, RegistrationFraudChecker.noop(), dynamicConfigurationManager, clock))
.build();
@BeforeEach
@@ -140,6 +143,8 @@ class VerificationControllerTest {
.thenReturn(Optional.empty());
when(dynamicConfiguration.getRegistrationConfiguration())
.thenReturn(new DynamicRegistrationConfiguration(false));
when(dynamicConfiguration.getCarrierDataLookupConfiguration())
.thenReturn(new DynamicCarrierDataLookupConfiguration());
when(dynamicConfigurationManager.getConfiguration())
.thenReturn(dynamicConfiguration);
when(phoneNumberIdentifiers.getPhoneNumberIdentifier(NUMBER))