Add an endpoint for testing whether an account with a given ACI or PNI exists

This commit is contained in:
Jon Chambers
2021-11-11 12:32:11 -05:00
parent e6237480f8
commit 975f753c2b
4 changed files with 96 additions and 0 deletions

View File

@@ -63,6 +63,9 @@ public class RateLimitsConfiguration {
@JsonProperty
private RateLimitConfiguration usernameSet = new RateLimitConfiguration(100, 100 / (24.0 * 60.0));
@JsonProperty
private RateLimitConfiguration checkAccountExistence = new RateLimitConfiguration(1_000, 1_000 / 60.0);
public RateLimitConfiguration getAutoBlock() {
return autoBlock;
}
@@ -135,6 +138,10 @@ public class RateLimitsConfiguration {
return usernameSet;
}
public RateLimitConfiguration getCheckAccountExistence() {
return checkAccountExistence;
}
public static class RateLimitConfiguration {
@JsonProperty
private int bucketSize;

View File

@@ -15,6 +15,7 @@ import io.dropwizard.auth.Auth;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tag;
import java.security.SecureRandom;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
@@ -22,13 +23,17 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HEAD;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
@@ -36,8 +41,11 @@ import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
@@ -627,6 +635,30 @@ public class AccountController {
return Response.ok().build();
}
@HEAD
@Path("/account/{uuid}")
public Response accountExists(
@HeaderParam("X-Forwarded-For") final String forwardedFor,
@PathParam("uuid") final UUID uuid,
@Context HttpServletRequest request) throws RateLimitExceededException {
// Disallow clients from making authenticated requests to this endpoint
if (StringUtils.isNotBlank(request.getHeader("Authorization"))) {
throw new BadRequestException();
}
final String mostRecentProxy = ForwardedIpUtil.getMostRecentProxy(forwardedFor)
.orElseThrow(() -> new RateLimitExceededException(Duration.ofHours(1)));
rateLimiters.getCheckAccountExistenceLimiter().validate(mostRecentProxy);
final Status status = accounts.getByAccountIdentifier(uuid)
.or(() -> accounts.getByPhoneNumberIdentifier(uuid))
.isPresent() ? Status.OK : Status.NOT_FOUND;
return Response.status(status).build();
}
private void verifyRegistrationLock(final Account existingAccount, @Nullable final String clientRegistrationLock)
throws RateLimitExceededException, WebApplicationException {

View File

@@ -39,6 +39,8 @@ public class RateLimiters {
private final RateLimiter usernameLookupLimiter;
private final RateLimiter usernameSetLimiter;
private final RateLimiter checkAccountExistenceLimiter;
private final AtomicReference<CardinalityRateLimiter> unsealedSenderCardinalityLimiter;
private final AtomicReference<RateLimiter> unsealedIpLimiter;
private final AtomicReference<RateLimiter> rateLimitResetLimiter;
@@ -127,6 +129,10 @@ public class RateLimiters {
config.getUsernameSet().getBucketSize(),
config.getUsernameSet().getLeakRatePerMinute());
this.checkAccountExistenceLimiter = new RateLimiter(cacheCluster, "checkAccountExistence",
config.getCheckAccountExistence().getBucketSize(),
config.getCheckAccountExistence().getLeakRatePerMinute());
this.dailyPreKeysLimiter = new AtomicReference<>(createDailyPreKeysLimiter(cacheCluster, dynamicConfig.getConfiguration().getLimits().getDailyPreKeys()));
this.unsealedSenderCardinalityLimiter = new AtomicReference<>(createUnsealedSenderCardinalityLimiter(cacheCluster, dynamicConfig.getConfiguration().getLimits().getUnsealedSenderNumber()));
@@ -287,6 +293,10 @@ public class RateLimiters {
return usernameSetLimiter;
}
public RateLimiter getCheckAccountExistenceLimiter() {
return checkAccountExistenceLimiter;
}
private CardinalityRateLimiter createUnsealedSenderCardinalityLimiter(FaultTolerantRedisCluster cacheCluster, CardinalityRateLimitConfiguration configuration) {
return new CardinalityRateLimiter(cacheCluster, "unsealedSender", configuration.getTtl(), configuration.getMaxCardinality());
}