diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java index f07f291b6..e6420b704 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java @@ -97,6 +97,7 @@ import org.whispersystems.websocket.auth.Mutable; import org.whispersystems.websocket.auth.ReadOnly; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; +import javax.annotation.Nullable; @SuppressWarnings("OptionalUsedAsFieldOrParameterType") @Path("/v1/profile") @@ -123,6 +124,7 @@ public class ProfileController { private static final String EXPIRING_PROFILE_KEY_CREDENTIAL_TYPE = "expiringProfileKey"; private static final String VERSION_NOT_FOUND_COUNTER_NAME = name(ProfileController.class, "versionNotFound"); + private static final String DUPLICATE_AUTHENTICATION_COUNTER_NAME = name(ProfileController.class, "duplicateAuthentication"); public ProfileController( Clock clock, @@ -230,11 +232,12 @@ public class ProfileController { @HeaderParam(HeaderUtils.UNIDENTIFIED_ACCESS_KEY) Optional accessKey, @Context ContainerRequestContext containerRequestContext, @PathParam("identifier") AciServiceIdentifier accountIdentifier, - @PathParam("version") String version) + @PathParam("version") String version, + @HeaderParam(HttpHeaders.USER_AGENT) String userAgent) throws RateLimitExceededException { final Optional maybeRequester = auth.map(AuthenticatedDevice::getAccount); - final Account targetAccount = verifyPermissionToReceiveProfile(maybeRequester, accessKey, accountIdentifier); + final Account targetAccount = verifyPermissionToReceiveProfile(maybeRequester, accessKey, accountIdentifier, "getVersionedProfile", userAgent); return buildVersionedProfileResponse(targetAccount, version, @@ -253,7 +256,8 @@ public class ProfileController { @PathParam("identifier") AciServiceIdentifier accountIdentifier, @PathParam("version") String version, @PathParam("credentialRequest") String credentialRequest, - @QueryParam("credentialType") String credentialType) + @QueryParam("credentialType") String credentialType, + @HeaderParam(HttpHeaders.USER_AGENT) String userAgent) throws RateLimitExceededException { if (!EXPIRING_PROFILE_KEY_CREDENTIAL_TYPE.equals(credentialType)) { @@ -261,7 +265,7 @@ public class ProfileController { } final Optional maybeRequester = auth.map(AuthenticatedDevice::getAccount); - final Account targetAccount = verifyPermissionToReceiveProfile(maybeRequester, accessKey, accountIdentifier); + final Account targetAccount = verifyPermissionToReceiveProfile(maybeRequester, accessKey, accountIdentifier, "credentialRequest", userAgent); final boolean isSelf = maybeRequester.map(requester -> ProfileHelper.isSelfProfileRequest(requester.getUuid(), accountIdentifier)).orElse(false); return buildExpiringProfileKeyCredentialProfileResponse(targetAccount, @@ -303,7 +307,7 @@ public class ProfileController { } } else { targetAccount = verifyPermissionToReceiveProfile( - maybeRequester, accessKey.filter(ignored -> identifier.identityType() == IdentityType.ACI), identifier); + maybeRequester, accessKey.filter(ignored -> identifier.identityType() == IdentityType.ACI), identifier, "getUnversionedProfile", userAgent); } return switch (identifier.identityType()) { case ACI -> buildBaseProfileResponseForAccountIdentity(targetAccount, @@ -386,7 +390,7 @@ public class ProfileController { profileKeyCredentialResponse = ProfileHelper.getExpiringProfileKeyCredential(HexFormat.of().parseHex(encodedCredentialRequest), profile, new ServiceId.Aci(account.getUuid()), zkProfileOperations); } catch (VerificationFailedException | InvalidInputException e) { - throw new BadRequestException(Response.status(Response.Status.BAD_REQUEST).build(), e); + throw new BadRequestException(e); } return profileKeyCredentialResponse; }) @@ -474,7 +478,15 @@ public class ProfileController { */ private Account verifyPermissionToReceiveProfile(final Optional maybeRequester, final Optional maybeAccessKey, - final ServiceIdentifier accountIdentifier) throws RateLimitExceededException { + final ServiceIdentifier accountIdentifier, + final String endpoint, + @Nullable final String userAgent) throws RateLimitExceededException { + + if (maybeRequester.isPresent() && maybeAccessKey.isPresent()) { + Metrics.counter(DUPLICATE_AUTHENTICATION_COUNTER_NAME, + Tags.of(UserAgentTagUtil.getPlatformTag(userAgent), io.micrometer.core.instrument.Tag.of("endpoint", endpoint))) + .increment(); + } if (maybeRequester.isPresent()) { rateLimiters.getProfileLimiter().validate(maybeRequester.get().getUuid());