Add a counter for cases where clients use both an authenticated identity and UAK when fetching profiles

This commit is contained in:
Jon Chambers 2025-04-24 10:02:46 -04:00 committed by Jon Chambers
parent 144c4c9223
commit f62f79c95c
1 changed files with 19 additions and 7 deletions

View File

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