mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-22 09:49:30 +01:00
Add Group Send Endorsements support.
This commit is contained in:
@@ -77,6 +77,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
|
||||
import org.thoughtcrime.securesms.logging.PersistentLogger;
|
||||
import org.thoughtcrime.securesms.messageprocessingalarm.RoutineMessageFetchReceiver;
|
||||
import org.thoughtcrime.securesms.messages.GroupSendEndorsementInternalNotifier;
|
||||
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
||||
import org.thoughtcrime.securesms.mms.SignalGlideComponents;
|
||||
import org.thoughtcrime.securesms.mms.SignalGlideModule;
|
||||
@@ -222,6 +223,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||
.addPostRender(GroupRingCleanupJob::enqueue)
|
||||
.addPostRender(LinkedDeviceInactiveCheckJob::enqueueIfNecessary)
|
||||
.addPostRender(() -> ActiveCallManager.clearNotifications(this))
|
||||
.addPostRender(() -> GroupSendEndorsementInternalNotifier.init())
|
||||
.execute();
|
||||
|
||||
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");
|
||||
|
||||
@@ -241,7 +241,7 @@ fun RecipientTable.restoreGroupFromBackup(group: Group): RecipientId {
|
||||
}
|
||||
|
||||
val recipientId = writableDatabase.insert(RecipientTable.TABLE_NAME, null, values)
|
||||
val restoredId = SignalDatabase.groups.create(masterKey, decryptedState)
|
||||
val restoredId = SignalDatabase.groups.create(masterKey, decryptedState, groupSendEndorsements = null)
|
||||
if (restoredId != null) {
|
||||
SignalDatabase.groups.setShowAsStoryState(restoredId, group.storySendMode.toGroupShowAsStoryState())
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ class InternalConversationSettingsFragment : DSLSettingsFragment(
|
||||
|
||||
textPref(
|
||||
title = DSLSettingsText.from("Sealed Sender Mode"),
|
||||
summary = DSLSettingsText.from(recipient.unidentifiedAccessMode.toString())
|
||||
summary = DSLSettingsText.from(recipient.sealedSenderAccessMode.toString())
|
||||
)
|
||||
|
||||
textPref(
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.signal.core.util.Base64;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.metadata.certificate.CertificateValidator;
|
||||
import org.signal.libsignal.metadata.certificate.InvalidCertificateException;
|
||||
import org.signal.libsignal.metadata.certificate.SenderCertificate;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.signal.libsignal.protocol.ecc.Curve;
|
||||
import org.signal.libsignal.protocol.ecc.ECPublicKey;
|
||||
@@ -21,10 +21,8 @@ import org.thoughtcrime.securesms.keyvalue.CertificateType;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.signal.core.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
@@ -35,9 +33,9 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class UnidentifiedAccessUtil {
|
||||
public class SealedSenderAccessUtil {
|
||||
|
||||
private static final String TAG = Log.tag(UnidentifiedAccessUtil.class);
|
||||
private static final String TAG = Log.tag(SealedSenderAccessUtil.class);
|
||||
|
||||
private static final byte[] UNRESTRICTED_KEY = new byte[16];
|
||||
|
||||
@@ -46,28 +44,34 @@ public class UnidentifiedAccessUtil {
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static Optional<UnidentifiedAccessPair> getAccessFor(@NonNull Context context, @NonNull Recipient recipient) {
|
||||
return getAccessFor(context, recipient, true);
|
||||
public static @Nullable SealedSenderAccess getSealedSenderAccessFor(@NonNull Recipient recipient) {
|
||||
return getSealedSenderAccessFor(recipient, true);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static Optional<UnidentifiedAccessPair> getAccessFor(@NonNull Context context, @NonNull Recipient recipient, boolean log) {
|
||||
return getAccessFor(context, Collections.singletonList(recipient), log).get(0);
|
||||
public static @Nullable SealedSenderAccess getSealedSenderAccessFor(@NonNull Recipient recipient, boolean log) {
|
||||
return SealedSenderAccess.forIndividual(getAccessFor(recipient, log));
|
||||
}
|
||||
|
||||
public static @Nullable SealedSenderAccess getSealedSenderAccessFor(@NonNull Recipient recipient, @Nullable SealedSenderAccess.CreateGroupSendToken createGroupSendToken) {
|
||||
return SealedSenderAccess.forIndividualWithGroupFallback(getAccessFor(recipient, true), getSealedSenderCertificate(), createGroupSendToken);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static List<Optional<UnidentifiedAccessPair>> getAccessFor(@NonNull Context context, @NonNull List<Recipient> recipients) {
|
||||
return getAccessFor(context, recipients, true);
|
||||
private static @Nullable UnidentifiedAccess getAccessFor(@NonNull Recipient recipient, boolean log) {
|
||||
return getAccessFor(Collections.singletonList(recipient), false, log)
|
||||
.get(0)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static Map<RecipientId, Optional<UnidentifiedAccessPair>> getAccessMapFor(@NonNull Context context, @NonNull List<Recipient> recipients, boolean isForStory) {
|
||||
List<Optional<UnidentifiedAccessPair>> accessList = getAccessFor(context, recipients, isForStory, true);
|
||||
public static Map<RecipientId, Optional<UnidentifiedAccess>> getAccessMapFor(@NonNull List<Recipient> recipients, boolean isForStory) {
|
||||
List<Optional<UnidentifiedAccess>> accessList = getAccessFor(recipients, isForStory, true);
|
||||
|
||||
Iterator<Recipient> recipientIterator = recipients.iterator();
|
||||
Iterator<Optional<UnidentifiedAccessPair>> accessIterator = accessList.iterator();
|
||||
Iterator<Recipient> recipientIterator = recipients.iterator();
|
||||
Iterator<Optional<UnidentifiedAccess>> accessIterator = accessList.iterator();
|
||||
|
||||
Map<RecipientId, Optional<UnidentifiedAccessPair>> accessMap = new HashMap<>(recipients.size());
|
||||
Map<RecipientId, Optional<UnidentifiedAccess>> accessMap = new HashMap<>(recipients.size());
|
||||
|
||||
while (recipientIterator.hasNext()) {
|
||||
accessMap.put(recipientIterator.next().getId(), accessIterator.next());
|
||||
@@ -77,40 +81,22 @@ public class UnidentifiedAccessUtil {
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static List<Optional<UnidentifiedAccessPair>> getAccessFor(@NonNull Context context, @NonNull List<Recipient> recipients, boolean log) {
|
||||
return getAccessFor(context, recipients, false, log);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static List<Optional<UnidentifiedAccessPair>> getAccessFor(@NonNull Context context, @NonNull List<Recipient> recipients, boolean isForStory, boolean log) {
|
||||
final byte[] ourUnidentifiedAccessKey;
|
||||
|
||||
if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) {
|
||||
ourUnidentifiedAccessKey = UNRESTRICTED_KEY;
|
||||
} else {
|
||||
ourUnidentifiedAccessKey = ProfileKeyUtil.getSelfProfileKey().deriveAccessKey();
|
||||
}
|
||||
|
||||
private static List<Optional<UnidentifiedAccess>> getAccessFor(@NonNull List<Recipient> recipients, boolean isForStory, boolean log) {
|
||||
CertificateType certificateType = getUnidentifiedAccessCertificateType();
|
||||
byte[] ourUnidentifiedAccessCertificate = SignalStore.certificate().getUnidentifiedAccessCertificate(certificateType);
|
||||
|
||||
List<Optional<UnidentifiedAccessPair>> access = recipients.parallelStream().map(recipient -> {
|
||||
UnidentifiedAccessPair unidentifiedAccessPair = null;
|
||||
List<Optional<UnidentifiedAccess>> access = recipients.parallelStream().map(recipient -> {
|
||||
UnidentifiedAccess unidentifiedAccess = null;
|
||||
if (ourUnidentifiedAccessCertificate != null) {
|
||||
try {
|
||||
UnidentifiedAccess theirAccess = getTargetUnidentifiedAccess(recipient, ourUnidentifiedAccessCertificate, isForStory);
|
||||
UnidentifiedAccess ourAccess = new UnidentifiedAccess(ourUnidentifiedAccessKey, ourUnidentifiedAccessCertificate, false);
|
||||
|
||||
if (theirAccess != null) {
|
||||
unidentifiedAccessPair = new UnidentifiedAccessPair(theirAccess, ourAccess);
|
||||
}
|
||||
unidentifiedAccess = getTargetUnidentifiedAccess(recipient, ourUnidentifiedAccessCertificate, isForStory);
|
||||
} catch (InvalidCertificateException e) {
|
||||
Log.w(TAG, "Invalid unidentified access certificate!", e);
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "Missing unidentified access certificate!");
|
||||
Log.w(TAG, "Missing our unidentified access certificate!");
|
||||
}
|
||||
return Optional.ofNullable(unidentifiedAccessPair);
|
||||
return Optional.ofNullable(unidentifiedAccess);
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
int unidentifiedCount = Stream.of(access).filter(Optional::isPresent).toList().size();
|
||||
@@ -123,28 +109,17 @@ public class UnidentifiedAccessUtil {
|
||||
return access;
|
||||
}
|
||||
|
||||
public static Optional<UnidentifiedAccessPair> getAccessForSync(@NonNull Context context) {
|
||||
public static @Nullable SenderCertificate getSealedSenderCertificate() {
|
||||
byte[] unidentifiedAccessCertificate = getUnidentifiedAccessCertificate();
|
||||
if (unidentifiedAccessCertificate == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
byte[] ourUnidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(ProfileKeyUtil.getSelfProfileKey());
|
||||
byte[] ourUnidentifiedAccessCertificate = getUnidentifiedAccessCertificate();
|
||||
|
||||
if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) {
|
||||
ourUnidentifiedAccessKey = UNRESTRICTED_KEY;
|
||||
}
|
||||
|
||||
if (ourUnidentifiedAccessCertificate != null) {
|
||||
return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(ourUnidentifiedAccessKey,
|
||||
ourUnidentifiedAccessCertificate,
|
||||
false),
|
||||
new UnidentifiedAccess(ourUnidentifiedAccessKey,
|
||||
ourUnidentifiedAccessCertificate,
|
||||
false)));
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
return new SenderCertificate(unidentifiedAccessCertificate);
|
||||
} catch (InvalidCertificateException e) {
|
||||
Log.w(TAG, e);
|
||||
return Optional.empty();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +141,7 @@ public class UnidentifiedAccessUtil {
|
||||
|
||||
byte[] accessKey;
|
||||
|
||||
switch (recipient.resolve().getUnidentifiedAccessMode()) {
|
||||
switch (recipient.resolve().getSealedSenderAccessMode()) {
|
||||
case UNKNOWN:
|
||||
if (theirProfileKey == null) {
|
||||
if (isForStory) {
|
||||
@@ -192,7 +167,7 @@ public class UnidentifiedAccessUtil {
|
||||
accessKey = UNRESTRICTED_KEY;
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unknown mode: " + recipient.getUnidentifiedAccessMode().getMode());
|
||||
throw new AssertionError("Unknown mode: " + recipient.getSealedSenderAccessMode().getMode());
|
||||
}
|
||||
|
||||
if (accessKey == null && isForStory) {
|
||||
@@ -19,7 +19,9 @@ import org.signal.core.util.isAbsent
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.optionalString
|
||||
import org.signal.core.util.readToList
|
||||
import org.signal.core.util.readToMap
|
||||
import org.signal.core.util.readToSingleInt
|
||||
import org.signal.core.util.readToSingleLong
|
||||
import org.signal.core.util.readToSingleObject
|
||||
import org.signal.core.util.requireBlob
|
||||
import org.signal.core.util.requireBoolean
|
||||
@@ -30,7 +32,11 @@ import org.signal.core.util.requireString
|
||||
import org.signal.core.util.select
|
||||
import org.signal.core.util.update
|
||||
import org.signal.core.util.withinTransaction
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException
|
||||
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
|
||||
import org.signal.libsignal.zkgroup.groups.GroupSecretParams
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsement
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendFullToken
|
||||
import org.signal.storageservice.protos.groups.Member
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember
|
||||
@@ -39,6 +45,7 @@ import org.thoughtcrime.securesms.contacts.paged.collections.ContactSearchIterat
|
||||
import org.thoughtcrime.securesms.crypto.SenderKeyUtil
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.recipients
|
||||
import org.thoughtcrime.securesms.database.model.GroupRecord
|
||||
import org.thoughtcrime.securesms.database.model.GroupSendEndorsementRecords
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.groups.BadGroupIdException
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
@@ -50,6 +57,7 @@ import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupChangeReconstruct
|
||||
import org.whispersystems.signalservice.api.groupsv2.ReceivedGroupSendEndorsements
|
||||
import org.whispersystems.signalservice.api.groupsv2.findMemberByAci
|
||||
import org.whispersystems.signalservice.api.groupsv2.findPendingByServiceId
|
||||
import org.whispersystems.signalservice.api.groupsv2.findRequestingByAci
|
||||
@@ -63,6 +71,7 @@ import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
||||
import java.io.Closeable
|
||||
import java.security.SecureRandom
|
||||
import java.time.Instant
|
||||
import java.util.Optional
|
||||
import java.util.stream.Collectors
|
||||
import javax.annotation.CheckReturnValue
|
||||
@@ -94,6 +103,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
const val DISTRIBUTION_ID = "distribution_id"
|
||||
const val SHOW_AS_STORY_STATE = "show_as_story_state"
|
||||
const val LAST_FORCE_UPDATE_TIMESTAMP = "last_force_update_timestamp"
|
||||
const val GROUP_SEND_ENDORSEMENTS_EXPIRATION = "group_send_endorsements_expiration"
|
||||
|
||||
/** 32 bytes serialized [GroupMasterKey] */
|
||||
const val V2_MASTER_KEY = "master_key"
|
||||
@@ -125,12 +135,13 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
$UNMIGRATED_V1_MEMBERS TEXT DEFAULT NULL,
|
||||
$DISTRIBUTION_ID TEXT UNIQUE DEFAULT NULL,
|
||||
$SHOW_AS_STORY_STATE INTEGER DEFAULT ${ShowAsStoryState.IF_ACTIVE.code},
|
||||
$LAST_FORCE_UPDATE_TIMESTAMP INTEGER DEFAULT 0
|
||||
$LAST_FORCE_UPDATE_TIMESTAMP INTEGER DEFAULT 0,
|
||||
$GROUP_SEND_ENDORSEMENTS_EXPIRATION INTEGER DEFAULT 0
|
||||
)
|
||||
"""
|
||||
|
||||
@JvmField
|
||||
val CREATE_INDEXS = MembershipTable.CREATE_INDEXES
|
||||
val CREATE_INDEXS: Array<String> = MembershipTable.CREATE_INDEXES
|
||||
|
||||
private val GROUP_PROJECTION = arrayOf(
|
||||
GROUP_ID,
|
||||
@@ -147,7 +158,8 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
V2_MASTER_KEY,
|
||||
V2_REVISION,
|
||||
V2_DECRYPTED_GROUP,
|
||||
LAST_FORCE_UPDATE_TIMESTAMP
|
||||
LAST_FORCE_UPDATE_TIMESTAMP,
|
||||
GROUP_SEND_ENDORSEMENTS_EXPIRATION
|
||||
)
|
||||
|
||||
val TYPED_GROUP_PROJECTION = GROUP_PROJECTION
|
||||
@@ -165,6 +177,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
const val ID = "_id"
|
||||
const val GROUP_ID = "group_id"
|
||||
const val RECIPIENT_ID = "recipient_id"
|
||||
const val ENDORSEMENT = "endorsement"
|
||||
|
||||
//language=sql
|
||||
const val CREATE_TABLE = """
|
||||
@@ -172,6 +185,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
$ID INTEGER PRIMARY KEY,
|
||||
$GROUP_ID TEXT NOT NULL REFERENCES ${GroupTable.TABLE_NAME} (${GroupTable.GROUP_ID}) ON DELETE CASCADE,
|
||||
$RECIPIENT_ID INTEGER NOT NULL REFERENCES ${RecipientTable.TABLE_NAME} (${RecipientTable.ID}) ON DELETE CASCADE,
|
||||
$ENDORSEMENT BLOB DEFAULT NULL,
|
||||
UNIQUE($GROUP_ID, $RECIPIENT_ID)
|
||||
)
|
||||
"""
|
||||
@@ -568,19 +582,19 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
throw LegacyGroupInsertException(groupId)
|
||||
}
|
||||
|
||||
return create(groupId, title, members, avatar, null, null)
|
||||
return create(groupId, title, members, avatar, null, null, null)
|
||||
}
|
||||
|
||||
@CheckReturnValue
|
||||
fun create(groupId: GroupId.Mms, title: String?, members: Collection<RecipientId>): Boolean {
|
||||
return create(groupId, if (title.isNullOrEmpty()) null else title, members, null, null, null)
|
||||
return create(groupId, if (title.isNullOrEmpty()) null else title, members, null, null, null, null)
|
||||
}
|
||||
|
||||
@CheckReturnValue
|
||||
fun create(groupMasterKey: GroupMasterKey, groupState: DecryptedGroup): GroupId.V2? {
|
||||
fun create(groupMasterKey: GroupMasterKey, groupState: DecryptedGroup, groupSendEndorsements: ReceivedGroupSendEndorsements?): GroupId.V2? {
|
||||
val groupId = GroupId.v2(groupMasterKey)
|
||||
|
||||
return if (create(groupId = groupId, title = groupState.title, memberCollection = emptyList(), avatar = null, groupMasterKey = groupMasterKey, groupState = groupState)) {
|
||||
return if (create(groupId = groupId, title = groupState.title, memberCollection = emptyList(), avatar = null, groupMasterKey = groupMasterKey, groupState = groupState, receivedGroupSendEndorsements = groupSendEndorsements)) {
|
||||
groupId
|
||||
} else {
|
||||
null
|
||||
@@ -604,10 +618,9 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
if (updated < 1) {
|
||||
Log.w(TAG, "No group entry. Creating restore placeholder for $groupId")
|
||||
create(
|
||||
groupMasterKey,
|
||||
DecryptedGroup.Builder()
|
||||
.revision(GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION)
|
||||
.build()
|
||||
groupMasterKey = groupMasterKey,
|
||||
groupState = DecryptedGroup(revision = GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION),
|
||||
groupSendEndorsements = null
|
||||
)
|
||||
} else {
|
||||
Log.w(TAG, "Had a group entry, but it was missing a master key. Updated.")
|
||||
@@ -628,7 +641,8 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
memberCollection: Collection<RecipientId>,
|
||||
avatar: SignalServiceAttachmentPointer?,
|
||||
groupMasterKey: GroupMasterKey?,
|
||||
groupState: DecryptedGroup?
|
||||
groupState: DecryptedGroup?,
|
||||
receivedGroupSendEndorsements: ReceivedGroupSendEndorsements?
|
||||
): Boolean {
|
||||
val membershipValues = mutableListOf<ContentValues>()
|
||||
val groupRecipientId = recipients.getOrInsertFromGroupId(groupId)
|
||||
@@ -640,7 +654,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
values.put(RECIPIENT_ID, groupRecipientId.serialize())
|
||||
values.put(GROUP_ID, groupId.toString())
|
||||
values.put(TITLE, title)
|
||||
membershipValues.addAll(members.toContentValues(groupId))
|
||||
membershipValues.addAll(members.toContentValues(groupId, receivedGroupSendEndorsements?.toGroupSendEndorsementRecords()))
|
||||
values.put(MMS, groupId.isMms)
|
||||
|
||||
if (avatar != null) {
|
||||
@@ -657,6 +671,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
if (groupId.isV2) {
|
||||
values.put(ACTIVE, if (groupState != null && gv2GroupActive(groupState)) 1 else 0)
|
||||
values.put(DISTRIBUTION_ID, DistributionId.create().toString())
|
||||
values.put(GROUP_SEND_ENDORSEMENTS_EXPIRATION, receivedGroupSendEndorsements?.expirationMs ?: 0)
|
||||
} else if (groupId.isV1) {
|
||||
values.put(ACTIVE, 1)
|
||||
values.put(EXPECTED_V2_ID, groupId.requireV1().deriveV2MigrationGroupId().toString())
|
||||
@@ -676,7 +691,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
values.put(V2_REVISION, groupState.revision)
|
||||
values.put(V2_DECRYPTED_GROUP, groupState.encode())
|
||||
membershipValues.clear()
|
||||
membershipValues.addAll(groupMembers.toContentValues(groupId))
|
||||
membershipValues.addAll(groupMembers.toContentValues(groupId, receivedGroupSendEndorsements?.toGroupSendEndorsementRecords()))
|
||||
} else {
|
||||
if (groupId.isV2) {
|
||||
throw AssertionError("V2 group id but no master key")
|
||||
@@ -691,9 +706,10 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
return false
|
||||
}
|
||||
|
||||
for (query in SqlUtil.buildBulkInsert(MembershipTable.TABLE_NAME, arrayOf(MembershipTable.GROUP_ID, MembershipTable.RECIPIENT_ID), membershipValues)) {
|
||||
for (query in SqlUtil.buildBulkInsert(MembershipTable.TABLE_NAME, arrayOf(MembershipTable.GROUP_ID, MembershipTable.RECIPIENT_ID, MembershipTable.ENDORSEMENT), membershipValues)) {
|
||||
writableDatabase.execSQL(query.where, query.whereArgs)
|
||||
}
|
||||
|
||||
writableDatabase.setTransactionSuccessful()
|
||||
} finally {
|
||||
writableDatabase.endTransaction()
|
||||
@@ -745,11 +761,11 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
notifyConversationListListeners()
|
||||
}
|
||||
|
||||
fun update(groupMasterKey: GroupMasterKey, decryptedGroup: DecryptedGroup) {
|
||||
update(GroupId.v2(groupMasterKey), decryptedGroup)
|
||||
fun update(groupMasterKey: GroupMasterKey, decryptedGroup: DecryptedGroup, groupSendEndorsements: ReceivedGroupSendEndorsements?) {
|
||||
update(GroupId.v2(groupMasterKey), decryptedGroup, groupSendEndorsements)
|
||||
}
|
||||
|
||||
fun update(groupId: GroupId.V2, decryptedGroup: DecryptedGroup) {
|
||||
fun update(groupId: GroupId.V2, decryptedGroup: DecryptedGroup, receivedGroupSendEndorsements: ReceivedGroupSendEndorsements?) {
|
||||
val groupRecipientId: RecipientId = recipients.getOrInsertFromGroupId(groupId)
|
||||
val existingGroup: Optional<GroupRecord> = getGroup(groupId)
|
||||
val title: String = decryptedGroup.title
|
||||
@@ -760,6 +776,10 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
contentValues.put(V2_DECRYPTED_GROUP, decryptedGroup.encode())
|
||||
contentValues.put(ACTIVE, if (gv2GroupActive(decryptedGroup)) 1 else 0)
|
||||
|
||||
if (receivedGroupSendEndorsements != null) {
|
||||
contentValues.put(GROUP_SEND_ENDORSEMENTS_EXPIRATION, receivedGroupSendEndorsements.expirationMs)
|
||||
}
|
||||
|
||||
if (existingGroup.isPresent && existingGroup.get().unmigratedV1Members.isNotEmpty() && existingGroup.get().isV2Group) {
|
||||
val unmigratedV1Members: MutableSet<RecipientId> = existingGroup.get().unmigratedV1Members.toMutableSet()
|
||||
|
||||
@@ -781,6 +801,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
}
|
||||
|
||||
val groupMembers = getV2GroupMembers(decryptedGroup, true)
|
||||
var groupSendEndorsementRecords: GroupSendEndorsementRecords? = receivedGroupSendEndorsements?.toGroupSendEndorsementRecords() ?: getGroupSendEndorsements(groupId)
|
||||
|
||||
val addedMembers: List<RecipientId> = if (existingGroup.isPresent && existingGroup.get().isV2Group) {
|
||||
val change = GroupChangeReconstruct.reconstructGroupChange(existingGroup.get().requireV2GroupProperties().decryptedGroup, decryptedGroup)
|
||||
@@ -800,6 +821,12 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
)
|
||||
}
|
||||
|
||||
if (receivedGroupSendEndorsements == null && (removed.isNotEmpty() || change.newMembers.isNotEmpty())) {
|
||||
Log.v(TAG, "Members were removed or added, and no new endorsements, clearing endorsements and GSE expiration")
|
||||
contentValues.put(GROUP_SEND_ENDORSEMENTS_EXPIRATION, 0)
|
||||
groupSendEndorsementRecords = null
|
||||
}
|
||||
|
||||
change.newMembers.toAciList().toRecipientIds()
|
||||
} else {
|
||||
groupMembers
|
||||
@@ -812,7 +839,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
.where("$GROUP_ID = ?", groupId.toString())
|
||||
.run()
|
||||
|
||||
performMembershipUpdate(database, groupId, groupMembers)
|
||||
performMembershipUpdate(database, groupId, groupMembers, groupSendEndorsementRecords)
|
||||
}
|
||||
|
||||
if (decryptedGroup.disappearingMessagesTimer != null) {
|
||||
@@ -867,7 +894,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
.toMutableList()
|
||||
}
|
||||
|
||||
private fun performMembershipUpdate(database: SQLiteDatabase, groupId: GroupId, members: Collection<RecipientId>) {
|
||||
private fun performMembershipUpdate(database: SQLiteDatabase, groupId: GroupId, members: Collection<RecipientId>, groupSendEndorsementRecords: GroupSendEndorsementRecords?) {
|
||||
check(database.inTransaction())
|
||||
database
|
||||
.delete(MembershipTable.TABLE_NAME)
|
||||
@@ -876,8 +903,8 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
|
||||
val inserts = SqlUtil.buildBulkInsert(
|
||||
MembershipTable.TABLE_NAME,
|
||||
arrayOf(MembershipTable.GROUP_ID, MembershipTable.RECIPIENT_ID),
|
||||
members.toSet().toContentValues(groupId)
|
||||
arrayOf(MembershipTable.GROUP_ID, MembershipTable.RECIPIENT_ID, MembershipTable.ENDORSEMENT),
|
||||
members.toSet().toContentValues(groupId, groupSendEndorsementRecords)
|
||||
)
|
||||
|
||||
inserts.forEach {
|
||||
@@ -906,6 +933,94 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
.run()
|
||||
}
|
||||
|
||||
fun getGroupSendEndorsementsExpiration(groupId: GroupId): Long {
|
||||
return writableDatabase
|
||||
.select(GROUP_SEND_ENDORSEMENTS_EXPIRATION)
|
||||
.from(TABLE_NAME)
|
||||
.where("$GROUP_ID = ?", groupId)
|
||||
.run()
|
||||
.readToSingleLong(0L)
|
||||
}
|
||||
|
||||
fun updateGroupSendEndorsements(groupId: GroupId.V2, receivedGroupSendEndorsements: ReceivedGroupSendEndorsements) {
|
||||
val endorsements: Map<RecipientId, GroupSendEndorsement?> = receivedGroupSendEndorsements.toGroupSendEndorsementRecords().endorsements
|
||||
|
||||
writableDatabase.withinTransaction { db ->
|
||||
db.update(MembershipTable.TABLE_NAME, contentValuesOf(MembershipTable.ENDORSEMENT to null), "${MembershipTable.GROUP_ID} = ?", arrayOf(groupId.serialize()))
|
||||
|
||||
for ((recipientId, endorsement) in endorsements) {
|
||||
db.update(MembershipTable.TABLE_NAME)
|
||||
.values(MembershipTable.ENDORSEMENT to endorsement?.serialize())
|
||||
.where("${MembershipTable.GROUP_ID} = ? AND ${MembershipTable.RECIPIENT_ID} = ?", groupId, recipientId)
|
||||
.run()
|
||||
}
|
||||
|
||||
writableDatabase
|
||||
.update(TABLE_NAME)
|
||||
.values(GROUP_SEND_ENDORSEMENTS_EXPIRATION to receivedGroupSendEndorsements.expirationMs)
|
||||
.where("$GROUP_ID = ?", groupId)
|
||||
.run()
|
||||
}
|
||||
}
|
||||
|
||||
fun getGroupSendEndorsements(groupId: GroupId): GroupSendEndorsementRecords {
|
||||
val allEndorsements: Map<RecipientId, GroupSendEndorsement?> = readableDatabase
|
||||
.select(MembershipTable.RECIPIENT_ID, MembershipTable.ENDORSEMENT)
|
||||
.from(MembershipTable.TABLE_NAME)
|
||||
.where("${MembershipTable.GROUP_ID} = ?", groupId)
|
||||
.run()
|
||||
.readToMap { cursor ->
|
||||
val recipientId = RecipientId.from(cursor.requireLong(MembershipTable.RECIPIENT_ID))
|
||||
val endorsement = cursor.requireBlob(MembershipTable.ENDORSEMENT)?.let { endorsementBlob ->
|
||||
try {
|
||||
GroupSendEndorsement(endorsementBlob)
|
||||
} catch (e: InvalidInputException) {
|
||||
Log.w(TAG, "Unable to parse group send endorsement for $recipientId", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
recipientId to endorsement
|
||||
}
|
||||
|
||||
return GroupSendEndorsementRecords(allEndorsements)
|
||||
}
|
||||
|
||||
fun getGroupSendFullToken(threadId: Long, recipientId: RecipientId): GroupSendFullToken? {
|
||||
val threadRecipient = SignalDatabase.threads.getRecipientForThreadId(threadId)
|
||||
if (threadRecipient == null || !threadRecipient.isGroup) {
|
||||
return null
|
||||
}
|
||||
|
||||
return getGroupSendFullToken(threadRecipient.requireGroupId().requireV2(), recipientId)
|
||||
}
|
||||
|
||||
fun getGroupSendFullToken(groupId: GroupId.V2, recipientId: RecipientId): GroupSendFullToken? {
|
||||
val groupRecord = SignalDatabase.groups.getGroup(groupId).orElse(null) ?: return null
|
||||
val endorsement = SignalDatabase.groups.getGroupSendEndorsement(groupId, recipientId) ?: return null
|
||||
|
||||
val groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupRecord.requireV2GroupProperties().groupMasterKey)
|
||||
return endorsement.toFullToken(groupSecretParams, Instant.ofEpochMilli(groupRecord.groupSendEndorsementExpiration))
|
||||
}
|
||||
|
||||
private fun getGroupSendEndorsement(groupId: GroupId, recipientId: RecipientId): GroupSendEndorsement? {
|
||||
return readableDatabase
|
||||
.select(MembershipTable.ENDORSEMENT)
|
||||
.from(MembershipTable.TABLE_NAME)
|
||||
.where("${MembershipTable.GROUP_ID} = ? AND ${MembershipTable.RECIPIENT_ID} = ?", groupId, recipientId)
|
||||
.run()
|
||||
.readToSingleObject { c ->
|
||||
c.requireBlob(MembershipTable.ENDORSEMENT)?.let { endorsementBlob ->
|
||||
try {
|
||||
GroupSendEndorsement(endorsementBlob)
|
||||
} catch (e: InvalidInputException) {
|
||||
Log.w(TAG, "Unable to parse group send endorsement for $recipientId", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
fun isCurrentMember(groupId: Push, recipientId: RecipientId): Boolean {
|
||||
return readableDatabase
|
||||
@@ -1008,7 +1123,8 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
groupRevision = cursor.requireInt(V2_REVISION),
|
||||
decryptedGroupBytes = cursor.requireBlob(V2_DECRYPTED_GROUP),
|
||||
distributionId = cursor.optionalString(DISTRIBUTION_ID).map { id -> DistributionId.from(id) }.orElse(null),
|
||||
lastForceUpdateTimestamp = cursor.requireLong(LAST_FORCE_UPDATE_TIMESTAMP)
|
||||
lastForceUpdateTimestamp = cursor.requireLong(LAST_FORCE_UPDATE_TIMESTAMP),
|
||||
groupSendEndorsementExpiration = cursor.requireLong(GROUP_SEND_ENDORSEMENTS_EXPIRATION)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1220,15 +1336,20 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
||||
return RecipientId.toSerializedList(this)
|
||||
}
|
||||
|
||||
private fun Collection<RecipientId>.toContentValues(groupId: GroupId): List<ContentValues> {
|
||||
private fun Collection<RecipientId>.toContentValues(groupId: GroupId, groupSendEndorsementRecords: GroupSendEndorsementRecords?): List<ContentValues> {
|
||||
return map {
|
||||
contentValuesOf(
|
||||
MembershipTable.GROUP_ID to groupId.serialize(),
|
||||
MembershipTable.RECIPIENT_ID to it.serialize()
|
||||
MembershipTable.RECIPIENT_ID to it.serialize(),
|
||||
MembershipTable.ENDORSEMENT to groupSendEndorsementRecords?.endorsements?.get(it)?.serialize()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ReceivedGroupSendEndorsements.toGroupSendEndorsementRecords(): GroupSendEndorsementRecords {
|
||||
return GroupSendEndorsementRecords(endorsements.map { (aci, endorsement) -> RecipientId.from(aci) to endorsement }.toMap())
|
||||
}
|
||||
|
||||
private fun serviceIdsToRecipientIds(serviceIds: Sequence<ServiceId>): MutableList<RecipientId> {
|
||||
return serviceIds
|
||||
.map { serviceId ->
|
||||
|
||||
@@ -958,10 +958,9 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
|
||||
Log.i(TAG, "Creating restore placeholder for $groupId")
|
||||
val createdId = groups.create(
|
||||
masterKey,
|
||||
DecryptedGroup.Builder()
|
||||
.revision(GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION)
|
||||
.build()
|
||||
groupMasterKey = masterKey,
|
||||
groupState = DecryptedGroup(revision = GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION),
|
||||
groupSendEndorsements = null
|
||||
)
|
||||
|
||||
if (createdId == null) {
|
||||
@@ -1469,9 +1468,9 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
}
|
||||
}
|
||||
|
||||
fun setUnidentifiedAccessMode(id: RecipientId, unidentifiedAccessMode: UnidentifiedAccessMode) {
|
||||
fun setSealedSenderAccessMode(id: RecipientId, sealedSenderAccessMode: SealedSenderAccessMode) {
|
||||
val values = ContentValues(1).apply {
|
||||
put(SEALED_SENDER_MODE, unidentifiedAccessMode.mode)
|
||||
put(SEALED_SENDER_MODE, sealedSenderAccessMode.mode)
|
||||
}
|
||||
if (update(id, values)) {
|
||||
AppDependencies.databaseObserver.notifyRecipientChanged(id)
|
||||
@@ -1554,7 +1553,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
val valuesToSet = ContentValues(3).apply {
|
||||
put(PROFILE_KEY, encodedProfileKey)
|
||||
putNull(EXPIRING_PROFILE_KEY_CREDENTIAL)
|
||||
put(SEALED_SENDER_MODE, UnidentifiedAccessMode.UNKNOWN.mode)
|
||||
put(SEALED_SENDER_MODE, SealedSenderAccessMode.UNKNOWN.mode)
|
||||
}
|
||||
|
||||
val updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, valuesToCompare)
|
||||
@@ -1586,7 +1585,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
val valuesToSet = ContentValues(3).apply {
|
||||
put(PROFILE_KEY, Base64.encodeWithPadding(profileKey.serialize()))
|
||||
putNull(EXPIRING_PROFILE_KEY_CREDENTIAL)
|
||||
put(SEALED_SENDER_MODE, UnidentifiedAccessMode.UNKNOWN.mode)
|
||||
put(SEALED_SENDER_MODE, SealedSenderAccessMode.UNKNOWN.mode)
|
||||
}
|
||||
|
||||
if (writableDatabase.update(TABLE_NAME, valuesToSet, selection, args) > 0) {
|
||||
@@ -4610,14 +4609,14 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
}
|
||||
}
|
||||
|
||||
enum class UnidentifiedAccessMode(val mode: Int) {
|
||||
enum class SealedSenderAccessMode(val mode: Int) {
|
||||
UNKNOWN(0),
|
||||
DISABLED(1),
|
||||
ENABLED(2),
|
||||
UNRESTRICTED(3);
|
||||
|
||||
companion object {
|
||||
fun fromMode(mode: Int): UnidentifiedAccessMode {
|
||||
fun fromMode(mode: Int): SealedSenderAccessMode {
|
||||
return values()[mode]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ object RecipientTableCursorUtil {
|
||||
profileSharing = cursor.requireBoolean(RecipientTable.PROFILE_SHARING),
|
||||
lastProfileFetch = cursor.requireLong(RecipientTable.LAST_PROFILE_FETCH),
|
||||
notificationChannel = cursor.requireString(RecipientTable.NOTIFICATION_CHANNEL),
|
||||
unidentifiedAccessMode = RecipientTable.UnidentifiedAccessMode.fromMode(cursor.requireInt(RecipientTable.SEALED_SENDER_MODE)),
|
||||
sealedSenderAccessMode = RecipientTable.SealedSenderAccessMode.fromMode(cursor.requireInt(RecipientTable.SEALED_SENDER_MODE)),
|
||||
capabilities = readCapabilities(cursor),
|
||||
storageId = Base64.decodeNullableOrThrow(cursor.requireString(RecipientTable.STORAGE_SERVICE_ID)),
|
||||
mentionSetting = RecipientTable.MentionSetting.fromId(cursor.requireInt(RecipientTable.MENTION_SETTING)),
|
||||
|
||||
@@ -95,6 +95,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V234_ThumbnailResto
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V235_AttachmentUuidColumn
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V236_FixInAppSubscriberCurrencyIfAble
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V237_ResetGroupForceUpdateTimestamps
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V238_AddGroupSendEndorsementsColumns
|
||||
|
||||
/**
|
||||
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
|
||||
@@ -192,10 +193,11 @@ object SignalDatabaseMigrations {
|
||||
234 to V234_ThumbnailRestoreStateColumn,
|
||||
235 to V235_AttachmentUuidColumn,
|
||||
236 to V236_FixInAppSubscriberCurrencyIfAble,
|
||||
237 to V237_ResetGroupForceUpdateTimestamps
|
||||
237 to V237_ResetGroupForceUpdateTimestamps,
|
||||
238 to V238_AddGroupSendEndorsementsColumns
|
||||
)
|
||||
|
||||
const val DATABASE_VERSION = 237
|
||||
const val DATABASE_VERSION = 238
|
||||
|
||||
@JvmStatic
|
||||
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.database.helpers.migration
|
||||
|
||||
import android.app.Application
|
||||
import net.zetetic.database.sqlcipher.SQLiteDatabase
|
||||
|
||||
/**
|
||||
* Add columns to group and group membership tables needed for group send endorsements.
|
||||
*/
|
||||
@Suppress("ClassName")
|
||||
object V238_AddGroupSendEndorsementsColumns : SignalDatabaseMigration {
|
||||
|
||||
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
db.execSQL("ALTER TABLE groups ADD COLUMN group_send_endorsements_expiration INTEGER DEFAULT 0")
|
||||
db.execSQL("ALTER TABLE group_membership ADD COLUMN endorsement BLOB DEFAULT NULL")
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,8 @@ class GroupRecord(
|
||||
groupRevision: Int,
|
||||
decryptedGroupBytes: ByteArray?,
|
||||
val distributionId: DistributionId?,
|
||||
val lastForceUpdateTimestamp: Long
|
||||
val lastForceUpdateTimestamp: Long,
|
||||
val groupSendEndorsementExpiration: Long
|
||||
) {
|
||||
|
||||
val members: List<RecipientId> by lazy {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.database.model
|
||||
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsement
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
|
||||
/**
|
||||
* Contains the individual group send endorsements for a specific group
|
||||
* source from our local db.
|
||||
*/
|
||||
data class GroupSendEndorsementRecords(val endorsements: Map<RecipientId, GroupSendEndorsement?>) {
|
||||
fun getEndorsement(recipientId: RecipientId): GroupSendEndorsement? {
|
||||
return endorsements[recipientId]
|
||||
}
|
||||
|
||||
fun isMissingAnyEndorsements(): Boolean {
|
||||
return endorsements.values.any { it == null }
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import org.thoughtcrime.securesms.database.RecipientTable
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.MentionSetting
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.PhoneNumberSharingState
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.RegisteredState
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.UnidentifiedAccessMode
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.SealedSenderAccessMode
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.VibrateState
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName
|
||||
@@ -60,7 +60,7 @@ data class RecipientRecord(
|
||||
val profileSharing: Boolean,
|
||||
val lastProfileFetch: Long,
|
||||
val notificationChannel: String?,
|
||||
val unidentifiedAccessMode: UnidentifiedAccessMode,
|
||||
val sealedSenderAccessMode: SealedSenderAccessMode,
|
||||
val capabilities: Capabilities,
|
||||
val storageId: ByteArray?,
|
||||
val mentionSetting: MentionSetting,
|
||||
|
||||
@@ -172,6 +172,16 @@ public final class GroupManager {
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static void updateGroupSendEndorsements(@NonNull Context context,
|
||||
@NonNull GroupMasterKey groupMasterKey)
|
||||
throws GroupChangeBusyException, IOException, GroupNotAMemberException
|
||||
{
|
||||
try (GroupManagerV2.GroupUpdater updater = new GroupManagerV2(context).updater(groupMasterKey)) {
|
||||
updater.updateGroupSendEndorsements();
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static void setMemberAdmin(@NonNull Context context,
|
||||
@NonNull GroupId.V2 groupId,
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||
import org.signal.storageservice.protos.groups.AccessControl;
|
||||
import org.signal.storageservice.protos.groups.GroupChange;
|
||||
import org.signal.storageservice.protos.groups.GroupChangeResponse;
|
||||
import org.signal.storageservice.protos.groups.GroupExternalCredential;
|
||||
import org.signal.storageservice.protos.groups.Member;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||
@@ -49,6 +50,8 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.util.ProfileUtil;
|
||||
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupResponse;
|
||||
import org.whispersystems.signalservice.api.groupsv2.ReceivedGroupSendEndorsements;
|
||||
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupCandidate;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupChangeReconstruct;
|
||||
@@ -219,16 +222,18 @@ final class GroupManagerV2 {
|
||||
throws GroupChangeFailedException, IOException, MembershipNotSuitableForV2Exception
|
||||
{
|
||||
GroupSecretParams groupSecretParams = GroupSecretParams.generate();
|
||||
DecryptedGroup decryptedGroup;
|
||||
DecryptedGroupResponse createGroupResponse;
|
||||
|
||||
try {
|
||||
decryptedGroup = createGroupOnServer(groupSecretParams, name, avatar, members, disappearingMessagesTimer);
|
||||
createGroupResponse = createGroupOnServer(groupSecretParams, name, avatar, members, disappearingMessagesTimer);
|
||||
} catch (GroupAlreadyExistsException e) {
|
||||
throw new GroupChangeFailedException(e);
|
||||
}
|
||||
|
||||
GroupMasterKey masterKey = groupSecretParams.getMasterKey();
|
||||
GroupId.V2 groupId = groupDatabase.create(masterKey, decryptedGroup);
|
||||
DecryptedGroup decryptedGroup = createGroupResponse.getGroup();
|
||||
GroupMasterKey masterKey = groupSecretParams.getMasterKey();
|
||||
ReceivedGroupSendEndorsements groupSendEndorsements = groupsV2Operations.forGroup(groupSecretParams).receiveGroupSendEndorsements(selfAci, decryptedGroup, createGroupResponse.getGroupSendEndorsementsResponse());
|
||||
GroupId.V2 groupId = groupDatabase.create(masterKey, decryptedGroup, groupSendEndorsements);
|
||||
|
||||
if (groupId == null) {
|
||||
throw new GroupChangeFailedException("Unable to create group, group already exists");
|
||||
@@ -670,7 +675,8 @@ final class GroupManagerV2 {
|
||||
|
||||
previousGroupState = v2GroupProperties.getDecryptedGroup();
|
||||
|
||||
GroupChange signedGroupChange = commitToServer(changeActions);
|
||||
GroupChangeResponse changeResponse = commitToServer(changeActions);
|
||||
GroupChange signedGroupChange = changeResponse.groupChange;
|
||||
try {
|
||||
//noinspection OptionalGetWithoutIsPresent
|
||||
decryptedChange = groupOperations.decryptChange(signedGroupChange, false).get();
|
||||
@@ -680,7 +686,7 @@ final class GroupManagerV2 {
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
groupDatabase.update(groupId, decryptedGroupState);
|
||||
groupDatabase.update(groupId, decryptedGroupState, groupsV2Operations.forGroup(groupSecretParams).receiveGroupSendEndorsements(selfAci, decryptedGroupState, changeResponse.groupSendEndorsementsResponse));
|
||||
|
||||
GroupMutation groupMutation = new GroupMutation(previousGroupState, decryptedChange, decryptedGroupState);
|
||||
RecipientAndThread recipientAndThread = sendGroupUpdateHelper.sendGroupUpdate(groupMasterKey, groupMutation, signedGroupChange, sendToMembers);
|
||||
@@ -690,7 +696,7 @@ final class GroupManagerV2 {
|
||||
return new GroupManager.GroupActionResult(recipientAndThread.groupRecipient, recipientAndThread.threadId, newMembersCount, newPendingMembers);
|
||||
}
|
||||
|
||||
private @NonNull GroupChange commitToServer(@NonNull GroupChange.Actions change)
|
||||
private @NonNull GroupChangeResponse commitToServer(@NonNull GroupChange.Actions change)
|
||||
throws GroupNotAMemberException, GroupChangeFailedException, IOException, GroupInsufficientRightsException
|
||||
{
|
||||
try {
|
||||
@@ -747,6 +753,14 @@ final class GroupManagerV2 {
|
||||
.forceSanityUpdateFromServer(timestamp);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
void updateGroupSendEndorsements()
|
||||
throws IOException, GroupNotAMemberException
|
||||
{
|
||||
GroupsV2StateProcessor.forGroup(serviceIds, groupMasterKey)
|
||||
.updateGroupSendEndorsements();
|
||||
}
|
||||
|
||||
private DecryptedGroupChange getDecryptedGroupChange(@Nullable byte[] signedGroupChange) {
|
||||
if (signedGroupChange != null && signedGroupChange.length > 0) {
|
||||
GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(GroupSecretParams.deriveFromMasterKey(groupMasterKey));
|
||||
@@ -764,11 +778,11 @@ final class GroupManagerV2 {
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private @NonNull DecryptedGroup createGroupOnServer(@NonNull GroupSecretParams groupSecretParams,
|
||||
@Nullable String name,
|
||||
@Nullable byte[] avatar,
|
||||
@NonNull Collection<RecipientId> members,
|
||||
int disappearingMessageTimerSeconds)
|
||||
private @NonNull DecryptedGroupResponse createGroupOnServer(@NonNull GroupSecretParams groupSecretParams,
|
||||
@Nullable String name,
|
||||
@Nullable byte[] avatar,
|
||||
@NonNull Collection<RecipientId> members,
|
||||
int disappearingMessageTimerSeconds)
|
||||
throws GroupChangeFailedException, IOException, MembershipNotSuitableForV2Exception, GroupAlreadyExistsException
|
||||
{
|
||||
if (!GroupsV2CapabilityChecker.allAndSelfHaveServiceId(members)) {
|
||||
@@ -797,15 +811,10 @@ final class GroupManagerV2 {
|
||||
disappearingMessageTimerSeconds);
|
||||
|
||||
try {
|
||||
groupsV2Api.putNewGroup(newGroup, authorization.getAuthorizationForToday(serviceIds, groupSecretParams));
|
||||
DecryptedGroupResponse groupResponse = groupsV2Api.putNewGroup(newGroup, authorization.getAuthorizationForToday(serviceIds, groupSecretParams));
|
||||
|
||||
DecryptedGroup decryptedGroup = groupsV2Api.getGroup(groupSecretParams, AppDependencies.getGroupsV2Authorization().getAuthorizationForToday(serviceIds, groupSecretParams));
|
||||
if (decryptedGroup == null) {
|
||||
throw new GroupChangeFailedException();
|
||||
}
|
||||
|
||||
return decryptedGroup;
|
||||
} catch (VerificationFailedException | InvalidGroupStateException e) {
|
||||
return groupResponse;
|
||||
} catch (VerificationFailedException | InvalidGroupStateException | InvalidInputException e) {
|
||||
throw new GroupChangeFailedException(e);
|
||||
} catch (GroupExistsException e) {
|
||||
throw new GroupAlreadyExistsException(e);
|
||||
@@ -837,8 +846,9 @@ final class GroupManagerV2 {
|
||||
@Nullable byte[] avatar)
|
||||
throws GroupChangeFailedException, IOException, MembershipNotSuitableForV2Exception, GroupLinkNotActiveException
|
||||
{
|
||||
boolean requestToJoin = joinInfo.addFromInviteLink == AccessControl.AccessRequired.ADMINISTRATOR;
|
||||
boolean alreadyAMember = false;
|
||||
boolean requestToJoin = joinInfo.addFromInviteLink == AccessControl.AccessRequired.ADMINISTRATOR;
|
||||
boolean alreadyAMember = false;
|
||||
boolean groupAlreadyExists = false;
|
||||
|
||||
if (requestToJoin) {
|
||||
Log.i(TAG, "Requesting to join " + groupId);
|
||||
@@ -846,10 +856,13 @@ final class GroupManagerV2 {
|
||||
Log.i(TAG, "Joining " + groupId);
|
||||
}
|
||||
|
||||
GroupChange signedGroupChange = null;
|
||||
DecryptedGroupChange decryptedChange = null;
|
||||
GroupChangeResponse groupChangeResponse = null;
|
||||
GroupChange signedGroupChange = null;
|
||||
DecryptedGroupChange decryptedChange = null;
|
||||
|
||||
try {
|
||||
signedGroupChange = joinGroupOnServer(requestToJoin, joinInfo.revision);
|
||||
groupChangeResponse = joinGroupOnServer(requestToJoin, joinInfo.revision);
|
||||
signedGroupChange = groupChangeResponse.groupChange;
|
||||
|
||||
if (requestToJoin) {
|
||||
Log.i(TAG, String.format("Successfully requested to join %s on server", groupId));
|
||||
@@ -857,7 +870,7 @@ final class GroupManagerV2 {
|
||||
Log.i(TAG, String.format("Successfully added self to %s on server", groupId));
|
||||
}
|
||||
|
||||
decryptedChange = decryptChange(signedGroupChange);
|
||||
decryptedChange = decryptChange(Objects.requireNonNull(signedGroupChange));
|
||||
} catch (GroupJoinAlreadyAMemberException e) {
|
||||
Log.i(TAG, "Server reports that we are already a member of " + groupId);
|
||||
alreadyAMember = true;
|
||||
@@ -869,28 +882,37 @@ final class GroupManagerV2 {
|
||||
|
||||
if (group.isPresent()) {
|
||||
Log.i(TAG, "Group already present locally");
|
||||
if (decryptedChange != null) {
|
||||
try {
|
||||
GroupsV2StateProcessor.forGroup(SignalStore.account().getServiceIds(), groupMasterKey)
|
||||
.updateLocalGroupToRevision(decryptedChange.revision, System.currentTimeMillis(), decryptedChange);
|
||||
} catch (GroupNotAMemberException e) {
|
||||
Log.w(TAG, "Unable to apply join change to existing group", e);
|
||||
}
|
||||
}
|
||||
groupAlreadyExists = true;
|
||||
} else {
|
||||
GroupId.V2 groupId = groupDatabase.create(groupMasterKey, decryptedGroup);
|
||||
GroupId.V2 groupId = groupDatabase.create(groupMasterKey, decryptedGroup, null);
|
||||
|
||||
if (groupId != null) {
|
||||
Log.i(TAG, "Created local group with placeholder");
|
||||
} else {
|
||||
Log.i(TAG, "Create placeholder failed, group suddenly present locally, attempting to apply change");
|
||||
if (decryptedChange != null) {
|
||||
try {
|
||||
GroupsV2StateProcessor.forGroup(SignalStore.account().getServiceIds(), groupMasterKey)
|
||||
.updateLocalGroupToRevision(decryptedChange.revision, System.currentTimeMillis(), decryptedChange);
|
||||
} catch (GroupNotAMemberException e) {
|
||||
Log.w(TAG, "Unable to apply join change to existing group", e);
|
||||
}
|
||||
}
|
||||
Log.i(TAG, "Create placeholder failed, group suddenly present locally");
|
||||
groupAlreadyExists = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (groupAlreadyExists) {
|
||||
Log.i(TAG, "Attempting to update local group with change/server");
|
||||
try {
|
||||
GroupsV2StateProcessor.forGroup(SignalStore.account().getServiceIds(), groupMasterKey)
|
||||
.updateLocalGroupToRevision(decryptedChange != null ? decryptedChange.revision : GroupsV2StateProcessor.LATEST, System.currentTimeMillis(), decryptedChange);
|
||||
} catch (GroupNotAMemberException e) {
|
||||
Log.w(TAG, "Despite adding self to group, change/server says we are not a member, scheduling refresh of group info " + groupId, e);
|
||||
|
||||
AppDependencies.getJobManager()
|
||||
.add(new RequestGroupV2InfoJob(groupId));
|
||||
|
||||
throw new GroupChangeFailedException(e);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Group data fetch failed, scheduling refresh of group info " + groupId, e);
|
||||
|
||||
AppDependencies.getJobManager()
|
||||
.add(new RequestGroupV2InfoJob(groupId));
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1006,7 +1028,7 @@ final class GroupManagerV2 {
|
||||
return group.build();
|
||||
}
|
||||
|
||||
private @NonNull GroupChange joinGroupOnServer(boolean requestToJoin, int currentRevision)
|
||||
private @NonNull GroupChangeResponse joinGroupOnServer(boolean requestToJoin, int currentRevision)
|
||||
throws GroupChangeFailedException, IOException, MembershipNotSuitableForV2Exception, GroupLinkNotActiveException, GroupJoinAlreadyAMemberException
|
||||
{
|
||||
if (!GroupsV2CapabilityChecker.allAndSelfHaveServiceId(Collections.singleton(Recipient.self().getId()))) {
|
||||
@@ -1029,7 +1051,7 @@ final class GroupManagerV2 {
|
||||
return commitJoinChangeWithConflictResolution(currentRevision, change);
|
||||
}
|
||||
|
||||
private @NonNull GroupChange commitJoinChangeWithConflictResolution(int currentRevision, @NonNull GroupChange.Actions.Builder change)
|
||||
private @NonNull GroupChangeResponse commitJoinChangeWithConflictResolution(int currentRevision, @NonNull GroupChange.Actions.Builder change)
|
||||
throws GroupChangeFailedException, IOException, GroupLinkNotActiveException, GroupJoinAlreadyAMemberException
|
||||
{
|
||||
for (int attempt = 0; attempt < 5; attempt++) {
|
||||
@@ -1038,10 +1060,10 @@ final class GroupManagerV2 {
|
||||
.build();
|
||||
|
||||
Log.i(TAG, "Trying to join group at V" + changeActions.revision);
|
||||
GroupChange signedGroupChange = commitJoinToServer(changeActions);
|
||||
GroupChangeResponse changeResponse = commitJoinToServer(changeActions);
|
||||
|
||||
Log.i(TAG, "Successfully joined group at V" + changeActions.revision);
|
||||
return signedGroupChange;
|
||||
return changeResponse;
|
||||
} catch (GroupPatchNotAcceptedException e) {
|
||||
Log.w(TAG, "Patch not accepted", e);
|
||||
|
||||
@@ -1051,7 +1073,7 @@ final class GroupManagerV2 {
|
||||
} else {
|
||||
throw new GroupChangeFailedException(e);
|
||||
}
|
||||
} catch (VerificationFailedException | InvalidGroupStateException ex) {
|
||||
} catch (VerificationFailedException | InvalidGroupStateException | InvalidInputException ex) {
|
||||
throw new GroupChangeFailedException(ex);
|
||||
}
|
||||
} catch (ConflictException e) {
|
||||
@@ -1064,7 +1086,7 @@ final class GroupManagerV2 {
|
||||
throw new GroupChangeFailedException("Unable to join group after conflicts");
|
||||
}
|
||||
|
||||
private @NonNull GroupChange commitJoinToServer(@NonNull GroupChange.Actions change)
|
||||
private @NonNull GroupChangeResponse commitJoinToServer(@NonNull GroupChange.Actions change)
|
||||
throws GroupChangeFailedException, IOException, GroupLinkNotActiveException
|
||||
{
|
||||
try {
|
||||
@@ -1109,7 +1131,7 @@ final class GroupManagerV2 {
|
||||
}
|
||||
|
||||
private boolean testGroupMembership()
|
||||
throws IOException, VerificationFailedException, InvalidGroupStateException
|
||||
throws IOException, VerificationFailedException, InvalidGroupStateException, InvalidInputException
|
||||
{
|
||||
try {
|
||||
groupsV2Api.getGroup(groupSecretParams, authorization.getAuthorizationForToday(serviceIds, groupSecretParams));
|
||||
@@ -1140,7 +1162,7 @@ final class GroupManagerV2 {
|
||||
DecryptedGroupChange decryptedChange = groupOperations.decryptChange(signedGroupChange, false).get();
|
||||
DecryptedGroup newGroup = DecryptedGroupUtil.applyWithoutRevisionCheck(decryptedGroup, decryptedChange);
|
||||
|
||||
groupDatabase.update(groupId, resetRevision(newGroup, decryptedGroup.revision));
|
||||
groupDatabase.update(groupId, resetRevision(newGroup, decryptedGroup.revision), null);
|
||||
|
||||
sendGroupUpdateHelper.sendGroupUpdate(groupMasterKey, new GroupMutation(decryptedGroup, decryptedChange, newGroup), signedGroupChange, false);
|
||||
} catch (VerificationFailedException | InvalidGroupStateException | NotAbleToApplyGroupV2ChangeException e) {
|
||||
@@ -1165,7 +1187,7 @@ final class GroupManagerV2 {
|
||||
.build();
|
||||
|
||||
Log.i(TAG, "Trying to cancel request group at V" + changeActions.revision);
|
||||
GroupChange signedGroupChange = commitJoinToServer(changeActions);
|
||||
GroupChange signedGroupChange = commitJoinToServer(changeActions).groupChange;
|
||||
|
||||
Log.i(TAG, "Successfully cancelled group join at V" + changeActions.revision);
|
||||
return signedGroupChange;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.thoughtcrime.securesms.groups.v2.processing
|
||||
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsementsResponse
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange
|
||||
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupChangeLog
|
||||
@@ -9,10 +10,15 @@ import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupChangeLog
|
||||
*/
|
||||
class GroupStateDiff(
|
||||
val previousGroupState: DecryptedGroup?,
|
||||
val serverHistory: List<DecryptedGroupChangeLog>
|
||||
val serverHistory: List<DecryptedGroupChangeLog>,
|
||||
val groupSendEndorsementsResponse: GroupSendEndorsementsResponse?
|
||||
) {
|
||||
|
||||
constructor(previousGroupState: DecryptedGroup?, changedGroupState: DecryptedGroup?, change: DecryptedGroupChange?) : this(previousGroupState, listOf(DecryptedGroupChangeLog(changedGroupState, change)))
|
||||
constructor(
|
||||
previousGroupState: DecryptedGroup?,
|
||||
changedGroupState: DecryptedGroup?,
|
||||
change: DecryptedGroupChange?
|
||||
) : this(previousGroupState, listOf(DecryptedGroupChangeLog(changedGroupState, change)), null)
|
||||
|
||||
val earliestRevisionNumber: Int
|
||||
get() {
|
||||
|
||||
@@ -9,6 +9,7 @@ import androidx.annotation.VisibleForTesting
|
||||
import androidx.annotation.WorkerThread
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.orNull
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException
|
||||
import org.signal.libsignal.zkgroup.VerificationFailedException
|
||||
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
|
||||
import org.signal.libsignal.zkgroup.groups.GroupSecretParams
|
||||
@@ -43,6 +44,7 @@ import org.whispersystems.signalservice.api.groupsv2.GroupChangeReconstruct
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupHistoryPage
|
||||
import org.whispersystems.signalservice.api.groupsv2.InvalidGroupStateException
|
||||
import org.whispersystems.signalservice.api.groupsv2.NotAbleToApplyGroupV2ChangeException
|
||||
import org.whispersystems.signalservice.api.groupsv2.ReceivedGroupSendEndorsements
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.api.push.ServiceIds
|
||||
@@ -95,8 +97,9 @@ class GroupsV2StateProcessor private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private val groupsApi = AppDependencies.signalServiceAccountManager.groupsV2Api
|
||||
private val groupsApi = AppDependencies.signalServiceAccountManager.getGroupsV2Api()
|
||||
private val groupsV2Authorization = AppDependencies.groupsV2Authorization
|
||||
private val groupOperations = AppDependencies.groupsV2Operations.forGroup(groupSecretParams)
|
||||
private val groupId = GroupId.v2(groupSecretParams.getPublicParams().getGroupIdentifier())
|
||||
private val profileAndMessageHelper = ProfileAndMessageHelper.create(serviceIds.aci, groupMasterKey, groupId)
|
||||
|
||||
@@ -124,6 +127,36 @@ class GroupsV2StateProcessor private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and save the latest group send endorsements from the server. This endorsements returned may
|
||||
* not match our local view of the membership if the membership has changed on the server and we haven't updated the
|
||||
* group state yet. This is only an issue when trying to send to a group member that has been removed and should be handled
|
||||
* gracefully as a fallback in the sending flow.
|
||||
*/
|
||||
@WorkerThread
|
||||
@Throws(IOException::class, GroupNotAMemberException::class)
|
||||
fun updateGroupSendEndorsements() {
|
||||
val result = groupsApi.getGroupAsResult(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(serviceIds, groupSecretParams))
|
||||
|
||||
val groupResponse = when (result) {
|
||||
is NetworkResult.Success -> result.result
|
||||
else -> when (val cause = result.getCause()!!) {
|
||||
is NotInGroupException, is GroupNotFoundException -> throw GroupNotAMemberException(cause)
|
||||
is IOException -> throw cause
|
||||
else -> throw IOException(cause)
|
||||
}
|
||||
}
|
||||
|
||||
val receivedGroupSendEndorsements = groupOperations.receiveGroupSendEndorsements(serviceIds.aci, groupResponse.group, groupResponse.groupSendEndorsementsResponse)
|
||||
|
||||
if (receivedGroupSendEndorsements != null) {
|
||||
Log.i(TAG, "$logPrefix Updating group send endorsements")
|
||||
SignalDatabase.groups.updateGroupSendEndorsements(groupId, receivedGroupSendEndorsements)
|
||||
} else {
|
||||
Log.w(TAG, "$logPrefix No group send endorsements on response")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Using network where required, will attempt to bring the local copy of the group up to the revision specified.
|
||||
*
|
||||
@@ -233,7 +266,8 @@ class GroupsV2StateProcessor private constructor(
|
||||
return saveGroupUpdate(
|
||||
timestamp = timestamp,
|
||||
serverGuid = serverGuid,
|
||||
groupStateDiff = groupStateDiff
|
||||
groupStateDiff = groupStateDiff,
|
||||
groupSendEndorsements = null
|
||||
)
|
||||
}
|
||||
|
||||
@@ -259,6 +293,8 @@ class GroupsV2StateProcessor private constructor(
|
||||
else -> return InternalUpdateResult.from(result.getCause()!!)
|
||||
}
|
||||
|
||||
val sendEndorsementExpiration = groupRecord.map { it.groupSendEndorsementExpiration }.orElse(0L)
|
||||
|
||||
var includeFirstState = currentLocalState == null ||
|
||||
currentLocalState.revision < 0 ||
|
||||
currentLocalState.revision == joinedAtRevision ||
|
||||
@@ -273,20 +309,30 @@ class GroupsV2StateProcessor private constructor(
|
||||
var hasRemainingRemoteChanges = false
|
||||
|
||||
while (hasMore) {
|
||||
Log.i(TAG, "$logPrefix Requesting change logs from server, currentRevision=${currentLocalState?.revision ?: "null"} logsNeededFrom=$logsNeededFrom includeFirstState=$includeFirstState")
|
||||
Log.i(TAG, "$logPrefix Requesting change logs from server, currentRevision=${currentLocalState?.revision ?: "null"} logsNeededFrom=$logsNeededFrom includeFirstState=$includeFirstState sendEndorsementExpiration=${sendEndorsementExpiration > 0}")
|
||||
|
||||
val (remoteGroupStateDiff, pagingData) = getGroupChangeLogs(currentLocalState, logsNeededFrom, includeFirstState)
|
||||
val (remoteGroupStateDiff, pagingData) = getGroupChangeLogs(currentLocalState, logsNeededFrom, includeFirstState, sendEndorsementExpiration)
|
||||
val applyGroupStateDiffResult: AdvanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(remoteGroupStateDiff, targetRevision)
|
||||
val updatedGroupState: DecryptedGroup? = applyGroupStateDiffResult.updatedGroupState
|
||||
|
||||
if (updatedGroupState == null || updatedGroupState == remoteGroupStateDiff.previousGroupState) {
|
||||
Log.i(TAG, "$logPrefix Local state is at or later than server revision: ${currentLocalState?.revision ?: "null"}")
|
||||
|
||||
if (currentLocalState != null) {
|
||||
val endorsements = groupOperations.receiveGroupSendEndorsements(serviceIds.aci, currentLocalState, remoteGroupStateDiff.groupSendEndorsementsResponse)
|
||||
|
||||
if (endorsements != null) {
|
||||
Log.i(TAG, "$logPrefix Received updated send endorsements, saving")
|
||||
SignalDatabase.groups.updateGroupSendEndorsements(groupId, endorsements)
|
||||
}
|
||||
}
|
||||
|
||||
return InternalUpdateResult.NoUpdateNeeded
|
||||
}
|
||||
|
||||
Log.i(TAG, "$logPrefix Saving updated group state at revision: ${updatedGroupState.revision}")
|
||||
|
||||
saveGroupState(remoteGroupStateDiff, updatedGroupState)
|
||||
saveGroupState(remoteGroupStateDiff, updatedGroupState, groupOperations.receiveGroupSendEndorsements(serviceIds.aci, updatedGroupState, remoteGroupStateDiff.groupSendEndorsementsResponse))
|
||||
|
||||
if (addMessagesForAllUpdates) {
|
||||
Log.d(TAG, "$logPrefix Inserting group changes into chat history")
|
||||
@@ -320,7 +366,7 @@ class GroupsV2StateProcessor private constructor(
|
||||
}
|
||||
|
||||
if (!addMessagesForAllUpdates) {
|
||||
Log.i(TAG, "Inserting single update message for restore placeholder")
|
||||
Log.i(TAG, "$logPrefix Inserting single update message for restore placeholder")
|
||||
profileAndMessageHelper.insertUpdateMessages(runningTimestamp, null, setOf(AppliedGroupChangeLog(currentLocalState!!, null)), serverGuid)
|
||||
}
|
||||
|
||||
@@ -341,19 +387,20 @@ class GroupsV2StateProcessor private constructor(
|
||||
private fun updateToLatestViaServer(timestamp: Long, currentLocalState: DecryptedGroup?, reconstructChange: Boolean): InternalUpdateResult {
|
||||
val result = groupsApi.getGroupAsResult(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(serviceIds, groupSecretParams))
|
||||
|
||||
val serverState = if (result is NetworkResult.Success) {
|
||||
val groupResponse = if (result is NetworkResult.Success) {
|
||||
result.result
|
||||
} else {
|
||||
return InternalUpdateResult.from(result.getCause()!!)
|
||||
}
|
||||
|
||||
val completeGroupChange = if (reconstructChange) GroupChangeReconstruct.reconstructGroupChange(currentLocalState, serverState) else null
|
||||
val remoteGroupStateDiff = GroupStateDiff(currentLocalState, serverState, completeGroupChange)
|
||||
val completeGroupChange = if (reconstructChange) GroupChangeReconstruct.reconstructGroupChange(currentLocalState, groupResponse.group) else null
|
||||
val remoteGroupStateDiff = GroupStateDiff(currentLocalState, groupResponse.group, completeGroupChange)
|
||||
|
||||
return saveGroupUpdate(
|
||||
timestamp = timestamp,
|
||||
serverGuid = null,
|
||||
groupStateDiff = remoteGroupStateDiff
|
||||
groupStateDiff = remoteGroupStateDiff,
|
||||
groupSendEndorsements = groupOperations.receiveGroupSendEndorsements(serviceIds.aci, groupResponse.group, groupResponse.groupSendEndorsementsResponse)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -403,22 +450,30 @@ class GroupsV2StateProcessor private constructor(
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun getGroupChangeLogs(localState: DecryptedGroup?, logsNeededFromRevision: Int, includeFirstState: Boolean): Pair<GroupStateDiff, GroupHistoryPage.PagingData> {
|
||||
private fun getGroupChangeLogs(
|
||||
localState: DecryptedGroup?,
|
||||
logsNeededFromRevision: Int,
|
||||
includeFirstState: Boolean,
|
||||
sendEndorsementsExpirationMs: Long
|
||||
): Pair<GroupStateDiff, GroupHistoryPage.PagingData> {
|
||||
try {
|
||||
val groupHistoryPage = groupsApi.getGroupHistoryPage(groupSecretParams, logsNeededFromRevision, groupsV2Authorization.getAuthorizationForToday(serviceIds, groupSecretParams), includeFirstState)
|
||||
val groupHistoryPage = groupsApi.getGroupHistoryPage(groupSecretParams, logsNeededFromRevision, groupsV2Authorization.getAuthorizationForToday(serviceIds, groupSecretParams), includeFirstState, sendEndorsementsExpirationMs)
|
||||
|
||||
return GroupStateDiff(localState, groupHistoryPage.changeLogs) to groupHistoryPage.pagingData
|
||||
return GroupStateDiff(localState, groupHistoryPage.changeLogs, groupHistoryPage.groupSendEndorsementsResponse) to groupHistoryPage.pagingData
|
||||
} catch (e: InvalidGroupStateException) {
|
||||
throw IOException(e)
|
||||
} catch (e: VerificationFailedException) {
|
||||
throw IOException(e)
|
||||
} catch (e: InvalidInputException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveGroupUpdate(
|
||||
timestamp: Long,
|
||||
serverGuid: String?,
|
||||
groupStateDiff: GroupStateDiff
|
||||
groupStateDiff: GroupStateDiff,
|
||||
groupSendEndorsements: ReceivedGroupSendEndorsements?
|
||||
): InternalUpdateResult {
|
||||
val currentLocalState: DecryptedGroup? = groupStateDiff.previousGroupState
|
||||
val applyGroupStateDiffResult = GroupStatePatcher.applyGroupStateDiff(groupStateDiff, GroupStatePatcher.LATEST)
|
||||
@@ -426,12 +481,18 @@ class GroupsV2StateProcessor private constructor(
|
||||
|
||||
if (updatedGroupState == null || updatedGroupState == groupStateDiff.previousGroupState) {
|
||||
Log.i(TAG, "$logPrefix Local state and server state are equal")
|
||||
|
||||
if (groupSendEndorsements != null) {
|
||||
Log.i(TAG, "$logPrefix Saving new send endorsements")
|
||||
SignalDatabase.groups.updateGroupSendEndorsements(groupId, groupSendEndorsements)
|
||||
}
|
||||
|
||||
return InternalUpdateResult.NoUpdateNeeded
|
||||
} else {
|
||||
Log.i(TAG, "$logPrefix Local state (revision: ${currentLocalState?.revision}) does not match, updating to ${updatedGroupState.revision}")
|
||||
}
|
||||
|
||||
saveGroupState(groupStateDiff, updatedGroupState)
|
||||
saveGroupState(groupStateDiff, updatedGroupState, groupSendEndorsements)
|
||||
|
||||
if (currentLocalState == null || currentLocalState.revision == RESTORE_PLACEHOLDER_REVISION) {
|
||||
Log.i(TAG, "$logPrefix Inserting single update message for no local state or restore placeholder")
|
||||
@@ -453,20 +514,24 @@ class GroupsV2StateProcessor private constructor(
|
||||
return InternalUpdateResult.Updated(updatedGroupState)
|
||||
}
|
||||
|
||||
private fun saveGroupState(groupStateDiff: GroupStateDiff, updatedGroupState: DecryptedGroup) {
|
||||
private fun saveGroupState(groupStateDiff: GroupStateDiff, updatedGroupState: DecryptedGroup, groupSendEndorsements: ReceivedGroupSendEndorsements?) {
|
||||
val previousGroupState = groupStateDiff.previousGroupState
|
||||
|
||||
if (groupSendEndorsements != null) {
|
||||
Log.i(TAG, "$logPrefix Updating send endorsements")
|
||||
}
|
||||
|
||||
val needsAvatarFetch = if (previousGroupState == null) {
|
||||
val groupId = SignalDatabase.groups.create(groupMasterKey, updatedGroupState)
|
||||
val groupId = SignalDatabase.groups.create(groupMasterKey, updatedGroupState, groupSendEndorsements)
|
||||
|
||||
if (groupId == null) {
|
||||
Log.w(TAG, "$logPrefix Group create failed, trying to update")
|
||||
SignalDatabase.groups.update(groupMasterKey, updatedGroupState)
|
||||
SignalDatabase.groups.update(groupMasterKey, updatedGroupState, groupSendEndorsements)
|
||||
}
|
||||
|
||||
updatedGroupState.avatar.isNotEmpty()
|
||||
} else {
|
||||
SignalDatabase.groups.update(groupMasterKey, updatedGroupState)
|
||||
SignalDatabase.groups.update(groupMasterKey, updatedGroupState, groupSendEndorsements)
|
||||
|
||||
updatedGroupState.avatar != previousGroupState.avatar
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.MessageTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DeviceLastResetTime;
|
||||
@@ -18,14 +18,12 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
@@ -136,12 +134,11 @@ public class AutomaticSessionResetJob extends BaseJob {
|
||||
return;
|
||||
}
|
||||
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
|
||||
Optional<UnidentifiedAccessPair> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient);
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
|
||||
|
||||
try {
|
||||
messageSender.sendNullMessage(address, unidentifiedAccess);
|
||||
messageSender.sendNullMessage(address, SealedSenderAccessUtil.getSealedSenderAccessFor(recipient));
|
||||
} catch (UntrustedIdentityException e) {
|
||||
Log.w(TAG, "Unable to send null message.");
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSy
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException
|
||||
import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException
|
||||
import org.whispersystems.signalservice.internal.push.SyncMessage.CallLinkUpdate
|
||||
import java.util.Optional
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
@@ -83,7 +82,7 @@ class CallLinkUpdateSendJob private constructor(
|
||||
)
|
||||
|
||||
AppDependencies.signalServiceMessageSender
|
||||
.sendSyncMessage(SignalServiceSyncMessage.forCallLinkUpdate(callLinkUpdate), Optional.empty())
|
||||
.sendSyncMessage(SignalServiceSyncMessage.forCallLinkUpdate(callLinkUpdate))
|
||||
|
||||
if (callLinkUpdateType == CallLinkUpdate.Type.DELETE) {
|
||||
SignalDatabase.callLinks.deleteCallLink(callLinkRoomId)
|
||||
|
||||
@@ -17,7 +17,6 @@ import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSy
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException
|
||||
import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException
|
||||
import org.whispersystems.signalservice.internal.push.SyncMessage
|
||||
import java.util.Optional
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
@@ -98,10 +97,7 @@ class CallLogEventSendJob private constructor(
|
||||
|
||||
override fun onRun() {
|
||||
AppDependencies.signalServiceMessageSender
|
||||
.sendSyncMessage(
|
||||
SignalServiceSyncMessage.forCallLogEvent(callLogEvent),
|
||||
Optional.empty()
|
||||
)
|
||||
.sendSyncMessage(SignalServiceSyncMessage.forCallLogEvent(callLogEvent))
|
||||
}
|
||||
|
||||
override fun onShouldRetry(e: Exception): Boolean {
|
||||
|
||||
@@ -15,7 +15,6 @@ import org.thoughtcrime.securesms.ringrtc.RemotePeer
|
||||
import org.thoughtcrime.securesms.service.webrtc.CallEventSyncMessageUtil
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage
|
||||
import org.whispersystems.signalservice.internal.push.SyncMessage
|
||||
import java.util.Optional
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
@@ -142,7 +141,7 @@ class CallSyncEventJob private constructor(
|
||||
val syncMessage = createSyncMessage(syncTimestamp, callSyncEvent, call.type)
|
||||
|
||||
return try {
|
||||
AppDependencies.signalServiceMessageSender.sendSyncMessage(SignalServiceSyncMessage.forCallEvent(syncMessage), Optional.empty())
|
||||
AppDependencies.signalServiceMessageSender.sendSyncMessage(SignalServiceSyncMessage.forCallEvent(syncMessage))
|
||||
null
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Unable to send call event sync message for ${callSyncEvent.callId}", e)
|
||||
|
||||
@@ -10,11 +10,11 @@ import com.annimon.stream.Stream;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.MessageTable;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
import org.thoughtcrime.securesms.database.PaymentTable;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.UnidentifiedAccessMode;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.SealedSenderAccessMode;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
@@ -37,7 +37,6 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender.IndividualSendEvents;
|
||||
import org.whispersystems.signalservice.api.crypto.ContentHint;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||
@@ -160,7 +159,7 @@ public class IndividualSendJob extends PushSendJob {
|
||||
|
||||
Recipient recipient = message.getThreadRecipient().fresh();
|
||||
byte[] profileKey = recipient.getProfileKey();
|
||||
UnidentifiedAccessMode accessMode = recipient.getUnidentifiedAccessMode();
|
||||
SealedSenderAccessMode accessMode = recipient.getSealedSenderAccessMode();
|
||||
|
||||
boolean unidentified = deliver(message, originalEditedMessage);
|
||||
|
||||
@@ -177,15 +176,15 @@ public class IndividualSendJob extends PushSendJob {
|
||||
SignalDatabase.messages().incrementViewedReceiptCount(message.getSentTimeMillis(), recipient.getId(), System.currentTimeMillis());
|
||||
}
|
||||
|
||||
if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN && profileKey == null) {
|
||||
if (unidentified && accessMode == SealedSenderAccessMode.UNKNOWN && profileKey == null) {
|
||||
log(TAG, String.valueOf(message.getSentTimeMillis()), "Marking recipient as UD-unrestricted following a UD send.");
|
||||
SignalDatabase.recipients().setUnidentifiedAccessMode(recipient.getId(), UnidentifiedAccessMode.UNRESTRICTED);
|
||||
} else if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN) {
|
||||
SignalDatabase.recipients().setSealedSenderAccessMode(recipient.getId(), SealedSenderAccessMode.UNRESTRICTED);
|
||||
} else if (unidentified && accessMode == SealedSenderAccessMode.UNKNOWN) {
|
||||
log(TAG, String.valueOf(message.getSentTimeMillis()), "Marking recipient as UD-enabled following a UD send.");
|
||||
SignalDatabase.recipients().setUnidentifiedAccessMode(recipient.getId(), UnidentifiedAccessMode.ENABLED);
|
||||
} else if (!unidentified && accessMode != UnidentifiedAccessMode.DISABLED) {
|
||||
SignalDatabase.recipients().setSealedSenderAccessMode(recipient.getId(), SealedSenderAccessMode.ENABLED);
|
||||
} else if (!unidentified && accessMode != SealedSenderAccessMode.DISABLED) {
|
||||
log(TAG, String.valueOf(message.getSentTimeMillis()), "Marking recipient as UD-disabled following a non-UD send.");
|
||||
SignalDatabase.recipients().setUnidentifiedAccessMode(recipient.getId(), UnidentifiedAccessMode.DISABLED);
|
||||
SignalDatabase.recipients().setSealedSenderAccessMode(recipient.getId(), SealedSenderAccessMode.DISABLED);
|
||||
}
|
||||
|
||||
if (originalEditedMessage != null && originalEditedMessage.getExpireStarted() > 0) {
|
||||
@@ -306,25 +305,35 @@ public class IndividualSendJob extends PushSendJob {
|
||||
|
||||
if (originalEditedMessage != null) {
|
||||
if (Util.equals(SignalStore.account().getAci(), address.getServiceId())) {
|
||||
Optional<UnidentifiedAccessPair> syncAccess = UnidentifiedAccessUtil.getAccessForSync(context);
|
||||
SendMessageResult result = messageSender.sendSelfSyncEditMessage(new SignalServiceEditMessage(originalEditedMessage.getDateSent(), mediaMessage));
|
||||
SignalDatabase.messageLog().insertIfPossible(messageRecipient.getId(), message.getSentTimeMillis(), result, ContentHint.RESENDABLE, new MessageId(messageId), false);
|
||||
|
||||
return syncAccess.isPresent();
|
||||
return SealedSenderAccessUtil.getSealedSenderCertificate() != null;
|
||||
} else {
|
||||
SendMessageResult result = messageSender.sendEditMessage(address, UnidentifiedAccessUtil.getAccessFor(context, messageRecipient), ContentHint.RESENDABLE, mediaMessage, IndividualSendEvents.EMPTY, message.isUrgent(), originalEditedMessage.getDateSent());
|
||||
SendMessageResult result = messageSender.sendEditMessage(address,
|
||||
SealedSenderAccessUtil.getSealedSenderAccessFor(messageRecipient),
|
||||
ContentHint.RESENDABLE,
|
||||
mediaMessage,
|
||||
IndividualSendEvents.EMPTY,
|
||||
message.isUrgent(),
|
||||
originalEditedMessage.getDateSent());
|
||||
SignalDatabase.messageLog().insertIfPossible(messageRecipient.getId(), message.getSentTimeMillis(), result, ContentHint.RESENDABLE, new MessageId(messageId), false);
|
||||
|
||||
return result.getSuccess().isUnidentified();
|
||||
}
|
||||
} else if (Util.equals(SignalStore.account().getAci(), address.getServiceId())) {
|
||||
Optional<UnidentifiedAccessPair> syncAccess = UnidentifiedAccessUtil.getAccessForSync(context);
|
||||
SendMessageResult result = messageSender.sendSyncMessage(mediaMessage);
|
||||
SignalDatabase.messageLog().insertIfPossible(messageRecipient.getId(), message.getSentTimeMillis(), result, ContentHint.RESENDABLE, new MessageId(messageId), false);
|
||||
return syncAccess.isPresent();
|
||||
return SealedSenderAccessUtil.getSealedSenderCertificate() != null;
|
||||
} else {
|
||||
SignalLocalMetrics.IndividualMessageSend.onDeliveryStarted(messageId, message.getSentTimeMillis());
|
||||
SendMessageResult result = messageSender.sendDataMessage(address, UnidentifiedAccessUtil.getAccessFor(context, messageRecipient), ContentHint.RESENDABLE, mediaMessage, new MetricEventListener(messageId), message.isUrgent(), messageRecipient.getNeedsPniSignature());
|
||||
SendMessageResult result = messageSender.sendDataMessage(address,
|
||||
SealedSenderAccessUtil.getSealedSenderAccessFor(messageRecipient),
|
||||
ContentHint.RESENDABLE,
|
||||
mediaMessage,
|
||||
new MetricEventListener(messageId),
|
||||
message.isUrgent(),
|
||||
messageRecipient.getNeedsPniSignature());
|
||||
|
||||
SignalDatabase.messageLog().insertIfPossible(messageRecipient.getId(), message.getSentTimeMillis(), result, ContentHint.RESENDABLE, new MessageId(messageId), message.isUrgent());
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.RecipientReader;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
@@ -88,8 +87,8 @@ public class MultiDeviceBlockedUpdateJob extends BaseJob {
|
||||
}
|
||||
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forBlocked(new BlockedListMessage(blockedIndividuals, blockedGroups)),
|
||||
UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forBlocked(new BlockedListMessage(blockedIndividuals, blockedGroups))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import org.thoughtcrime.securesms.service.webrtc.links.CallLinkCredentials
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException
|
||||
import org.whispersystems.signalservice.internal.push.SyncMessage.CallLinkUpdate
|
||||
import java.util.Optional
|
||||
import kotlin.time.Duration.Companion.days
|
||||
|
||||
/**
|
||||
@@ -57,7 +56,7 @@ class MultiDeviceCallLinkSyncJob private constructor(
|
||||
val syncMessage = SignalServiceSyncMessage.forCallLinkUpdate(callLinkUpdate)
|
||||
|
||||
try {
|
||||
AppDependencies.signalServiceMessageSender.sendSyncMessage(syncMessage, Optional.empty())
|
||||
AppDependencies.signalServiceMessageSender.sendSyncMessage(syncMessage)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Unable to send call link update message.", e)
|
||||
throw e
|
||||
|
||||
@@ -5,10 +5,9 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
@@ -106,8 +105,8 @@ public class MultiDeviceConfigurationUpdateJob extends BaseJob {
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forConfiguration(new ConfigurationMessage(Optional.of(readReceiptsEnabled),
|
||||
Optional.of(unidentifiedDeliveryIndicatorsEnabled),
|
||||
Optional.of(typingIndicatorsEnabled),
|
||||
Optional.of(linkPreviewsEnabled))),
|
||||
UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
Optional.of(linkPreviewsEnabled)))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,13 +15,12 @@ import org.signal.libsignal.protocol.IdentityKey;
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColorsMapper;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
@@ -298,8 +297,8 @@ public class MultiDeviceContactUpdateJob extends BaseJob {
|
||||
.withLength(length)
|
||||
.withResumableUploadSpec(messageSender.getResumableUploadSpec());
|
||||
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream.build(), complete)),
|
||||
UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream.build(), complete))
|
||||
);
|
||||
} catch (IOException ioe) {
|
||||
throw new NetworkException(ioe);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
@@ -73,8 +72,8 @@ public class MultiDeviceKeysUpdateJob extends BaseJob {
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
StorageKey storageServiceKey = SignalStore.storageService().getOrCreateStorageKey();
|
||||
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forKeys(new KeysMessage(Optional.ofNullable(storageServiceKey), Optional.of(SignalStore.svr().getOrCreateMasterKey()))),
|
||||
UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forKeys(new KeysMessage(Optional.ofNullable(storageServiceKey), Optional.of(SignalStore.svr().getOrCreateMasterKey())))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -5,10 +5,9 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@@ -122,8 +121,8 @@ public class MultiDeviceMessageRequestResponseJob extends BaseJob {
|
||||
}
|
||||
|
||||
if (response != null) {
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forMessageRequestResponse(response),
|
||||
UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forMessageRequestResponse(response)
|
||||
);
|
||||
} else {
|
||||
Log.w(TAG, recipient.getId() + " not registered!");
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.PaymentTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
@@ -117,8 +116,8 @@ public final class MultiDeviceOutgoingPaymentSyncJob extends BaseJob {
|
||||
|
||||
|
||||
AppDependencies.getSignalServiceMessageSender()
|
||||
.sendSyncMessage(SignalServiceSyncMessage.forOutgoingPayment(outgoingPaymentMessage),
|
||||
UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
.sendSyncMessage(SignalServiceSyncMessage.forOutgoingPayment(outgoingPaymentMessage)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -4,7 +4,6 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
@@ -58,8 +57,8 @@ public class MultiDeviceProfileContentUpdateJob extends BaseJob {
|
||||
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.LOCAL_PROFILE),
|
||||
UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.LOCAL_PROFILE)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -7,7 +7,6 @@ import androidx.annotation.Nullable;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
@@ -98,7 +97,7 @@ public class MultiDeviceProfileKeyUpdateJob extends BaseJob {
|
||||
|
||||
SignalServiceSyncMessage syncMessage = SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream, false));
|
||||
|
||||
messageSender.sendSyncMessage(syncMessage, UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
messageSender.sendSyncMessage(syncMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -8,12 +8,11 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.signal.core.util.ListUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.MessageTable.SyncMessageId;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@@ -121,7 +120,7 @@ public class MultiDeviceReadUpdateJob extends BaseJob {
|
||||
}
|
||||
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forRead(readMessages), UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forRead(readMessages));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -3,13 +3,12 @@ package org.thoughtcrime.securesms.jobs;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.signal.core.util.Hex;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
|
||||
@@ -93,8 +92,8 @@ public class MultiDeviceStickerPackOperationJob extends BaseJob {
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
StickerPackOperationMessage stickerPackOperation = new StickerPackOperationMessage(packIdBytes, packKeyBytes, remoteType);
|
||||
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forStickerPackOperations(Collections.singletonList(stickerPackOperation)),
|
||||
UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forStickerPackOperations(Collections.singletonList(stickerPackOperation))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -3,8 +3,8 @@ package org.thoughtcrime.securesms.jobs;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.Hex;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.StickerTable.StickerPackRecordReader;
|
||||
import org.thoughtcrime.securesms.database.model.StickerPackRecord;
|
||||
@@ -13,7 +13,6 @@ import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.signal.core.util.Hex;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
|
||||
@@ -80,8 +79,8 @@ public class MultiDeviceStickerPackSyncJob extends BaseJob {
|
||||
}
|
||||
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forStickerPackOperations(operations),
|
||||
UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forStickerPackOperations(operations)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -4,7 +4,6 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
@@ -58,8 +57,7 @@ public class MultiDeviceStorageSyncRequestJob extends BaseJob {
|
||||
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.STORAGE_MANIFEST),
|
||||
UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.STORAGE_MANIFEST));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -11,7 +11,6 @@ import org.whispersystems.signalservice.api.messages.SignalServiceStoryMessageRe
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
import java.lang.Exception
|
||||
import java.util.Optional
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@@ -57,7 +56,7 @@ class MultiDeviceStorySendSyncJob private constructor(parameters: Parameters, pr
|
||||
val updateManifest = SignalDatabase.storySends.getLocalManifest(sentTimestamp)
|
||||
val recipientsSet: Set<SignalServiceStoryMessageRecipient> = updateManifest.toRecipientsSet()
|
||||
val transcriptMessage: SignalServiceSyncMessage = SignalServiceSyncMessage.forSentTranscript(buildSentTranscript(recipientsSet))
|
||||
val sendMessageResult = AppDependencies.signalServiceMessageSender.sendSyncMessage(transcriptMessage, Optional.empty())
|
||||
val sendMessageResult = AppDependencies.signalServiceMessageSender.sendSyncMessage(transcriptMessage)
|
||||
|
||||
Log.i(TAG, "Sent transcript message with ${recipientsSet.size} recipients")
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||
@@ -57,10 +56,7 @@ class MultiDeviceSubscriptionSyncRequestJob private constructor(parameters: Para
|
||||
|
||||
val messageSender = AppDependencies.signalServiceMessageSender
|
||||
|
||||
messageSender.sendSyncMessage(
|
||||
SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.SUBSCRIPTION_STATUS),
|
||||
UnidentifiedAccessUtil.getAccessForSync(context)
|
||||
)
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.SUBSCRIPTION_STATUS))
|
||||
}
|
||||
|
||||
override fun onShouldRetry(e: Exception): Boolean {
|
||||
|
||||
@@ -4,20 +4,19 @@ package org.thoughtcrime.securesms.jobs;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.Base64;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.IdentityKey;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.IdentityTable.VerifiedStatus;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.signal.core.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
@@ -116,8 +115,8 @@ public class MultiDeviceVerifiedUpdateJob extends BaseJob {
|
||||
SignalServiceAddress verifiedAddress = RecipientUtil.toSignalServiceAddress(context, recipient);
|
||||
VerifiedMessage verifiedMessage = new VerifiedMessage(verifiedAddress, new IdentityKey(identityKey, 0), verifiedState, timestamp);
|
||||
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forVerified(verifiedMessage),
|
||||
UnidentifiedAccessUtil.getAccessFor(context, recipient));
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forVerified(verifiedMessage)
|
||||
);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
@@ -6,11 +6,10 @@ import androidx.annotation.Nullable;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.MessageTable.SyncMessageId;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@@ -92,7 +91,7 @@ public class MultiDeviceViewOnceOpenJob extends BaseJob {
|
||||
|
||||
ViewOnceOpenMessage openMessage = new ViewOnceOpenMessage(RecipientUtil.getOrFetchServiceId(context, recipient), messageId.timestamp);
|
||||
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forViewOnceOpen(openMessage), UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forViewOnceOpen(openMessage));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -8,12 +8,11 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.signal.core.util.ListUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.MessageTable.SyncMessageId;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@@ -121,7 +120,7 @@ public class MultiDeviceViewedUpdateJob extends BaseJob {
|
||||
}
|
||||
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forViewed(viewedMessages), UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forViewed(viewedMessages));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -4,21 +4,19 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
@@ -72,12 +70,11 @@ public class NullMessageSendJob extends BaseJob {
|
||||
Log.w(TAG, recipient.getId() + " not registered!");
|
||||
}
|
||||
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
|
||||
Optional<UnidentifiedAccessPair> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient);
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
|
||||
|
||||
try {
|
||||
messageSender.sendNullMessage(address, unidentifiedAccess);
|
||||
messageSender.sendNullMessage(address, SealedSenderAccessUtil.getSealedSenderAccessFor(recipient));
|
||||
} catch (UntrustedIdentityException e) {
|
||||
Log.w(TAG, "Unable to send null message.");
|
||||
}
|
||||
|
||||
@@ -4,12 +4,12 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.PaymentTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
@@ -18,14 +18,12 @@ import org.thoughtcrime.securesms.transport.RetryLaterException;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender.IndividualSendEvents;
|
||||
import org.whispersystems.signalservice.api.crypto.ContentHint;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class PaymentNotificationSendJob extends BaseJob {
|
||||
@@ -81,9 +79,8 @@ public final class PaymentNotificationSendJob extends BaseJob {
|
||||
return;
|
||||
}
|
||||
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
|
||||
Optional<UnidentifiedAccessPair> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient);
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
|
||||
|
||||
PaymentTable.PaymentTransaction payment = paymentDatabase.getPayment(uuid);
|
||||
|
||||
@@ -101,7 +98,13 @@ public final class PaymentNotificationSendJob extends BaseJob {
|
||||
.withPayment(new SignalServiceDataMessage.Payment(new SignalServiceDataMessage.PaymentNotification(payment.getReceipt(), payment.getNote()), null))
|
||||
.build();
|
||||
|
||||
SendMessageResult sendMessageResult = messageSender.sendDataMessage(address, unidentifiedAccess, ContentHint.DEFAULT, dataMessage, IndividualSendEvents.EMPTY, false, recipient.getNeedsPniSignature());
|
||||
SendMessageResult sendMessageResult = messageSender.sendDataMessage(address,
|
||||
SealedSenderAccessUtil.getSealedSenderAccessFor(recipient),
|
||||
ContentHint.DEFAULT,
|
||||
dataMessage,
|
||||
IndividualSendEvents.EMPTY,
|
||||
false,
|
||||
recipient.getNeedsPniSignature());
|
||||
|
||||
if (recipient.getNeedsPniSignature()) {
|
||||
SignalDatabase.pendingPniSignatureMessages().insertIfNecessary(recipientId, dataMessage.getTimestamp(), sendMessageResult);
|
||||
|
||||
@@ -7,7 +7,7 @@ import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.SignalProtocolAddress;
|
||||
import org.signal.libsignal.protocol.message.SenderKeyDistributionMessage;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListRecord;
|
||||
import org.thoughtcrime.securesms.database.model.GroupRecord;
|
||||
@@ -22,7 +22,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.crypto.ContentHint;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
|
||||
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||
import org.whispersystems.signalservice.api.push.DistributionId;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
@@ -141,9 +141,9 @@ public class ResendMessageJob extends BaseJob {
|
||||
return;
|
||||
}
|
||||
|
||||
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
|
||||
Optional<UnidentifiedAccessPair> access = UnidentifiedAccessUtil.getAccessFor(context, recipient);
|
||||
Content contentToSend = content;
|
||||
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
|
||||
Content contentToSend = content;
|
||||
SealedSenderAccess.CreateGroupSendToken createGroupSendToken = null;
|
||||
|
||||
if (distributionId != null) {
|
||||
if (groupId != null) {
|
||||
@@ -157,6 +157,8 @@ public class ResendMessageJob extends BaseJob {
|
||||
Log.w(TAG, "The target user is no longer in the group! Skipping message send.");
|
||||
return;
|
||||
}
|
||||
|
||||
createGroupSendToken = () -> SignalDatabase.groups().getGroupSendFullToken(groupId, recipientId);
|
||||
} else {
|
||||
Log.d(TAG, "GroupId is not present. Assuming this is a message for a distribution list.");
|
||||
DistributionListRecord listRecord = SignalDatabase.distributionLists().getListByDistributionId(distributionId);
|
||||
@@ -178,6 +180,8 @@ public class ResendMessageJob extends BaseJob {
|
||||
|
||||
SendMessageResult result;
|
||||
|
||||
SealedSenderAccess access = SealedSenderAccessUtil.getSealedSenderAccessFor(recipient, createGroupSendToken);
|
||||
|
||||
try {
|
||||
result = messageSender.resendContent(address, access, sentTimestamp, contentToSend, contentHint, Optional.ofNullable(groupId).map(GroupId::getDecodedId), urgent);
|
||||
} catch (IllegalStateException e) {
|
||||
|
||||
@@ -19,7 +19,7 @@ import org.thoughtcrime.securesms.database.GroupTable
|
||||
import org.thoughtcrime.securesms.database.RecipientTable
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.Companion.maskCapabilitiesToLong
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.PhoneNumberSharingState
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.UnidentifiedAccessMode
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.SealedSenderAccessMode
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.RecipientRecord
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
@@ -222,7 +222,7 @@ class RetrieveProfileJob private constructor(parameters: Parameters, private val
|
||||
unrestrictedUnidentifiedAccess = remoteProfile.isUnrestrictedUnidentifiedAccess
|
||||
)
|
||||
|
||||
if (localRecipientRecord.unidentifiedAccessMode != accessMode) {
|
||||
if (localRecipientRecord.sealedSenderAccessMode != accessMode) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -322,8 +322,8 @@ class RetrieveProfileJob private constructor(parameters: Parameters, private val
|
||||
val profileKey = ProfileKeyUtil.profileKeyOrNull(recipient.profileKey)
|
||||
val newMode = deriveUnidentifiedAccessMode(profileKey, unidentifiedAccessVerifier, unrestrictedUnidentifiedAccess)
|
||||
|
||||
if (recipient.unidentifiedAccessMode !== newMode) {
|
||||
if (newMode === UnidentifiedAccessMode.UNRESTRICTED) {
|
||||
if (recipient.sealedSenderAccessMode !== newMode) {
|
||||
if (newMode === SealedSenderAccessMode.UNRESTRICTED) {
|
||||
Log.i(TAG, "Marking recipient UD status as unrestricted.")
|
||||
} else if (profileKey == null || unidentifiedAccessVerifier == null) {
|
||||
Log.i(TAG, "Marking recipient UD status as disabled.")
|
||||
@@ -331,15 +331,15 @@ class RetrieveProfileJob private constructor(parameters: Parameters, private val
|
||||
Log.i(TAG, "Marking recipient UD status as " + newMode.name + " after verification.")
|
||||
}
|
||||
|
||||
SignalDatabase.recipients.setUnidentifiedAccessMode(recipient.id, newMode)
|
||||
SignalDatabase.recipients.setSealedSenderAccessMode(recipient.id, newMode)
|
||||
}
|
||||
}
|
||||
|
||||
private fun deriveUnidentifiedAccessMode(profileKey: ProfileKey?, unidentifiedAccessVerifier: String?, unrestrictedUnidentifiedAccess: Boolean): UnidentifiedAccessMode {
|
||||
private fun deriveUnidentifiedAccessMode(profileKey: ProfileKey?, unidentifiedAccessVerifier: String?, unrestrictedUnidentifiedAccess: Boolean): SealedSenderAccessMode {
|
||||
return if (unrestrictedUnidentifiedAccess && unidentifiedAccessVerifier != null) {
|
||||
UnidentifiedAccessMode.UNRESTRICTED
|
||||
SealedSenderAccessMode.UNRESTRICTED
|
||||
} else if (profileKey == null || unidentifiedAccessVerifier == null) {
|
||||
UnidentifiedAccessMode.DISABLED
|
||||
SealedSenderAccessMode.DISABLED
|
||||
} else {
|
||||
val profileCipher = ProfileCipher(profileKey)
|
||||
val verifiedUnidentifiedAccess: Boolean = try {
|
||||
@@ -350,9 +350,9 @@ class RetrieveProfileJob private constructor(parameters: Parameters, private val
|
||||
}
|
||||
|
||||
if (verifiedUnidentifiedAccess) {
|
||||
UnidentifiedAccessMode.ENABLED
|
||||
SealedSenderAccessMode.ENABLED
|
||||
} else {
|
||||
UnidentifiedAccessMode.DISABLED
|
||||
SealedSenderAccessMode.DISABLED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,13 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendFullToken;
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@@ -123,7 +124,7 @@ public class SendDeliveryReceiptJob extends BaseJob {
|
||||
timestamp);
|
||||
|
||||
SendMessageResult result = messageSender.sendReceipt(remoteAddress,
|
||||
UnidentifiedAccessUtil.getAccessFor(context, recipient),
|
||||
SealedSenderAccessUtil.getSealedSenderAccessFor(recipient, this::getGroupSendFullToken),
|
||||
receiptMessage,
|
||||
recipient.getNeedsPniSignature());
|
||||
|
||||
@@ -132,6 +133,19 @@ public class SendDeliveryReceiptJob extends BaseJob {
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable GroupSendFullToken getGroupSendFullToken() {
|
||||
if (messageId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
long threadId = SignalDatabase.messages().getThreadIdForMessage(messageId.getId());
|
||||
if (threadId == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return SignalDatabase.groups().getGroupSendFullToken(threadId, recipientId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onShouldRetry(@NonNull Exception e) {
|
||||
if (e instanceof ServerRejectedException) return false;
|
||||
|
||||
@@ -9,14 +9,14 @@ import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import org.signal.core.util.ListUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.MessageTable.MarkedMessageInfo;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@@ -188,7 +188,8 @@ public class SendReadReceiptJob extends BaseJob {
|
||||
SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.READ, messageSentTimestamps, timestamp);
|
||||
|
||||
SendMessageResult result = messageSender.sendReceipt(remoteAddress,
|
||||
UnidentifiedAccessUtil.getAccessFor(context, Recipient.resolved(recipientId)),
|
||||
SealedSenderAccessUtil.getSealedSenderAccessFor(recipient,
|
||||
() -> SignalDatabase.groups().getGroupSendFullToken(threadId, recipientId)),
|
||||
receiptMessage,
|
||||
recipient.getNeedsPniSignature());
|
||||
|
||||
|
||||
@@ -4,19 +4,18 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.InvalidMessageException;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.signal.libsignal.protocol.InvalidMessageException;
|
||||
import org.signal.libsignal.protocol.message.DecryptionErrorMessage;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||
|
||||
@@ -87,12 +86,11 @@ public final class SendRetryReceiptJob extends BaseJob {
|
||||
return;
|
||||
}
|
||||
|
||||
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
|
||||
Optional<UnidentifiedAccessPair> access = UnidentifiedAccessUtil.getAccessFor(context, recipient);
|
||||
Optional<byte[]> group = groupId.map(GroupId::getDecodedId);
|
||||
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
|
||||
Optional<byte[]> group = groupId.map(GroupId::getDecodedId);
|
||||
|
||||
Log.i(TAG, "Sending retry receipt for " + errorMessage.getTimestamp() + " to " + recipientId + ", device: " + errorMessage.getDeviceId());
|
||||
AppDependencies.getSignalServiceMessageSender().sendRetryReceipt(address, access, group, errorMessage);
|
||||
AppDependencies.getSignalServiceMessageSender().sendRetryReceipt(address, SealedSenderAccessUtil.getSealedSenderAccessFor(recipient), group, errorMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -8,15 +8,15 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.ListUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.MessageTable.MarkedMessageInfo;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
||||
import org.thoughtcrime.securesms.database.model.StoryType;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
@@ -208,7 +208,8 @@ public class SendViewedReceiptJob extends BaseJob {
|
||||
timestamp);
|
||||
|
||||
SendMessageResult result = messageSender.sendReceipt(remoteAddress,
|
||||
UnidentifiedAccessUtil.getAccessFor(context, Recipient.resolved(recipientId)),
|
||||
SealedSenderAccessUtil.getSealedSenderAccessFor(recipient,
|
||||
() -> SignalDatabase.groups().getGroupSendFullToken(threadId, recipientId)),
|
||||
receiptMessage,
|
||||
recipient.getNeedsPniSignature());
|
||||
|
||||
|
||||
@@ -6,19 +6,19 @@ import androidx.annotation.Nullable;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.SignalProtocolAddress;
|
||||
import org.signal.libsignal.protocol.message.SenderKeyDistributionMessage;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
|
||||
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||
import org.whispersystems.signalservice.api.push.DistributionId;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
@@ -85,12 +85,14 @@ public final class SenderKeyDistributionSendJob extends BaseJob {
|
||||
return;
|
||||
}
|
||||
|
||||
GroupId.V2 groupId;
|
||||
DistributionId distributionId;
|
||||
GroupId.V2 groupId;
|
||||
DistributionId distributionId;
|
||||
SealedSenderAccess.CreateGroupSendToken createGroupSendFullToken = null;
|
||||
|
||||
if (threadRecipient.isPushV2Group()) {
|
||||
groupId = threadRecipient.requireGroupId().requireV2();
|
||||
distributionId = SignalDatabase.groups().getOrCreateDistributionId(groupId);
|
||||
groupId = threadRecipient.requireGroupId().requireV2();
|
||||
distributionId = SignalDatabase.groups().getOrCreateDistributionId(groupId);
|
||||
createGroupSendFullToken = () -> SignalDatabase.groups().getGroupSendFullToken(groupId, targetRecipientId);
|
||||
} else if (threadRecipient.isDistributionList()) {
|
||||
groupId = null;
|
||||
distributionId = SignalDatabase.distributionLists().getDistributionId(threadRecipientId);
|
||||
@@ -116,10 +118,10 @@ public final class SenderKeyDistributionSendJob extends BaseJob {
|
||||
}
|
||||
}
|
||||
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
List<SignalServiceAddress> address = Collections.singletonList(RecipientUtil.toSignalServiceAddress(context, targetRecipient));
|
||||
SenderKeyDistributionMessage message = messageSender.getOrCreateNewGroupSession(distributionId);
|
||||
List<Optional<UnidentifiedAccessPair>> access = UnidentifiedAccessUtil.getAccessFor(context, Collections.singletonList(targetRecipient));
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
List<SignalServiceAddress> address = Collections.singletonList(RecipientUtil.toSignalServiceAddress(context, targetRecipient));
|
||||
SenderKeyDistributionMessage message = messageSender.getOrCreateNewGroupSession(distributionId);
|
||||
List<SealedSenderAccess> access = Collections.singletonList(SealedSenderAccessUtil.getSealedSenderAccessFor(targetRecipient, createGroupSendFullToken));
|
||||
|
||||
SendMessageResult result = messageSender.sendSenderKeyDistributionMessage(distributionId, address, access, message, Optional.ofNullable(groupId).map(GroupId::getDecodedId), false, false).get(0);
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import net.zetetic.database.sqlcipher.SQLiteDatabase;
|
||||
import org.signal.core.util.Stopwatch;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.UnknownStorageIdTable;
|
||||
@@ -209,7 +208,7 @@ public class StorageSyncJob extends BaseJob {
|
||||
} else {
|
||||
Log.w(TAG, "Failed to decrypt remote storage! Requesting new keys from primary.", e);
|
||||
SignalStore.storageService().clearStorageKeyFromPrimary();
|
||||
AppDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forRequest(RequestMessage.forType(SyncMessage.Request.Type.KEYS)), UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
AppDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forRequest(RequestMessage.forType(SyncMessage.Request.Type.KEYS)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.messages
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import org.signal.core.util.PendingIntentFlags
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogActivity
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels
|
||||
import org.thoughtcrime.securesms.notifications.NotificationIds
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
/**
|
||||
* Internal user only notifier when "bad" things happen with group send endorsement sends.
|
||||
*/
|
||||
object GroupSendEndorsementInternalNotifier : SealedSenderAccess.FallbackListener {
|
||||
|
||||
private const val TAG = "GSENotifier"
|
||||
|
||||
private var lastGroupSendNotify: Duration = 0.milliseconds
|
||||
private var skippedGroupSendNotifies = 0
|
||||
|
||||
private var lastMissingNotify: Duration = 0.milliseconds
|
||||
|
||||
private var lastFallbackNotify: Duration = 0.milliseconds
|
||||
|
||||
@JvmStatic
|
||||
fun init() {
|
||||
if (RemoteConfig.internalUser) {
|
||||
SealedSenderAccess.fallbackListener = this
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAccessToTokenFallback() {
|
||||
Log.w(TAG, "Fallback from access key to token", Throwable())
|
||||
postFallbackError(AppDependencies.application)
|
||||
}
|
||||
|
||||
override fun onTokenToAccessFallback(hasAccessKeyFallback: Boolean) {
|
||||
Log.w(TAG, "Fallback from token hasAccessKey=$hasAccessKeyFallback", Throwable())
|
||||
postFallbackError(AppDependencies.application)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun postGroupSendFallbackError(context: Context) {
|
||||
val now = System.currentTimeMillis().milliseconds
|
||||
if (lastGroupSendNotify + 5.minutes > now && skippedGroupSendNotifies < 5) {
|
||||
skippedGroupSendNotifies++
|
||||
return
|
||||
}
|
||||
|
||||
val notification: Notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().FAILURES)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentTitle("[Internal-only] GSE failed for group send")
|
||||
.setContentText("Please tap to send a debug log")
|
||||
.setContentIntent(PendingIntent.getActivity(context, 0, Intent(context, SubmitDebugLogActivity::class.java), PendingIntentFlags.mutable()))
|
||||
.build()
|
||||
|
||||
NotificationManagerCompat.from(context).notify(NotificationIds.INTERNAL_ERROR, notification)
|
||||
|
||||
lastGroupSendNotify = now
|
||||
skippedGroupSendNotifies = 0
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun postMissingGroupSendEndorsement(context: Context) {
|
||||
val now = System.currentTimeMillis().milliseconds
|
||||
if (lastMissingNotify + 5.minutes > now) {
|
||||
return
|
||||
}
|
||||
|
||||
val notification: Notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().FAILURES)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentTitle("[Internal-only] GSE missing for recipient")
|
||||
.setContentText("Please tap to send a debug log")
|
||||
.setContentIntent(PendingIntent.getActivity(context, 0, Intent(context, SubmitDebugLogActivity::class.java), PendingIntentFlags.mutable()))
|
||||
.build()
|
||||
|
||||
NotificationManagerCompat.from(context).notify(NotificationIds.INTERNAL_ERROR, notification)
|
||||
|
||||
lastMissingNotify = now
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun postFallbackError(context: Context) {
|
||||
val now = System.currentTimeMillis().milliseconds
|
||||
if (lastFallbackNotify + 5.minutes > now) {
|
||||
return
|
||||
}
|
||||
|
||||
val notification: Notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().FAILURES)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentTitle("[Internal-only] GSE fallback occurred!")
|
||||
.setContentText("Please tap to send a debug log")
|
||||
.setContentIntent(PendingIntent.getActivity(context, 0, Intent(context, SubmitDebugLogActivity::class.java), PendingIntentFlags.mutable()))
|
||||
.build()
|
||||
|
||||
NotificationManagerCompat.from(context).notify(NotificationIds.INTERNAL_ERROR, notification)
|
||||
|
||||
lastFallbackNotify = now
|
||||
}
|
||||
}
|
||||
@@ -7,34 +7,44 @@ import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.metadata.certificate.SenderCertificate;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.signal.libsignal.protocol.InvalidRegistrationIdException;
|
||||
import org.signal.libsignal.protocol.NoSessionException;
|
||||
import org.signal.libsignal.zkgroup.groups.GroupSecretParams;
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsement;
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendFullToken;
|
||||
import org.thoughtcrime.securesms.crypto.SenderKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.model.GroupRecord;
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.MessageSendLogTables;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListId;
|
||||
import org.thoughtcrime.securesms.database.model.GroupRecord;
|
||||
import org.thoughtcrime.securesms.database.model.GroupSendEndorsementRecords;
|
||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupChangeException;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.GroupManager;
|
||||
import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
import org.thoughtcrime.securesms.util.RecipientAccessList;
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
import org.thoughtcrime.securesms.util.SignalLocalMetrics;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.signalservice.api.CancelationException;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender.LegacyGroupEvents;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender.SenderKeyGroupEvents;
|
||||
import org.whispersystems.signalservice.api.crypto.ContentHint;
|
||||
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupSendEndorsements;
|
||||
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEditMessage;
|
||||
@@ -43,6 +53,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceStoryMessageRe
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
|
||||
import org.whispersystems.signalservice.api.push.DistributionId;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
|
||||
import org.whispersystems.signalservice.api.util.Preconditions;
|
||||
@@ -52,6 +63,7 @@ import org.whispersystems.signalservice.internal.push.http.PartialSendBatchCompl
|
||||
import org.whispersystems.signalservice.internal.push.http.PartialSendCompleteListener;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -248,28 +260,81 @@ public final class GroupSendUtil {
|
||||
Set<Recipient> unregisteredTargets = allTargets.stream().filter(Recipient::isUnregistered).collect(Collectors.toSet());
|
||||
List<Recipient> registeredTargets = allTargets.stream().filter(r -> !unregisteredTargets.contains(r)).collect(Collectors.toList());
|
||||
|
||||
RecipientData recipients = new RecipientData(context, registeredTargets, isStorySend);
|
||||
Optional<GroupRecord> groupRecord = groupId != null ? SignalDatabase.groups().getGroup(groupId) : Optional.empty();
|
||||
RecipientData recipients = new RecipientData(context, registeredTargets, isStorySend);
|
||||
Optional<GroupRecord> groupRecord = groupId != null ? SignalDatabase.groups().getGroup(groupId) : Optional.empty();
|
||||
GroupSendEndorsementRecords groupSendEndorsementRecords = groupRecord.filter(GroupRecord::isV2Group).map(g -> SignalDatabase.groups().getGroupSendEndorsements(g.getId())).orElse(null);
|
||||
long groupSendEndorsementExpiration = groupRecord.map(GroupRecord::getGroupSendEndorsementExpiration).orElse(0L);
|
||||
SenderCertificate senderCertificate = SealedSenderAccessUtil.getSealedSenderCertificate();
|
||||
boolean useGroupSendEndorsements = groupSendEndorsementRecords != null;
|
||||
|
||||
if (useGroupSendEndorsements && senderCertificate == null) {
|
||||
Log.w(TAG, "Can't use group send endorsements without a sealed sender certificate, falling back to access key");
|
||||
useGroupSendEndorsements = false;
|
||||
} else if (useGroupSendEndorsements) {
|
||||
boolean refreshGroupSendEndorsements = false;
|
||||
|
||||
if (groupSendEndorsementExpiration == 0) {
|
||||
Log.i(TAG, "No group send endorsements expiration set, need to refresh");
|
||||
refreshGroupSendEndorsements = true;
|
||||
} else if (groupSendEndorsementExpiration - TimeUnit.HOURS.toMillis(2) < System.currentTimeMillis()) {
|
||||
Log.i(TAG, "Group send endorsements are expired or expire imminently, refresh. Expires in " + (groupSendEndorsementExpiration - System.currentTimeMillis()) + "ms");
|
||||
refreshGroupSendEndorsements = true;
|
||||
} else if (groupSendEndorsementRecords.isMissingAnyEndorsements()) {
|
||||
Log.i(TAG, "Missing group send endorsements for some members, refresh.");
|
||||
refreshGroupSendEndorsements = true;
|
||||
}
|
||||
|
||||
if (refreshGroupSendEndorsements) {
|
||||
try {
|
||||
GroupManager.updateGroupSendEndorsements(context, groupRecord.get().requireV2GroupProperties().getGroupMasterKey());
|
||||
|
||||
groupSendEndorsementExpiration = SignalDatabase.groups().getGroupSendEndorsementsExpiration(groupId);
|
||||
groupSendEndorsementRecords = SignalDatabase.groups().getGroupSendEndorsements(groupId);
|
||||
} catch (GroupChangeException | IOException e) {
|
||||
if (groupSendEndorsementExpiration == 0) {
|
||||
Log.w(TAG, "Unable to update group send endorsements, falling back to access key", e);
|
||||
useGroupSendEndorsements = false;
|
||||
groupSendEndorsementRecords = new GroupSendEndorsementRecords(Collections.emptyMap());
|
||||
} else {
|
||||
Log.w(TAG, "Unable to update group send endorsements, using what we have", e);
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "Refresh all group state because we needed to refresh gse");
|
||||
AppDependencies.getJobManager().add(new RequestGroupV2InfoJob(groupId));
|
||||
}
|
||||
}
|
||||
|
||||
List<Recipient> senderKeyTargets = new LinkedList<>();
|
||||
List<Recipient> legacyTargets = new LinkedList<>();
|
||||
|
||||
for (Recipient recipient : registeredTargets) {
|
||||
Optional<UnidentifiedAccessPair> access = recipients.getAccessPair(recipient.getId());
|
||||
boolean validMembership = true;
|
||||
Optional<UnidentifiedAccess> access = recipients.getAccessPair(recipient.getId());
|
||||
boolean validMembership = groupId == null || (groupRecord.isPresent() && groupRecord.get().getMembers().contains(recipient.getId()));
|
||||
|
||||
if (groupId != null && (!groupRecord.isPresent() || !groupRecord.get().getMembers().contains(recipient.getId()))) {
|
||||
validMembership = false;
|
||||
}
|
||||
|
||||
if (recipient.getHasServiceId() &&
|
||||
access.isPresent() &&
|
||||
access.get().getTargetUnidentifiedAccess().isPresent() &&
|
||||
validMembership)
|
||||
{
|
||||
senderKeyTargets.add(recipient);
|
||||
if (useGroupSendEndorsements) {
|
||||
GroupSendEndorsement groupSendEndorsement = groupSendEndorsementRecords.getEndorsement(recipient.getId());
|
||||
if (groupSendEndorsement != null && recipient.getHasAci() && validMembership) {
|
||||
senderKeyTargets.add(recipient);
|
||||
} else {
|
||||
legacyTargets.add(recipient);
|
||||
if (validMembership) {
|
||||
Log.w(TAG, "Should be using group send endorsement but not found for " + recipient.getId());
|
||||
if (RemoteConfig.internalUser()) {
|
||||
GroupSendEndorsementInternalNotifier.postMissingGroupSendEndorsement(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
legacyTargets.add(recipient);
|
||||
// Use sender key
|
||||
if (recipient.getHasServiceId() &&
|
||||
access.isPresent() &&
|
||||
validMembership)
|
||||
{
|
||||
senderKeyTargets.add(recipient);
|
||||
} else {
|
||||
legacyTargets.add(recipient);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,7 +364,7 @@ public final class GroupSendUtil {
|
||||
List<SendMessageResult> allResults = new ArrayList<>(allTargets.size());
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
|
||||
if (senderKeyTargets.size() > 0 && distributionId != null) {
|
||||
if (Util.hasItems(senderKeyTargets) && distributionId != null) {
|
||||
long keyCreateTime = SenderKeyUtil.getCreateTimeForOurKey(distributionId);
|
||||
long keyAge = System.currentTimeMillis() - keyCreateTime;
|
||||
|
||||
@@ -309,14 +374,36 @@ public final class GroupSendUtil {
|
||||
}
|
||||
|
||||
try {
|
||||
List<SignalServiceAddress> targets = senderKeyTargets.stream().map(r -> recipients.getAddress(r.getId())).collect(Collectors.toList());
|
||||
List<UnidentifiedAccess> access = senderKeyTargets.stream().map(r -> recipients.requireAccess(r.getId())).collect(Collectors.toList());
|
||||
List<SignalServiceAddress> targets = new ArrayList<>(senderKeyTargets.size());
|
||||
List<UnidentifiedAccess> access = new ArrayList<>(senderKeyTargets.size());
|
||||
Map<ServiceId.ACI, GroupSendEndorsement> senderKeyEndorsements = new HashMap<>(senderKeyTargets.size());
|
||||
GroupSendEndorsements groupSendEndorsements = null;
|
||||
|
||||
for (Recipient recipient : senderKeyTargets) {
|
||||
targets.add(recipients.getAddress(recipient.getId()));
|
||||
|
||||
if (useGroupSendEndorsements) {
|
||||
senderKeyEndorsements.put(recipient.requireAci(), groupSendEndorsementRecords.getEndorsement(recipient.getId()));
|
||||
access.add(recipients.getAccess(recipient.getId()));
|
||||
} else {
|
||||
access.add(recipients.requireAccess(recipient.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
if (useGroupSendEndorsements) {
|
||||
groupSendEndorsements = new GroupSendEndorsements(
|
||||
groupSendEndorsementExpiration,
|
||||
senderKeyEndorsements,
|
||||
senderCertificate,
|
||||
GroupSecretParams.deriveFromMasterKey(groupRecord.get().requireV2GroupProperties().getGroupMasterKey())
|
||||
);
|
||||
}
|
||||
|
||||
final MessageSendLogTables messageLogDatabase = SignalDatabase.messageLog();
|
||||
final AtomicLong entryId = new AtomicLong(-1);
|
||||
final boolean includeInMessageLog = sendOperation.shouldIncludeInMessageLog();
|
||||
|
||||
List<SendMessageResult> results = sendOperation.sendWithSenderKey(messageSender, distributionId, targets, access, isRecipientUpdate, partialResults -> {
|
||||
List<SendMessageResult> results = sendOperation.sendWithSenderKey(messageSender, distributionId, targets, access, groupSendEndorsements, isRecipientUpdate, partialResults -> {
|
||||
if (!includeInMessageLog) {
|
||||
return;
|
||||
}
|
||||
@@ -343,6 +430,10 @@ public final class GroupSendUtil {
|
||||
} catch (InvalidUnidentifiedAccessHeaderException e) {
|
||||
Log.w(TAG, "Someone had a bad UD header. Falling back to legacy sends.", e);
|
||||
legacyTargets.addAll(senderKeyTargets);
|
||||
|
||||
if (useGroupSendEndorsements && RemoteConfig.internalUser()) {
|
||||
GroupSendEndorsementInternalNotifier.postGroupSendFallbackError(context);
|
||||
}
|
||||
} catch (NoSessionException e) {
|
||||
Log.w(TAG, "No session. Falling back to legacy sends.", e);
|
||||
legacyTargets.addAll(senderKeyTargets);
|
||||
@@ -377,15 +468,32 @@ public final class GroupSendUtil {
|
||||
Log.i(TAG, "Need to do a legacy send to send a sync message for a group of only ourselves.");
|
||||
}
|
||||
|
||||
List<SignalServiceAddress> targets = legacyTargets.stream().map(r -> recipients.getAddress(r.getId())).collect(Collectors.toList());
|
||||
List<Optional<UnidentifiedAccessPair>> access = legacyTargets.stream().map(r -> recipients.getAccessPair(r.getId())).collect(Collectors.toList());
|
||||
boolean recipientUpdate = isRecipientUpdate || allResults.size() > 0;
|
||||
List<SignalServiceAddress> legacyTargetAddresses = legacyTargets.stream().map(r -> recipients.getAddress(r.getId())).collect(Collectors.toList());
|
||||
List<UnidentifiedAccess> legacyTargetAccesses = legacyTargets.stream().map(r -> recipients.getAccess(r.getId())).collect(Collectors.toList());
|
||||
List<GroupSendFullToken> groupSendTokens = null;
|
||||
boolean recipientUpdate = isRecipientUpdate || allResults.isEmpty();
|
||||
|
||||
if (useGroupSendEndorsements) {
|
||||
Instant expiration = Instant.ofEpochMilli(groupSendEndorsementExpiration);
|
||||
GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupRecord.get().requireV2GroupProperties().getGroupMasterKey());
|
||||
|
||||
groupSendTokens = new ArrayList<>(legacyTargetAddresses.size());
|
||||
|
||||
for (Recipient r : legacyTargets) {
|
||||
GroupSendEndorsement endorsement = groupSendEndorsementRecords.getEndorsement(r.getId());
|
||||
if (r.getHasAci() && endorsement != null) {
|
||||
groupSendTokens.add(endorsement.toFullToken(groupSecretParams, expiration));
|
||||
} else {
|
||||
groupSendTokens.add(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final MessageSendLogTables messageLogDatabase = SignalDatabase.messageLog();
|
||||
final AtomicLong entryId = new AtomicLong(-1);
|
||||
final boolean includeInMessageLog = sendOperation.shouldIncludeInMessageLog();
|
||||
|
||||
List<SendMessageResult> results = sendOperation.sendLegacy(messageSender, targets, legacyTargets, access, recipientUpdate, result -> {
|
||||
List<SendMessageResult> results = sendOperation.sendLegacy(messageSender, legacyTargetAddresses, legacyTargets, SealedSenderAccess.forFanOutGroupSend(groupSendTokens, SealedSenderAccessUtil.getSealedSenderCertificate(), legacyTargetAccesses), recipientUpdate, result -> {
|
||||
if (!includeInMessageLog) {
|
||||
return;
|
||||
}
|
||||
@@ -402,7 +510,7 @@ public final class GroupSendUtil {
|
||||
allResults.addAll(results);
|
||||
|
||||
int successCount = (int) results.stream().filter(SendMessageResult::isSuccess).count();
|
||||
Log.d(TAG, "Successfully sent using 1:1 to " + successCount + "/" + targets.size() + " legacy targets.");
|
||||
Log.d(TAG, "Successfully sent using 1:1 to " + successCount + "/" + legacyTargetAddresses.size() + " legacy targets.");
|
||||
} else if (relatedMessageId != null) {
|
||||
SignalLocalMetrics.GroupMessageSend.onLegacyMessageSent(relatedMessageId.getId());
|
||||
SignalLocalMetrics.GroupMessageSend.onLegacySyncFinished(relatedMessageId.getId());
|
||||
@@ -448,6 +556,7 @@ public final class GroupSendUtil {
|
||||
@NonNull DistributionId distributionId,
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<UnidentifiedAccess> access,
|
||||
@Nullable GroupSendEndorsements groupSendEndorsements,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendBatchCompleteListener partialListener)
|
||||
throws NoSessionException, UntrustedIdentityException, InvalidKeyException, IOException, InvalidRegistrationIdException;
|
||||
@@ -455,7 +564,7 @@ public final class GroupSendUtil {
|
||||
@NonNull List<SendMessageResult> sendLegacy(@NonNull SignalServiceMessageSender messageSender,
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<Recipient> targetRecipients,
|
||||
@NonNull List<Optional<UnidentifiedAccessPair>> access,
|
||||
@NonNull List<SealedSenderAccess> sealedSenderAccesses,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendCompleteListener partialListener,
|
||||
@Nullable CancelationSignal cancelationSignal)
|
||||
@@ -504,19 +613,20 @@ public final class GroupSendUtil {
|
||||
@NonNull DistributionId distributionId,
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<UnidentifiedAccess> access,
|
||||
@Nullable GroupSendEndorsements groupSendEndorsements,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendBatchCompleteListener partialListener)
|
||||
throws NoSessionException, UntrustedIdentityException, InvalidKeyException, IOException, InvalidRegistrationIdException
|
||||
{
|
||||
SenderKeyGroupEvents listener = relatedMessageId != null ? new SenderKeyMetricEventListener(relatedMessageId.getId()) : SenderKeyGroupEvents.EMPTY;
|
||||
return messageSender.sendGroupDataMessage(distributionId, targets, access, isRecipientUpdate, contentHint, message, listener, urgent, isForStory, editMessage, partialListener);
|
||||
return messageSender.sendGroupDataMessage(distributionId, targets, access, groupSendEndorsements, isRecipientUpdate, contentHint, message, listener, urgent, isForStory, editMessage, partialListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull List<SendMessageResult> sendLegacy(@NonNull SignalServiceMessageSender messageSender,
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<Recipient> targetRecipients,
|
||||
@NonNull List<Optional<UnidentifiedAccessPair>> access,
|
||||
@NonNull List<SealedSenderAccess> sealedSenderAccesses,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendCompleteListener partialListener,
|
||||
@Nullable CancelationSignal cancelationSignal)
|
||||
@@ -524,13 +634,14 @@ public final class GroupSendUtil {
|
||||
{
|
||||
// PniSignatures are only needed for 1:1 messages, but some message jobs use the GroupSendUtil methods to send 1:1
|
||||
if (targets.size() == 1 && relatedMessageId == null) {
|
||||
Recipient targetRecipient = targetRecipients.get(0);
|
||||
SendMessageResult result;
|
||||
Recipient targetRecipient = targetRecipients.get(0);
|
||||
SealedSenderAccess sealedSenderAccess = sealedSenderAccesses.get(0);
|
||||
SendMessageResult result;
|
||||
|
||||
if (editMessage != null) {
|
||||
result = messageSender.sendEditMessage(targets.get(0), access.get(0), contentHint, message, SignalServiceMessageSender.IndividualSendEvents.EMPTY, urgent, editMessage.getTargetSentTimestamp());
|
||||
result = messageSender.sendEditMessage(targets.get(0), sealedSenderAccess, contentHint, message, SignalServiceMessageSender.IndividualSendEvents.EMPTY, urgent, editMessage.getTargetSentTimestamp());
|
||||
} else {
|
||||
result = messageSender.sendDataMessage(targets.get(0), access.get(0), contentHint, message, SignalServiceMessageSender.IndividualSendEvents.EMPTY, urgent, targetRecipient.getNeedsPniSignature());
|
||||
result = messageSender.sendDataMessage(targets.get(0), sealedSenderAccess, contentHint, message, SignalServiceMessageSender.IndividualSendEvents.EMPTY, urgent, targetRecipient.getNeedsPniSignature());
|
||||
}
|
||||
|
||||
if (targetRecipient.getNeedsPniSignature()) {
|
||||
@@ -540,10 +651,11 @@ public final class GroupSendUtil {
|
||||
return Collections.singletonList(result);
|
||||
} else {
|
||||
LegacyGroupEvents listener = relatedMessageId != null ? new LegacyMetricEventListener(relatedMessageId.getId()) : LegacyGroupEvents.EMPTY;
|
||||
|
||||
if (editMessage != null) {
|
||||
return messageSender.sendEditMessage(targets, access, isRecipientUpdate, contentHint, message, listener, partialListener, cancelationSignal, urgent, editMessage.getTargetSentTimestamp());
|
||||
return messageSender.sendEditMessage(targets, sealedSenderAccesses, isRecipientUpdate, contentHint, message, listener, partialListener, cancelationSignal, urgent, editMessage.getTargetSentTimestamp());
|
||||
} else {
|
||||
return messageSender.sendDataMessage(targets, access, isRecipientUpdate, contentHint, message, listener, partialListener, cancelationSignal, urgent);
|
||||
return messageSender.sendDataMessage(targets, sealedSenderAccesses, isRecipientUpdate, contentHint, message, listener, partialListener, cancelationSignal, urgent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -591,11 +703,12 @@ public final class GroupSendUtil {
|
||||
@NonNull DistributionId distributionId,
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<UnidentifiedAccess> access,
|
||||
@Nullable GroupSendEndorsements groupSendEndorsements,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendBatchCompleteListener partialListener)
|
||||
throws NoSessionException, UntrustedIdentityException, InvalidKeyException, IOException, InvalidRegistrationIdException
|
||||
{
|
||||
messageSender.sendGroupTyping(distributionId, targets, access, message);
|
||||
messageSender.sendGroupTyping(distributionId, targets, access, groupSendEndorsements, message);
|
||||
List<SendMessageResult> results = targets.stream().map(a -> SendMessageResult.success(a, Collections.emptyList(), true, false, -1, Optional.empty())).collect(Collectors.toList());
|
||||
|
||||
if (partialListener != null) {
|
||||
@@ -609,13 +722,13 @@ public final class GroupSendUtil {
|
||||
public @NonNull List<SendMessageResult> sendLegacy(@NonNull SignalServiceMessageSender messageSender,
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<Recipient> targetRecipients,
|
||||
@NonNull List<Optional<UnidentifiedAccessPair>> access,
|
||||
@NonNull List<SealedSenderAccess> sealedSenderAccesses,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendCompleteListener partialListener,
|
||||
@Nullable CancelationSignal cancelationSignal)
|
||||
throws IOException
|
||||
{
|
||||
messageSender.sendTyping(targets, access, message, cancelationSignal);
|
||||
messageSender.sendTyping(targets, sealedSenderAccesses, message, cancelationSignal);
|
||||
return targets.stream().map(a -> SendMessageResult.success(a, Collections.emptyList(), true, false, -1, Optional.empty())).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@@ -658,24 +771,25 @@ public final class GroupSendUtil {
|
||||
@NonNull DistributionId distributionId,
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<UnidentifiedAccess> access,
|
||||
@Nullable GroupSendEndorsements groupSendEndorsements,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendBatchCompleteListener partialSendListener)
|
||||
throws NoSessionException, UntrustedIdentityException, InvalidKeyException, IOException, InvalidRegistrationIdException
|
||||
{
|
||||
return messageSender.sendCallMessage(distributionId, targets, access, message, partialSendListener);
|
||||
return messageSender.sendCallMessage(distributionId, targets, access, groupSendEndorsements, message, partialSendListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull List<SendMessageResult> sendLegacy(@NonNull SignalServiceMessageSender messageSender,
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<Recipient> targetRecipients,
|
||||
@NonNull List<Optional<UnidentifiedAccessPair>> access,
|
||||
@NonNull List<SealedSenderAccess> sealedSenderAccesses,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendCompleteListener partialListener,
|
||||
@Nullable CancelationSignal cancelationSignal)
|
||||
throws IOException
|
||||
{
|
||||
return messageSender.sendCallMessage(targets, access, message);
|
||||
return messageSender.sendCallMessage(targets, sealedSenderAccesses, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -730,18 +844,19 @@ public final class GroupSendUtil {
|
||||
@NonNull DistributionId distributionId,
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<UnidentifiedAccess> access,
|
||||
@Nullable GroupSendEndorsements groupSendEndorsements,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendBatchCompleteListener partialListener)
|
||||
throws NoSessionException, UntrustedIdentityException, InvalidKeyException, IOException, InvalidRegistrationIdException
|
||||
{
|
||||
return messageSender.sendGroupStory(distributionId, Optional.ofNullable(groupId).map(GroupId::getDecodedId), targets, access, isRecipientUpdate, message, getSentTimestamp(), manifest, partialListener);
|
||||
return messageSender.sendGroupStory(distributionId, Optional.ofNullable(groupId).map(GroupId::getDecodedId), targets, access, groupSendEndorsements, isRecipientUpdate, message, getSentTimestamp(), manifest, partialListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull List<SendMessageResult> sendLegacy(@NonNull SignalServiceMessageSender messageSender,
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<Recipient> targetRecipients,
|
||||
@NonNull List<Optional<UnidentifiedAccessPair>> access,
|
||||
@NonNull List<SealedSenderAccess> sealedSenderAccesses,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendCompleteListener partialListener,
|
||||
@Nullable CancelationSignal cancelationSignal)
|
||||
@@ -839,12 +954,12 @@ public final class GroupSendUtil {
|
||||
*/
|
||||
private static final class RecipientData {
|
||||
|
||||
private final Map<RecipientId, Optional<UnidentifiedAccessPair>> accessById;
|
||||
private final Map<RecipientId, Optional<UnidentifiedAccess>> accessById;
|
||||
private final Map<RecipientId, SignalServiceAddress> addressById;
|
||||
private final RecipientAccessList accessList;
|
||||
|
||||
RecipientData(@NonNull Context context, @NonNull List<Recipient> recipients, boolean isForStory) throws IOException {
|
||||
this.accessById = UnidentifiedAccessUtil.getAccessMapFor(context, recipients, isForStory);
|
||||
this.accessById = SealedSenderAccessUtil.getAccessMapFor(recipients, isForStory);
|
||||
this.addressById = mapAddresses(context, recipients);
|
||||
this.accessList = new RecipientAccessList(recipients);
|
||||
}
|
||||
@@ -853,22 +968,22 @@ public final class GroupSendUtil {
|
||||
return Objects.requireNonNull(addressById.get(id));
|
||||
}
|
||||
|
||||
@NonNull Optional<UnidentifiedAccessPair> getAccessPair(@NonNull RecipientId id) {
|
||||
@NonNull Optional<UnidentifiedAccess> getAccessPair(@NonNull RecipientId id) {
|
||||
return Objects.requireNonNull(accessById.get(id));
|
||||
}
|
||||
|
||||
@Nullable UnidentifiedAccess getAccess(@NonNull RecipientId id) {
|
||||
return Objects.requireNonNull(accessById.get(id)).orElse(null);
|
||||
}
|
||||
|
||||
@NonNull UnidentifiedAccess requireAccess(@NonNull RecipientId id) {
|
||||
return Objects.requireNonNull(accessById.get(id)).get().getTargetUnidentifiedAccess().get();
|
||||
return Objects.requireNonNull(accessById.get(id)).get();
|
||||
}
|
||||
|
||||
@NonNull RecipientId requireRecipientId(@NonNull SignalServiceAddress address) {
|
||||
return accessList.requireIdByAddress(address);
|
||||
}
|
||||
|
||||
@NonNull List<RecipientId> requireRecipientIds(@NonNull List<SignalServiceAddress> addresses) {
|
||||
return addresses.stream().map(accessList::requireIdByAddress).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static @NonNull Map<RecipientId, SignalServiceAddress> mapAddresses(@NonNull Context context, @NonNull List<Recipient> recipients) throws IOException {
|
||||
List<SignalServiceAddress> addresses = RecipientUtil.toSignalServiceAddressesFromResolved(context, recipients);
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ import org.signal.libsignal.protocol.message.SenderKeyDistributionMessage
|
||||
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.crypto.ReentrantSessionLock
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.groups.BadGroupIdException
|
||||
@@ -137,7 +137,7 @@ object MessageDecryptor {
|
||||
|
||||
val bufferedStore = bufferedProtocolStore.get(destination)
|
||||
val localAddress = SignalServiceAddress(selfAci, SignalStore.account.e164)
|
||||
val cipher = SignalServiceCipher(localAddress, SignalStore.account.deviceId, bufferedStore, ReentrantSessionLock.INSTANCE, UnidentifiedAccessUtil.getCertificateValidator())
|
||||
val cipher = SignalServiceCipher(localAddress, SignalStore.account.deviceId, bufferedStore, ReentrantSessionLock.INSTANCE, SealedSenderAccessUtil.getCertificateValidator())
|
||||
|
||||
return try {
|
||||
val startTimeNanos = System.nanoTime()
|
||||
|
||||
@@ -27,7 +27,7 @@ import org.thoughtcrime.securesms.database.RecipientTable.MentionSetting
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.MissingRecipientException
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.PhoneNumberSharingState
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.RegisteredState
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.UnidentifiedAccessMode
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.SealedSenderAccessMode
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.VibrateState
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListId
|
||||
@@ -98,7 +98,7 @@ class Recipient(
|
||||
val hiddenState: HiddenState = HiddenState.NOT_HIDDEN,
|
||||
val lastProfileFetchTime: Long = 0,
|
||||
private val notificationChannelValue: String? = null,
|
||||
private val unidentifiedAccessModeValue: UnidentifiedAccessMode = UnidentifiedAccessMode.UNKNOWN,
|
||||
private val sealedSenderAccessModeValue: SealedSenderAccessMode = SealedSenderAccessMode.UNKNOWN,
|
||||
private val capabilities: RecipientRecord.Capabilities = RecipientRecord.Capabilities.UNKNOWN,
|
||||
val storageId: ByteArray? = null,
|
||||
val mentionSetting: MentionSetting = MentionSetting.ALWAYS_NOTIFY,
|
||||
@@ -318,10 +318,10 @@ class Recipient(
|
||||
val deleteSyncCapability: Capability = capabilities.deleteSync
|
||||
|
||||
/** The state around whether we can send sealed sender to this user. */
|
||||
val unidentifiedAccessMode: UnidentifiedAccessMode = if (pni.isPresent && pni == serviceId) {
|
||||
UnidentifiedAccessMode.DISABLED
|
||||
val sealedSenderAccessMode: SealedSenderAccessMode = if (pni.isPresent && pni == serviceId) {
|
||||
SealedSenderAccessMode.DISABLED
|
||||
} else {
|
||||
unidentifiedAccessModeValue
|
||||
sealedSenderAccessModeValue
|
||||
}
|
||||
|
||||
/** The wallpaper to render as the chat background, if present. */
|
||||
@@ -760,7 +760,7 @@ class Recipient(
|
||||
systemProfileName == other.systemProfileName &&
|
||||
profileAvatar == other.profileAvatar &&
|
||||
notificationChannelValue == other.notificationChannelValue &&
|
||||
unidentifiedAccessModeValue == other.unidentifiedAccessModeValue &&
|
||||
sealedSenderAccessModeValue == other.sealedSenderAccessModeValue &&
|
||||
storageId.contentEquals(other.storageId) &&
|
||||
mentionSetting == other.mentionSetting &&
|
||||
wallpaperValue == other.wallpaperValue &&
|
||||
|
||||
@@ -162,7 +162,7 @@ object RecipientCreator {
|
||||
lastProfileFetchTime = record.lastProfileFetch,
|
||||
isSelf = isSelf,
|
||||
notificationChannelValue = record.notificationChannel,
|
||||
unidentifiedAccessModeValue = record.unidentifiedAccessMode,
|
||||
sealedSenderAccessModeValue = record.sealedSenderAccessMode,
|
||||
capabilities = record.capabilities,
|
||||
storageId = record.storageId,
|
||||
mentionSetting = record.mentionSetting,
|
||||
|
||||
@@ -36,7 +36,7 @@ import org.signal.ringrtc.PeekInfo;
|
||||
import org.signal.ringrtc.Remote;
|
||||
import org.signal.storageservice.protos.groups.GroupExternalCredential;
|
||||
import org.thoughtcrime.securesms.WebRtcCallActivity;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.CallLinkTable;
|
||||
import org.thoughtcrime.securesms.database.CallTable;
|
||||
import org.thoughtcrime.securesms.database.GroupTable;
|
||||
@@ -66,8 +66,8 @@ import org.thoughtcrime.securesms.service.webrtc.links.SignalCallLinkManager;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcEphemeralState;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
||||
import org.thoughtcrime.securesms.util.AppForegroundObserver;
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
import org.thoughtcrime.securesms.util.RecipientAccessList;
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.rx.RxStore;
|
||||
@@ -75,6 +75,7 @@ import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder;
|
||||
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager;
|
||||
import org.thoughtcrime.securesms.webrtc.locks.LockManager;
|
||||
import org.webrtc.PeerConnection;
|
||||
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||
import org.whispersystems.signalservice.api.messages.calls.CallingResponse;
|
||||
@@ -780,8 +781,8 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
|
||||
try {
|
||||
AppDependencies.getSignalServiceMessageSender()
|
||||
.sendCallMessage(RecipientUtil.toSignalServiceAddress(context, recipient),
|
||||
recipient.isSelf() ? Optional.empty() : UnidentifiedAccessUtil.getAccessFor(context, recipient),
|
||||
callMessage);
|
||||
recipient.isSelf() ? SealedSenderAccess.NONE : SealedSenderAccessUtil.getSealedSenderAccessFor(recipient),
|
||||
callMessage);
|
||||
} catch (UntrustedIdentityException e) {
|
||||
Log.i(TAG, "onSendCallMessage onFailure: ", e);
|
||||
RetrieveProfileJob.enqueue(recipient.getId());
|
||||
@@ -1129,8 +1130,8 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
|
||||
try {
|
||||
AppDependencies.getSignalServiceMessageSender()
|
||||
.sendCallMessage(RecipientUtil.toSignalServiceAddress(context, recipient),
|
||||
UnidentifiedAccessUtil.getAccessFor(context, recipient),
|
||||
callMessage);
|
||||
SealedSenderAccessUtil.getSealedSenderAccessFor(recipient),
|
||||
callMessage);
|
||||
process((s, p) -> p.handleMessageSentSuccess(s, remotePeer.getCallId()));
|
||||
} catch (UntrustedIdentityException e) {
|
||||
RetrieveProfileJob.enqueue(remotePeer.getId());
|
||||
@@ -1158,7 +1159,7 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
|
||||
networkExecutor.execute(() -> {
|
||||
try {
|
||||
SyncMessage.CallEvent callEvent = CallEventSyncMessageUtil.createAcceptedSyncMessage(remotePeer, System.currentTimeMillis(), isOutgoing, isVideoCall);
|
||||
AppDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forCallEvent(callEvent), Optional.empty());
|
||||
AppDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forCallEvent(callEvent));
|
||||
} catch (IOException | UntrustedIdentityException e) {
|
||||
Log.w(TAG, "Unable to send call event sync message for " + remotePeer.getCallId().longValue(), e);
|
||||
}
|
||||
@@ -1175,7 +1176,7 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
|
||||
networkExecutor.execute(() -> {
|
||||
try {
|
||||
SyncMessage.CallEvent callEvent = CallEventSyncMessageUtil.createNotAcceptedSyncMessage(remotePeer, System.currentTimeMillis(), isOutgoing, isVideoCall);
|
||||
AppDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forCallEvent(callEvent), Optional.empty());
|
||||
AppDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forCallEvent(callEvent));
|
||||
} catch (IOException | UntrustedIdentityException e) {
|
||||
Log.w(TAG, "Unable to send call event sync message for " + remotePeer.getCallId().longValue(), e);
|
||||
}
|
||||
@@ -1188,7 +1189,7 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
|
||||
networkExecutor.execute(() -> {
|
||||
try {
|
||||
SyncMessage.CallEvent callEvent = CallEventSyncMessageUtil.createNotAcceptedSyncMessage(remotePeer, System.currentTimeMillis(), isOutgoing, true);
|
||||
AppDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forCallEvent(callEvent), Optional.empty());
|
||||
AppDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forCallEvent(callEvent));
|
||||
} catch (IOException | UntrustedIdentityException e) {
|
||||
Log.w(TAG, "Unable to send call event sync message for " + remotePeer.getCallId().longValue(), e);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||
import org.thoughtcrime.securesms.badges.models.Badge;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
@@ -38,8 +38,7 @@ import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
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.crypto.SealedSenderAccess;
|
||||
import org.whispersystems.signalservice.api.profiles.AvatarUploadParams;
|
||||
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
|
||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
||||
@@ -118,7 +117,7 @@ public final class ProfileUtil {
|
||||
|
||||
ServiceResponse<ProfileAndCredential> response = Single
|
||||
.fromCallable(() -> new SignalServiceAddress(pni))
|
||||
.flatMap(address -> profileService.getProfile(address, Optional.empty(), Optional.empty(), requestType, Locale.getDefault()))
|
||||
.flatMap(address -> profileService.getProfile(address, Optional.empty(), SealedSenderAccess.NONE, requestType, Locale.getDefault()))
|
||||
.onErrorReturn(t -> ServiceResponse.forUnknownError(t))
|
||||
.blockingGet();
|
||||
|
||||
@@ -137,12 +136,12 @@ public final class ProfileUtil {
|
||||
@NonNull SignalServiceProfile.RequestType requestType,
|
||||
boolean allowUnidentifiedAccess)
|
||||
{
|
||||
ProfileService profileService = AppDependencies.getProfileService();
|
||||
Optional<UnidentifiedAccess> unidentifiedAccess = allowUnidentifiedAccess ? getUnidentifiedAccess(context, recipient) : Optional.empty();
|
||||
Optional<ProfileKey> profileKey = ProfileKeyUtil.profileKeyOptional(recipient.getProfileKey());
|
||||
ProfileService profileService = AppDependencies.getProfileService();
|
||||
SealedSenderAccess sealedSenderAccess = allowUnidentifiedAccess ? SealedSenderAccessUtil.getSealedSenderAccessFor(recipient, false) : SealedSenderAccess.NONE;
|
||||
Optional<ProfileKey> profileKey = ProfileKeyUtil.profileKeyOptional(recipient.getProfileKey());
|
||||
|
||||
return Single.fromCallable(() -> toSignalServiceAddress(context, recipient))
|
||||
.flatMap(address -> profileService.getProfile(address, profileKey, unidentifiedAccess, requestType, Locale.getDefault()).map(p -> new Pair<>(recipient, p)))
|
||||
.flatMap(address -> profileService.getProfile(address, profileKey, sealedSenderAccess, requestType, Locale.getDefault()).map(p -> new Pair<>(recipient, p)))
|
||||
.onErrorReturn(t -> new Pair<>(recipient, ServiceResponse.forUnknownError(t)));
|
||||
}
|
||||
|
||||
@@ -397,16 +396,6 @@ public final class ProfileUtil {
|
||||
}
|
||||
}
|
||||
|
||||
private static Optional<UnidentifiedAccess> getUnidentifiedAccess(@NonNull Context context, @NonNull Recipient recipient) {
|
||||
Optional<UnidentifiedAccessPair> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient, false);
|
||||
|
||||
if (unidentifiedAccess.isPresent()) {
|
||||
return unidentifiedAccess.get().getTargetUnidentifiedAccess();
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static @NonNull SignalServiceAddress toSignalServiceAddress(@NonNull Context context, @NonNull Recipient recipient) throws IOException {
|
||||
if (recipient.getRegistered() == RecipientTable.RegisteredState.NOT_REGISTERED) {
|
||||
if (recipient.getHasServiceId()) {
|
||||
|
||||
Reference in New Issue
Block a user