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()) {
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this);
if (userHexEncodedPublicKey != null) {
LokiFileServerAPI.Companion.getShared().getDeviceLinks(userHexEncodedPublicKey, true);
if (TextSecurePreferences.getNeedsIsRevokedSlaveDeviceCheck(this)) {
MultiDeviceUtilities.checkIsRevokedSlaveDevice(this);
}

View File

@ -206,14 +206,11 @@ public class GroupMessageProcessor {
@NonNull GroupRecord record)
{
String hexEncodedPublicKey = getMasterHexEncodedPublicKey(context, content.getSender());
String ourPublicKey = getMasterHexEncodedPublicKey(context, TextSecurePreferences.getLocalNumber(context));
// 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))) {
if (record.getMembers().contains(Address.fromSerialized(hexEncodedPublicKey))) {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new PushGroupUpdateJob(content.getSender(), group.getGroupId()));
}
return null;
}

View File

@ -85,18 +85,23 @@ public class MultiDeviceGroupUpdateJob extends BaseJob implements InjectableType
reader = DatabaseFactory.getGroupDatabase(context).getGroups();
while ((record = reader.getNext()) != null) {
if (!record.isMms() && !record.isPublicChat() && !record.isRSSFeed()) {
if (record.isSignalGroup()) {
List<String> members = new LinkedList<>();
List<String> admins = new LinkedList<>();
for (Address member : record.getMembers()) {
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);
Optional<Integer> expirationTimer = recipient.getExpireMessages() > 0 ? Optional.of(recipient.getExpireMessages()) : Optional.absent();
out.write(new DeviceGroup(record.getId(), Optional.fromNullable(record.getTitle()),
members, getAvatar(record.getAvatar()),
members, admins, getAvatar(record.getAvatar()),
record.isActive(), expirationTimer,
Optional.of(recipient.getColor().serialize()),
recipient.isBlocked()));
@ -120,7 +125,8 @@ public class MultiDeviceGroupUpdateJob extends BaseJob implements InjectableType
@Override
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;
}
@ -139,7 +145,6 @@ public class MultiDeviceGroupUpdateJob extends BaseJob implements InjectableType
.withLength(contactsFile.length())
.build();
// TODO: Message ID
messageSender.sendMessage(0, SignalServiceSyncMessage.forGroups(attachmentStream),
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.util.guava.Optional;
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.SignalServiceDataMessage;
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.DeviceContact;
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.RequestMessage;
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.getStickerPackOperations().isPresent()) handleSynchronizeStickerPackOperation(syncMessage.getStickerPackOperations().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 if (content.getCallMessage().isPresent()) {
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,
@NonNull SentTranscriptMessage message)
throws StorageFailedException

View File

@ -128,7 +128,7 @@ fun sendDeviceLinkMessage(context: Context, hexEncodedPublicKey: String, deviceL
}
Promise.ofSuccess(Unit)
} 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)
}
}

View File

@ -22,7 +22,6 @@ import org.thoughtcrime.securesms.loki.redesign.dialogs.*
import org.thoughtcrime.securesms.loki.signAndSendDeviceLinkMessage
import org.thoughtcrime.securesms.sms.MessageSender
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.LokiFileServerAPI
import java.util.*
@ -145,20 +144,19 @@ class LinkedDevicesActivity : PassphraseRequiredActionBarActivity, LoaderManager
override fun onDeviceLinkRequestAuthorized(deviceLink: DeviceLink) {
LokiFileServerAPI.shared.addDeviceLink(deviceLink).success {
signAndSendDeviceLinkMessage(this, deviceLink).success {
TextSecurePreferences.setMultiDevice(this, true)
Util.runOnMain {
signAndSendDeviceLinkMessage(this, deviceLink).successUi {
LoaderManager.getInstance(this).restartLoader(0, null, this)
}
}.success {
TextSecurePreferences.setMultiDevice(this, true)
Timer().schedule(4000) {
MessageSender.syncAllGroups(this@LinkedDevicesActivity)
MessageSender.syncAllContacts(this@LinkedDevicesActivity, Address.fromSerialized(deviceLink.slaveHexEncodedPublicKey))
}
}.failUi {
Toast.makeText(this, "Couldn't link device", Toast.LENGTH_LONG).show()
}.fail {
LokiFileServerAPI.shared.removeDeviceLink(deviceLink) // If this fails we have a problem
DatabaseFactory.getLokiPreKeyBundleDatabase(this).removePreKeyBundle(deviceLink.slaveHexEncodedPublicKey)
Util.runOnMain {
Toast.makeText(this, "Couldn't link device", Toast.LENGTH_LONG).show()
}
}
}.failUi {
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.TextSecurePreferences
import org.whispersystems.libsignal.IdentityKey
import org.whispersystems.libsignal.InvalidKeyException
import org.whispersystems.libsignal.ecc.Curve
import org.whispersystems.libsignal.state.PreKeyBundle
import org.whispersystems.libsignal.util.KeyHelper
@ -41,6 +42,25 @@ class LokiPreKeyBundleDatabase(context: Context, helper: SQLCipherOpenHelper) :
}
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)
if (registrationID == 0) {
registrationID = KeyHelper.generateRegistrationId(false)
@ -49,7 +69,7 @@ class LokiPreKeyBundleDatabase(context: Context, helper: SQLCipherOpenHelper) :
val deviceID = SignalServiceAddress.DEFAULT_DEVICE_ID
val preKeyRecord = DatabaseFactory.getLokiPreKeyRecordDatabase(context).getOrCreatePreKeyRecord(hexEncodedPublicKey)
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.")
} else {
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.jobs.MmsSendJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceGroupUpdateJob;
import org.thoughtcrime.securesms.jobs.PushGroupSendJob;
import org.thoughtcrime.securesms.jobs.PushMediaSendJob;
import org.thoughtcrime.securesms.jobs.PushTextSendJob;
@ -81,6 +82,10 @@ public class MessageSender {
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`
*/