diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountControllerV2.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountControllerV2.java index 94ffcb5d0..38f956ab0 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountControllerV2.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountControllerV2.java @@ -171,6 +171,8 @@ public class AccountControllerV2 { } } + // TODO Remove entirely on or after 2025-10-08 + @Deprecated(forRemoval = true) @PUT @Path("/phone_number_identity_key_distribution") @Consumes(MediaType.APPLICATION_JSON) @@ -181,11 +183,6 @@ public class AccountControllerV2 { @ApiResponse(responseCode = "401", description = "Account authentication check failed.") @ApiResponse(responseCode = "403", description = "This endpoint can only be invoked from the account's primary device.") @ApiResponse(responseCode = "422", description = "The request body failed validation.") - @ApiResponse(responseCode = "409", description = "The set of devices specified in the request does not match the set of devices active on the account.", - content = @Content(schema = @Schema(implementation = MismatchedDevicesResponse.class))) - @ApiResponse(responseCode = "410", description = "The registration IDs provided for some devices do not match those stored on the server.", - content = @Content(schema = @Schema(implementation = StaleDevicesResponse.class))) - @ApiResponse(responseCode = "413", description = "One or more device messages was too large") public AccountIdentityResponse distributePhoneNumberIdentityKeys( @Auth final AuthenticatedDevice authenticatedDevice, @HeaderParam(HttpHeaders.USER_AGENT) @Nullable final String userAgentString, @@ -199,38 +196,8 @@ public class AccountControllerV2 { throw new WebApplicationException("Invalid signature", 422); } - final Account account = accountsManager.getByAccountIdentifier(authenticatedDevice.accountIdentifier()) - .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); - - try { - final Account updatedAccount = changeNumberManager.updatePniKeys( - account, - request.pniIdentityKey(), - request.devicePniSignedPrekeys(), - request.devicePniPqLastResortPrekeys(), - request.deviceMessages(), - request.pniRegistrationIds(), - userAgentString); - - return AccountIdentityResponseBuilder.fromAccount(updatedAccount); - } catch (MismatchedDevicesException e) { - if (!e.getMismatchedDevices().staleDeviceIds().isEmpty()) { - throw new WebApplicationException(Response.status(410) - .type(MediaType.APPLICATION_JSON) - .entity(new StaleDevicesResponse(e.getMismatchedDevices().staleDeviceIds())) - .build()); - } else { - throw new WebApplicationException(Response.status(409) - .type(MediaType.APPLICATION_JSON_TYPE) - .entity(new MismatchedDevicesResponse(e.getMismatchedDevices().missingDeviceIds(), - e.getMismatchedDevices().extraDeviceIds())) - .build()); - } - } catch (IllegalArgumentException e) { - throw new BadRequestException(e); - } catch (MessageTooLargeException e) { - throw new WebApplicationException(Response.Status.REQUEST_ENTITY_TOO_LARGE); - } + return AccountIdentityResponseBuilder.fromAccount(accountsManager.getByAccountIdentifier(authenticatedDevice.accountIdentifier()) + .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED))); } @PUT diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerV2Test.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerV2Test.java index d29b56377..f4f9dd87f 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerV2Test.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerV2Test.java @@ -604,168 +604,6 @@ class AccountControllerV2Test { } } - @Nested - class PhoneNumberIdentityKeyDistribution { - - @BeforeEach - void setUp() throws Exception { - when(accountsManager.getByAccountIdentifier(AuthHelper.VALID_UUID)).thenReturn(Optional.of(AuthHelper.VALID_ACCOUNT)); - - when(changeNumberManager.updatePniKeys(any(), any(), any(), any(), any(), any(), any())).thenAnswer( - (Answer) invocation -> { - final Account account = invocation.getArgument(0); - final IdentityKey pniIdentityKey = invocation.getArgument(1); - - final UUID uuid = account.getUuid(); - final UUID pni = account.getPhoneNumberIdentifier(); - final String number = account.getNumber(); - final List devices = account.getDevices(); - - final Account updatedAccount = mock(Account.class); - when(updatedAccount.getUuid()).thenReturn(uuid); - when(updatedAccount.getNumber()).thenReturn(number); - when(updatedAccount.getIdentityKey(IdentityType.PNI)).thenReturn(pniIdentityKey); - when(updatedAccount.getPhoneNumberIdentifier()).thenReturn(pni); - when(updatedAccount.getDevices()).thenReturn(devices); - - for (byte i = 1; i <= 3; i++) { - final Optional d = account.getDevice(i); - when(updatedAccount.getDevice(i)).thenReturn(d); - } - - return updatedAccount; - }); - } - - @Test - void pniKeyDistributionSuccess() throws Exception { - final AccountIdentityResponse accountIdentityResponse = - resources.getJerseyTest() - .target("/v2/accounts/phone_number_identity_key_distribution") - .request() - .header(HttpHeaders.AUTHORIZATION, - AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) - .put(Entity.json(requestJson()), AccountIdentityResponse.class); - - verify(changeNumberManager).updatePniKeys(eq(AuthHelper.VALID_ACCOUNT), eq(IDENTITY_KEY), any(), any(), any(), any(), any()); - - assertEquals(AuthHelper.VALID_UUID, accountIdentityResponse.uuid()); - assertEquals(AuthHelper.VALID_NUMBER, accountIdentityResponse.number()); - assertEquals(AuthHelper.VALID_PNI, accountIdentityResponse.pni()); - } - - @Test - void unprocessableRequestJson() { - final Invocation.Builder request = resources.getJerseyTest() - .target("/v2/accounts/phone_number_identity_key_distribution") - .request() - .header(HttpHeaders.AUTHORIZATION, - AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)); - try (Response response = request.put(Entity.json(unprocessableJson()))) { - assertEquals(400, response.getStatus()); - } - } - - @Test - void missingBasicAuthorization() { - final Invocation.Builder request = resources.getJerseyTest() - .target("/v2/accounts/phone_number_identity_key_distribution") - .request(); - try (Response response = request.put(Entity.json(requestJson()))) { - assertEquals(401, response.getStatus()); - } - } - - @Test - void invalidBasicAuthorization() { - final Invocation.Builder request = resources.getJerseyTest() - .target("/v2/accounts/phone_number_identity_key_distribution") - .request() - .header(HttpHeaders.AUTHORIZATION, "Basic but-invalid"); - try (Response response = request.put(Entity.json(requestJson()))) { - assertEquals(401, response.getStatus()); - } - } - - @Test - void invalidRequestBody() { - final Invocation.Builder request = resources.getJerseyTest() - .target("/v2/accounts/phone_number_identity_key_distribution") - .request() - .header(HttpHeaders.AUTHORIZATION, - AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)); - try (Response response = request.put(Entity.json(invalidRequestJson()))) { - assertEquals(422, response.getStatus()); - } - } - - @Test - void deviceMessageTooLarge() throws Exception { - reset(changeNumberManager); - when(changeNumberManager.updatePniKeys(any(), any(), any(), any(), any(), any(), any())) - .thenThrow(MessageTooLargeException.class); - - try (final Response response = resources.getJerseyTest() - .target("/v2/accounts/phone_number_identity_key_distribution") - .request() - .header(HttpHeaders.AUTHORIZATION, - AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) - .put(Entity.json(requestJson()))) { - - assertEquals(413, response.getStatus()); - } - } - - /** - * Valid request JSON for a {@link org.whispersystems.textsecuregcm.entities.PhoneNumberIdentityKeyDistributionRequest} - */ - private static String requestJson() { - final ECSignedPreKey pniSignedPreKey = KeysHelper.signedECPreKey(1, IDENTITY_KEY_PAIR); - final KEMSignedPreKey pniLastResortPreKey = KeysHelper.signedKEMPreKey(2, IDENTITY_KEY_PAIR); - - return String.format(""" - { - "pniIdentityKey": "%s", - "deviceMessages": [], - "devicePniSignedPrekeys": {}, - "devicePniSignedPrekeys": {"1": {"keyId": %d, "publicKey": "%s", "signature": "%s"}}, - "devicePniPqLastResortPrekeys": {"1": {"keyId": %d, "publicKey": "%s", "signature": "%s"}}, - "pniRegistrationIds": {"1": 17} - } - """, Base64.getEncoder().encodeToString(IDENTITY_KEY.serialize()), - pniSignedPreKey.keyId(), Base64.getEncoder().encodeToString(pniSignedPreKey.serializedPublicKey()), Base64.getEncoder().encodeToString(pniSignedPreKey.signature()), - pniLastResortPreKey.keyId(), Base64.getEncoder().encodeToString(pniLastResortPreKey.serializedPublicKey()), Base64.getEncoder().encodeToString(pniLastResortPreKey.signature())); - } - - /** - * Request JSON in the shape of {@link org.whispersystems.textsecuregcm.entities.PhoneNumberIdentityKeyDistributionRequest}, but that - * fails validation - */ - private static String invalidRequestJson() { - return """ - { - "pniIdentityKey": null, - "deviceMessages": [], - "devicePniSignedPrekeys": {}, - "pniRegistrationIds": {} - } - """; - } - - /** - * Request JSON that cannot be marshalled into - * {@link org.whispersystems.textsecuregcm.entities.PhoneNumberIdentityKeyDistributionRequest} - */ - private static String unprocessableJson() { - return """ - { - "pniIdentityKey": [] - } - """; - } - - } - @Nested class PhoneNumberDiscoverability {