diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java index e6409054b..f21829a5d 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java @@ -8,6 +8,8 @@ import com.google.common.net.HttpHeaders; import io.dropwizard.auth.Auth; import io.lettuce.core.SetArgs; +import io.micrometer.core.instrument.Metrics; +import io.micrometer.core.instrument.Tags; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.headers.Header; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -59,6 +61,8 @@ import org.whispersystems.textsecuregcm.entities.PreKeySignatureValidator; import org.whispersystems.textsecuregcm.identity.IdentityType; import org.whispersystems.textsecuregcm.limits.RateLimiters; +import org.whispersystems.textsecuregcm.metrics.MetricsUtil; +import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; @@ -91,6 +95,9 @@ public class DeviceController { @VisibleForTesting static final Duration TOKEN_EXPIRATION_DURATION = Duration.ofMinutes(10); + private static final String MISSING_DEVICE_CAPABILITIES_COUNTER_NAME = + MetricsUtil.name(DeviceController.class, "missingDeviceCapabilities"); + public DeviceController(byte[] linkDeviceSecret, AccountsManager accounts, MessagesManager messages, @@ -175,7 +182,7 @@ public VerificationCode createDeviceToken(@Auth AuthenticatedAccount auth) } /** - * @deprecated callers should use {@link #linkDevice(BasicAuthorizationHeader, LinkDeviceRequest, ContainerRequest)} + * @deprecated callers should use {@link #linkDevice(BasicAuthorizationHeader, String, LinkDeviceRequest, ContainerRequest)} * instead */ @PUT @@ -186,6 +193,7 @@ public VerificationCode createDeviceToken(@Auth AuthenticatedAccount auth) @Deprecated(forRemoval = true) public DeviceResponse verifyDeviceToken(@PathParam("verification_code") String verificationCode, @HeaderParam(HttpHeaders.AUTHORIZATION) BasicAuthorizationHeader authorizationHeader, + @HeaderParam(HttpHeaders.USER_AGENT) final String userAgent, @NotNull @Valid AccountAttributes accountAttributes, @Context ContainerRequest containerRequest) throws RateLimitExceededException, DeviceLimitExceededException { @@ -194,7 +202,8 @@ public DeviceResponse verifyDeviceToken(@PathParam("verification_code") String v verificationCode, accountAttributes, containerRequest, - Optional.empty()); + Optional.empty(), + userAgent); final Account account = accountAndDevice.first(); final Device device = accountAndDevice.second(); @@ -219,6 +228,7 @@ public DeviceResponse verifyDeviceToken(@PathParam("verification_code") String v name = "Retry-After", description = "If present, an positive integer indicating the number of seconds before a subsequent attempt could succeed")) public DeviceResponse linkDevice(@HeaderParam(HttpHeaders.AUTHORIZATION) BasicAuthorizationHeader authorizationHeader, + @HeaderParam(HttpHeaders.USER_AGENT) final String userAgent, @NotNull @Valid LinkDeviceRequest linkDeviceRequest, @Context ContainerRequest containerRequest) throws RateLimitExceededException, DeviceLimitExceededException { @@ -227,7 +237,8 @@ public DeviceResponse linkDevice(@HeaderParam(HttpHeaders.AUTHORIZATION) BasicAu linkDeviceRequest.verificationCode(), linkDeviceRequest.accountAttributes(), containerRequest, - Optional.of(linkDeviceRequest.deviceActivationRequest())); + Optional.of(linkDeviceRequest.deviceActivationRequest()), + userAgent); final Account account = accountAndDevice.first(); final Device device = accountAndDevice.second(); @@ -341,7 +352,8 @@ private Pair createDevice(final String password, final String verificationCode, final AccountAttributes accountAttributes, final ContainerRequest containerRequest, - final Optional maybeDeviceActivationRequest) + final Optional maybeDeviceActivationRequest, + final String userAgent) throws RateLimitExceededException, DeviceLimitExceededException { final Optional maybeAciFromToken = checkVerificationToken(verificationCode); @@ -379,6 +391,14 @@ private Pair createDevice(final String password, } final DeviceCapabilities capabilities = accountAttributes.getCapabilities(); + + if (capabilities == null) { + Metrics.counter(MISSING_DEVICE_CAPABILITIES_COUNTER_NAME, + Tags.of(UserAgentTagUtil.getPlatformTag(userAgent), + io.micrometer.core.instrument.Tag.of("atomic", String.valueOf(maybeDeviceActivationRequest.isPresent())))) + .increment(); + } + if (capabilities != null && isCapabilityDowngrade(account, capabilities)) { throw new WebApplicationException(Response.status(409).build()); } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/RegistrationController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/RegistrationController.java index 00fbaa14d..13cd2838e 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/RegistrationController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/RegistrationController.java @@ -57,10 +57,12 @@ public class RegistrationController { .register(Metrics.globalRegistry); private static final String ACCOUNT_CREATED_COUNTER_NAME = name(RegistrationController.class, "accountCreated"); + private static final String MISSING_DEVICE_CAPABILITIES_COUNTER_NAME = + name(RegistrationController.class, "missingDeviceCapabilities"); + private static final String COUNTRY_CODE_TAG_NAME = "countryCode"; private static final String REGION_CODE_TAG_NAME = "regionCode"; private static final String VERIFICATION_TYPE_TAG_NAME = "verification"; - private static final String INVALID_ACCOUNT_ATTRS_COUNTER_NAME = name(RegistrationController.class, "invalidAccountAttrs"); private final AccountsManager accounts; private final PhoneVerificationTokenManager phoneVerificationTokenManager; @@ -106,6 +108,12 @@ public AccountIdentityResponse register( final String number = authorizationHeader.getUsername(); final String password = authorizationHeader.getPassword(); + if (registrationRequest.accountAttributes().getCapabilities() == null) { + Metrics.counter(MISSING_DEVICE_CAPABILITIES_COUNTER_NAME, + Tags.of(UserAgentTagUtil.getPlatformTag(userAgent))) + .increment(); + } + RateLimiter.adaptLegacyException(() -> rateLimiters.getRegistrationLimiter().validate(number)); final PhoneVerificationRequest.VerificationType verificationType = phoneVerificationTokenManager.verify(number,