Exclude ExpiringProfileKeyCredentialProfileResponse.credential for non-current versions

This commit is contained in:
Chris Eager
2026-03-27 17:39:18 -05:00
committed by Chris Eager
parent 5190b197e8
commit 68b3e9a07c
3 changed files with 73 additions and 21 deletions
@@ -434,18 +434,25 @@ public class ProfileController {
final boolean isSelf,
final ContainerRequestContext containerRequestContext) {
final ExpiringProfileKeyCredentialResponse expiringProfileKeyCredentialResponse = profilesManager.get(account.getUuid(), version)
.map(profile -> {
final ExpiringProfileKeyCredentialResponse profileKeyCredentialResponse;
try {
profileKeyCredentialResponse = ProfileHelper.getExpiringProfileKeyCredential(HexFormat.of().parseHex(encodedCredentialRequest),
profile, new ServiceId.Aci(account.getUuid()), zkProfileOperations);
} catch (VerificationFailedException | InvalidInputException e) {
throw new BadRequestException(e);
}
return profileKeyCredentialResponse;
})
.orElse(null);
final ExpiringProfileKeyCredentialResponse expiringProfileKeyCredentialResponse;
if (account.getCurrentProfileVersion().map(version::equals).orElse(false)) {
expiringProfileKeyCredentialResponse = profilesManager.get(account.getUuid(), version)
.map(profile -> {
final ExpiringProfileKeyCredentialResponse profileKeyCredentialResponse;
try {
profileKeyCredentialResponse = ProfileHelper.getExpiringProfileKeyCredential(
HexFormat.of().parseHex(encodedCredentialRequest),
profile, new ServiceId.Aci(account.getUuid()), zkProfileOperations);
} catch (VerificationFailedException | InvalidInputException e) {
throw new BadRequestException(e);
}
return profileKeyCredentialResponse;
})
.orElse(null);
} else {
expiringProfileKeyCredentialResponse = null;
}
return new ExpiringProfileKeyCredentialProfileResponse(
buildVersionedProfileResponse(account, version, isSelf, true, containerRequestContext),
@@ -19,7 +19,7 @@ public class ExpiringProfileKeyCredentialProfileResponse {
@JsonUnwrapped
private VersionedProfileResponse versionedProfileResponse;
@Schema(description = "Expiring profile key credential response. Null if profile version was not found")
@Schema(description = "Expiring profile key credential response. Null if profile version was not found or if the profile version is not current")
@JsonProperty
@JsonSerialize(using = ExpiringProfileKeyCredentialResponseAdapter.Serializing.class)
@JsonDeserialize(using = ExpiringProfileKeyCredentialResponseAdapter.Deserializing.class)
@@ -8,6 +8,7 @@ package org.whispersystems.textsecuregcm.controllers;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.refEq;
import static org.mockito.Mockito.any;
@@ -51,7 +52,6 @@ import java.util.stream.Stream;
import org.assertj.core.api.Condition;
import org.glassfish.jersey.server.ServerProperties;
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -176,7 +176,7 @@ class ProfileControllerTest {
@BeforeEach
void setup() {
reset(profilesManager);
reset(profilesManager, zkProfileOperations, accountsManager, rateLimiter);
clock.pin(Instant.ofEpochSecond(42));
AccountsHelper.setupMockUpdate(accountsManager);
@@ -241,12 +241,6 @@ class ProfileControllerTest {
clearInvocations(zkProfileOperations);
}
@AfterEach
void teardown() {
reset(accountsManager);
reset(rateLimiter);
}
@Test
void testProfileGetByAci() throws RateLimitExceededException {
final BaseProfileResponse profile = resources.getJerseyTest()
@@ -1288,6 +1282,7 @@ class ProfileControllerTest {
when(account.getUuid()).thenReturn(AuthHelper.VALID_UUID);
when(account.getUnidentifiedAccessKey()).thenReturn(Optional.of(UNIDENTIFIED_ACCESS_KEY));
when(account.isIdentifiedBy(new AciServiceIdentifier(AuthHelper.VALID_UUID))).thenReturn(true);
when(account.getCurrentProfileVersion()).thenReturn(Optional.of(version));
when(accountsManager.getByServiceIdentifier(new AciServiceIdentifier(AuthHelper.VALID_UUID))).thenReturn(Optional.of(account));
when(profilesManager.get(AuthHelper.VALID_UUID, version)).thenReturn(Optional.of(versionedProfile));
@@ -1305,6 +1300,56 @@ class ProfileControllerTest {
assertEquals(400, response.getStatus());
}
@Test
void testGetProfileWithExpiringProfileKeyCredentialNonCurrentVersion()
throws VerificationFailedException, InvalidInputException {
final String version = versionHex("version");
final ServerSecretParams serverSecretParams = ServerSecretParams.generate();
final ServerPublicParams serverPublicParams = serverSecretParams.getPublicParams();
final ClientZkProfileOperations clientZkProfile = new ClientZkProfileOperations(serverPublicParams);
final byte[] profileKeyBytes = TestRandomUtil.nextBytes(32);
final ProfileKey profileKey = new ProfileKey(profileKeyBytes);
final ProfileKeyCommitment profileKeyCommitment = profileKey.getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID));
final VersionedProfile versionedProfile = mock(VersionedProfile.class);
when(versionedProfile.commitment()).thenReturn(profileKeyCommitment.serialize());
final String avatar = "avatar";
when(versionedProfile.avatar()).thenReturn(avatar);
final ProfileKeyCredentialRequestContext profileKeyCredentialRequestContext =
clientZkProfile.createProfileKeyCredentialRequestContext(new ServiceId.Aci(AuthHelper.VALID_UUID), profileKey);
final ProfileKeyCredentialRequest credentialRequest = profileKeyCredentialRequestContext.getRequest();
final Account account = mock(Account.class);
when(account.getUuid()).thenReturn(AuthHelper.VALID_UUID);
when(account.getUnidentifiedAccessKey()).thenReturn(Optional.of(UNIDENTIFIED_ACCESS_KEY));
when(account.isIdentifiedBy(new AciServiceIdentifier(AuthHelper.VALID_UUID))).thenReturn(true);
when(account.getCurrentProfileVersion()).thenReturn(Optional.of(versionHex("the-current-version")));
when(accountsManager.getByServiceIdentifier(new AciServiceIdentifier(AuthHelper.VALID_UUID))).thenReturn(Optional.of(account));
when(profilesManager.get(AuthHelper.VALID_UUID, version)).thenReturn(Optional.of(versionedProfile));
final Response response = resources.getJerseyTest()
.target(String.format("/v1/profile/%s/%s/%s", AuthHelper.VALID_UUID, version,
HexFormat.of().formatHex(credentialRequest.serialize())))
.queryParam("credentialType", "expiringProfileKey")
.request()
.headers(new MultivaluedHashMap<>(Map.of(HeaderUtils.UNIDENTIFIED_ACCESS_KEY, Base64.getEncoder().encodeToString(UNIDENTIFIED_ACCESS_KEY))))
.get();
assertEquals(200, response.getStatus());
final ExpiringProfileKeyCredentialProfileResponse expiringProfileKeyCredentialProfileResponse = response.readEntity(
ExpiringProfileKeyCredentialProfileResponse.class);
assertNull(expiringProfileKeyCredentialProfileResponse.getCredential());
assertEquals(avatar, expiringProfileKeyCredentialProfileResponse.getVersionedProfileResponse().avatar());
}
@Test
void testSetProfileBadgesMissingFromRequest() throws InvalidInputException {
final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID));