Merge pull request #101 from loki-project/closed-group-sync

Closed group sync
This commit is contained in:
gmbnt 2020-02-21 15:20:16 +11:00 committed by GitHub
commit b6d22ae2dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 73 additions and 19 deletions

View File

@ -186,7 +186,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
if (setUpStorageAPIIfNeeded()) { if (setUpStorageAPIIfNeeded()) {
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this); String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this);
if (userHexEncodedPublicKey != null) { if (userHexEncodedPublicKey != null) {
LokiFileServerAPI.Companion.getShared().getDeviceLinks(userHexEncodedPublicKey, true);
if (TextSecurePreferences.getNeedsIsRevokedSlaveDeviceCheck(this)) { if (TextSecurePreferences.getNeedsIsRevokedSlaveDeviceCheck(this)) {
MultiDeviceUtilities.checkIsRevokedSlaveDevice(this); MultiDeviceUtilities.checkIsRevokedSlaveDevice(this);
} }

View File

@ -206,14 +206,11 @@ public class GroupMessageProcessor {
@NonNull GroupRecord record) @NonNull GroupRecord record)
{ {
String hexEncodedPublicKey = getMasterHexEncodedPublicKey(context, content.getSender()); String hexEncodedPublicKey = getMasterHexEncodedPublicKey(context, content.getSender());
String ourPublicKey = getMasterHexEncodedPublicKey(context, TextSecurePreferences.getLocalNumber(context)); if (record.getMembers().contains(Address.fromSerialized(hexEncodedPublicKey))) {
// If the requester is a group member and we are admin then we should send them the group update
if (record.getMembers().contains(Address.fromSerialized(hexEncodedPublicKey)) && record.getAdmins().contains(Address.fromSerialized(ourPublicKey))) {
ApplicationContext.getInstance(context) ApplicationContext.getInstance(context)
.getJobManager() .getJobManager()
.add(new PushGroupUpdateJob(content.getSender(), group.getGroupId())); .add(new PushGroupUpdateJob(content.getSender(), group.getGroupId()));
} }
return null; return null;
} }

View File

@ -85,18 +85,23 @@ public class MultiDeviceGroupUpdateJob extends BaseJob implements InjectableType
reader = DatabaseFactory.getGroupDatabase(context).getGroups(); reader = DatabaseFactory.getGroupDatabase(context).getGroups();
while ((record = reader.getNext()) != null) { while ((record = reader.getNext()) != null) {
if (!record.isMms() && !record.isPublicChat() && !record.isRSSFeed()) { if (record.isSignalGroup()) {
List<String> members = new LinkedList<>(); List<String> members = new LinkedList<>();
List<String> admins = new LinkedList<>();
for (Address member : record.getMembers()) { for (Address member : record.getMembers()) {
members.add(member.serialize()); members.add(member.serialize());
} }
for (Address admin : record.getAdmins()) {
admins.add(admin.serialize());
}
Recipient recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(record.getId(), record.isMms())), false); Recipient recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(record.getId(), record.isMms())), false);
Optional<Integer> expirationTimer = recipient.getExpireMessages() > 0 ? Optional.of(recipient.getExpireMessages()) : Optional.absent(); Optional<Integer> expirationTimer = recipient.getExpireMessages() > 0 ? Optional.of(recipient.getExpireMessages()) : Optional.absent();
out.write(new DeviceGroup(record.getId(), Optional.fromNullable(record.getTitle()), out.write(new DeviceGroup(record.getId(), Optional.fromNullable(record.getTitle()),
members, getAvatar(record.getAvatar()), members, admins, getAvatar(record.getAvatar()),
record.isActive(), expirationTimer, record.isActive(), expirationTimer,
Optional.of(recipient.getColor().serialize()), Optional.of(recipient.getColor().serialize()),
recipient.isBlocked())); recipient.isBlocked()));
@ -120,7 +125,8 @@ public class MultiDeviceGroupUpdateJob extends BaseJob implements InjectableType
@Override @Override
public boolean onShouldRetry(@NonNull Exception exception) { public boolean onShouldRetry(@NonNull Exception exception) {
if (exception instanceof PushNetworkException) return true; // Loki - Disabled because we have our own retrying
// if (exception instanceof PushNetworkException) return true;
return false; return false;
} }
@ -139,7 +145,6 @@ public class MultiDeviceGroupUpdateJob extends BaseJob implements InjectableType
.withLength(contactsFile.length()) .withLength(contactsFile.length())
.build(); .build();
// TODO: Message ID
messageSender.sendMessage(0, SignalServiceSyncMessage.forGroups(attachmentStream), messageSender.sendMessage(0, SignalServiceSyncMessage.forGroups(attachmentStream),
UnidentifiedAccessUtil.getAccessForSync(context)); UnidentifiedAccessUtil.getAccessForSync(context));
} }

View File

@ -107,6 +107,7 @@ import org.whispersystems.libsignal.state.PreKeyBundle;
import org.whispersystems.libsignal.state.SignalProtocolStore; import org.whispersystems.libsignal.state.SignalProtocolStore;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceContent; import org.whispersystems.signalservice.api.messages.SignalServiceContent;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Preview; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Preview;
@ -123,6 +124,8 @@ import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMess
import org.whispersystems.signalservice.api.messages.multidevice.ContactsMessage; import org.whispersystems.signalservice.api.messages.multidevice.ContactsMessage;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceContact; import org.whispersystems.signalservice.api.messages.multidevice.DeviceContact;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsInputStream; import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsInputStream;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroup;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsInputStream;
import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage; import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage;
import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage; import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage; import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
@ -389,6 +392,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(syncMessage.getVerified().get()); else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(syncMessage.getVerified().get());
else if (syncMessage.getStickerPackOperations().isPresent()) handleSynchronizeStickerPackOperation(syncMessage.getStickerPackOperations().get()); else if (syncMessage.getStickerPackOperations().isPresent()) handleSynchronizeStickerPackOperation(syncMessage.getStickerPackOperations().get());
else if (syncMessage.getContacts().isPresent()) handleContactSyncMessage(syncMessage.getContacts().get()); else if (syncMessage.getContacts().isPresent()) handleContactSyncMessage(syncMessage.getContacts().get());
else if (syncMessage.getGroups().isPresent()) handleGroupSyncMessage(content, syncMessage.getGroups().get());
else Log.w(TAG, "Contains no known sync types..."); else Log.w(TAG, "Contains no known sync types...");
} else if (content.getCallMessage().isPresent()) { } else if (content.getCallMessage().isPresent()) {
Log.i(TAG, "Got call message..."); Log.i(TAG, "Got call message...");
@ -719,6 +723,32 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} }
} }
private void handleGroupSyncMessage(@NonNull SignalServiceContent content, @NonNull SignalServiceAttachment groupMessage) {
if (groupMessage.isStream()) {
Log.d("Loki", "Received a group sync message.");
try {
InputStream in = groupMessage.asStream().getInputStream();
DeviceGroupsInputStream groupsInputStream = new DeviceGroupsInputStream(in);
List<DeviceGroup> groups = groupsInputStream.readAll();
for (DeviceGroup group : groups) {
SignalServiceGroup serviceGroup = new SignalServiceGroup(
SignalServiceGroup.Type.UPDATE,
group.getId(),
SignalServiceGroup.GroupType.SIGNAL,
group.getName().orNull(),
group.getMembers(),
group.getAvatar().orNull(),
group.getAdmins()
);
SignalServiceDataMessage dataMessage = new SignalServiceDataMessage(content.getTimestamp(), serviceGroup, null, null);
GroupMessageProcessor.process(context, content, dataMessage, false);
}
} catch (Exception e) {
Log.d("Loki", "Failed to sync group due to error: " + e + ".");
}
}
}
private void handleSynchronizeSentMessage(@NonNull SignalServiceContent content, private void handleSynchronizeSentMessage(@NonNull SignalServiceContent content,
@NonNull SentTranscriptMessage message) @NonNull SentTranscriptMessage message)
throws StorageFailedException throws StorageFailedException

