diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java index 2535c4825..51c0d2bc9 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java @@ -271,6 +271,7 @@ public class Account { case PRIMARY_DEVICE -> getPrimaryDevice().hasCapability(capability); case ANY_DEVICE -> devices.stream().anyMatch(device -> device.hasCapability(capability)); case ALL_DEVICES -> devices.stream().allMatch(device -> device.hasCapability(capability)); + case ALWAYS_CAPABLE -> true; }; } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/DeviceCapability.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/DeviceCapability.java index 1b43f35fc..d59f3ea22 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/DeviceCapability.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/DeviceCapability.java @@ -10,8 +10,10 @@ import java.util.Optional; public enum DeviceCapability { STORAGE("storage", AccountCapabilityMode.ANY_DEVICE, false, false), TRANSFER("transfer", AccountCapabilityMode.PRIMARY_DEVICE, false, false), - DELETE_SYNC("deleteSync", AccountCapabilityMode.ALL_DEVICES, true, true), - STORAGE_SERVICE_RECORD_KEY_ROTATION("ssre2", AccountCapabilityMode.ALL_DEVICES, true, true), + @Deprecated(forRemoval = true) // Can be removed sometime after 11/10/2025 + DELETE_SYNC("deleteSync", AccountCapabilityMode.ALWAYS_CAPABLE, true, true), + @Deprecated(forRemoval = true) // Can be removed sometime after 11/10/2025 + STORAGE_SERVICE_RECORD_KEY_ROTATION("ssre2", AccountCapabilityMode.ALWAYS_CAPABLE, true, true), ATTACHMENT_BACKFILL("attachmentBackfill", AccountCapabilityMode.PRIMARY_DEVICE, false, true), SPARSE_POST_QUANTUM_RATCHET("spqr", AccountCapabilityMode.ALL_DEVICES, true, true); @@ -28,6 +30,11 @@ public enum DeviceCapability { * The account will have the capability iff all devices on the account have the capability */ ALL_DEVICES, + /** + * The account always has this capability, regardless of the constituent devices' capabilities. + * This supports retiring capabilities where older clients still need the field provided. + */ + ALWAYS_CAPABLE, } private final String name; 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 81cc49e96..54ba1c2ad 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ProfileControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ProfileControllerTest.java @@ -442,9 +442,7 @@ class ProfileControllerTest { @CartesianTest void testProfileCapabilities( - @CartesianTest.Values(booleans = {true, false}) final boolean isDeleteSyncSupported, @CartesianTest.Values(booleans = {true, false}) final boolean isAttachmentBackfillSupported) { - when(capabilitiesAccount.hasCapability(DeviceCapability.DELETE_SYNC)).thenReturn(isDeleteSyncSupported); when(capabilitiesAccount.hasCapability(DeviceCapability.ATTACHMENT_BACKFILL)).thenReturn(isAttachmentBackfillSupported); final BaseProfileResponse profile = resources.getJerseyTest() .target("/v1/profile/" + AuthHelper.VALID_UUID) @@ -452,7 +450,6 @@ class ProfileControllerTest { .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) .get(BaseProfileResponse.class); - assertEquals(isDeleteSyncSupported, profile.getCapabilities().get("deleteSync")); assertEquals(isAttachmentBackfillSupported, profile.getCapabilities().get("attachmentBackfill")); } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/ProfileGrpcServiceTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/ProfileGrpcServiceTest.java index e02d42686..3729d1f13 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/ProfileGrpcServiceTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/ProfileGrpcServiceTest.java @@ -100,7 +100,6 @@ import org.whispersystems.textsecuregcm.s3.PostPolicyGenerator; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountBadge; import org.whispersystems.textsecuregcm.storage.AccountsManager; -import org.whispersystems.textsecuregcm.storage.DeviceCapability; import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; import org.whispersystems.textsecuregcm.storage.ProfilesManager; import org.whispersystems.textsecuregcm.storage.VersionedProfile; @@ -451,7 +450,6 @@ public class ProfileGrpcServiceTest extends SimpleBaseGrpcTest