diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileUploadJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileUploadJob.java index a9e68df9a6..001f16c024 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileUploadJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileUploadJob.java @@ -53,7 +53,7 @@ public final class ProfileUploadJob extends BaseJob { String avatarPath = null; try (StreamDetails avatar = AvatarHelper.getSelfProfileAvatarStream(context)) { - if (FeatureFlags.VERSIONED_PROFILES) { + if (FeatureFlags.versionedProfiles()) { avatarPath = accountManager.setVersionedProfile(Recipient.self().getUuid().get(), profileKey, profileName.serialize(), avatar).orNull(); } else { accountManager.setProfileName(profileKey, profileName.serialize()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshOwnProfileJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshOwnProfileJob.java index cce8b31811..4eac490ef7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshOwnProfileJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshOwnProfileJob.java @@ -93,7 +93,7 @@ public class RefreshOwnProfileJob extends BaseJob { } private static SignalServiceProfile.RequestType getRequestType(@NonNull Recipient recipient) { - return FeatureFlags.VERSIONED_PROFILES && !recipient.hasProfileKeyCredential() + return FeatureFlags.versionedProfiles() && !recipient.hasProfileKeyCredential() ? SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL : SignalServiceProfile.RequestType.PROFILE; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java index 595f709d94..906ac771db 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java @@ -145,7 +145,7 @@ public class RetrieveProfileJob extends BaseJob { } private static SignalServiceProfile.RequestType getRequestType(@NonNull Recipient recipient) { - return FeatureFlags.VERSIONED_PROFILES && !recipient.hasProfileKeyCredential() + return FeatureFlags.versionedProfiles() && !recipient.hasProfileKeyCredential() ? SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL : SignalServiceProfile.RequestType.PROFILE; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RotateProfileKeyJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RotateProfileKeyJob.java index 55e374ba96..a6312e2529 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RotateProfileKeyJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RotateProfileKeyJob.java @@ -57,7 +57,7 @@ public class RotateProfileKeyJob extends BaseJob { recipientDatabase.setProfileKey(self.getId(), profileKey); try (StreamDetails avatarStream = AvatarHelper.getSelfProfileAvatarStream(context)) { - if (FeatureFlags.VERSIONED_PROFILES) { + if (FeatureFlags.versionedProfiles()) { accountManager.setVersionedProfile(self.getUuid().get(), profileKey, Recipient.self().getProfileName().serialize(), diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java index 4a068c2940..ae5b54385e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java @@ -11,6 +11,7 @@ import com.google.android.collect.Sets; import org.json.JSONException; import org.json.JSONObject; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.jobs.ProfileUploadJob; import org.thoughtcrime.securesms.jobs.RefreshAttributesJob; import org.thoughtcrime.securesms.jobs.RemoteConfigRefreshJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; @@ -59,8 +60,9 @@ public final class FeatureFlags { private static final String REMOTE_DELETE = "android.remoteDelete"; private static final String PROFILE_FOR_CALLING = "android.profileForCalling"; private static final String CALLING_PIP = "android.callingPip"; - private static final String NEW_GROUP_UI = "android.newGroupUI"; private static final String REACT_WITH_ANY_EMOJI = "android.reactWithAnyEmoji"; + private static final String NEW_GROUP_UI = "android.newGroupUI"; + private static final String VERSIONED_PROFILES = "android.versionedProfiles"; private static final String GROUPS_V2 = "android.groupsv2"; private static final String GROUPS_V2_CREATE = "android.groupsv2.create"; @@ -81,7 +83,8 @@ public final class FeatureFlags { PROFILE_FOR_CALLING, CALLING_PIP, NEW_GROUP_UI, - REACT_WITH_ANY_EMOJI + REACT_WITH_ANY_EMOJI, + VERSIONED_PROFILES ); /** @@ -113,6 +116,7 @@ public final class FeatureFlags { private static final Set STICKY = Sets.newHashSet( PINS_FOR_ALL_LEGACY, PINS_FOR_ALL, + VERSIONED_PROFILES, GROUPS_V2 ); @@ -128,8 +132,9 @@ public final class FeatureFlags { * desired test state. */ private static final Map FLAG_CHANGE_LISTENERS = new HashMap() {{ - put(MESSAGE_REQUESTS, (change) -> SignalStore.setMessageRequestEnableTime(change == Change.ENABLED ? System.currentTimeMillis() : 0)); - put(GROUPS_V2, (change) -> ApplicationDependencies.getJobManager().add(new RefreshAttributesJob())); + put(MESSAGE_REQUESTS, (change) -> SignalStore.setMessageRequestEnableTime(change == Change.ENABLED ? System.currentTimeMillis() : 0)); + put(VERSIONED_PROFILES, (change) -> ApplicationDependencies.getJobManager().add(new ProfileUploadJob())); + put(GROUPS_V2, (change) -> ApplicationDependencies.getJobManager().add(new RefreshAttributesJob())); }}; private static final Map REMOTE_VALUES = new TreeMap<>(); @@ -260,9 +265,14 @@ public final class FeatureFlags { return getBoolean(REACT_WITH_ANY_EMOJI, false); } + /** Read and write versioned profile information. */ + public static boolean versionedProfiles() { + return getBoolean(VERSIONED_PROFILES, false); + } + /** Groups v2 send and receive. */ public static boolean groupsV2() { - return getBoolean(GROUPS_V2, false); + return versionedProfiles() && getBoolean(GROUPS_V2, false); } /** Groups v2 send and receive. */ @@ -504,7 +514,4 @@ public final class FeatureFlags { enum Change { ENABLED, DISABLED, CHANGED, REMOVED } - - /** Read and write versioned profile information. */ - public static final boolean VERSIONED_PROFILES = org.whispersystems.signalservice.FeatureFlags.VERSIONED_PROFILES; } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/FeatureFlags.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/FeatureFlags.java index 8dfb512cc9..f324922f1d 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/FeatureFlags.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/FeatureFlags.java @@ -6,6 +6,6 @@ package org.whispersystems.signalservice; */ public final class FeatureFlags { - /** Read and write versioned profile information. */ - public static final boolean VERSIONED_PROFILES = false; + /** Prevent usage of non-versioned profile endpoints. */ + public static final boolean DISALLOW_OLD_PROFILE_SETTING = false; } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java index adcdfbbefc..b11aa1bb46 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java @@ -648,7 +648,7 @@ public class SignalServiceAccountManager { public void setProfileName(ProfileKey key, String name) throws IOException { - if (FeatureFlags.VERSIONED_PROFILES) { + if (FeatureFlags.DISALLOW_OLD_PROFILE_SETTING) { throw new AssertionError(); } @@ -662,7 +662,7 @@ public class SignalServiceAccountManager { public Optional setProfileAvatar(ProfileKey key, StreamDetails avatar) throws IOException { - if (FeatureFlags.VERSIONED_PROFILES) { + if (FeatureFlags.DISALLOW_OLD_PROFILE_SETTING) { throw new AssertionError(); } @@ -684,10 +684,6 @@ public class SignalServiceAccountManager { public Optional setVersionedProfile(UUID uuid, ProfileKey profileKey, String name, StreamDetails avatar) throws IOException { - if (!FeatureFlags.VERSIONED_PROFILES) { - throw new AssertionError(); - } - if (name == null) name = ""; byte[] ciphertextName = new ProfileCipher(profileKey).encryptName(name.getBytes(StandardCharsets.UTF_8), ProfileCipher.NAME_PADDED_LENGTH); @@ -711,7 +707,7 @@ public class SignalServiceAccountManager { public Optional resolveProfileKeyCredential(UUID uuid, ProfileKey profileKey) throws NonSuccessfulResponseCodeException, PushNetworkException, VerificationFailedException { - return this.pushServiceSocket.retrieveProfile(uuid, profileKey, Optional.absent()).getProfileKeyCredential(); + return this.pushServiceSocket.retrieveVersionedProfileAndCredential(uuid, profileKey, Optional.absent()).getProfileKeyCredential(); } public void setUsername(String username) throws IOException { diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessagePipe.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessagePipe.java index c279db6e53..bfa3079e24 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessagePipe.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessagePipe.java @@ -19,7 +19,6 @@ import org.whispersystems.libsignal.InvalidVersionException; import org.whispersystems.libsignal.util.Hex; import org.whispersystems.libsignal.util.Pair; import org.whispersystems.libsignal.util.guava.Optional; -import org.whispersystems.signalservice.FeatureFlags; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.api.profiles.ProfileAndCredential; @@ -187,16 +186,21 @@ public class SignalServiceMessagePipe { .setVerb("GET") .addAllHeaders(headers); - if (FeatureFlags.VERSIONED_PROFILES && requestType == SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL && uuid.isPresent() && profileKey.isPresent()) { - UUID target = uuid.get(); - ProfileKeyVersion profileKeyIdentifier = profileKey.get().getProfileKeyVersion(target); - requestContext = clientZkProfile.createProfileKeyCredentialRequestContext(random, target, profileKey.get()); - ProfileKeyCredentialRequest request = requestContext.getRequest(); + if (uuid.isPresent() && profileKey.isPresent()) { + UUID target = uuid.get(); + ProfileKeyVersion profileKeyIdentifier = profileKey.get().getProfileKeyVersion(target); + String version = profileKeyIdentifier.serialize(); - String version = profileKeyIdentifier.serialize(); - String credentialRequest = Hex.toStringCondensed(request.serialize()); + if (requestType == SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL) { + requestContext = clientZkProfile.createProfileKeyCredentialRequestContext(random, target, profileKey.get()); - builder.setPath(String.format("/v1/profile/%s/%s/%s", target, version, credentialRequest)); + ProfileKeyCredentialRequest request = requestContext.getRequest(); + String credentialRequest = Hex.toStringCondensed(request.serialize()); + + builder.setPath(String.format("/v1/profile/%s/%s/%s", target, version, credentialRequest)); + } else { + builder.setPath(String.format("/v1/profile/%s/%s", target, version)); + } } else { builder.setPath(String.format("/v1/profile/%s", address.getIdentifier())); } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java index ff3a031967..abfe814706 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java @@ -9,10 +9,8 @@ package org.whispersystems.signalservice.api; import org.signal.zkgroup.VerificationFailedException; import org.signal.zkgroup.profiles.ClientZkProfileOperations; import org.signal.zkgroup.profiles.ProfileKey; -import org.signal.zkgroup.profiles.ProfileKeyCredential; import org.whispersystems.libsignal.InvalidMessageException; import org.whispersystems.libsignal.util.guava.Optional; -import org.whispersystems.signalservice.FeatureFlags; import org.whispersystems.signalservice.api.crypto.AttachmentCipherInputStream; import org.whispersystems.signalservice.api.crypto.ProfileCipherInputStream; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; @@ -130,12 +128,18 @@ public class SignalServiceMessageReceiver { { Optional uuid = address.getUuid(); - if (FeatureFlags.VERSIONED_PROFILES && requestType == SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL && uuid.isPresent() && profileKey.isPresent()) { - return socket.retrieveProfile(uuid.get(), profileKey.get(), unidentifiedAccess); + if (uuid.isPresent() && profileKey.isPresent()) { + if (requestType == SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL) { + return socket.retrieveVersionedProfileAndCredential(uuid.get(), profileKey.get(), unidentifiedAccess); + } else { + return new ProfileAndCredential(socket.retrieveVersionedProfile(uuid.get(), profileKey.get(), unidentifiedAccess), + SignalServiceProfile.RequestType.PROFILE, + Optional.absent()); + } } else { return new ProfileAndCredential(socket.retrieveProfile(address, unidentifiedAccess), SignalServiceProfile.RequestType.PROFILE, - Optional.absent()); + Optional.absent()); } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/profiles/SignalServiceProfile.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/profiles/SignalServiceProfile.java index da48a91c9c..9f87fd9fea 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/profiles/SignalServiceProfile.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/profiles/SignalServiceProfile.java @@ -131,8 +131,6 @@ public class SignalServiceProfile { } public ProfileKeyCredentialResponse getProfileKeyCredentialResponse() { - if (!FeatureFlags.VERSIONED_PROFILES) return null; - if (credential == null) return null; try { diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java index cf9f576289..f0522cb303 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java @@ -595,13 +595,9 @@ public class PushServiceSocket { } } - public ProfileAndCredential retrieveProfile(UUID target, ProfileKey profileKey, Optional unidentifiedAccess) + public ProfileAndCredential retrieveVersionedProfileAndCredential(UUID target, ProfileKey profileKey, Optional unidentifiedAccess) throws NonSuccessfulResponseCodeException, PushNetworkException, VerificationFailedException { - if (!FeatureFlags.VERSIONED_PROFILES) { - throw new AssertionError(); - } - ProfileKeyVersion profileKeyIdentifier = profileKey.getProfileKeyVersion(target); ProfileKeyCredentialRequestContext requestContext = clientZkProfileOperations.createProfileKeyCredentialRequestContext(random, target, profileKey); ProfileKeyCredentialRequest request = requestContext.getRequest(); @@ -626,6 +622,24 @@ public class PushServiceSocket { } } + public SignalServiceProfile retrieveVersionedProfile(UUID target, ProfileKey profileKey, Optional unidentifiedAccess) + throws NonSuccessfulResponseCodeException, PushNetworkException + { + ProfileKeyVersion profileKeyIdentifier = profileKey.getProfileKeyVersion(target); + + String version = profileKeyIdentifier.serialize(); + String subPath = String.format("%s/%s", target, version); + + String response = makeServiceRequest(String.format(PROFILE_PATH, subPath), "GET", null, NO_HEADERS, unidentifiedAccess); + + try { + return JsonUtil.fromJson(response, SignalServiceProfile.class); + } catch (IOException e) { + Log.w(TAG, e); + throw new NonSuccessfulResponseCodeException("Unable to parse entity"); + } + } + public void retrieveProfileAvatar(String path, File destination, long maxSizeBytes) throws NonSuccessfulResponseCodeException, PushNetworkException { try { @@ -636,7 +650,7 @@ public class PushServiceSocket { } public void setProfileName(String name) throws NonSuccessfulResponseCodeException, PushNetworkException { - if (FeatureFlags.VERSIONED_PROFILES) { + if (FeatureFlags.DISALLOW_OLD_PROFILE_SETTING) { throw new AssertionError(); } @@ -646,7 +660,7 @@ public class PushServiceSocket { public Optional setProfileAvatar(ProfileAvatarData profileAvatar) throws NonSuccessfulResponseCodeException, PushNetworkException { - if (FeatureFlags.VERSIONED_PROFILES) { + if (FeatureFlags.DISALLOW_OLD_PROFILE_SETTING) { throw new AssertionError(); } @@ -680,10 +694,6 @@ public class PushServiceSocket { public Optional writeProfile(SignalServiceProfileWrite signalServiceProfileWrite, ProfileAvatarData profileAvatar) throws NonSuccessfulResponseCodeException, PushNetworkException { - if (!FeatureFlags.VERSIONED_PROFILES) { - throw new AssertionError(); - } - String requestBody = JsonUtil.toJson(signalServiceProfileWrite); ProfileAvatarUploadAttributes formAttributes;