Add versioned profiles feature flag.

This commit is contained in:
Alan Evans 2020-05-26 16:01:01 -03:00 committed by Greyson Parrelli
parent 28bd245b96
commit 289f7aba63
11 changed files with 67 additions and 48 deletions

View file

@ -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());

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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(),

View file

@ -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<String> 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<String, OnFlagChange> FLAG_CHANGE_LISTENERS = new HashMap<String, OnFlagChange>() {{
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<String, Object> 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;
}

View file

@ -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;
}

View file

@ -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<String> 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<String> 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<ProfileKeyCredential> 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 {

View file

@ -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()));
}

View file

@ -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> 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.<ProfileKeyCredential>absent());
Optional.absent());
}
}

View file

@ -131,8 +131,6 @@ public class SignalServiceProfile {
}
public ProfileKeyCredentialResponse getProfileKeyCredentialResponse() {
if (!FeatureFlags.VERSIONED_PROFILES) return null;
if (credential == null) return null;
try {

View file

@ -595,13 +595,9 @@ public class PushServiceSocket {
}
}
public ProfileAndCredential retrieveProfile(UUID target, ProfileKey profileKey, Optional<UnidentifiedAccess> unidentifiedAccess)
public ProfileAndCredential retrieveVersionedProfileAndCredential(UUID target, ProfileKey profileKey, Optional<UnidentifiedAccess> 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> 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<String> 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<String> writeProfile(SignalServiceProfileWrite signalServiceProfileWrite, ProfileAvatarData profileAvatar)
throws NonSuccessfulResponseCodeException, PushNetworkException
{
if (!FeatureFlags.VERSIONED_PROFILES) {
throw new AssertionError();
}
String requestBody = JsonUtil.toJson(signalServiceProfileWrite);
ProfileAvatarUploadAttributes formAttributes;