mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-20 07:08:05 +01:00
Send 508 status code for legacy clients that produce rate limit challenges
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* Copyright 2013-2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm;
|
||||
@@ -110,6 +110,7 @@ import org.whispersystems.textsecuregcm.mappers.InvalidWebsocketAddressException
|
||||
import org.whispersystems.textsecuregcm.mappers.RateLimitChallengeExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.mappers.RetryLaterExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.mappers.ServerRejectedExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.metrics.BufferPoolGauges;
|
||||
import org.whispersystems.textsecuregcm.metrics.CpuUsageGauge;
|
||||
import org.whispersystems.textsecuregcm.metrics.FileDescriptorGauge;
|
||||
@@ -662,6 +663,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
environment.jersey().register(new InvalidWebsocketAddressExceptionMapper());
|
||||
environment.jersey().register(new DeviceLimitExceededExceptionMapper());
|
||||
environment.jersey().register(new RetryLaterExceptionMapper());
|
||||
environment.jersey().register(new ServerRejectedExceptionMapper());
|
||||
|
||||
webSocketEnvironment.jersey().register(new LoggingUnhandledExceptionMapper());
|
||||
webSocketEnvironment.jersey().register(new IOExceptionMapper());
|
||||
@@ -669,6 +671,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
webSocketEnvironment.jersey().register(new InvalidWebsocketAddressExceptionMapper());
|
||||
webSocketEnvironment.jersey().register(new DeviceLimitExceededExceptionMapper());
|
||||
webSocketEnvironment.jersey().register(new RetryLaterExceptionMapper());
|
||||
webSocketEnvironment.jersey().register(new ServerRejectedExceptionMapper());
|
||||
|
||||
provisioningEnvironment.jersey().register(new LoggingUnhandledExceptionMapper());
|
||||
provisioningEnvironment.jersey().register(new IOExceptionMapper());
|
||||
@@ -676,6 +679,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
provisioningEnvironment.jersey().register(new InvalidWebsocketAddressExceptionMapper());
|
||||
provisioningEnvironment.jersey().register(new DeviceLimitExceededExceptionMapper());
|
||||
provisioningEnvironment.jersey().register(new RetryLaterExceptionMapper());
|
||||
provisioningEnvironment.jersey().register(new ServerRejectedExceptionMapper());
|
||||
}
|
||||
|
||||
private void registerCorsFilter(Environment environment) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* Copyright 2013-2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.controllers;
|
||||
@@ -116,12 +116,12 @@ public class KeysController {
|
||||
@GET
|
||||
@Path("/{identifier}/{device_id}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response getDeviceKeys(@Auth Optional<Account> account,
|
||||
@HeaderParam(OptionalAccess.UNIDENTIFIED) Optional<Anonymous> accessKey,
|
||||
@PathParam("identifier") AmbiguousIdentifier targetName,
|
||||
@PathParam("device_id") String deviceId,
|
||||
@HeaderParam("User-Agent") String userAgent)
|
||||
throws RateLimitExceededException, RateLimitChallengeException {
|
||||
public Response getDeviceKeys(@Auth Optional<Account> account,
|
||||
@HeaderParam(OptionalAccess.UNIDENTIFIED) Optional<Anonymous> accessKey,
|
||||
@PathParam("identifier") AmbiguousIdentifier targetName,
|
||||
@PathParam("device_id") String deviceId,
|
||||
@HeaderParam("User-Agent") String userAgent)
|
||||
throws RateLimitExceededException, RateLimitChallengeException, ServerRejectedException {
|
||||
|
||||
targetName.incrementRequestCounter("getDeviceKeys", userAgent);
|
||||
|
||||
@@ -152,16 +152,17 @@ public class KeysController {
|
||||
preKeyRateLimiter.validate(account.get());
|
||||
} catch (RateLimitExceededException e) {
|
||||
|
||||
final boolean enforceLimit = rateLimitChallengeManager.shouldIssueRateLimitChallenge(userAgent);
|
||||
final boolean legacyClient = rateLimitChallengeManager.isClientBelowMinimumVersion(userAgent);
|
||||
|
||||
Metrics.counter(RATE_LIMITED_GET_PREKEYS_COUNTER_NAME,
|
||||
SOURCE_COUNTRY_TAG_NAME, Util.getCountryCode(account.get().getNumber()),
|
||||
"enforced", String.valueOf(enforceLimit))
|
||||
SOURCE_COUNTRY_TAG_NAME, Util.getCountryCode(account.get().getNumber()),
|
||||
"legacyClient", String.valueOf(legacyClient))
|
||||
.increment();
|
||||
|
||||
if (enforceLimit) {
|
||||
throw new RateLimitChallengeException(account.get(), e.getRetryDuration());
|
||||
if (legacyClient) {
|
||||
throw new ServerRejectedException();
|
||||
}
|
||||
throw new RateLimitChallengeException(account.get(), e.getRetryDuration());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* Copyright 2013-2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.controllers;
|
||||
@@ -19,6 +19,7 @@ import io.dropwizard.util.DataSize;
|
||||
import io.lettuce.core.ScriptOutputType;
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
import io.micrometer.core.instrument.Tags;
|
||||
import java.io.IOException;
|
||||
import java.security.MessageDigest;
|
||||
import java.time.Duration;
|
||||
@@ -56,7 +57,6 @@ import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
import io.micrometer.core.instrument.Tags;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -265,20 +265,18 @@ public class MessageController {
|
||||
unsealedSenderRateLimiter.validate(source.get(), destination.get());
|
||||
} catch (final RateLimitExceededException e) {
|
||||
|
||||
final boolean enforceLimit = rateLimitChallengeManager.shouldIssueRateLimitChallenge(userAgent);
|
||||
final boolean legacyClient = rateLimitChallengeManager.isClientBelowMinimumVersion(userAgent);
|
||||
|
||||
Metrics.counter(REJECT_UNSEALED_SENDER_COUNTER_NAME,
|
||||
SENDER_COUNTRY_TAG_NAME, senderCountryCode,
|
||||
"enforced", String.valueOf(enforceLimit))
|
||||
SENDER_COUNTRY_TAG_NAME, senderCountryCode,
|
||||
"legacyClient", String.valueOf(legacyClient))
|
||||
.increment();
|
||||
|
||||
if (enforceLimit) {
|
||||
logger.debug("Rejected unsealed sender limit from: {}", source.get().getNumber());
|
||||
|
||||
throw new RateLimitChallengeException(source.get(), e.getRetryDuration());
|
||||
} else {
|
||||
if (legacyClient) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
throw new RateLimitChallengeException(source.get(), e.getRetryDuration());
|
||||
}
|
||||
|
||||
final String destinationCountryCode = Util.getCountryCode(destination.get().getNumber());
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright 2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.controllers;
|
||||
|
||||
public class ServerRejectedException extends Exception {
|
||||
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
/*
|
||||
* Copyright 2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.limits;
|
||||
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import java.time.Duration;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
|
||||
public class RateLimitChallengeException extends Exception {
|
||||
|
||||
@@ -20,4 +25,5 @@ public class RateLimitChallengeException extends Exception {
|
||||
public Duration getRetryAfter() {
|
||||
return retryAfter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -95,15 +95,15 @@ public class RateLimitChallengeManager {
|
||||
unsealedSenderRateLimiter.handleRateLimitReset(account);
|
||||
}
|
||||
|
||||
public boolean shouldIssueRateLimitChallenge(final String userAgent) {
|
||||
public boolean isClientBelowMinimumVersion(final String userAgent) {
|
||||
try {
|
||||
final UserAgent client = UserAgentUtil.parseUserAgentString(userAgent);
|
||||
final Optional<Semver> minimumClientVersion = dynamicConfigurationManager.getConfiguration()
|
||||
.getRateLimitChallengeConfiguration()
|
||||
.getMinimumSupportedVersion(client.getPlatform());
|
||||
|
||||
return minimumClientVersion.map(version -> version.isLowerThanOrEqualTo(client.getVersion()))
|
||||
.orElse(false);
|
||||
return minimumClientVersion.map(version -> version.isGreaterThan(client.getVersion()))
|
||||
.orElse(true);
|
||||
} catch (final UnrecognizedUserAgentException ignored) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
/*
|
||||
* Copyright 2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.mappers;
|
||||
|
||||
import org.whispersystems.textsecuregcm.entities.RateLimitChallenge;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimitChallengeManager;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimitChallengeException;
|
||||
import java.util.UUID;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.ext.ExceptionMapper;
|
||||
import java.util.UUID;
|
||||
import org.whispersystems.textsecuregcm.entities.RateLimitChallenge;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimitChallengeException;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimitChallengeManager;
|
||||
|
||||
public class RateLimitChallengeExceptionMapper implements ExceptionMapper<RateLimitChallengeException> {
|
||||
|
||||
@@ -18,8 +23,10 @@ public class RateLimitChallengeExceptionMapper implements ExceptionMapper<RateLi
|
||||
@Override
|
||||
public Response toResponse(final RateLimitChallengeException exception) {
|
||||
return Response.status(428)
|
||||
.entity(new RateLimitChallenge(UUID.randomUUID().toString(), rateLimitChallengeManager.getChallengeOptions(exception.getAccount())))
|
||||
.entity(new RateLimitChallenge(UUID.randomUUID().toString(),
|
||||
rateLimitChallengeManager.getChallengeOptions(exception.getAccount())))
|
||||
.header("Retry-After", exception.getRetryAfter().toSeconds())
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.mappers;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.ext.ExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.controllers.ServerRejectedException;
|
||||
|
||||
public class ServerRejectedExceptionMapper implements ExceptionMapper<ServerRejectedException> {
|
||||
|
||||
@Override
|
||||
public Response toResponse(final ServerRejectedException exception) {
|
||||
return Response.status(508).build();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user