Skip to content

Commit

Permalink
Introduce common push notification interfaces/pathways
Browse files Browse the repository at this point in the history
  • Loading branch information
jon-signal committed Aug 3, 2022
1 parent 0d24828 commit 6f0faae
Show file tree
Hide file tree
Showing 23 changed files with 847 additions and 1,015 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@
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.ReceiptSender;
import org.whispersystems.textsecuregcm.recaptcha.RecaptchaClient;
import org.whispersystems.textsecuregcm.redis.ConnectionEventLogger;
Expand Down Expand Up @@ -446,8 +447,10 @@ public DistributionStatisticConfig configure(final Id id, final DistributionStat
RemoteConfigsManager remoteConfigsManager = new RemoteConfigsManager(remoteConfigs);
DispatchManager dispatchManager = new DispatchManager(pubSubClientFactory, Optional.empty());
PubSubManager pubSubManager = new PubSubManager(pubsubClient, dispatchManager);
APNSender apnSender = new APNSender(apnSenderExecutor, accountsManager, config.getApnConfiguration());
FcmSender fcmSender = new FcmSender(gcmSenderExecutor, accountsManager, config.getFcmConfiguration().credentials());
APNSender apnSender = new APNSender(apnSenderExecutor, config.getApnConfiguration());
FcmSender fcmSender = new FcmSender(gcmSenderExecutor, config.getFcmConfiguration().credentials());
ApnFallbackManager apnFallbackManager = new ApnFallbackManager(pushSchedulerCluster, apnSender, accountsManager);
PushNotificationManager pushNotificationManager = new PushNotificationManager(accountsManager, apnSender, fcmSender, apnFallbackManager);
RateLimiters rateLimiters = new RateLimiters(config.getLimitsConfiguration(), rateLimitersCluster);
DynamicRateLimiters dynamicRateLimiters = new DynamicRateLimiters(rateLimitersCluster, dynamicConfigurationManager);
ProvisioningManager provisioningManager = new ProvisioningManager(pubSubManager);
Expand All @@ -470,17 +473,16 @@ public DistributionStatisticConfig configure(final Id id, final DistributionStat
AccountAuthenticator accountAuthenticator = new AccountAuthenticator(accountsManager);
DisabledPermittedAccountAuthenticator disabledPermittedAccountAuthenticator = new DisabledPermittedAccountAuthenticator(accountsManager);

ApnFallbackManager apnFallbackManager = new ApnFallbackManager(pushSchedulerCluster, apnSender, accountsManager);
TwilioSmsSender twilioSmsSender = new TwilioSmsSender(config.getTwilioConfiguration(), dynamicConfigurationManager);
SmsSender smsSender = new SmsSender(twilioSmsSender);
MessageSender messageSender = new MessageSender(apnFallbackManager, clientPresenceManager, messagesManager, fcmSender, apnSender, pushLatencyManager);
MessageSender messageSender = new MessageSender(clientPresenceManager, messagesManager, pushNotificationManager, pushLatencyManager);
ReceiptSender receiptSender = new ReceiptSender(accountsManager, messageSender, receiptSenderExecutor);
TurnTokenGenerator turnTokenGenerator = new TurnTokenGenerator(dynamicConfigurationManager);
RecaptchaClient recaptchaClient = new RecaptchaClient(
config.getRecaptchaConfiguration().getProjectPath(),
config.getRecaptchaConfiguration().getCredentialConfigurationJson(),
dynamicConfigurationManager);
PushChallengeManager pushChallengeManager = new PushChallengeManager(apnSender, fcmSender, pushChallengeDynamoDb);
PushChallengeManager pushChallengeManager = new PushChallengeManager(pushNotificationManager, pushChallengeDynamoDb);
RateLimitChallengeManager rateLimitChallengeManager = new RateLimitChallengeManager(pushChallengeManager,
recaptchaClient, dynamicRateLimiters);
RateLimitChallengeOptionManager rateLimitChallengeOptionManager =
Expand Down Expand Up @@ -548,10 +550,9 @@ public DistributionStatisticConfig configure(final Id id, final DistributionStat
FtxClient ftxClient = new FtxClient(currencyClient);
CurrencyConversionManager currencyManager = new CurrencyConversionManager(fixerClient, ftxClient, config.getPaymentsServiceConfiguration().getPaymentCurrencies());

apnSender.setApnFallbackManager(apnFallbackManager);
environment.lifecycle().manage(apnSender);
environment.lifecycle().manage(apnFallbackManager);
environment.lifecycle().manage(pubSubManager);
environment.lifecycle().manage(messageSender);
environment.lifecycle().manage(accountDatabaseCrawler);
environment.lifecycle().manage(directoryReconciliationAccountDatabaseCrawler);
environment.lifecycle().manage(accountCleanerAccountDatabaseCrawler);
Expand Down Expand Up @@ -607,7 +608,7 @@ public DistributionStatisticConfig configure(final Id id, final DistributionStat
config.getWebSocketConfiguration(), 90000);
webSocketEnvironment.setAuthenticator(new WebSocketAccountAuthenticator(accountAuthenticator));
webSocketEnvironment.setConnectListener(
new AuthenticatedConnectListener(receiptSender, messagesManager, messageSender, apnFallbackManager,
new AuthenticatedConnectListener(receiptSender, messagesManager, pushNotificationManager, apnFallbackManager,
clientPresenceManager, websocketScheduledExecutor));
webSocketEnvironment.jersey().register(new WebsocketRefreshApplicationEventListener(accountsManager, clientPresenceManager));
webSocketEnvironment.jersey().register(new ContentLengthFilter(TrafficSource.WEBSOCKET));
Expand All @@ -619,7 +620,7 @@ public DistributionStatisticConfig configure(final Id id, final DistributionStat
environment.jersey().register(
new AccountController(pendingAccountsManager, accountsManager, abusiveHostRules, rateLimiters,
smsSender, dynamicConfigurationManager, turnTokenGenerator, config.getTestDevices(),
recaptchaClient, fcmSender, apnSender, verifyExperimentEnrollmentManager,
recaptchaClient, pushNotificationManager, verifyExperimentEnrollmentManager,
changeNumberManager, backupCredentialsGenerator));
environment.jersey().register(new KeysController(rateLimiters, keys, accountsManager));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.ForbiddenException;
import javax.ws.rs.GET;
import javax.ws.rs.HEAD;
Expand Down Expand Up @@ -74,10 +75,8 @@
import org.whispersystems.textsecuregcm.entities.StaleDevices;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil;
import org.whispersystems.textsecuregcm.push.APNSender;
import org.whispersystems.textsecuregcm.push.ApnMessage;
import org.whispersystems.textsecuregcm.push.FcmSender;
import org.whispersystems.textsecuregcm.push.GcmMessage;
import org.whispersystems.textsecuregcm.push.PushNotification;
import org.whispersystems.textsecuregcm.push.PushNotificationManager;
import org.whispersystems.textsecuregcm.recaptcha.RecaptchaClient;
import org.whispersystems.textsecuregcm.sms.SmsSender;
import org.whispersystems.textsecuregcm.sms.TwilioVerifyExperimentEnrollmentManager;
Expand Down Expand Up @@ -137,8 +136,7 @@ public class AccountController {
private final TurnTokenGenerator turnTokenGenerator;
private final Map<String, Integer> testDevices;
private final RecaptchaClient recaptchaClient;
private final FcmSender fcmSender;
private final APNSender apnSender;
private final PushNotificationManager pushNotificationManager;
private final ExternalServiceCredentialGenerator backupServiceCredentialGenerator;

private final TwilioVerifyExperimentEnrollmentManager verifyExperimentEnrollmentManager;
Expand All @@ -153,8 +151,7 @@ public AccountController(StoredVerificationCodeManager pendingAccounts,
TurnTokenGenerator turnTokenGenerator,
Map<String, Integer> testDevices,
RecaptchaClient recaptchaClient,
FcmSender fcmSender,
APNSender apnSender,
PushNotificationManager pushNotificationManager,
TwilioVerifyExperimentEnrollmentManager verifyExperimentEnrollmentManager,
ChangeNumberManager changeNumberManager,
ExternalServiceCredentialGenerator backupServiceCredentialGenerator)
Expand All @@ -168,8 +165,7 @@ public AccountController(StoredVerificationCodeManager pendingAccounts,
this.testDevices = testDevices;
this.turnTokenGenerator = turnTokenGenerator;
this.recaptchaClient = recaptchaClient;
this.fcmSender = fcmSender;
this.apnSender = apnSender;
this.pushNotificationManager = pushNotificationManager;
this.verifyExperimentEnrollmentManager = verifyExperimentEnrollmentManager;
this.backupServiceCredentialGenerator = backupServiceCredentialGenerator;
this.changeNumberManager = changeNumberManager;
Expand All @@ -179,15 +175,17 @@ public AccountController(StoredVerificationCodeManager pendingAccounts,
@GET
@Path("/{type}/preauth/{token}/{number}")
@Produces(MediaType.APPLICATION_JSON)
public Response getPreAuth(@PathParam("type") String pushType,
@PathParam("token") String pushToken,
public Response getPreAuth(@PathParam("type") String pushType,
@PathParam("token") String pushToken,
@PathParam("number") String number,
@QueryParam("voip") Optional<Boolean> useVoip)
@QueryParam("voip") @DefaultValue("true") boolean useVoip)
throws ImpossiblePhoneNumberException, NonNormalizedPhoneNumberException {

if (!"apn".equals(pushType) && !"fcm".equals(pushType)) {
return Response.status(400).build();
}
final PushNotification.TokenType tokenType = switch(pushType) {
case "apn" -> useVoip ? PushNotification.TokenType.APN_VOIP : PushNotification.TokenType.APN;
case "fcm" -> PushNotification.TokenType.FCM;
default -> throw new BadRequestException();
};

Util.requireNormalizedNumber(number);

Expand All @@ -198,14 +196,7 @@ public Response getPreAuth(@PathParam("type") String pushType,
null);

pendingAccounts.store(number, storedVerificationCode);

if ("fcm".equals(pushType)) {
fcmSender.sendMessage(new GcmMessage(pushToken, null, 0, GcmMessage.Type.CHALLENGE, Optional.of(storedVerificationCode.getPushCode())));
} else if ("apn".equals(pushType)) {
apnSender.sendMessage(new ApnMessage(pushToken, null, 0, useVoip.orElse(true), ApnMessage.Type.CHALLENGE, Optional.of(storedVerificationCode.getPushCode())));
} else {
throw new AssertionError();
}
pushNotificationManager.sendRegistrationChallengeNotification(pushToken, tokenType, storedVerificationCode.getPushCode());

return Response.ok().build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,19 @@
import io.micrometer.core.instrument.Metrics;
import java.security.SecureRandom;
import java.time.Duration;
import java.util.Optional;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.StringUtils;
import org.whispersystems.textsecuregcm.push.APNSender;
import org.whispersystems.textsecuregcm.push.ApnMessage;
import org.whispersystems.textsecuregcm.push.ApnMessage.Type;
import org.whispersystems.textsecuregcm.push.FcmSender;
import org.whispersystems.textsecuregcm.push.GcmMessage;
import org.whispersystems.textsecuregcm.push.NotPushRegisteredException;
import org.whispersystems.textsecuregcm.push.PushNotificationManager;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.storage.PushChallengeDynamoDb;
import org.whispersystems.textsecuregcm.util.Util;
import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;

public class PushChallengeManager {
private final APNSender apnSender;
private final FcmSender fcmSender;

private final PushNotificationManager pushNotificationManager;
private final PushChallengeDynamoDb pushChallengeDynamoDb;

private final SecureRandom random = new SecureRandom();
Expand All @@ -45,39 +38,35 @@ public class PushChallengeManager {
private static final String SUCCESS_TAG_NAME = "success";
private static final String SOURCE_COUNTRY_TAG_NAME = "sourceCountry";

public PushChallengeManager(final APNSender apnSender, final FcmSender fcmSender,
public PushChallengeManager(final PushNotificationManager pushNotificationManager,
final PushChallengeDynamoDb pushChallengeDynamoDb) {

this.apnSender = apnSender;
this.fcmSender = fcmSender;
this.pushNotificationManager = pushNotificationManager;
this.pushChallengeDynamoDb = pushChallengeDynamoDb;
}

public void sendChallenge(final Account account) throws NotPushRegisteredException {
final Device masterDevice = account.getMasterDevice().orElseThrow(NotPushRegisteredException::new);

if (StringUtils.isAllBlank(masterDevice.getGcmId(), masterDevice.getApnId())) {
throw new NotPushRegisteredException();
}

final byte[] token = new byte[CHALLENGE_TOKEN_LENGTH];
random.nextBytes(token);

final boolean sent;
final String platform;

if (pushChallengeDynamoDb.add(account.getUuid(), token, CHALLENGE_TTL)) {
final String tokenHex = Hex.encodeHexString(token);
pushNotificationManager.sendRateLimitChallengeNotification(account, Hex.encodeHexString(token));

sent = true;

if (StringUtils.isNotBlank(masterDevice.getGcmId())) {
fcmSender.sendMessage(new GcmMessage(masterDevice.getGcmId(), account.getUuid(), 0, GcmMessage.Type.RATE_LIMIT_CHALLENGE, Optional.of(tokenHex)));
platform = ClientPlatform.ANDROID.name().toLowerCase();
} else if (StringUtils.isNotBlank(masterDevice.getApnId())) {
apnSender.sendMessage(new ApnMessage(masterDevice.getApnId(), account.getUuid(), 0, false, Type.RATE_LIMIT_CHALLENGE, Optional.of(tokenHex)));
platform = ClientPlatform.IOS.name().toLowerCase();
} else {
throw new AssertionError();
// This should never happen; if the account has neither an APN nor FCM token, sending the challenge will result
// in a `NotPushRegisteredException`
platform = "unrecognized";
}
} else {
sent = false;
Expand Down

0 comments on commit 6f0faae

Please sign in to comment.