You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-android/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt

125 lines
6.3 KiB
Kotlin

package org.thoughtcrime.securesms.loki
import android.content.Context
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.deferred
import nl.komponents.kovenant.functional.bind
import nl.komponents.kovenant.functional.map
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.logging.Log
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.libsignal.util.guava.Optional
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.loki.api.LokiStorageAPI
import org.whispersystems.signalservice.loki.api.PairingAuthorisation
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
import org.whispersystems.signalservice.loki.utilities.retryIfNeeded
fun getAllDeviceFriendRequestStatus(context: Context, hexEncodedPublicKey: String, storageAPI: LokiStorageAPI): Promise<Map<String, LokiThreadFriendRequestStatus>, Exception> {
val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context)
return storageAPI.getAllDevicePublicKeys(hexEncodedPublicKey).map { keys ->
val map = mutableMapOf<String, LokiThreadFriendRequestStatus>()
for (devicePublicKey in keys) {
val device = Recipient.from(context, Address.fromSerialized(devicePublicKey), false)
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(device)
val friendRequestStatus = if (threadID < 0) LokiThreadFriendRequestStatus.NONE else lokiThreadDatabase.getFriendRequestStatus(threadID)
map.put(devicePublicKey, friendRequestStatus);
}
map
}
}
6 years ago
fun getAllDevicePublicKeys(context: Context, hexEncodedPublicKey: String, storageAPI: LokiStorageAPI, block: (devicePublicKey: String, isFriend: Boolean, friendCount: Int) -> Unit) {
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
storageAPI.getAllDevicePublicKeys(hexEncodedPublicKey).success { items ->
6 years ago
val devices = items.toMutableSet()
6 years ago
if (hexEncodedPublicKey != userHexEncodedPublicKey) {
devices.remove(userHexEncodedPublicKey)
6 years ago
}
val friends = getFriendPublicKeys(context, devices)
6 years ago
for (device in devices) {
block(device, friends.contains(device), friends.count())
}
}
}
6 years ago
fun shouldAutomaticallyBecomeFriendsWithDevice(publicKey: String, context: Context): Promise<Boolean, Unit> {
val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context)
6 years ago
val storageAPI = LokiStorageAPI.shared
val deferred = deferred<Boolean, Unit>()
6 years ago
storageAPI.getPrimaryDevicePublicKey(publicKey).success { primaryDevicePublicKey ->
if (primaryDevicePublicKey == null) {
deferred.resolve(false)
return@success
}
6 years ago
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
if (primaryDevicePublicKey == userHexEncodedPublicKey) {
storageAPI.getSecondaryDevicePublicKeys(userHexEncodedPublicKey).success { secondaryDevices ->
deferred.resolve(secondaryDevices.contains(publicKey))
}.fail {
deferred.resolve(false)
}
return@success
}
6 years ago
val primaryDevice = Recipient.from(context, Address.fromSerialized(primaryDevicePublicKey), false)
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(primaryDevice)
if (threadID < 0) {
deferred.resolve(false)
return@success
}
deferred.resolve(lokiThreadDatabase.getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS)
}
return deferred.promise
}
fun sendPairingAuthorisationMessage(context: Context, contactHexEncodedPublicKey: String, authorisation: PairingAuthorisation): Promise<Unit, Exception> {
val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender()
val address = SignalServiceAddress(contactHexEncodedPublicKey)
val message = SignalServiceDataMessage.newBuilder().withBody("").withPairingAuthorisation(authorisation)
// A REQUEST should always act as a friend request. A GRANT should always be replying back as a normal message.
if (authorisation.type == PairingAuthorisation.Type.REQUEST) {
val preKeyBundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(address.number)
message.asFriendRequest(true).withPreKeyBundle(preKeyBundle)
}
return try {
6 years ago
Log.d("Loki", "Sending authorisation message to: $contactHexEncodedPublicKey.")
val result = messageSender.sendMessage(0, address, Optional.absent<UnidentifiedAccessPair>(), message.build())
if (result.success == null) {
val exception = when {
6 years ago
result.isNetworkFailure -> "Failed to send authorisation message due to a network error."
else -> "Failed to send authorisation message."
}
throw Exception(exception)
}
Promise.ofSuccess(Unit)
} catch (e: Exception) {
Log.d("Loki", "Failed to send authorisation message to: $contactHexEncodedPublicKey.")
Promise.ofFail(e)
}
}
fun signAndSendPairingAuthorisationMessage(context: Context, pairingAuthorisation: PairingAuthorisation) {
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
val signedPairingAuthorisation = pairingAuthorisation.sign(PairingAuthorisation.Type.GRANT, userPrivateKey)
if (signedPairingAuthorisation == null || signedPairingAuthorisation.type != PairingAuthorisation.Type.GRANT) {
Log.d("Loki", "Failed to sign pairing authorization.")
return
}
retryIfNeeded(8) {
sendPairingAuthorisationMessage(context, pairingAuthorisation.secondaryDevicePublicKey, signedPairingAuthorisation).get()
}.fail {
Log.d("Loki", "Failed to send pairing authorization message to ${pairingAuthorisation.secondaryDevicePublicKey}.")
}
DatabaseFactory.getLokiAPIDatabase(context).insertOrUpdatePairingAuthorisation(signedPairingAuthorisation)
LokiStorageAPI.shared.updateUserDeviceMappings().fail { exception ->
Log.w("Loki", "Failed to update device mapping")
}
}