mirror of
https://github.com/oxen-io/session-android.git
synced 2025-08-26 05:43:39 +00:00
Versioned Profiles support (disabled).
This commit is contained in:

committed by
Greyson Parrelli

parent
f10d1eac61
commit
7ecb50a3fe
@@ -233,6 +233,7 @@ android {
|
||||
buildConfigField "String", "KEY_BACKUP_ENCLAVE_NAME", "\"fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe\""
|
||||
buildConfigField "String", "KEY_BACKUP_MRENCLAVE", "\"a3baab19ef6ce6f34ab9ebb25ba722725ae44a8872dc0ff08ad6d83a9489de87\""
|
||||
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF\""
|
||||
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"\""
|
||||
buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}'
|
||||
buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode"
|
||||
|
||||
@@ -305,6 +306,7 @@ android {
|
||||
buildConfigField "String", "MRENCLAVE", "\"ba4ebb438bc07713819ee6c98d94037747006d7df63fc9e44d2d6f1fec962a79\""
|
||||
buildConfigField "String", "KEY_BACKUP_ENCLAVE_NAME", "\"a1e9c1d3f352b5c4f0fc7a421b98119e60e5ff703c28fbea85c66bfa7306deab\""
|
||||
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\""
|
||||
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"\""
|
||||
}
|
||||
release {
|
||||
minifyEnabled true
|
||||
|
@@ -23,7 +23,6 @@ import com.annimon.stream.Stream;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.contacts.ContactAccessor;
|
||||
import org.thoughtcrime.securesms.crypto.SessionUtil;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
@@ -37,19 +36,16 @@ import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.thoughtcrime.securesms.service.IncomingMessageObserver;
|
||||
import org.thoughtcrime.securesms.sms.IncomingJoinedMessage;
|
||||
import org.thoughtcrime.securesms.util.ProfileUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessagePipe;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
||||
import org.whispersystems.signalservice.api.push.ContactTokenDetails;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Calendar;
|
||||
@@ -343,25 +339,8 @@ class DirectoryHelperV1 {
|
||||
}
|
||||
|
||||
private static boolean isUuidRegistered(@NonNull Context context, @NonNull Recipient recipient) throws IOException {
|
||||
Optional<UnidentifiedAccessPair> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient);
|
||||
SignalServiceMessagePipe authPipe = IncomingMessageObserver.getPipe();
|
||||
SignalServiceMessagePipe unidentifiedPipe = IncomingMessageObserver.getUnidentifiedPipe();
|
||||
SignalServiceMessagePipe pipe = unidentifiedPipe != null && unidentifiedAccess.isPresent() ? unidentifiedPipe : authPipe;
|
||||
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
|
||||
|
||||
if (pipe != null) {
|
||||
try {
|
||||
pipe.getProfile(address, unidentifiedAccess.get().getTargetUnidentifiedAccess());
|
||||
return true;
|
||||
} catch (NotFoundException e) {
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Websocket request failed. Falling back to REST.");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
ApplicationDependencies.getSignalServiceMessageReceiver().retrieveProfile(address, unidentifiedAccess.get().getTargetUnidentifiedAccess());
|
||||
ProfileUtil.retrieveProfile(context, recipient, SignalServiceProfile.RequestType.PROFILE);
|
||||
return true;
|
||||
} catch (NotFoundException e) {
|
||||
return false;
|
||||
|
@@ -18,6 +18,7 @@ import org.whispersystems.signalservice.api.storage.SignalContactRecord;
|
||||
import org.whispersystems.signalservice.api.storage.SignalContactRecord.IdentityState;
|
||||
import org.whispersystems.signalservice.api.storage.SignalStorageManifest;
|
||||
import org.whispersystems.signalservice.api.storage.SignalStorageRecord;
|
||||
import org.whispersystems.signalservice.api.util.OptionalUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
@@ -362,7 +363,7 @@ public final class StorageSyncHelper {
|
||||
private final SignalContactRecord oldContact;
|
||||
private final SignalContactRecord newContact;
|
||||
|
||||
public ContactUpdate(@NonNull SignalContactRecord oldContact, @NonNull SignalContactRecord newContact) {
|
||||
ContactUpdate(@NonNull SignalContactRecord oldContact, @NonNull SignalContactRecord newContact) {
|
||||
this.oldContact = oldContact;
|
||||
this.newContact = newContact;
|
||||
}
|
||||
@@ -377,6 +378,10 @@ public final class StorageSyncHelper {
|
||||
return newContact;
|
||||
}
|
||||
|
||||
public boolean profileKeyChanged() {
|
||||
return !OptionalUtil.byteArrayEquals(oldContact.getProfileKey(), newContact.getProfileKey());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
@@ -494,7 +499,7 @@ public final class StorageSyncHelper {
|
||||
private final WriteOperationResult writeResult;
|
||||
private final Map<RecipientId, byte[]> storageKeyUpdates;
|
||||
|
||||
public LocalWriteResult(WriteOperationResult writeResult, Map<RecipientId, byte[]> storageKeyUpdates) {
|
||||
private LocalWriteResult(WriteOperationResult writeResult, Map<RecipientId, byte[]> storageKeyUpdates) {
|
||||
this.writeResult = writeResult;
|
||||
this.storageKeyUpdates = storageKeyUpdates;
|
||||
}
|
||||
@@ -510,17 +515,17 @@ public final class StorageSyncHelper {
|
||||
|
||||
private static final class ContactRecordMergeResult {
|
||||
final Set<SignalContactRecord> localInserts;
|
||||
final Set<ContactUpdate> localUpdates;
|
||||
final Set<ContactUpdate> localUpdates;
|
||||
final Set<SignalContactRecord> remoteInserts;
|
||||
final Set<ContactUpdate> remoteUpdates;
|
||||
final Set<ContactUpdate> remoteUpdates;
|
||||
|
||||
ContactRecordMergeResult(@NonNull Set<SignalContactRecord> localInserts,
|
||||
@NonNull Set<ContactUpdate> localUpdates,
|
||||
@NonNull Set<SignalContactRecord> remoteInserts,
|
||||
@NonNull Set<ContactUpdate> remoteUpdates)
|
||||
{
|
||||
this.localInserts = localInserts;
|
||||
this.localUpdates = localUpdates;
|
||||
this.localInserts = localInserts;
|
||||
this.localUpdates = localUpdates;
|
||||
this.remoteInserts = remoteInserts;
|
||||
this.remoteUpdates = remoteUpdates;
|
||||
}
|
||||
|
@@ -3,17 +3,27 @@ package org.thoughtcrime.securesms.crypto;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.zkgroup.InvalidInputException;
|
||||
import org.signal.zkgroup.profiles.ProfileKey;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public final class ProfileKeyUtil {
|
||||
|
||||
private static final String TAG = Log.tag(ProfileKeyUtil.class);
|
||||
|
||||
private ProfileKeyUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Will inline later as part of Versioned profiles.
|
||||
*/
|
||||
/** @deprecated Use strongly typed {@link org.signal.zkgroup.profiles.ProfileKey}
|
||||
* from {@link #getSelfProfileKey()}
|
||||
* or {@code getSelfProfileKey().serialize()} if you need the bytes. */
|
||||
@Deprecated
|
||||
public static @NonNull byte[] getProfileKey(@NonNull Context context) {
|
||||
byte[] profileKey = Recipient.self().getProfileKey();
|
||||
@@ -22,4 +32,48 @@ public final class ProfileKeyUtil {
|
||||
}
|
||||
return profileKey;
|
||||
}
|
||||
|
||||
public static synchronized @NonNull ProfileKey getSelfProfileKey() {
|
||||
try {
|
||||
return new ProfileKey(Recipient.self().getProfileKey());
|
||||
} catch (InvalidInputException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static @Nullable ProfileKey profileKeyOrNull(@Nullable byte[] profileKey) {
|
||||
if (profileKey != null) {
|
||||
try {
|
||||
return new ProfileKey(profileKey);
|
||||
} catch (InvalidInputException e) {
|
||||
Log.w(TAG, String.format(Locale.US, "Seen non-null profile key of wrong length %d", profileKey.length), e);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static @NonNull ProfileKey profileKeyOrThrow(@NonNull byte[] profileKey) {
|
||||
try {
|
||||
return new ProfileKey(profileKey);
|
||||
} catch (InvalidInputException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static @NonNull Optional<ProfileKey> profileKeyOptional(@Nullable byte[] profileKey) {
|
||||
return Optional.fromNullable(profileKeyOrNull(profileKey));
|
||||
}
|
||||
|
||||
public static @NonNull Optional<ProfileKey> profileKeyOptionalOrThrow(@NonNull byte[] profileKey) {
|
||||
return Optional.of(profileKeyOrThrow(profileKey));
|
||||
}
|
||||
|
||||
public static @NonNull ProfileKey createNew() {
|
||||
try {
|
||||
return new ProfileKey(Util.getSecretBytes(32));
|
||||
} catch (InvalidInputException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.signal.libsignal.metadata.certificate.CertificateValidator;
|
||||
import org.signal.libsignal.metadata.certificate.InvalidCertificateException;
|
||||
import org.signal.zkgroup.profiles.ProfileKey;
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@@ -42,7 +43,7 @@ public class UnidentifiedAccessUtil {
|
||||
{
|
||||
try {
|
||||
byte[] theirUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient);
|
||||
byte[] ourUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(ProfileKeyUtil.getProfileKey(context));
|
||||
byte[] ourUnidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(ProfileKeyUtil.getSelfProfileKey());
|
||||
byte[] ourUnidentifiedAccessCertificate = recipient.resolve().isUuidSupported() && Recipient.self().isUuidSupported()
|
||||
? TextSecurePreferences.getUnidentifiedAccessCertificate(context)
|
||||
: TextSecurePreferences.getUnidentifiedAccessCertificateLegacy(context);
|
||||
@@ -75,7 +76,7 @@ public class UnidentifiedAccessUtil {
|
||||
|
||||
public static Optional<UnidentifiedAccessPair> getAccessForSync(@NonNull Context context) {
|
||||
try {
|
||||
byte[] ourUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(ProfileKeyUtil.getProfileKey(context));
|
||||
byte[] ourUnidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(ProfileKeyUtil.getSelfProfileKey());
|
||||
byte[] ourUnidentifiedAccessCertificate = Recipient.self().isUuidSupported() ? TextSecurePreferences.getUnidentifiedAccessCertificate(context)
|
||||
: TextSecurePreferences.getUnidentifiedAccessCertificateLegacy(context);
|
||||
|
||||
@@ -97,12 +98,8 @@ public class UnidentifiedAccessUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static @NonNull byte[] getSelfUnidentifiedAccessKey(@NonNull byte[] selfProfileKey) {
|
||||
return UnidentifiedAccess.deriveAccessKeyFrom(selfProfileKey);
|
||||
}
|
||||
|
||||
private static @Nullable byte[] getTargetUnidentifiedAccessKey(@NonNull Recipient recipient) {
|
||||
byte[] theirProfileKey = recipient.resolve().getProfileKey();
|
||||
ProfileKey theirProfileKey = ProfileKeyUtil.profileKeyOrNull(recipient.resolve().getProfileKey());
|
||||
|
||||
switch (recipient.resolve().getUnidentifiedAccessMode()) {
|
||||
case UNKNOWN:
|
||||
|
@@ -14,6 +14,8 @@ import com.google.android.gms.common.util.ArrayUtils;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
import org.signal.zkgroup.profiles.ProfileKey;
|
||||
import org.signal.zkgroup.profiles.ProfileKeyCredential;
|
||||
import org.thoughtcrime.securesms.color.MaterialColor;
|
||||
import org.thoughtcrime.securesms.contacts.sync.StorageSyncHelper;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
||||
@@ -31,7 +33,6 @@ import org.thoughtcrime.securesms.util.SqlUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.storage.SignalContactRecord;
|
||||
@@ -80,6 +81,7 @@ public class RecipientDatabase extends Database {
|
||||
private static final String SYSTEM_CONTACT_URI = "system_contact_uri";
|
||||
private static final String SYSTEM_INFO_PENDING = "system_info_pending";
|
||||
private static final String PROFILE_KEY = "profile_key";
|
||||
private static final String PROFILE_KEY_CREDENTIAL = "profile_key_credential";
|
||||
private static final String SIGNAL_PROFILE_AVATAR = "signal_profile_avatar";
|
||||
private static final String PROFILE_SHARING = "profile_sharing";
|
||||
private static final String UNIDENTIFIED_ACCESS_MODE = "unidentified_access_mode";
|
||||
@@ -100,7 +102,8 @@ public class RecipientDatabase extends Database {
|
||||
private static final String[] RECIPIENT_PROJECTION = new String[] {
|
||||
UUID, USERNAME, PHONE, EMAIL, GROUP_ID,
|
||||
BLOCKED, MESSAGE_RINGTONE, CALL_RINGTONE, MESSAGE_VIBRATE, CALL_VIBRATE, MUTE_UNTIL, COLOR, SEEN_INVITE_REMINDER, DEFAULT_SUBSCRIPTION_ID, MESSAGE_EXPIRATION_TIME, REGISTERED,
|
||||
PROFILE_KEY, SYSTEM_DISPLAY_NAME, SYSTEM_PHOTO_URI, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, SYSTEM_CONTACT_URI,
|
||||
PROFILE_KEY, PROFILE_KEY_CREDENTIAL,
|
||||
SYSTEM_DISPLAY_NAME, SYSTEM_PHOTO_URI, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, SYSTEM_CONTACT_URI,
|
||||
PROFILE_GIVEN_NAME, PROFILE_FAMILY_NAME, SIGNAL_PROFILE_AVATAR, PROFILE_SHARING, NOTIFICATION_CHANNEL,
|
||||
UNIDENTIFIED_ACCESS_MODE,
|
||||
FORCE_SMS_SELECTION, UUID_SUPPORTED, STORAGE_SERVICE_KEY, DIRTY
|
||||
@@ -242,6 +245,7 @@ public class RecipientDatabase extends Database {
|
||||
SYSTEM_CONTACT_URI + " TEXT DEFAULT NULL, " +
|
||||
SYSTEM_INFO_PENDING + " INTEGER DEFAULT 0, " +
|
||||
PROFILE_KEY + " TEXT DEFAULT NULL, " +
|
||||
PROFILE_KEY_CREDENTIAL + " TEXT DEFAULT NULL, " +
|
||||
PROFILE_GIVEN_NAME + " TEXT DEFAULT NULL, " +
|
||||
PROFILE_FAMILY_NAME + " TEXT DEFAULT NULL, " +
|
||||
PROFILE_JOINED_NAME + " TEXT DEFAULT NULL, " +
|
||||
@@ -428,6 +432,10 @@ public class RecipientDatabase extends Database {
|
||||
|
||||
RecipientId recipientId = getByStorageKeyOrThrow(update.getNewContact().getKey());
|
||||
|
||||
if (update.profileKeyChanged()) {
|
||||
clearProfileKeyCredential(recipientId);
|
||||
}
|
||||
|
||||
try {
|
||||
Optional<IdentityRecord> oldIdentityRecord = identityDatabase.getIdentity(recipientId);
|
||||
IdentityKey identityKey = update.getNewContact().getIdentityKey().isPresent() ? new IdentityKey(update.getNewContact().getIdentityKey().get(), 0) : null;
|
||||
@@ -562,42 +570,44 @@ public class RecipientDatabase extends Database {
|
||||
}
|
||||
|
||||
@NonNull RecipientSettings getRecipientSettings(@NonNull Cursor cursor) {
|
||||
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
|
||||
UUID uuid = UuidUtil.parseOrNull(cursor.getString(cursor.getColumnIndexOrThrow(UUID)));
|
||||
String username = cursor.getString(cursor.getColumnIndexOrThrow(USERNAME));
|
||||
String e164 = cursor.getString(cursor.getColumnIndexOrThrow(PHONE));
|
||||
String email = cursor.getString(cursor.getColumnIndexOrThrow(EMAIL));
|
||||
String groupId = cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID));
|
||||
boolean blocked = cursor.getInt(cursor.getColumnIndexOrThrow(BLOCKED)) == 1;
|
||||
String messageRingtone = cursor.getString(cursor.getColumnIndexOrThrow(MESSAGE_RINGTONE));
|
||||
String callRingtone = cursor.getString(cursor.getColumnIndexOrThrow(CALL_RINGTONE));
|
||||
int messageVibrateState = cursor.getInt(cursor.getColumnIndexOrThrow(MESSAGE_VIBRATE));
|
||||
int callVibrateState = cursor.getInt(cursor.getColumnIndexOrThrow(CALL_VIBRATE));
|
||||
long muteUntil = cursor.getLong(cursor.getColumnIndexOrThrow(MUTE_UNTIL));
|
||||
String serializedColor = cursor.getString(cursor.getColumnIndexOrThrow(COLOR));
|
||||
int insightsBannerTier = cursor.getInt(cursor.getColumnIndexOrThrow(SEEN_INVITE_REMINDER));
|
||||
int defaultSubscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(DEFAULT_SUBSCRIPTION_ID));
|
||||
int expireMessages = cursor.getInt(cursor.getColumnIndexOrThrow(MESSAGE_EXPIRATION_TIME));
|
||||
int registeredState = cursor.getInt(cursor.getColumnIndexOrThrow(REGISTERED));
|
||||
String profileKeyString = cursor.getString(cursor.getColumnIndexOrThrow(PROFILE_KEY));
|
||||
String systemDisplayName = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_DISPLAY_NAME));
|
||||
String systemContactPhoto = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_PHOTO_URI));
|
||||
String systemPhoneLabel = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_PHONE_LABEL));
|
||||
String systemContactUri = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_CONTACT_URI));
|
||||
String profileGivenName = cursor.getString(cursor.getColumnIndexOrThrow(PROFILE_GIVEN_NAME));
|
||||
String profileFamilyName = cursor.getString(cursor.getColumnIndexOrThrow(PROFILE_FAMILY_NAME));
|
||||
String signalProfileAvatar = cursor.getString(cursor.getColumnIndexOrThrow(SIGNAL_PROFILE_AVATAR));
|
||||
boolean profileSharing = cursor.getInt(cursor.getColumnIndexOrThrow(PROFILE_SHARING)) == 1;
|
||||
String notificationChannel = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION_CHANNEL));
|
||||
int unidentifiedAccessMode = cursor.getInt(cursor.getColumnIndexOrThrow(UNIDENTIFIED_ACCESS_MODE));
|
||||
boolean forceSmsSelection = cursor.getInt(cursor.getColumnIndexOrThrow(FORCE_SMS_SELECTION)) == 1;
|
||||
boolean uuidSupported = cursor.getInt(cursor.getColumnIndexOrThrow(UUID_SUPPORTED)) == 1;
|
||||
String storageKeyRaw = cursor.getString(cursor.getColumnIndexOrThrow(STORAGE_SERVICE_KEY));
|
||||
String identityKeyRaw = cursor.getString(cursor.getColumnIndexOrThrow(IDENTITY_KEY));
|
||||
int identityStatusRaw = cursor.getInt(cursor.getColumnIndexOrThrow(IDENTITY_STATUS));
|
||||
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
|
||||
UUID uuid = UuidUtil.parseOrNull(cursor.getString(cursor.getColumnIndexOrThrow(UUID)));
|
||||
String username = cursor.getString(cursor.getColumnIndexOrThrow(USERNAME));
|
||||
String e164 = cursor.getString(cursor.getColumnIndexOrThrow(PHONE));
|
||||
String email = cursor.getString(cursor.getColumnIndexOrThrow(EMAIL));
|
||||
String groupId = cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID));
|
||||
boolean blocked = cursor.getInt(cursor.getColumnIndexOrThrow(BLOCKED)) == 1;
|
||||
String messageRingtone = cursor.getString(cursor.getColumnIndexOrThrow(MESSAGE_RINGTONE));
|
||||
String callRingtone = cursor.getString(cursor.getColumnIndexOrThrow(CALL_RINGTONE));
|
||||
int messageVibrateState = cursor.getInt(cursor.getColumnIndexOrThrow(MESSAGE_VIBRATE));
|
||||
int callVibrateState = cursor.getInt(cursor.getColumnIndexOrThrow(CALL_VIBRATE));
|
||||
long muteUntil = cursor.getLong(cursor.getColumnIndexOrThrow(MUTE_UNTIL));
|
||||
String serializedColor = cursor.getString(cursor.getColumnIndexOrThrow(COLOR));
|
||||
int insightsBannerTier = cursor.getInt(cursor.getColumnIndexOrThrow(SEEN_INVITE_REMINDER));
|
||||
int defaultSubscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(DEFAULT_SUBSCRIPTION_ID));
|
||||
int expireMessages = cursor.getInt(cursor.getColumnIndexOrThrow(MESSAGE_EXPIRATION_TIME));
|
||||
int registeredState = cursor.getInt(cursor.getColumnIndexOrThrow(REGISTERED));
|
||||
String profileKeyString = cursor.getString(cursor.getColumnIndexOrThrow(PROFILE_KEY));
|
||||
String profileKeyCredentialString = cursor.getString(cursor.getColumnIndexOrThrow(PROFILE_KEY_CREDENTIAL));
|
||||
String systemDisplayName = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_DISPLAY_NAME));
|
||||
String systemContactPhoto = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_PHOTO_URI));
|
||||
String systemPhoneLabel = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_PHONE_LABEL));
|
||||
String systemContactUri = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_CONTACT_URI));
|
||||
String profileGivenName = cursor.getString(cursor.getColumnIndexOrThrow(PROFILE_GIVEN_NAME));
|
||||
String profileFamilyName = cursor.getString(cursor.getColumnIndexOrThrow(PROFILE_FAMILY_NAME));
|
||||
String signalProfileAvatar = cursor.getString(cursor.getColumnIndexOrThrow(SIGNAL_PROFILE_AVATAR));
|
||||
boolean profileSharing = cursor.getInt(cursor.getColumnIndexOrThrow(PROFILE_SHARING)) == 1;
|
||||
String notificationChannel = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION_CHANNEL));
|
||||
int unidentifiedAccessMode = cursor.getInt(cursor.getColumnIndexOrThrow(UNIDENTIFIED_ACCESS_MODE));
|
||||
boolean forceSmsSelection = cursor.getInt(cursor.getColumnIndexOrThrow(FORCE_SMS_SELECTION)) == 1;
|
||||
boolean uuidSupported = cursor.getInt(cursor.getColumnIndexOrThrow(UUID_SUPPORTED)) == 1;
|
||||
String storageKeyRaw = cursor.getString(cursor.getColumnIndexOrThrow(STORAGE_SERVICE_KEY));
|
||||
String identityKeyRaw = cursor.getString(cursor.getColumnIndexOrThrow(IDENTITY_KEY));
|
||||
int identityStatusRaw = cursor.getInt(cursor.getColumnIndexOrThrow(IDENTITY_STATUS));
|
||||
|
||||
MaterialColor color;
|
||||
byte[] profileKey = null;
|
||||
byte[] profileKey = null;
|
||||
byte[] profileKeyCredential = null;
|
||||
|
||||
try {
|
||||
color = serializedColor == null ? null : MaterialColor.fromSerialized(serializedColor);
|
||||
@@ -613,6 +623,15 @@ public class RecipientDatabase extends Database {
|
||||
Log.w(TAG, e);
|
||||
profileKey = null;
|
||||
}
|
||||
|
||||
if (profileKeyCredentialString != null) {
|
||||
try {
|
||||
profileKeyCredential = Base64.decode(profileKeyCredentialString);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
profileKeyCredential = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
byte[] storageKey = null;
|
||||
@@ -637,7 +656,8 @@ public class RecipientDatabase extends Database {
|
||||
Util.uri(messageRingtone), Util.uri(callRingtone),
|
||||
color, defaultSubscriptionId, expireMessages,
|
||||
RegisteredState.fromId(registeredState),
|
||||
profileKey, systemDisplayName, systemContactPhoto,
|
||||
profileKey, profileKeyCredential,
|
||||
systemDisplayName, systemContactPhoto,
|
||||
systemPhoneLabel, systemContactUri,
|
||||
ProfileName.fromParts(profileGivenName, profileFamilyName), signalProfileAvatar, profileSharing,
|
||||
notificationChannel, UnidentifiedAccessMode.fromMode(unidentifiedAccessMode),
|
||||
@@ -776,9 +796,56 @@ public class RecipientDatabase extends Database {
|
||||
Recipient.live(id).refresh();
|
||||
}
|
||||
|
||||
public void setProfileKey(@NonNull RecipientId id, @Nullable byte[] profileKey) {
|
||||
/**
|
||||
* Updates the profile key.
|
||||
* <p>
|
||||
* If it changes, it clears out the profile key credential.
|
||||
*/
|
||||
public void setProfileKey(@NonNull RecipientId id, @Nullable ProfileKey profileKey) {
|
||||
String selection = ID + " = ?";
|
||||
String[] args = new String[]{id.serialize()};
|
||||
ContentValues valuesToCompare = new ContentValues(1);
|
||||
ContentValues valuesToSet = new ContentValues(2);
|
||||
String encodedProfileKey = profileKey == null ? null : Base64.encodeBytes(profileKey.serialize());
|
||||
|
||||
valuesToCompare.put(PROFILE_KEY, encodedProfileKey);
|
||||
|
||||
valuesToSet.put(PROFILE_KEY, encodedProfileKey);
|
||||
valuesToSet.putNull(PROFILE_KEY_CREDENTIAL);
|
||||
|
||||
SqlUtil.UpdateQuery updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, valuesToCompare);
|
||||
|
||||
if (update(updateQuery, valuesToSet)) {
|
||||
markDirty(id, DirtyState.UPDATE);
|
||||
Recipient.live(id).refresh();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the profile key credential as long as the profile key matches.
|
||||
*/
|
||||
public void setProfileKeyCredential(@NonNull RecipientId id,
|
||||
@NonNull ProfileKey profileKey,
|
||||
@NonNull ProfileKeyCredential profileKeyCredential)
|
||||
{
|
||||
String selection = ID + " = ? AND " + PROFILE_KEY + " = ?";
|
||||
String[] args = new String[]{id.serialize(), Base64.encodeBytes(profileKey.serialize())};
|
||||
ContentValues values = new ContentValues(1);
|
||||
|
||||
values.put(PROFILE_KEY_CREDENTIAL, Base64.encodeBytes(profileKeyCredential.serialize()));
|
||||
|
||||
SqlUtil.UpdateQuery updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, values);
|
||||
|
||||
if (update(updateQuery, values)) {
|
||||
// TODO [greyson] If we sync this in future, mark dirty
|
||||
//markDirty(id, DirtyState.UPDATE);
|
||||
Recipient.live(id).refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private void clearProfileKeyCredential(@NonNull RecipientId id) {
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(PROFILE_KEY, profileKey == null ? null : Base64.encodeBytes(profileKey));
|
||||
values.putNull(PROFILE_KEY_CREDENTIAL);
|
||||
if (update(id, values)) {
|
||||
markDirty(id, DirtyState.UPDATE);
|
||||
Recipient.live(id).refresh();
|
||||
@@ -1224,14 +1291,23 @@ public class RecipientDatabase extends Database {
|
||||
* Will update the database with the content values you specified. It will make an intelligent
|
||||
* query such that this will only return true if a row was *actually* updated.
|
||||
*/
|
||||
private boolean update(@NonNull RecipientId id, ContentValues contentValues) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
String selection = ID + " = ?";
|
||||
String[] args = new String[]{id.serialize()};
|
||||
private boolean update(@NonNull RecipientId id, @NonNull ContentValues contentValues) {
|
||||
String selection = ID + " = ?";
|
||||
String[] args = new String[]{id.serialize()};
|
||||
SqlUtil.UpdateQuery updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, contentValues);
|
||||
|
||||
Pair<String, String[]> result = SqlUtil.buildTrueUpdateQuery(selection, args, contentValues);
|
||||
return update(updateQuery, contentValues);
|
||||
}
|
||||
|
||||
return database.update(TABLE_NAME, contentValues, result.first(), result.second()) > 0;
|
||||
/**
|
||||
* Will update the database with the {@param contentValues} you specified.
|
||||
* <p>
|
||||
* This will only return true if a row was *actually* updated with respect to the where clause of the {@param updateQuery}.
|
||||
*/
|
||||
private boolean update(@NonNull SqlUtil.UpdateQuery updateQuery, @NonNull ContentValues contentValues) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
|
||||
return database.update(TABLE_NAME, contentValues, updateQuery.getWhere(), updateQuery.getWhereArgs()) > 0;
|
||||
}
|
||||
|
||||
private @NonNull Optional<RecipientId> getByColumn(@NonNull String column, String value) {
|
||||
@@ -1374,6 +1450,7 @@ public class RecipientDatabase extends Database {
|
||||
private final int expireMessages;
|
||||
private final RegisteredState registered;
|
||||
private final byte[] profileKey;
|
||||
private final byte[] profileKeyCredential;
|
||||
private final String systemDisplayName;
|
||||
private final String systemContactPhoto;
|
||||
private final String systemPhoneLabel;
|
||||
@@ -1406,6 +1483,7 @@ public class RecipientDatabase extends Database {
|
||||
int expireMessages,
|
||||
@NonNull RegisteredState registered,
|
||||
@Nullable byte[] profileKey,
|
||||
@Nullable byte[] profileKeyCredential,
|
||||
@Nullable String systemDisplayName,
|
||||
@Nullable String systemContactPhoto,
|
||||
@Nullable String systemPhoneLabel,
|
||||
@@ -1439,6 +1517,7 @@ public class RecipientDatabase extends Database {
|
||||
this.expireMessages = expireMessages;
|
||||
this.registered = registered;
|
||||
this.profileKey = profileKey;
|
||||
this.profileKeyCredential = profileKeyCredential;
|
||||
this.systemDisplayName = systemDisplayName;
|
||||
this.systemContactPhoto = systemContactPhoto;
|
||||
this.systemPhoneLabel = systemPhoneLabel;
|
||||
@@ -1528,6 +1607,10 @@ public class RecipientDatabase extends Database {
|
||||
return profileKey;
|
||||
}
|
||||
|
||||
public @Nullable byte[] getProfileKeyCredential() {
|
||||
return profileKeyCredential;
|
||||
}
|
||||
|
||||
public @Nullable String getSystemDisplayName() {
|
||||
return systemDisplayName;
|
||||
}
|
||||
|
@@ -109,8 +109,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
private static final int MEGAPHONES = 45;
|
||||
private static final int MEGAPHONE_FIRST_APPEARANCE = 46;
|
||||
private static final int PROFILE_KEY_TO_DB = 47;
|
||||
private static final int PROFILE_KEY_CREDENTIALS = 48;
|
||||
|
||||
private static final int DATABASE_VERSION = 47;
|
||||
private static final int DATABASE_VERSION = 48;
|
||||
private static final String DATABASE_NAME = "signal.db";
|
||||
|
||||
private final Context context;
|
||||
@@ -743,6 +744,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
}
|
||||
}
|
||||
|
||||
if (oldVersion < PROFILE_KEY_CREDENTIALS) {
|
||||
db.execSQL("ALTER TABLE recipient ADD COLUMN profile_key_credential TEXT DEFAULT NULL");
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
|
@@ -8,7 +8,9 @@ import android.provider.ContactsContract;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.zkgroup.profiles.ProfileKey;
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
@@ -134,7 +136,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob {
|
||||
getSystemAvatar(recipient.getContactUri()),
|
||||
Optional.fromNullable(recipient.getColor().serialize()),
|
||||
verifiedMessage,
|
||||
Optional.fromNullable(recipient.getProfileKey()),
|
||||
ProfileKeyUtil.profileKeyOptional(recipient.getProfileKey()),
|
||||
recipient.isBlocked(),
|
||||
recipient.getExpireMessages() > 0 ? Optional.of(recipient.getExpireMessages())
|
||||
: Optional.absent(),
|
||||
@@ -181,7 +183,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob {
|
||||
Optional<VerifiedMessage> verified = getVerifiedMessage(recipient, identity);
|
||||
Optional<String> name = Optional.fromNullable(recipient.getName(context));
|
||||
Optional<String> color = Optional.of(recipient.getColor().serialize());
|
||||
Optional<byte[]> profileKey = Optional.fromNullable(recipient.getProfileKey());
|
||||
Optional<ProfileKey> profileKey = ProfileKeyUtil.profileKeyOptional(recipient.getProfileKey());
|
||||
boolean blocked = recipient.isBlocked();
|
||||
Optional<Integer> expireTimer = recipient.getExpireMessages() > 0 ? Optional.of(recipient.getExpireMessages()) : Optional.absent();
|
||||
Optional<Integer> inboxPosition = Optional.fromNullable(inboxPositions.get(recipient.getId()));
|
||||
@@ -208,7 +210,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob {
|
||||
Optional.absent(),
|
||||
Optional.of(self.getColor().serialize()),
|
||||
Optional.absent(),
|
||||
Optional.of(profileKey),
|
||||
ProfileKeyUtil.profileKeyOptionalOrThrow(self.getProfileKey()),
|
||||
false,
|
||||
self.getExpireMessages() > 0 ? Optional.of(self.getExpireMessages()) : Optional.absent(),
|
||||
Optional.fromNullable(inboxPositions.get(self.getId())),
|
||||
|
@@ -3,14 +3,14 @@ package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.zkgroup.profiles.ProfileKey;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
@@ -66,7 +66,7 @@ public class MultiDeviceProfileKeyUpdateJob extends BaseJob {
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<byte[]> profileKey = Optional.of(ProfileKeyUtil.getProfileKey(context));
|
||||
Optional<ProfileKey> profileKey = Optional.of(ProfileKeyUtil.getSelfProfileKey());
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
DeviceContactsOutputStream out = new DeviceContactsOutputStream(baos);
|
||||
|
||||
|
@@ -4,6 +4,7 @@ import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.zkgroup.profiles.ProfileKey;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
@@ -11,16 +12,11 @@ import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
import org.whispersystems.signalservice.api.util.StreamDetails;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
public final class ProfileUploadJob extends BaseJob {
|
||||
|
||||
public static final String KEY = "ProfileUploadJob";
|
||||
@@ -47,8 +43,17 @@ public final class ProfileUploadJob extends BaseJob {
|
||||
|
||||
@Override
|
||||
protected void onRun() throws Exception {
|
||||
uploadProfileName();
|
||||
uploadAvatar();
|
||||
ProfileKey profileKey = ProfileKeyUtil.getSelfProfileKey();
|
||||
ProfileName profileName = TextSecurePreferences.getProfileName(context);
|
||||
|
||||
try (StreamDetails avatar = AvatarHelper.getSelfProfileAvatarStream(context)) {
|
||||
if (FeatureFlags.VERSIONED_PROFILES) {
|
||||
accountManager.setVersionedProfile(profileKey, profileName.serialize(), avatar);
|
||||
} else {
|
||||
accountManager.setProfileName(profileKey, profileName.serialize());
|
||||
accountManager.setProfileAvatar(profileKey, avatar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -70,33 +75,6 @@ public final class ProfileUploadJob extends BaseJob {
|
||||
public void onFailure() {
|
||||
}
|
||||
|
||||
private void uploadProfileName() throws Exception {
|
||||
ProfileName profileName = TextSecurePreferences.getProfileName(context);
|
||||
accountManager.setProfileName(ProfileKeyUtil.getProfileKey(context), profileName.serialize());
|
||||
}
|
||||
|
||||
private void uploadAvatar() throws Exception {
|
||||
final RecipientId selfId = Recipient.self().getId();
|
||||
final byte[] avatar;
|
||||
|
||||
if (AvatarHelper.getAvatarFile(context, selfId).exists() && AvatarHelper.getAvatarFile(context, selfId).length() > 0) {
|
||||
avatar = Util.readFully(AvatarHelper.getInputStreamFor(context, Recipient.self().getId()));
|
||||
} else {
|
||||
avatar = null;
|
||||
}
|
||||
|
||||
final StreamDetails avatarDetails;
|
||||
if (avatar == null || avatar.length == 0) {
|
||||
avatarDetails = null;
|
||||
} else {
|
||||
avatarDetails = new StreamDetails(new ByteArrayInputStream(avatar),
|
||||
MediaUtil.IMAGE_JPEG,
|
||||
avatar.length);
|
||||
}
|
||||
|
||||
accountManager.setProfileAvatar(ProfileKeyUtil.getProfileKey(context), avatarDetails);
|
||||
}
|
||||
|
||||
public static class Factory implements Job.Factory {
|
||||
|
||||
@NonNull
|
||||
|
@@ -12,6 +12,7 @@ import androidx.annotation.Nullable;
|
||||
import com.annimon.stream.Collectors;
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.signal.zkgroup.profiles.ProfileKey;
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||
@@ -20,6 +21,7 @@ import org.thoughtcrime.securesms.attachments.TombstoneAttachment;
|
||||
import org.thoughtcrime.securesms.attachments.UriAttachment;
|
||||
import org.thoughtcrime.securesms.contactshare.Contact;
|
||||
import org.thoughtcrime.securesms.contactshare.ContactModelMapper;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SecurityEvent;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
@@ -102,7 +104,6 @@ import org.whispersystems.signalservice.api.messages.shared.SharedContact;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -271,8 +272,8 @@ public final class PushProcessMessageJob extends BaseJob {
|
||||
handleUnknownGroupMessage(content, message.getGroupInfo().get());
|
||||
}
|
||||
|
||||
if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) {
|
||||
handleProfileKey(content, message);
|
||||
if (message.getProfileKey().isPresent()) {
|
||||
handleProfileKey(content, message.getProfileKey().get());
|
||||
}
|
||||
|
||||
if (content.isNeedsReceipt()) {
|
||||
@@ -1175,13 +1176,15 @@ public final class PushProcessMessageJob extends BaseJob {
|
||||
}
|
||||
|
||||
private void handleProfileKey(@NonNull SignalServiceContent content,
|
||||
@NonNull SignalServiceDataMessage message)
|
||||
@NonNull byte[] messageProfileKeyBytes)
|
||||
{
|
||||
RecipientDatabase database = DatabaseFactory.getRecipientDatabase(context);
|
||||
Recipient recipient = Recipient.externalPush(context, content.getSender());
|
||||
RecipientDatabase database = DatabaseFactory.getRecipientDatabase(context);
|
||||
Recipient recipient = Recipient.externalPush(context, content.getSender());
|
||||
ProfileKey currentProfileKey = ProfileKeyUtil.profileKeyOrNull(recipient.getProfileKey());
|
||||
ProfileKey messageProfileKey = ProfileKeyUtil.profileKeyOrNull(messageProfileKeyBytes);
|
||||
|
||||
if (recipient.getProfileKey() == null || !MessageDigest.isEqual(recipient.getProfileKey(), message.getProfileKey().get())) {
|
||||
database.setProfileKey(recipient.getId(), message.getProfileKey().get());
|
||||
if (messageProfileKey != null && !messageProfileKey.equals(currentProfileKey)) {
|
||||
database.setProfileKey(recipient.getId(), messageProfileKey);
|
||||
database.setUnidentifiedAccessMode(recipient.getId(), RecipientDatabase.UnidentifiedAccessMode.UNKNOWN);
|
||||
ApplicationDependencies.getJobManager().add(new RetrieveProfileJob(recipient));
|
||||
}
|
||||
|
@@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.jobs;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
@@ -13,6 +12,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NetworkFailureException;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -48,7 +48,7 @@ public class RefreshAttributesJob extends BaseJob {
|
||||
public void onRun() throws IOException {
|
||||
int registrationId = TextSecurePreferences.getLocalRegistrationId(context);
|
||||
boolean fetchesMessages = TextSecurePreferences.isFcmDisabled(context);
|
||||
byte[] unidentifiedAccessKey = UnidentifiedAccessUtil.getSelfUnidentifiedAccessKey(ProfileKeyUtil.getProfileKey(context));
|
||||
byte[] unidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(ProfileKeyUtil.getSelfProfileKey());
|
||||
boolean universalUnidentifiedAccess = TextSecurePreferences.isUniversalUnidentifiedAccess(context);
|
||||
String pin = null;
|
||||
String registrationLockToken = null;
|
||||
|
@@ -3,8 +3,11 @@ package org.thoughtcrime.securesms.jobs;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.zkgroup.profiles.ProfileKey;
|
||||
import org.signal.zkgroup.profiles.ProfileKeyCredential;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
@@ -12,9 +15,12 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.ProfileUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
|
||||
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
|
||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||
|
||||
@@ -58,11 +64,31 @@ public class RefreshOwnProfileJob extends BaseJob {
|
||||
|
||||
@Override
|
||||
protected void onRun() throws Exception {
|
||||
SignalServiceProfile profile = ProfileUtil.retrieveProfile(context, Recipient.self());
|
||||
Recipient self = Recipient.self();
|
||||
ProfileAndCredential profileAndCredential = ProfileUtil.retrieveProfile(context, self, getRequestType(self));
|
||||
SignalServiceProfile profile = profileAndCredential.getProfile();
|
||||
|
||||
setProfileName(profile.getName());
|
||||
setProfileAvatar(profile.getAvatar());
|
||||
setProfileCapabilities(profile.getCapabilities());
|
||||
Optional<ProfileKeyCredential> profileKeyCredential = profileAndCredential.getProfileKeyCredential();
|
||||
if (profileKeyCredential.isPresent()) {
|
||||
setProfileKeyCredential(self, ProfileKeyUtil.getSelfProfileKey(), profileKeyCredential.get());
|
||||
}
|
||||
}
|
||||
|
||||
private void setProfileKeyCredential(@NonNull Recipient recipient,
|
||||
@NonNull ProfileKey recipientProfileKey,
|
||||
@NonNull ProfileKeyCredential credential)
|
||||
{
|
||||
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
|
||||
recipientDatabase.setProfileKeyCredential(recipient.getId(), recipientProfileKey, credential);
|
||||
}
|
||||
|
||||
private static SignalServiceProfile.RequestType getRequestType(@NonNull Recipient recipient) {
|
||||
return FeatureFlags.VERSIONED_PROFILES && !recipient.hasProfileKeyCredential()
|
||||
? SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL
|
||||
: SignalServiceProfile.RequestType.PROFILE;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,7 +101,7 @@ public class RefreshOwnProfileJob extends BaseJob {
|
||||
|
||||
private void setProfileName(@Nullable String encryptedName) {
|
||||
try {
|
||||
byte[] profileKey = ProfileKeyUtil.getProfileKey(context);
|
||||
ProfileKey profileKey = ProfileKeyUtil.getSelfProfileKey();
|
||||
String plaintextName = ProfileUtil.decryptName(profileKey, encryptedName);
|
||||
ProfileName profileName = ProfileName.fromSerialized(plaintextName);
|
||||
|
||||
@@ -86,7 +112,7 @@ public class RefreshOwnProfileJob extends BaseJob {
|
||||
}
|
||||
}
|
||||
|
||||
private void setProfileAvatar(@Nullable String avatar) {
|
||||
private static void setProfileAvatar(@Nullable String avatar) {
|
||||
ApplicationDependencies.getJobManager().add(new RetrieveProfileAvatarJob(Recipient.self(), avatar));
|
||||
}
|
||||
|
||||
|
@@ -1,9 +1,12 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.zkgroup.profiles.ProfileKey;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
@@ -37,8 +40,8 @@ public class RetrieveProfileAvatarJob extends BaseJob {
|
||||
private static final String KEY_PROFILE_AVATAR = "profile_avatar";
|
||||
private static final String KEY_RECIPIENT = "recipient";
|
||||
|
||||
private String profileAvatar;
|
||||
private Recipient recipient;
|
||||
private final String profileAvatar;
|
||||
private final Recipient recipient;
|
||||
|
||||
public RetrieveProfileAvatarJob(Recipient recipient, String profileAvatar) {
|
||||
this(new Job.Parameters.Builder()
|
||||
@@ -73,7 +76,7 @@ public class RetrieveProfileAvatarJob extends BaseJob {
|
||||
@Override
|
||||
public void onRun() throws IOException {
|
||||
RecipientDatabase database = DatabaseFactory.getRecipientDatabase(context);
|
||||
byte[] profileKey = recipient.resolve().getProfileKey();
|
||||
ProfileKey profileKey = ProfileKeyUtil.profileKeyOrNull(recipient.resolve().getProfileKey());
|
||||
|
||||
if (profileKey == null) {
|
||||
Log.w(TAG, "Recipient profile key is gone!");
|
||||
|
@@ -1,11 +1,14 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.signal.zkgroup.profiles.ProfileKey;
|
||||
import org.signal.zkgroup.profiles.ProfileKeyCredential;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode;
|
||||
@@ -24,8 +27,10 @@ import org.thoughtcrime.securesms.util.ProfileUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
|
||||
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
|
||||
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
|
||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -92,9 +97,11 @@ public class RetrieveProfileJob extends BaseJob {
|
||||
}
|
||||
|
||||
private void handlePhoneNumberRecipient(Recipient recipient) throws IOException {
|
||||
SignalServiceProfile profile = ProfileUtil.retrieveProfile(context, recipient);
|
||||
ProfileAndCredential profileAndCredential = ProfileUtil.retrieveProfile(context, recipient, getRequestType(recipient));
|
||||
SignalServiceProfile profile = profileAndCredential.getProfile();
|
||||
ProfileKey recipientProfileKey = ProfileKeyUtil.profileKeyOrNull(recipient.getProfileKey());
|
||||
|
||||
if (recipient.getProfileKey() == null) {
|
||||
if (recipientProfileKey == null) {
|
||||
Log.i(TAG, "No profile key available for " + recipient.getId());
|
||||
} else {
|
||||
Log.i(TAG, "Profile key available for " + recipient.getId());
|
||||
@@ -106,6 +113,27 @@ public class RetrieveProfileJob extends BaseJob {
|
||||
setProfileCapabilities(recipient, profile.getCapabilities());
|
||||
setIdentityKey(recipient, profile.getIdentityKey());
|
||||
setUnidentifiedAccessMode(recipient, profile.getUnidentifiedAccess(), profile.isUnrestrictedUnidentifiedAccess());
|
||||
|
||||
if (recipientProfileKey != null) {
|
||||
Optional<ProfileKeyCredential> profileKeyCredential = profileAndCredential.getProfileKeyCredential();
|
||||
if (profileKeyCredential.isPresent()) {
|
||||
setProfileKeyCredential(recipient, recipientProfileKey, profileKeyCredential.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setProfileKeyCredential(@NonNull Recipient recipient,
|
||||
@NonNull ProfileKey recipientProfileKey,
|
||||
@NonNull ProfileKeyCredential credential)
|
||||
{
|
||||
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
|
||||
recipientDatabase.setProfileKeyCredential(recipient.getId(), recipientProfileKey, credential);
|
||||
}
|
||||
|
||||
private static SignalServiceProfile.RequestType getRequestType(@NonNull Recipient recipient) {
|
||||
return FeatureFlags.VERSIONED_PROFILES && !recipient.hasProfileKeyCredential()
|
||||
? SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL
|
||||
: SignalServiceProfile.RequestType.PROFILE;
|
||||
}
|
||||
|
||||
private void handleGroupRecipient(Recipient group) throws IOException {
|
||||
@@ -141,7 +169,7 @@ public class RetrieveProfileJob extends BaseJob {
|
||||
|
||||
private void setUnidentifiedAccessMode(Recipient recipient, String unidentifiedAccessVerifier, boolean unrestrictedUnidentifiedAccess) {
|
||||
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
|
||||
byte[] profileKey = recipient.getProfileKey();
|
||||
ProfileKey profileKey = ProfileKeyUtil.profileKeyOrNull(recipient.getProfileKey());
|
||||
|
||||
if (unrestrictedUnidentifiedAccess && unidentifiedAccessVerifier != null) {
|
||||
if (recipient.getUnidentifiedAccessMode() != UnidentifiedAccessMode.UNRESTRICTED) {
|
||||
@@ -175,7 +203,7 @@ public class RetrieveProfileJob extends BaseJob {
|
||||
|
||||
private void setProfileName(Recipient recipient, String profileName) {
|
||||
try {
|
||||
byte[] profileKey = recipient.getProfileKey();
|
||||
ProfileKey profileKey = ProfileKeyUtil.profileKeyOrNull(recipient.getProfileKey());
|
||||
if (profileKey == null) return;
|
||||
|
||||
String plaintextProfileName = ProfileUtil.decryptName(profileKey, profileName);
|
||||
|
@@ -1,8 +1,9 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.zkgroup.profiles.ProfileKey;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
@@ -11,15 +12,11 @@ import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||
import org.whispersystems.signalservice.api.util.StreamDetails;
|
||||
import org.whispersystems.signalservice.internal.util.Util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class RotateProfileKeyJob extends BaseJob {
|
||||
|
||||
@@ -52,12 +49,20 @@ public class RotateProfileKeyJob extends BaseJob {
|
||||
public void onRun() throws Exception {
|
||||
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
||||
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
|
||||
byte[] profileKey = Util.getSecretBytes(32);
|
||||
ProfileKey profileKey = ProfileKeyUtil.createNew();
|
||||
Recipient self = Recipient.self();
|
||||
|
||||
recipientDatabase.setProfileKey(self.getId(), profileKey);
|
||||
accountManager.setProfileName(profileKey, TextSecurePreferences.getProfileName(context).serialize());
|
||||
accountManager.setProfileAvatar(profileKey, getProfileAvatar());
|
||||
try (StreamDetails avatarStream = AvatarHelper.getSelfProfileAvatarStream(context)) {
|
||||
if (FeatureFlags.VERSIONED_PROFILES) {
|
||||
accountManager.setVersionedProfile(profileKey,
|
||||
TextSecurePreferences.getProfileName(context).serialize(),
|
||||
avatarStream);
|
||||
} else {
|
||||
accountManager.setProfileName(profileKey, TextSecurePreferences.getProfileName(context).serialize());
|
||||
accountManager.setProfileAvatar(profileKey, avatarStream);
|
||||
}
|
||||
}
|
||||
|
||||
ApplicationDependencies.getJobManager().add(new RefreshAttributesJob());
|
||||
}
|
||||
@@ -72,19 +77,6 @@ public class RotateProfileKeyJob extends BaseJob {
|
||||
return exception instanceof PushNetworkException;
|
||||
}
|
||||
|
||||
private @Nullable StreamDetails getProfileAvatar() {
|
||||
try {
|
||||
File avatarFile = AvatarHelper.getAvatarFile(context, Recipient.self().getId());
|
||||
|
||||
if (avatarFile.exists()) {
|
||||
return new StreamDetails(new FileInputStream(avatarFile), "image/jpeg", avatarFile.length());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static final class Factory implements Job.Factory<RotateProfileKeyJob> {
|
||||
@Override
|
||||
public @NonNull RotateProfileKeyJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||
|
@@ -2,15 +2,21 @@ package org.thoughtcrime.securesms.profiles;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.whispersystems.signalservice.api.util.StreamDetails;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -57,4 +63,24 @@ public class AvatarHelper {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static @NonNull StreamDetails avatarStream(@NonNull byte[] data) {
|
||||
return new StreamDetails(new ByteArrayInputStream(data), MediaUtil.IMAGE_JPEG, data.length);
|
||||
}
|
||||
|
||||
public static @Nullable StreamDetails getSelfProfileAvatarStream(@NonNull Context context) {
|
||||
File avatarFile = getAvatarFile(context, Recipient.self().getId());
|
||||
|
||||
if (avatarFile.exists() && avatarFile.length() > 0) {
|
||||
try {
|
||||
FileInputStream stream = new FileInputStream(avatarFile);
|
||||
|
||||
return new StreamDetails(stream, MediaUtil.IMAGE_JPEG, avatarFile.length());
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -20,17 +20,14 @@ import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||
import org.thoughtcrime.securesms.profiles.SystemProfileUtil;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.service.IncomingMessageObserver;
|
||||
import org.thoughtcrime.securesms.util.ProfileUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessagePipe;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
|
||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
@@ -132,7 +129,7 @@ class EditProfileRepository {
|
||||
@WorkerThread
|
||||
private @NonNull Optional<String> getUsernameInternal() {
|
||||
try {
|
||||
SignalServiceProfile profile = retrieveOwnProfile();
|
||||
SignalServiceProfile profile = ProfileUtil.retrieveProfile(context, Recipient.self(), SignalServiceProfile.RequestType.PROFILE).getProfile();
|
||||
TextSecurePreferences.setLocalUsername(context, profile.getUsername());
|
||||
DatabaseFactory.getRecipientDatabase(context).setUsername(Recipient.self().getId(), profile.getUsername());
|
||||
} catch (IOException e) {
|
||||
@@ -141,22 +138,6 @@ class EditProfileRepository {
|
||||
return Optional.fromNullable(TextSecurePreferences.getLocalUsername(context));
|
||||
}
|
||||
|
||||
private SignalServiceProfile retrieveOwnProfile() throws IOException {
|
||||
SignalServiceAddress address = new SignalServiceAddress(TextSecurePreferences.getLocalUuid(context), TextSecurePreferences.getLocalNumber(context));
|
||||
SignalServiceMessageReceiver receiver = ApplicationDependencies.getSignalServiceMessageReceiver();
|
||||
SignalServiceMessagePipe pipe = IncomingMessageObserver.getPipe();
|
||||
|
||||
if (pipe != null) {
|
||||
try {
|
||||
return pipe.getProfile(address, Optional.absent());
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
return receiver.retrieveProfile(address, Optional.absent());
|
||||
}
|
||||
|
||||
public enum UploadResult {
|
||||
SUCCESS,
|
||||
ERROR_FILE_IO
|
||||
|
@@ -7,6 +7,7 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
import org.thoughtcrime.securesms.net.UserAgentInterceptor;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.signalservice.api.push.TrustStore;
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl;
|
||||
@@ -16,6 +17,7 @@ import org.whispersystems.signalservice.internal.configuration.SignalServiceConf
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalServiceUrl;
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalStorageUrl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -144,7 +146,13 @@ public class SignalServiceNetworkAccess {
|
||||
final SignalStorageUrl qatarGoogleStorage = new SignalStorageUrl("https://www.google.com.qa/storage", SERVICE_REFLECTOR_HOST, trustStore, GMAIL_CONNECTION_SPEC);
|
||||
|
||||
final List<Interceptor> interceptors = Collections.singletonList(new UserAgentInterceptor());
|
||||
final byte[] zkGroupServerPublicParams;
|
||||
|
||||
try {
|
||||
zkGroupServerPublicParams = Base64.decode(BuildConfig.ZKGROUP_SERVER_PUBLIC_PARAMS);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
this.censorshipConfiguration = new HashMap<String, SignalServiceConfiguration>() {{
|
||||
put(COUNTRY_CODE_EGYPT, new SignalServiceConfiguration(new SignalServiceUrl[] {egyptGoogleService, baseGoogleService, baseAndroidService, mapsOneAndroidService, mapsTwoAndroidService, mailAndroidService},
|
||||
@@ -152,21 +160,24 @@ public class SignalServiceNetworkAccess {
|
||||
new SignalContactDiscoveryUrl[] {egyptGoogleDiscovery, baseGoogleDiscovery, baseAndroidDiscovery, mapsOneAndroidDiscovery, mapsTwoAndroidDiscovery, mailAndroidDiscovery},
|
||||
new SignalKeyBackupServiceUrl[] {egyptGoogleKbs, baseGoogleKbs, baseAndroidKbs, mapsOneAndroidKbs, mapsTwoAndroidKbs, mailAndroidKbs},
|
||||
new SignalStorageUrl[] {egyptGoogleStorage, baseGoogleStorage, baseAndroidStorage, mapsOneAndroidStorage, mapsTwoAndroidStorage, mailAndroidStorage},
|
||||
interceptors));
|
||||
interceptors,
|
||||
zkGroupServerPublicParams));
|
||||
|
||||
put(COUNTRY_CODE_UAE, new SignalServiceConfiguration(new SignalServiceUrl[] {uaeGoogleService, baseAndroidService, baseGoogleService, mapsOneAndroidService, mapsTwoAndroidService, mailAndroidService},
|
||||
new SignalCdnUrl[] {uaeGoogleCdn, baseAndroidCdn, baseGoogleCdn, mapsOneAndroidCdn, mapsTwoAndroidCdn, mailAndroidCdn},
|
||||
new SignalContactDiscoveryUrl[] {uaeGoogleDiscovery, baseGoogleDiscovery, baseAndroidDiscovery, mapsOneAndroidDiscovery, mapsTwoAndroidDiscovery, mailAndroidDiscovery},
|
||||
new SignalKeyBackupServiceUrl[] {uaeGoogleKbs, baseGoogleKbs, baseAndroidKbs, mapsOneAndroidKbs, mapsTwoAndroidKbs, mailAndroidKbs},
|
||||
new SignalStorageUrl[] {uaeGoogleStorage, baseGoogleStorage, baseAndroidStorage, mapsOneAndroidStorage, mapsTwoAndroidStorage, mailAndroidStorage},
|
||||
interceptors));
|
||||
interceptors,
|
||||
zkGroupServerPublicParams));
|
||||
|
||||
put(COUNTRY_CODE_OMAN, new SignalServiceConfiguration(new SignalServiceUrl[] {omanGoogleService, baseAndroidService, baseGoogleService, mapsOneAndroidService, mapsTwoAndroidService, mailAndroidService},
|
||||
new SignalCdnUrl[] {omanGoogleCdn, baseAndroidCdn, baseGoogleCdn, mapsOneAndroidCdn, mapsTwoAndroidCdn, mailAndroidCdn},
|
||||
new SignalContactDiscoveryUrl[] {omanGoogleDiscovery, baseGoogleDiscovery, baseAndroidDiscovery, mapsOneAndroidDiscovery, mapsTwoAndroidDiscovery, mailAndroidDiscovery},
|
||||
new SignalKeyBackupServiceUrl[] {omanGoogleKbs, baseGoogleKbs, baseAndroidKbs, mapsOneAndroidKbs, mapsTwoAndroidKbs, mailAndroidKbs},
|
||||
new SignalStorageUrl[] {omanGoogleStorage, baseGoogleStorage, baseAndroidStorage, mapsOneAndroidStorage, mapsTwoAndroidStorage, mailAndroidStorage},
|
||||
interceptors));
|
||||
interceptors,
|
||||
zkGroupServerPublicParams));
|
||||
|
||||
|
||||
put(COUNTRY_CODE_QATAR, new SignalServiceConfiguration(new SignalServiceUrl[] {qatarGoogleService, baseAndroidService, baseGoogleService, mapsOneAndroidService, mapsTwoAndroidService, mailAndroidService},
|
||||
@@ -174,7 +185,8 @@ public class SignalServiceNetworkAccess {
|
||||
new SignalContactDiscoveryUrl[] {qatarGoogleDiscovery, baseGoogleDiscovery, baseAndroidDiscovery, mapsOneAndroidDiscovery, mapsTwoAndroidDiscovery, mailAndroidDiscovery},
|
||||
new SignalKeyBackupServiceUrl[] {qatarGoogleKbs, baseGoogleKbs, baseAndroidKbs, mapsOneAndroidKbs, mapsTwoAndroidKbs, mailAndroidKbs},
|
||||
new SignalStorageUrl[] {qatarGoogleStorage, baseGoogleStorage, baseAndroidStorage, mapsOneAndroidStorage, mapsTwoAndroidStorage, mailAndroidStorage},
|
||||
interceptors));
|
||||
interceptors,
|
||||
zkGroupServerPublicParams));
|
||||
}};
|
||||
|
||||
this.uncensoredConfiguration = new SignalServiceConfiguration(new SignalServiceUrl[] {new SignalServiceUrl(BuildConfig.SIGNAL_URL, new SignalServiceTrustStore(context))},
|
||||
@@ -182,7 +194,8 @@ public class SignalServiceNetworkAccess {
|
||||
new SignalContactDiscoveryUrl[] {new SignalContactDiscoveryUrl(BuildConfig.SIGNAL_CONTACT_DISCOVERY_URL, new SignalServiceTrustStore(context))},
|
||||
new SignalKeyBackupServiceUrl[] { new SignalKeyBackupServiceUrl(BuildConfig.SIGNAL_KEY_BACKUP_URL, new SignalServiceTrustStore(context)) },
|
||||
new SignalStorageUrl[] {new SignalStorageUrl(BuildConfig.STORAGE_URL, new SignalServiceTrustStore(context))},
|
||||
interceptors);
|
||||
interceptors,
|
||||
zkGroupServerPublicParams);
|
||||
|
||||
this.censoredCountries = this.censorshipConfiguration.keySet().toArray(new String[0]);
|
||||
}
|
||||
|
@@ -78,6 +78,7 @@ public class Recipient {
|
||||
private final int expireMessages;
|
||||
private final RegisteredState registered;
|
||||
private final byte[] profileKey;
|
||||
private final byte[] profileKeyCredential;
|
||||
private final String name;
|
||||
private final Uri systemContactPhoto;
|
||||
private final String customLabel;
|
||||
@@ -297,6 +298,7 @@ public class Recipient {
|
||||
this.expireMessages = 0;
|
||||
this.registered = RegisteredState.UNKNOWN;
|
||||
this.profileKey = null;
|
||||
this.profileKeyCredential = null;
|
||||
this.name = null;
|
||||
this.systemContactPhoto = null;
|
||||
this.customLabel = null;
|
||||
@@ -336,6 +338,7 @@ public class Recipient {
|
||||
this.expireMessages = details.expireMessages;
|
||||
this.registered = details.registered;
|
||||
this.profileKey = details.profileKey;
|
||||
this.profileKeyCredential = details.profileKeyCredential;
|
||||
this.name = details.name;
|
||||
this.systemContactPhoto = details.systemContactPhoto;
|
||||
this.customLabel = details.customLabel;
|
||||
@@ -666,6 +669,14 @@ public class Recipient {
|
||||
return profileKey;
|
||||
}
|
||||
|
||||
public @Nullable byte[] getProfileKeyCredential() {
|
||||
return profileKeyCredential;
|
||||
}
|
||||
|
||||
public boolean hasProfileKeyCredential() {
|
||||
return profileKeyCredential != null;
|
||||
}
|
||||
|
||||
public @Nullable byte[] getStorageServiceKey() {
|
||||
return storageKey;
|
||||
}
|
||||
|
@@ -47,6 +47,7 @@ public class RecipientDetails {
|
||||
final Optional<Integer> defaultSubscriptionId;
|
||||
final RegisteredState registered;
|
||||
final byte[] profileKey;
|
||||
final byte[] profileKeyCredential;
|
||||
final String profileAvatar;
|
||||
final boolean profileSharing;
|
||||
final boolean systemContact;
|
||||
@@ -90,6 +91,7 @@ public class RecipientDetails {
|
||||
this.defaultSubscriptionId = settings.getDefaultSubscriptionId();
|
||||
this.registered = settings.getRegistered();
|
||||
this.profileKey = settings.getProfileKey();
|
||||
this.profileKeyCredential = settings.getProfileKeyCredential();
|
||||
this.profileAvatar = settings.getProfileAvatar();
|
||||
this.profileSharing = settings.isProfileSharing();
|
||||
this.systemContact = systemContact;
|
||||
@@ -134,6 +136,7 @@ public class RecipientDetails {
|
||||
this.defaultSubscriptionId = Optional.absent();
|
||||
this.registered = RegisteredState.UNKNOWN;
|
||||
this.profileKey = null;
|
||||
this.profileKeyCredential = null;
|
||||
this.profileAvatar = null;
|
||||
this.profileSharing = false;
|
||||
this.systemContact = true;
|
||||
|
@@ -6,10 +6,11 @@ import android.os.AsyncTask;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.zkgroup.profiles.ProfileKey;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.PreKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SessionUtil;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
@@ -28,7 +29,6 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
|
||||
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||
import org.whispersystems.libsignal.state.PreKeyRecord;
|
||||
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
||||
@@ -39,6 +39,7 @@ import org.whispersystems.signalservice.api.KeyBackupServicePinException;
|
||||
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
|
||||
import org.whispersystems.signalservice.api.RegistrationLockData;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
||||
import org.whispersystems.signalservice.api.kbs.HashedPin;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.RateLimitException;
|
||||
@@ -191,17 +192,17 @@ public final class CodeVerificationRequest {
|
||||
@Nullable String fcmToken)
|
||||
throws IOException, KeyBackupSystemWrongPinException, KeyBackupSystemNoDataException
|
||||
{
|
||||
boolean isV2KbsPin = kbsTokenResponse != null;
|
||||
int registrationId = KeyHelper.generateRegistrationId(false);
|
||||
boolean universalUnidentifiedAccess = TextSecurePreferences.isUniversalUnidentifiedAccess(context);
|
||||
byte[] profileKey = findExistingProfileKey(context, credentials.getE164number());
|
||||
boolean isV2KbsPin = kbsTokenResponse != null;
|
||||
int registrationId = KeyHelper.generateRegistrationId(false);
|
||||
boolean universalUnidentifiedAccess = TextSecurePreferences.isUniversalUnidentifiedAccess(context);
|
||||
ProfileKey profileKey = findExistingProfileKey(context, credentials.getE164number());
|
||||
|
||||
if (profileKey == null) {
|
||||
profileKey = Util.getSecretBytes(32);
|
||||
profileKey = ProfileKeyUtil.createNew();
|
||||
Log.i(TAG, "No profile key found, created a new one");
|
||||
}
|
||||
|
||||
byte[] unidentifiedAccessKey = UnidentifiedAccessUtil.getSelfUnidentifiedAccessKey(profileKey);
|
||||
byte[] unidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(profileKey);
|
||||
|
||||
TextSecurePreferences.setLocalRegistrationId(context, registrationId);
|
||||
SessionUtil.archiveAllSessions(context);
|
||||
@@ -269,12 +270,12 @@ public final class CodeVerificationRequest {
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable byte[] findExistingProfileKey(@NonNull Context context, @NonNull String e164number) {
|
||||
private static @Nullable ProfileKey findExistingProfileKey(@NonNull Context context, @NonNull String e164number) {
|
||||
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
|
||||
Optional<RecipientId> recipient = recipientDatabase.getByE164(e164number);
|
||||
|
||||
if (recipient.isPresent()) {
|
||||
return Recipient.resolved(recipient.get()).getProfileKey();
|
||||
return ProfileKeyUtil.profileKeyOrNull(Recipient.resolved(recipient.get()).getProfileKey());
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@@ -312,4 +312,7 @@ public final class FeatureFlags {
|
||||
return disk;
|
||||
}
|
||||
}
|
||||
|
||||
/** Read and write versioned profile information. */
|
||||
public static final boolean VERSIONED_PROFILES = org.whispersystems.signalservice.FeatureFlags.VERSIONED_PROFILES;
|
||||
}
|
||||
|
@@ -6,6 +6,9 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.signal.zkgroup.VerificationFailedException;
|
||||
import org.signal.zkgroup.profiles.ProfileKey;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
@@ -19,6 +22,7 @@ import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
|
||||
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
|
||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
|
||||
@@ -28,22 +32,30 @@ import java.io.IOException;
|
||||
/**
|
||||
* Aids in the retrieval and decryption of profiles.
|
||||
*/
|
||||
public class ProfileUtil {
|
||||
public final class ProfileUtil {
|
||||
|
||||
private ProfileUtil() {
|
||||
}
|
||||
|
||||
private static final String TAG = Log.tag(ProfileUtil.class);
|
||||
|
||||
@WorkerThread
|
||||
public static SignalServiceProfile retrieveProfile(@NonNull Context context, @NonNull Recipient recipient) throws IOException {
|
||||
public static @NonNull ProfileAndCredential retrieveProfile(@NonNull Context context,
|
||||
@NonNull Recipient recipient,
|
||||
@NonNull SignalServiceProfile.RequestType requestType)
|
||||
throws IOException
|
||||
{
|
||||
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
|
||||
Optional<UnidentifiedAccess> unidentifiedAccess = getUnidentifiedAccess(context, recipient);
|
||||
Optional<ProfileKey> profileKey = ProfileKeyUtil.profileKeyOptional(recipient.getProfileKey());
|
||||
|
||||
SignalServiceProfile profile;
|
||||
ProfileAndCredential profile;
|
||||
|
||||
try {
|
||||
profile = retrieveProfileInternal(address, unidentifiedAccess);
|
||||
profile = retrieveProfileInternal(address, profileKey, unidentifiedAccess, requestType);
|
||||
} catch (NonSuccessfulResponseCodeException e) {
|
||||
if (unidentifiedAccess.isPresent()) {
|
||||
profile = retrieveProfileInternal(address, Optional.absent());
|
||||
profile = retrieveProfileInternal(address, profileKey, Optional.absent(), requestType);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
@@ -52,7 +64,7 @@ public class ProfileUtil {
|
||||
return profile;
|
||||
}
|
||||
|
||||
public static @Nullable String decryptName(@NonNull byte[] profileKey, @Nullable String encryptedName)
|
||||
public static @Nullable String decryptName(@NonNull ProfileKey profileKey, @Nullable String encryptedName)
|
||||
throws InvalidCiphertextException, IOException
|
||||
{
|
||||
if (encryptedName == null) {
|
||||
@@ -64,8 +76,11 @@ public class ProfileUtil {
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private static SignalServiceProfile retrieveProfileInternal(@NonNull SignalServiceAddress address, Optional<UnidentifiedAccess> unidentifiedAccess)
|
||||
throws IOException
|
||||
private static @NonNull ProfileAndCredential retrieveProfileInternal(@NonNull SignalServiceAddress address,
|
||||
@NonNull Optional<ProfileKey> profileKey,
|
||||
@NonNull Optional<UnidentifiedAccess> unidentifiedAccess,
|
||||
@NonNull SignalServiceProfile.RequestType requestType)
|
||||
throws IOException
|
||||
{
|
||||
SignalServiceMessagePipe authPipe = IncomingMessageObserver.getPipe();
|
||||
SignalServiceMessagePipe unidentifiedPipe = IncomingMessageObserver.getUnidentifiedPipe();
|
||||
@@ -74,14 +89,18 @@ public class ProfileUtil {
|
||||
|
||||
if (pipe != null) {
|
||||
try {
|
||||
return pipe.getProfile(address, unidentifiedAccess);
|
||||
return pipe.getProfile(address, profileKey, unidentifiedAccess, requestType);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
Log.w(TAG, "Websocket request failed. Falling back to REST.", e);
|
||||
}
|
||||
}
|
||||
|
||||
SignalServiceMessageReceiver receiver = ApplicationDependencies.getSignalServiceMessageReceiver();
|
||||
return receiver.retrieveProfile(address, unidentifiedAccess);
|
||||
try {
|
||||
return receiver.retrieveProfile(address, profileKey, unidentifiedAccess, requestType);
|
||||
} catch (VerificationFailedException e) {
|
||||
throw new IOException("Verification Problem", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Optional<UnidentifiedAccess> getUnidentifiedAccess(@NonNull Context context, @NonNull Recipient recipient) {
|
||||
|
@@ -7,8 +7,6 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -46,9 +44,9 @@ public final class SqlUtil {
|
||||
* change. In other words, if {@link SQLiteDatabase#update(String, ContentValues, String, String[])}
|
||||
* returns > 0, then you know something *actually* changed.
|
||||
*/
|
||||
public static @NonNull Pair<String, String[]> buildTrueUpdateQuery(@NonNull String selection,
|
||||
@NonNull String[] args,
|
||||
@NonNull ContentValues contentValues)
|
||||
public static @NonNull UpdateQuery buildTrueUpdateQuery(@NonNull String selection,
|
||||
@NonNull String[] args,
|
||||
@NonNull ContentValues contentValues)
|
||||
{
|
||||
StringBuilder qualifier = new StringBuilder();
|
||||
Set<Map.Entry<String, Object>> valueSet = contentValues.valueSet();
|
||||
@@ -73,6 +71,24 @@ public final class SqlUtil {
|
||||
i++;
|
||||
}
|
||||
|
||||
return new Pair<>("(" + selection + ") AND (" + qualifier + ")", fullArgs.toArray(new String[0]));
|
||||
return new UpdateQuery("(" + selection + ") AND (" + qualifier + ")", fullArgs.toArray(new String[0]));
|
||||
}
|
||||
|
||||
public static class UpdateQuery {
|
||||
private final String where;
|
||||
private final String[] whereArgs;
|
||||
|
||||
private UpdateQuery(@NonNull String where, @NonNull String[] whereArgs) {
|
||||
this.where = where;
|
||||
this.whereArgs = whereArgs;
|
||||
}
|
||||
|
||||
public String getWhere() {
|
||||
return where;
|
||||
}
|
||||
|
||||
public String[] getWhereArgs() {
|
||||
return whereArgs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.contacts.sync;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -21,13 +22,13 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import edu.emory.mathcs.backport.java.util.Arrays;
|
||||
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
|
||||
public class StorageSyncHelperTest {
|
||||
public final class StorageSyncHelperTest {
|
||||
|
||||
private static final UUID UUID_A = UuidUtil.parseOrThrow("ebef429e-695e-4f51-bcc4-526a60ac68c7");
|
||||
private static final UUID UUID_B = UuidUtil.parseOrThrow("32119989-77fb-4e18-af70-81d55185c6b1");
|
||||
@@ -253,8 +254,63 @@ public class StorageSyncHelperTest {
|
||||
assertByteListEquals(byteListOf(1), result.getDeletes());
|
||||
}
|
||||
|
||||
private static <E> Set<E> setOf(E... vals) {
|
||||
return new LinkedHashSet<E>(Arrays.asList(vals));
|
||||
@Test
|
||||
public void contacts_with_same_profile_key_contents_are_equal() {
|
||||
byte[] profileKey = new byte[32];
|
||||
byte[] profileKeyCopy = profileKey.clone();
|
||||
|
||||
SignalContactRecord a = contactBuilder(1, UUID_A, E164_A, "a").setProfileKey(profileKey).build();
|
||||
SignalContactRecord b = contactBuilder(1, UUID_A, E164_A, "a").setProfileKey(profileKeyCopy).build();
|
||||
|
||||
assertEquals(a, b);
|
||||
assertEquals(a.hashCode(), b.hashCode());
|
||||
|
||||
assertFalse(contactUpdate(a, b).profileKeyChanged());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contacts_with_different_profile_key_contents_are_not_equal() {
|
||||
byte[] profileKey = new byte[32];
|
||||
byte[] profileKeyCopy = profileKey.clone();
|
||||
profileKeyCopy[0] = 1;
|
||||
|
||||
SignalContactRecord a = contactBuilder(1, UUID_A, E164_A, "a").setProfileKey(profileKey).build();
|
||||
SignalContactRecord b = contactBuilder(1, UUID_A, E164_A, "a").setProfileKey(profileKeyCopy).build();
|
||||
|
||||
assertNotEquals(a, b);
|
||||
assertNotEquals(a.hashCode(), b.hashCode());
|
||||
|
||||
assertTrue(contactUpdate(a, b).profileKeyChanged());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contacts_with_same_identity_key_contents_are_equal() {
|
||||
byte[] profileKey = new byte[32];
|
||||
byte[] profileKeyCopy = profileKey.clone();
|
||||
|
||||
SignalContactRecord a = contactBuilder(1, UUID_A, E164_A, "a").setIdentityKey(profileKey).build();
|
||||
SignalContactRecord b = contactBuilder(1, UUID_A, E164_A, "a").setIdentityKey(profileKeyCopy).build();
|
||||
|
||||
assertEquals(a, b);
|
||||
assertEquals(a.hashCode(), b.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contacts_with_different_identity_key_contents_are_not_equal() {
|
||||
byte[] profileKey = new byte[32];
|
||||
byte[] profileKeyCopy = profileKey.clone();
|
||||
profileKeyCopy[0] = 1;
|
||||
|
||||
SignalContactRecord a = contactBuilder(1, UUID_A, E164_A, "a").setIdentityKey(profileKey).build();
|
||||
SignalContactRecord b = contactBuilder(1, UUID_A, E164_A, "a").setIdentityKey(profileKeyCopy).build();
|
||||
|
||||
assertNotEquals(a, b);
|
||||
assertNotEquals(a.hashCode(), b.hashCode());
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private static <E> Set<E> setOf(E... values) {
|
||||
return Sets.newHashSet(values);
|
||||
}
|
||||
|
||||
private static Set<SignalStorageRecord> recordSetOf(SignalContactRecord... contactRecords) {
|
||||
@@ -267,14 +323,21 @@ public class StorageSyncHelperTest {
|
||||
return storageRecords;
|
||||
}
|
||||
|
||||
private static SignalContactRecord.Builder contactBuilder(int key,
|
||||
UUID uuid,
|
||||
String e164,
|
||||
String profileName)
|
||||
{
|
||||
return new SignalContactRecord.Builder(byteArray(key), new SignalServiceAddress(uuid, e164))
|
||||
.setProfileName(profileName);
|
||||
}
|
||||
|
||||
private static SignalContactRecord contact(int key,
|
||||
UUID uuid,
|
||||
String e164,
|
||||
String profileName)
|
||||
{
|
||||
return new SignalContactRecord.Builder(byteArray(key), new SignalServiceAddress(uuid, e164))
|
||||
.setProfileName(profileName)
|
||||
.build();
|
||||
return contactBuilder(key, uuid, e164, profileName).build();
|
||||
}
|
||||
|
||||
private static StorageSyncHelper.ContactUpdate contactUpdate(SignalContactRecord oldContact, SignalContactRecord newContact) {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.database;
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.ContentValues;
|
||||
@@ -7,15 +7,13 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.thoughtcrime.securesms.util.SqlUtil;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE, application = Application.class)
|
||||
public class SqlUtilTest {
|
||||
public final class SqlUtilTest {
|
||||
|
||||
@Test
|
||||
public void buildTrueUpdateQuery_simple() {
|
||||
@@ -25,10 +23,10 @@ public class SqlUtilTest {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("a", 2);
|
||||
|
||||
Pair<String, String[]> result = SqlUtil.buildTrueUpdateQuery(selection, args, values);
|
||||
SqlUtil.UpdateQuery updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, values);
|
||||
|
||||
assertEquals("(_id = ?) AND (a != ? OR a IS NULL)", result.first());
|
||||
assertArrayEquals(new String[] { "1", "2" }, result.second());
|
||||
assertEquals("(_id = ?) AND (a != ? OR a IS NULL)", updateQuery.getWhere());
|
||||
assertArrayEquals(new String[] { "1", "2" }, updateQuery.getWhereArgs());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -39,10 +37,10 @@ public class SqlUtilTest {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("a", 4);
|
||||
|
||||
Pair<String, String[]> result = SqlUtil.buildTrueUpdateQuery(selection, args, values);
|
||||
SqlUtil.UpdateQuery updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, values);
|
||||
|
||||
assertEquals("(_id = ? AND (foo = ? OR bar != ?)) AND (a != ? OR a IS NULL)", result.first());
|
||||
assertArrayEquals(new String[] { "1", "2", "3", "4" }, result.second());
|
||||
assertEquals("(_id = ? AND (foo = ? OR bar != ?)) AND (a != ? OR a IS NULL)", updateQuery.getWhere());
|
||||
assertArrayEquals(new String[] { "1", "2", "3", "4" }, updateQuery.getWhereArgs());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -55,10 +53,10 @@ public class SqlUtilTest {
|
||||
values.put("b", 3);
|
||||
values.put("c", 4);
|
||||
|
||||
Pair<String, String[]> result = SqlUtil.buildTrueUpdateQuery(selection, args, values);
|
||||
SqlUtil.UpdateQuery updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, values);
|
||||
|
||||
assertEquals("(_id = ?) AND (a != ? OR a IS NULL OR b != ? OR b IS NULL OR c != ? OR c IS NULL)", result.first());
|
||||
assertArrayEquals(new String[] { "1", "2", "3", "4"}, result.second());
|
||||
assertEquals("(_id = ?) AND (a != ? OR a IS NULL OR b != ? OR b IS NULL OR c != ? OR c IS NULL)", updateQuery.getWhere());
|
||||
assertArrayEquals(new String[] { "1", "2", "3", "4"}, updateQuery.getWhereArgs());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -69,10 +67,10 @@ public class SqlUtilTest {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("a", (String) null);
|
||||
|
||||
Pair<String, String[]> result = SqlUtil.buildTrueUpdateQuery(selection, args, values);
|
||||
SqlUtil.UpdateQuery updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, values);
|
||||
|
||||
assertEquals("(_id = ?) AND (a NOT NULL)", result.first());
|
||||
assertArrayEquals(new String[] { "1" }, result.second());
|
||||
assertEquals("(_id = ?) AND (a NOT NULL)", updateQuery.getWhere());
|
||||
assertArrayEquals(new String[] { "1" }, updateQuery.getWhereArgs());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -87,9 +85,9 @@ public class SqlUtilTest {
|
||||
values.put("d", (String) null);
|
||||
values.put("e", (String) null);
|
||||
|
||||
Pair<String, String[]> result = SqlUtil.buildTrueUpdateQuery(selection, args, values);
|
||||
SqlUtil.UpdateQuery updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, values);
|
||||
|
||||
assertEquals("(_id = ?) AND (a NOT NULL OR b != ? OR b IS NULL OR c != ? OR c IS NULL OR d NOT NULL OR e NOT NULL)", result.first());
|
||||
assertArrayEquals(new String[] { "1", "2", "3" }, result.second());
|
||||
assertEquals("(_id = ?) AND (a NOT NULL OR b != ? OR b IS NULL OR c != ? OR c IS NULL OR d NOT NULL OR e NOT NULL)", updateQuery.getWhere());
|
||||
assertArrayEquals(new String[] { "1", "2", "3" }, updateQuery.getWhereArgs());
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user