View File

@ -128,7 +128,7 @@ fun sendDeviceLinkMessage(context: Context, hexEncodedPublicKey: String, deviceL
} }
Promise.ofSuccess(Unit) Promise.ofSuccess(Unit)
} catch (e: Exception) { } catch (e: Exception) {
Log.d("Loki", "Failed to send device link message to: $hexEncodedPublicKey.") Log.d("Loki", "Failed to send device link message to: $hexEncodedPublicKey due to error: $e.")
Promise.ofFail(e) Promise.ofFail(e)
} }
} }

View File

@ -22,7 +22,6 @@ import org.thoughtcrime.securesms.loki.redesign.dialogs.*
import org.thoughtcrime.securesms.loki.signAndSendDeviceLinkMessage import org.thoughtcrime.securesms.loki.signAndSendDeviceLinkMessage
import org.thoughtcrime.securesms.sms.MessageSender import org.thoughtcrime.securesms.sms.MessageSender
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.loki.api.DeviceLink import org.whispersystems.signalservice.loki.api.DeviceLink
import org.whispersystems.signalservice.loki.api.LokiFileServerAPI import org.whispersystems.signalservice.loki.api.LokiFileServerAPI
import java.util.* import java.util.*
@ -145,20 +144,19 @@ class LinkedDevicesActivity : PassphraseRequiredActionBarActivity, LoaderManager
override fun onDeviceLinkRequestAuthorized(deviceLink: DeviceLink) { override fun onDeviceLinkRequestAuthorized(deviceLink: DeviceLink) {
LokiFileServerAPI.shared.addDeviceLink(deviceLink).success { LokiFileServerAPI.shared.addDeviceLink(deviceLink).success {
signAndSendDeviceLinkMessage(this, deviceLink).success { signAndSendDeviceLinkMessage(this, deviceLink).successUi {
LoaderManager.getInstance(this).restartLoader(0, null, this)
}.success {
TextSecurePreferences.setMultiDevice(this, true) TextSecurePreferences.setMultiDevice(this, true)
Util.runOnMain {
LoaderManager.getInstance(this).restartLoader(0, null, this)
}
Timer().schedule(4000) { Timer().schedule(4000) {
MessageSender.syncAllGroups(this@LinkedDevicesActivity)
MessageSender.syncAllContacts(this@LinkedDevicesActivity, Address.fromSerialized(deviceLink.slaveHexEncodedPublicKey)) MessageSender.syncAllContacts(this@LinkedDevicesActivity, Address.fromSerialized(deviceLink.slaveHexEncodedPublicKey))
} }
}.failUi {
Toast.makeText(this, "Couldn't link device", Toast.LENGTH_LONG).show()
}.fail { }.fail {
LokiFileServerAPI.shared.removeDeviceLink(deviceLink) // If this fails we have a problem LokiFileServerAPI.shared.removeDeviceLink(deviceLink) // If this fails we have a problem
DatabaseFactory.getLokiPreKeyBundleDatabase(this).removePreKeyBundle(deviceLink.slaveHexEncodedPublicKey) DatabaseFactory.getLokiPreKeyBundleDatabase(this).removePreKeyBundle(deviceLink.slaveHexEncodedPublicKey)
Util.runOnMain {
Toast.makeText(this, "Couldn't link device", Toast.LENGTH_LONG).show()
}
} }
}.failUi { }.failUi {
DatabaseFactory.getLokiPreKeyBundleDatabase(this).removePreKeyBundle(deviceLink.slaveHexEncodedPublicKey) DatabaseFactory.getLokiPreKeyBundleDatabase(this).removePreKeyBundle(deviceLink.slaveHexEncodedPublicKey)

View File

@ -16,6 +16,7 @@ import org.thoughtcrime.securesms.loki.redesign.utilities.insertOrUpdate
import org.thoughtcrime.securesms.util.Base64 import org.thoughtcrime.securesms.util.Base64
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.libsignal.IdentityKey import org.whispersystems.libsignal.IdentityKey
import org.whispersystems.libsignal.InvalidKeyException
import org.whispersystems.libsignal.ecc.Curve import org.whispersystems.libsignal.ecc.Curve
import org.whispersystems.libsignal.state.PreKeyBundle import org.whispersystems.libsignal.state.PreKeyBundle
import org.whispersystems.libsignal.util.KeyHelper import org.whispersystems.libsignal.util.KeyHelper
@ -41,6 +42,25 @@ class LokiPreKeyBundleDatabase(context: Context, helper: SQLCipherOpenHelper) :
} }
fun generatePreKeyBundle(hexEncodedPublicKey: String): PreKeyBundle? { fun generatePreKeyBundle(hexEncodedPublicKey: String): PreKeyBundle? {
var failureCount = 0
while (failureCount < 3) {
try {
val preKey = generatePreKeyBundle(hexEncodedPublicKey, failureCount > 0) ?: return null
// Verify the bundle is correct
if (!Curve.verifySignature(preKey.identityKey.publicKey, preKey.signedPreKey.serialize(), preKey.signedPreKeySignature)) {
throw InvalidKeyException()
}
return preKey;
} catch (e: InvalidKeyException) {
failureCount += 1
}
}
Log.w("Loki", "Failed to generate a valid pre key bundle for: $hexEncodedPublicKey.")
return null
}
private fun generatePreKeyBundle(hexEncodedPublicKey: String, forceClean: Boolean): PreKeyBundle? {
if (hexEncodedPublicKey.isEmpty()) return null
var registrationID = TextSecurePreferences.getLocalRegistrationId(context) var registrationID = TextSecurePreferences.getLocalRegistrationId(context)
if (registrationID == 0) { if (registrationID == 0) {
registrationID = KeyHelper.generateRegistrationId(false) registrationID = KeyHelper.generateRegistrationId(false)
@ -49,7 +69,7 @@ class LokiPreKeyBundleDatabase(context: Context, helper: SQLCipherOpenHelper) :
val deviceID = SignalServiceAddress.DEFAULT_DEVICE_ID val deviceID = SignalServiceAddress.DEFAULT_DEVICE_ID
val preKeyRecord = DatabaseFactory.getLokiPreKeyRecordDatabase(context).getOrCreatePreKeyRecord(hexEncodedPublicKey) val preKeyRecord = DatabaseFactory.getLokiPreKeyRecordDatabase(context).getOrCreatePreKeyRecord(hexEncodedPublicKey)
val identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context) val identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context)
if (TextSecurePreferences.isSignedPreKeyRegistered(context)) { if (!forceClean && TextSecurePreferences.isSignedPreKeyRegistered(context)) {
Log.d("Loki", "A signed pre key has already been registered.") Log.d("Loki", "A signed pre key has already been registered.")
} else { } else {
Log.d("Loki", "Registering a new signed pre key.") Log.d("Loki", "Registering a new signed pre key.")

View File

@ -37,6 +37,7 @@ import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobs.MmsSendJob; import org.thoughtcrime.securesms.jobs.MmsSendJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceGroupUpdateJob;
import org.thoughtcrime.securesms.jobs.PushGroupSendJob; import org.thoughtcrime.securesms.jobs.PushGroupSendJob;
import org.thoughtcrime.securesms.jobs.PushMediaSendJob; import org.thoughtcrime.securesms.jobs.PushMediaSendJob;
import org.thoughtcrime.securesms.jobs.PushTextSendJob; import org.thoughtcrime.securesms.jobs.PushTextSendJob;
@ -81,6 +82,10 @@ public class MessageSender {
ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceContactUpdateJob(context, recipient, true)); ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceContactUpdateJob(context, recipient, true));
} }
public static void syncAllGroups(Context context) {
ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceGroupUpdateJob());
}
/** /**
* Send a contact sync message to all our devices telling them that we want to sync `contact` * Send a contact sync message to all our devices telling them that we want to sync `contact`
*/ */