diff --git a/src/org/thoughtcrime/securesms/loki/activities/JoinPublicChatActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/JoinPublicChatActivity.kt index 769838560f..5b8fefb023 100644 --- a/src/org/thoughtcrime/securesms/loki/activities/JoinPublicChatActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/activities/JoinPublicChatActivity.kt @@ -20,8 +20,8 @@ import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragmentDelegate +import org.thoughtcrime.securesms.loki.protocol.SyncMessagesProtocol import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities -import org.thoughtcrime.securesms.sms.MessageSender class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCodeWrapperFragmentDelegate { private val adapter = JoinPublicChatActivityAdapter(this) @@ -66,10 +66,9 @@ class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCode return Toast.makeText(this, "Invalid URL", Toast.LENGTH_SHORT).show() } showLoader() - val channel: Long = 1 OpenGroupUtilities.addGroup(this, url, channel).success { - MessageSender.syncAllOpenGroups(this) + SyncMessagesProtocol.syncAllOpenGroups(this@JoinPublicChatActivity) }.successUi { finish() }.failUi { diff --git a/src/org/thoughtcrime/securesms/loki/activities/LandingActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/LandingActivity.kt index dc3f600dc3..33b35bc84b 100644 --- a/src/org/thoughtcrime/securesms/loki/activities/LandingActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/activities/LandingActivity.kt @@ -15,7 +15,8 @@ import org.thoughtcrime.securesms.database.IdentityDatabase import org.thoughtcrime.securesms.logging.Log import org.thoughtcrime.securesms.loki.dialogs.LinkDeviceSlaveModeDialog import org.thoughtcrime.securesms.loki.dialogs.LinkDeviceSlaveModeDialogDelegate -import org.thoughtcrime.securesms.loki.sendDeviceLinkMessage +import org.thoughtcrime.securesms.loki.protocol.LokiSessionResetImplementation +import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol import org.thoughtcrime.securesms.loki.utilities.push import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo import org.thoughtcrime.securesms.loki.utilities.show @@ -26,7 +27,12 @@ import org.whispersystems.curve25519.Curve25519 import org.whispersystems.libsignal.ecc.Curve import org.whispersystems.libsignal.ecc.ECKeyPair import org.whispersystems.libsignal.util.KeyHelper +import org.whispersystems.signalservice.loki.protocol.friendrequests.FriendRequestProtocol +import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager +import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink +import org.whispersystems.signalservice.loki.protocol.sessionmanagement.SessionManagementProtocol +import org.whispersystems.signalservice.loki.protocol.syncmessages.SyncMessagesProtocol import org.whispersystems.signalservice.loki.utilities.hexEncodedPublicKey import org.whispersystems.signalservice.loki.utilities.retryIfNeeded @@ -100,6 +106,17 @@ class LandingActivity : BaseActionBarActivity(), LinkDeviceSlaveModeDialogDelega } val application = ApplicationContext.getInstance(this) application.startPollingIfNeeded() + val apiDB = DatabaseFactory.getLokiAPIDatabase(this) + val threadDB = DatabaseFactory.getLokiThreadDatabase(this) + val userDB = DatabaseFactory.getLokiUserDatabase(this) + val userPublicKey = TextSecurePreferences.getLocalNumber(this) + val sessionResetImpl = LokiSessionResetImplementation(this) + FriendRequestProtocol.configureIfNeeded(apiDB, userPublicKey) + MentionsManager.configureIfNeeded(userPublicKey, threadDB, userDB) + SessionMetaProtocol.configureIfNeeded(apiDB, userPublicKey) + org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol.configureIfNeeded(apiDB) + SessionManagementProtocol.configureIfNeeded(sessionResetImpl, threadDB, application) + SyncMessagesProtocol.configureIfNeeded(apiDB, userPublicKey) application.setUpP2PAPIIfNeeded() application.setUpStorageAPIIfNeeded() val linkDeviceDialog = LinkDeviceSlaveModeDialog() @@ -107,7 +124,7 @@ class LandingActivity : BaseActionBarActivity(), LinkDeviceSlaveModeDialogDelega linkDeviceDialog.show(supportFragmentManager, "Link Device Dialog") AsyncTask.execute { retryIfNeeded(8) { - sendDeviceLinkMessage(this@LandingActivity, deviceLink.masterHexEncodedPublicKey, deviceLink) + MultiDeviceProtocol.sendDeviceLinkMessage(this@LandingActivity, deviceLink.masterHexEncodedPublicKey, deviceLink) } } } diff --git a/src/org/thoughtcrime/securesms/loki/activities/LinkedDevicesActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/LinkedDevicesActivity.kt index fcff2af510..81b2571730 100644 --- a/src/org/thoughtcrime/securesms/loki/activities/LinkedDevicesActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/activities/LinkedDevicesActivity.kt @@ -1,6 +1,5 @@ package org.thoughtcrime.securesms.loki.activities -import android.os.AsyncTask import android.os.Bundle import android.support.v4.app.LoaderManager import android.support.v4.content.Loader @@ -15,17 +14,20 @@ import network.loki.messenger.R import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.ui.failUi import nl.komponents.kovenant.ui.successUi +import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity -import org.thoughtcrime.securesms.database.Address +import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.devicelist.Device import org.thoughtcrime.securesms.loki.dialogs.* -import org.thoughtcrime.securesms.loki.signAndSendDeviceLinkMessage -import org.thoughtcrime.securesms.sms.MessageSender +import org.thoughtcrime.securesms.loki.protocol.EphemeralMessage +import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol +import org.thoughtcrime.securesms.loki.protocol.PushEphemeralMessageSendJob +import org.thoughtcrime.securesms.loki.protocol.SyncMessagesProtocol import org.thoughtcrime.securesms.util.TextSecurePreferences -import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink import org.whispersystems.signalservice.loki.api.LokiAPI import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI +import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink import java.util.* import kotlin.concurrent.schedule @@ -121,41 +123,42 @@ class LinkedDevicesActivity : PassphraseRequiredActionBarActivity, LoaderManager bottomSheet.show(supportFragmentManager, bottomSheet.tag) } - private fun unlinkDevice(slaveDeviceHexEncodedPublicKey: String) { - val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this) - val database = DatabaseFactory.getLokiAPIDatabase(this) - val deviceLinks = database.getDeviceLinks(userHexEncodedPublicKey) - val deviceLink = deviceLinks.find { it.masterHexEncodedPublicKey == userHexEncodedPublicKey && it.slaveHexEncodedPublicKey == slaveDeviceHexEncodedPublicKey } + private fun unlinkDevice(slaveDevicePublicKey: String) { + val userPublicKey = TextSecurePreferences.getLocalNumber(this) + val apiDB = DatabaseFactory.getLokiAPIDatabase(this) + val deviceLinks = apiDB.getDeviceLinks(userPublicKey) + val deviceLink = deviceLinks.find { it.masterHexEncodedPublicKey == userPublicKey && it.slaveHexEncodedPublicKey == slaveDevicePublicKey } if (deviceLink == null) { return Toast.makeText(this, "Couldn't unlink device.", Toast.LENGTH_LONG).show() } LokiFileServerAPI.shared.setDeviceLinks(setOf()).successUi { - AsyncTask.execute { - DatabaseFactory.getLokiAPIDatabase(this).clearDeviceLinks(userHexEncodedPublicKey) - deviceLinks.forEach { deviceLink -> - DatabaseFactory.getLokiPreKeyBundleDatabase(this).removePreKeyBundle(deviceLink.slaveHexEncodedPublicKey) - } - MessageSender.sendUnpairRequest(this, slaveDeviceHexEncodedPublicKey) + DatabaseFactory.getLokiAPIDatabase(this).clearDeviceLinks(userPublicKey) + deviceLinks.forEach { deviceLink -> + DatabaseFactory.getLokiPreKeyBundleDatabase(this).removePreKeyBundle(deviceLink.slaveHexEncodedPublicKey) + val sessionStore = TextSecureSessionStore(this@LinkedDevicesActivity) + sessionStore.deleteAllSessions(deviceLink.slaveHexEncodedPublicKey) } + val unlinkingRequest = EphemeralMessage.createUnlinkingRequest(slaveDevicePublicKey) + ApplicationContext.getInstance(this@LinkedDevicesActivity).jobManager.add(PushEphemeralMessageSendJob(unlinkingRequest)) LoaderManager.getInstance(this).restartLoader(0, null, this) Toast.makeText(this, "Your device was unlinked successfully", Toast.LENGTH_LONG).show() - }.fail { + }.failUi { Toast.makeText(this, "Couldn't unlink device.", Toast.LENGTH_LONG).show() } } override fun onDeviceLinkRequestAuthorized(deviceLink: DeviceLink) { LokiFileServerAPI.shared.addDeviceLink(deviceLink).bind(LokiAPI.sharedContext) { - signAndSendDeviceLinkMessage(this, deviceLink) - }.successUi { - LoaderManager.getInstance(this).restartLoader(0, null, this) + MultiDeviceProtocol.signAndSendDeviceLinkMessage(this, deviceLink) }.success { TextSecurePreferences.setMultiDevice(this, true) - Timer().schedule(4000) { - MessageSender.syncAllGroups(this@LinkedDevicesActivity) - MessageSender.syncAllContacts(this@LinkedDevicesActivity, Address.fromSerialized(deviceLink.slaveHexEncodedPublicKey)) - MessageSender.syncAllOpenGroups(this@LinkedDevicesActivity) + SyncMessagesProtocol.syncAllClosedGroups(this@LinkedDevicesActivity) + SyncMessagesProtocol.syncAllOpenGroups(this@LinkedDevicesActivity) + Timer().schedule(4000) { // Not the best way to do this but the idea is to wait for the closed groups sync to go through first + SyncMessagesProtocol.syncAllContacts(this@LinkedDevicesActivity) } + }.successUi { + LoaderManager.getInstance(this).restartLoader(0, null, this) }.fail { LokiFileServerAPI.shared.removeDeviceLink(deviceLink) // If this fails we have a problem DatabaseFactory.getLokiPreKeyBundleDatabase(this).removePreKeyBundle(deviceLink.slaveHexEncodedPublicKey) diff --git a/src/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt index fd055ce8f4..c43d222549 100644 --- a/src/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt +++ b/src/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt @@ -2,16 +2,23 @@ package org.thoughtcrime.securesms.loki.protocol import android.content.Context import android.util.Log +import nl.komponents.kovenant.Promise import org.thoughtcrime.securesms.ApplicationContext +import org.thoughtcrime.securesms.crypto.IdentityKeyUtil +import org.thoughtcrime.securesms.crypto.ProfileKeyUtil +import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil import org.thoughtcrime.securesms.database.Address import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.jobs.PushMediaSendJob import org.thoughtcrime.securesms.jobs.PushSendJob import org.thoughtcrime.securesms.jobs.PushTextSendJob import org.thoughtcrime.securesms.loki.utilities.Broadcaster +import org.thoughtcrime.securesms.loki.utilities.recipient import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.util.TextSecurePreferences import org.whispersystems.signalservice.api.messages.SignalServiceContent +import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage +import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLink @@ -19,6 +26,7 @@ import org.whispersystems.signalservice.loki.protocol.multidevice.DeviceLinkingS import org.whispersystems.signalservice.loki.protocol.multidevice.MultiDeviceProtocol import org.whispersystems.signalservice.loki.protocol.todo.LokiMessageFriendRequestStatus import org.whispersystems.signalservice.loki.protocol.todo.LokiThreadFriendRequestStatus +import org.whispersystems.signalservice.loki.utilities.retryIfNeeded object MultiDeviceProtocol { @@ -94,6 +102,47 @@ object MultiDeviceProtocol { } } + fun sendDeviceLinkMessage(context: Context, publicKey: String, deviceLink: DeviceLink): Promise { + val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender() + val address = SignalServiceAddress(publicKey) + val message = SignalServiceDataMessage.newBuilder().withDeviceLink(deviceLink) + // A request should include a pre key bundle. An authorization should be a normal message. + if (deviceLink.type == DeviceLink.Type.REQUEST) { + val preKeyBundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(address.number) + message.asFriendRequest(true).withPreKeyBundle(preKeyBundle) + } else { + // Include the user's profile key so that the slave device can get the user's profile picture + message.withProfileKey(ProfileKeyUtil.getProfileKey(context)) + } + return try { + Log.d("Loki", "Sending device link message to: $publicKey.") + val udAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient(context, publicKey)) + val result = messageSender.sendMessage(0, address, udAccess, message.build()) + if (result.success == null) { + val exception = when { + result.isNetworkFailure -> "Failed to send device link message due to a network error." + else -> "Failed to send device link message." + } + throw Exception(exception) + } + Promise.ofSuccess(Unit) + } catch (e: Exception) { + Log.d("Loki", "Failed to send device link message to: $publicKey due to error: $e.") + Promise.ofFail(e) + } + } + + fun signAndSendDeviceLinkMessage(context: Context, deviceLink: DeviceLink): Promise { + val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize() + val signedDeviceLink = deviceLink.sign(DeviceLink.Type.AUTHORIZATION, userPrivateKey) + if (signedDeviceLink == null || signedDeviceLink.type != DeviceLink.Type.AUTHORIZATION) { + return Promise.ofFail(Exception("Failed to sign device link.")) + } + return retryIfNeeded(8) { + sendDeviceLinkMessage(context, deviceLink.slaveHexEncodedPublicKey, signedDeviceLink) + } + } + @JvmStatic fun handleDeviceLinkMessageIfNeeded(context: Context, deviceLink: DeviceLink, content: SignalServiceContent) { val userPublicKey = TextSecurePreferences.getLocalNumber(context)