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 4bfa9cd83..0480f1c62 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java @@ -36,8 +36,8 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.Optional; -import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.function.Function; @@ -371,24 +371,21 @@ public class ProfileController { private void checkFingerprintAndAdd(BatchIdentityCheckRequest.Element element, Collection responseElements, MessageDigest md) { - final Optional maybeAccount = accountsManager.getByServiceIdentifier(element.uuid()); + final ServiceIdentifier identifier = Objects.requireNonNullElse(element.uuid(), element.aci()); + final Optional maybeAccount = accountsManager.getByServiceIdentifier(identifier); maybeAccount.ifPresent(account -> { - if (account.getIdentityKey() == null || account.getPhoneNumberIdentityKey() == null) { + final IdentityKey identityKey = account.getIdentityKey(identifier.identityType()); + if (identityKey == null) { return; } - final IdentityKey identityKey = switch (element.uuid().identityType()) { - case ACI -> account.getIdentityKey(); - case PNI -> account.getPhoneNumberIdentityKey(); - }; - md.reset(); byte[] digest = md.digest(identityKey.serialize()); byte[] fingerprint = Util.truncate(digest, 4); if (!Arrays.equals(fingerprint, element.fingerprint())) { - responseElements.add(new BatchIdentityCheckResponse.Element(element.uuid(), identityKey)); + responseElements.add(new BatchIdentityCheckResponse.Element(element.uuid(), element.aci(), identityKey)); } }); } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/BatchIdentityCheckRequest.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/BatchIdentityCheckRequest.java index 9b69b787c..45ab13963 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/BatchIdentityCheckRequest.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/entities/BatchIdentityCheckRequest.java @@ -8,9 +8,11 @@ package org.whispersystems.textsecuregcm.entities; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import java.util.List; +import javax.annotation.Nullable; import javax.validation.Valid; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; +import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier; import org.whispersystems.textsecuregcm.identity.ServiceIdentifier; import org.whispersystems.textsecuregcm.util.ExactlySize; import org.whispersystems.textsecuregcm.util.ServiceIdentifierAdapter; @@ -22,13 +24,30 @@ public record BatchIdentityCheckRequest(@Valid @NotNull @Size(max = 1000) List elements) { public record Element(@JsonInclude(JsonInclude.Include.NON_EMPTY) @JsonSerialize(using = ServiceIdentifierAdapter.ServiceIdentifierSerializer.class) @JsonDeserialize(using = ServiceIdentifierAdapter.ServiceIdentifierDeserializer.class) - @NotNull + @Nullable ServiceIdentifier uuid, + @JsonInclude(JsonInclude.Include.NON_EMPTY) + @JsonSerialize(using = ServiceIdentifierAdapter.ServiceIdentifierSerializer.class) + @JsonDeserialize(using = ServiceIdentifierAdapter.AciServiceIdentifierDeserializer.class) + @Nullable + @Deprecated // remove after 2023-11-01 + ServiceIdentifier aci, + @NotNull @JsonSerialize(using = IdentityKeyAdapter.Serializer.class) @JsonDeserialize(using = IdentityKeyAdapter.Deserializer.class) IdentityKey identityKey) { + + public Element { + if (aci == null && uuid == null) { + throw new IllegalArgumentException("aci and uuid cannot both be null"); + } + + if (aci != null && uuid != null) { + throw new IllegalArgumentException("aci and uuid cannot both be non-null"); + } + } } } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ProfileControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ProfileControllerTest.java index fa9effe74..ac7bdfc43 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ProfileControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ProfileControllerTest.java @@ -38,6 +38,7 @@ import java.util.Collections; import java.util.HexFormat; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.UUID; import java.util.concurrent.Executors; @@ -91,6 +92,7 @@ import org.whispersystems.textsecuregcm.entities.ExpiringProfileKeyCredentialPro import org.whispersystems.textsecuregcm.entities.ProfileAvatarUploadAttributes; import org.whispersystems.textsecuregcm.entities.VersionedProfileResponse; import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier; +import org.whispersystems.textsecuregcm.identity.IdentityType; import org.whispersystems.textsecuregcm.identity.PniServiceIdentifier; import org.whispersystems.textsecuregcm.identity.ServiceIdentifier; import org.whispersystems.textsecuregcm.limits.RateLimiter; @@ -192,7 +194,9 @@ class ProfileControllerTest { profileAccount = mock(Account.class); when(profileAccount.getIdentityKey()).thenReturn(ACCOUNT_TWO_IDENTITY_KEY); + when(profileAccount.getIdentityKey(IdentityType.ACI)).thenReturn(ACCOUNT_TWO_IDENTITY_KEY); when(profileAccount.getPhoneNumberIdentityKey()).thenReturn(ACCOUNT_TWO_PHONE_NUMBER_IDENTITY_KEY); + when(profileAccount.getIdentityKey(IdentityType.PNI)).thenReturn(ACCOUNT_TWO_PHONE_NUMBER_IDENTITY_KEY); when(profileAccount.getUuid()).thenReturn(AuthHelper.VALID_UUID_TWO); when(profileAccount.getPhoneNumberIdentifier()).thenReturn(AuthHelper.VALID_PNI_TWO); when(profileAccount.isEnabled()).thenReturn(true); @@ -207,7 +211,9 @@ class ProfileControllerTest { when(capabilitiesAccount.getUuid()).thenReturn(AuthHelper.VALID_UUID); when(capabilitiesAccount.getIdentityKey()).thenReturn(ACCOUNT_IDENTITY_KEY); + when(capabilitiesAccount.getIdentityKey(IdentityType.ACI)).thenReturn(ACCOUNT_IDENTITY_KEY); when(capabilitiesAccount.getPhoneNumberIdentityKey()).thenReturn(ACCOUNT_PHONE_NUMBER_IDENTITY_KEY); + when(capabilitiesAccount.getIdentityKey(IdentityType.PNI)).thenReturn(ACCOUNT_PHONE_NUMBER_IDENTITY_KEY); when(capabilitiesAccount.isEnabled()).thenReturn(true); when(capabilitiesAccount.isSenderKeySupported()).thenReturn(true); when(capabilitiesAccount.isAnnouncementGroupSupported()).thenReturn(true); @@ -1164,13 +1170,13 @@ class ProfileControllerTest { void testBatchIdentityCheck() { try (final Response response = resources.getJerseyTest().target("/v1/profile/identity_check/batch").request() .post(Entity.json(new BatchIdentityCheckRequest(List.of( - new BatchIdentityCheckRequest.Element(new AciServiceIdentifier(AuthHelper.VALID_UUID), + new BatchIdentityCheckRequest.Element(new AciServiceIdentifier(AuthHelper.VALID_UUID), null, convertKeyToFingerprint(ACCOUNT_IDENTITY_KEY)), - new BatchIdentityCheckRequest.Element(new PniServiceIdentifier(AuthHelper.VALID_PNI_TWO), + new BatchIdentityCheckRequest.Element(new PniServiceIdentifier(AuthHelper.VALID_PNI_TWO), null, convertKeyToFingerprint(ACCOUNT_TWO_PHONE_NUMBER_IDENTITY_KEY)), - new BatchIdentityCheckRequest.Element(new AciServiceIdentifier(AuthHelper.VALID_UUID_TWO), + new BatchIdentityCheckRequest.Element(null, new AciServiceIdentifier(AuthHelper.VALID_UUID_TWO), convertKeyToFingerprint(ACCOUNT_TWO_IDENTITY_KEY)), - new BatchIdentityCheckRequest.Element(new AciServiceIdentifier(AuthHelper.INVALID_UUID), + new BatchIdentityCheckRequest.Element(new AciServiceIdentifier(AuthHelper.INVALID_UUID), null, convertKeyToFingerprint(ACCOUNT_TWO_PHONE_NUMBER_IDENTITY_KEY)) ))))) { assertThat(response).isNotNull(); @@ -1186,7 +1192,8 @@ class ProfileControllerTest { new AciServiceIdentifier(AuthHelper.VALID_UUID_TWO), ACCOUNT_TWO_IDENTITY_KEY); final Condition isAnExpectedUuid = - new Condition<>(element -> element.identityKey().equals(expectedIdentityKeys.get(element.uuid())), + new Condition<>(element -> element.identityKey() + .equals(expectedIdentityKeys.get(Objects.requireNonNullElse(element.uuid(), element.aci()))), "is an expected UUID with the correct identity key"); final IdentityKey validAciIdentityKey = new IdentityKey(Curve.generateKeyPair().getPublicKey()); @@ -1196,13 +1203,13 @@ class ProfileControllerTest { try (final Response response = resources.getJerseyTest().target("/v1/profile/identity_check/batch").request() .post(Entity.json(new BatchIdentityCheckRequest(List.of( - new BatchIdentityCheckRequest.Element(new AciServiceIdentifier(AuthHelper.VALID_UUID), + new BatchIdentityCheckRequest.Element(new AciServiceIdentifier(AuthHelper.VALID_UUID), null, convertKeyToFingerprint(validAciIdentityKey)), - new BatchIdentityCheckRequest.Element(new PniServiceIdentifier(AuthHelper.VALID_PNI_TWO), + new BatchIdentityCheckRequest.Element(new PniServiceIdentifier(AuthHelper.VALID_PNI_TWO), null, convertKeyToFingerprint(secondValidPniIdentityKey)), - new BatchIdentityCheckRequest.Element(new AciServiceIdentifier(AuthHelper.VALID_UUID_TWO), + new BatchIdentityCheckRequest.Element(null, new AciServiceIdentifier(AuthHelper.VALID_UUID_TWO), convertKeyToFingerprint(secondValidAciIdentityKey)), - new BatchIdentityCheckRequest.Element(new AciServiceIdentifier(AuthHelper.INVALID_UUID), + new BatchIdentityCheckRequest.Element(new AciServiceIdentifier(AuthHelper.INVALID_UUID), null, convertKeyToFingerprint(invalidAciIdentityKey)) ))))) { assertThat(response).isNotNull(); @@ -1216,13 +1223,17 @@ class ProfileControllerTest { } final List largeElementList = new ArrayList<>(List.of( - new BatchIdentityCheckRequest.Element(new AciServiceIdentifier(AuthHelper.VALID_UUID), convertKeyToFingerprint(validAciIdentityKey)), - new BatchIdentityCheckRequest.Element(new PniServiceIdentifier(AuthHelper.VALID_PNI_TWO), convertKeyToFingerprint(secondValidPniIdentityKey)), - new BatchIdentityCheckRequest.Element(new AciServiceIdentifier(AuthHelper.INVALID_UUID), convertKeyToFingerprint(invalidAciIdentityKey)))); + new BatchIdentityCheckRequest.Element(new AciServiceIdentifier(AuthHelper.VALID_UUID), null, + convertKeyToFingerprint(validAciIdentityKey)), + new BatchIdentityCheckRequest.Element(new PniServiceIdentifier(AuthHelper.VALID_PNI_TWO), null, + convertKeyToFingerprint(secondValidPniIdentityKey)), + new BatchIdentityCheckRequest.Element(new AciServiceIdentifier(AuthHelper.INVALID_UUID), null, + convertKeyToFingerprint(invalidAciIdentityKey)))); for (int i = 0; i < 900; i++) { largeElementList.add( - new BatchIdentityCheckRequest.Element(new AciServiceIdentifier(UUID.randomUUID()), convertKeyToFingerprint(new IdentityKey(Curve.generateKeyPair().getPublicKey())))); + new BatchIdentityCheckRequest.Element(new AciServiceIdentifier(UUID.randomUUID()), null, + convertKeyToFingerprint(new IdentityKey(Curve.generateKeyPair().getPublicKey())))); } try (final Response response = resources.getJerseyTest().target("/v1/profile/identity_check/batch").request() @@ -1300,7 +1311,7 @@ class ProfileControllerTest { ] } """, Base64.getEncoder().encodeToString(convertKeyToFingerprint(new IdentityKey(Curve.generateKeyPair().getPublicKey())))), - 422), + 400), Arguments.of( // a blank string is invalid String.format(""" {