Partially implement feedback

This commit is contained in:
Niels Andriesse 2019-10-07 16:15:06 +11:00
parent 19ec4db687
commit 742d9bfa46
14 changed files with 111 additions and 162 deletions

View File

@ -432,9 +432,9 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
public void setUpStorageAPIIfNeeded() { public void setUpStorageAPIIfNeeded() {
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this); String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this);
if (userHexEncodedPublicKey != null && IdentityKeyUtil.hasIdentityKey(this)) { if (userHexEncodedPublicKey != null && IdentityKeyUtil.hasIdentityKey(this)) {
boolean isDebugMode = BuildConfig.DEBUG;
byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize(); byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize();
LokiAPIDatabaseProtocol database = DatabaseFactory.getLokiAPIDatabase(this); LokiAPIDatabaseProtocol database = DatabaseFactory.getLokiAPIDatabase(this);
boolean isDebugMode = BuildConfig.DEBUG;
LokiStorageAPI.Companion.configure(isDebugMode, userHexEncodedPublicKey, userPrivateKey, database); LokiStorageAPI.Companion.configure(isDebugMode, userHexEncodedPublicKey, userPrivateKey, database);
} }
} }

View File

@ -335,7 +335,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
QRCodeDialog.INSTANCE.show(getContext()); QRCodeDialog.INSTANCE.show(getContext());
break; break;
case PREFERENCE_CATEGORY_LINK_DEVICE: case PREFERENCE_CATEGORY_LINK_DEVICE:
DeviceLinkingDialog.Companion.show(getContext(), DeviceLinkingView.Mode.Master); DeviceLinkingDialog.Companion.show(getContext(), DeviceLinkingView.Mode.Master, null);
break; break;
case PREFERENCE_CATEGORY_SEED: case PREFERENCE_CATEGORY_SEED:
Analytics.Companion.getShared().track("Seed Modal Shown"); Analytics.Companion.getShared().track("Seed Modal Shown");

View File

@ -125,7 +125,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL(LokiAPIDatabase.getCreateGroupChatAuthTokenTableCommand()); db.execSQL(LokiAPIDatabase.getCreateGroupChatAuthTokenTableCommand());
db.execSQL(LokiAPIDatabase.getCreateLastMessageServerIDTableCommand()); db.execSQL(LokiAPIDatabase.getCreateLastMessageServerIDTableCommand());
db.execSQL(LokiAPIDatabase.getCreateLastDeletionServerIDTableCommand()); db.execSQL(LokiAPIDatabase.getCreateLastDeletionServerIDTableCommand());
db.execSQL(LokiAPIDatabase.getCreateMultiDeviceAuthTableCommand()); db.execSQL(LokiAPIDatabase.getCreatePairingAuthorisationTableCommand());
db.execSQL(LokiPreKeyBundleDatabase.getCreateTableCommand()); db.execSQL(LokiPreKeyBundleDatabase.getCreateTableCommand());
db.execSQL(LokiPreKeyRecordDatabase.getCreateTableCommand()); db.execSQL(LokiPreKeyRecordDatabase.getCreateTableCommand());
db.execSQL(LokiMessageDatabase.getCreateTableCommand()); db.execSQL(LokiMessageDatabase.getCreateTableCommand());
@ -496,7 +496,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
} }
if (oldVersion < lokiV3) { if (oldVersion < lokiV3) {
db.execSQL(LokiAPIDatabase.getCreateMultiDeviceAuthTableCommand()); db.execSQL(LokiAPIDatabase.getCreatePairingAuthorisationTableCommand());
} }
db.setTransactionSuccessful(); db.setTransactionSuccessful();

View File

@ -12,14 +12,14 @@ import org.whispersystems.signalservice.loki.api.LokiStorageAPI
import org.whispersystems.signalservice.loki.api.PairingAuthorisation import org.whispersystems.signalservice.loki.api.PairingAuthorisation
import org.whispersystems.signalservice.loki.utilities.retryIfNeeded import org.whispersystems.signalservice.loki.utilities.retryIfNeeded
class DeviceLinkingDialog private constructor(private val context: Context, private val mode: DeviceLinkingView.Mode, private val delegate: DeviceLinkingDialogDelegate? = null): DeviceLinkingViewDelegate, DeviceLinkingSessionListener { class DeviceLinkingDialog private constructor(private val context: Context, private val mode: DeviceLinkingView.Mode, private val delegate: DeviceLinkingDialogDelegate?) : DeviceLinkingViewDelegate, DeviceLinkingSessionListener {
private lateinit var view: DeviceLinkingView private lateinit var view: DeviceLinkingView
private lateinit var dialog: AlertDialog private lateinit var dialog: AlertDialog
private val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize() private val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
companion object { companion object {
fun show(context: Context, mode: DeviceLinkingView.Mode): DeviceLinkingDialog { return show(context, mode, null) }
fun show(context: Context, mode: DeviceLinkingView.Mode, delegate: DeviceLinkingDialogDelegate?): DeviceLinkingDialog { fun show(context: Context, mode: DeviceLinkingView.Mode, delegate: DeviceLinkingDialogDelegate?): DeviceLinkingDialog {
val dialog = DeviceLinkingDialog(context, mode, delegate) val dialog = DeviceLinkingDialog(context, mode, delegate)
dialog.show() dialog.show()
@ -27,20 +27,18 @@ class DeviceLinkingDialog private constructor(private val context: Context, priv
} }
} }
public fun dismiss() {
this.stopListening()
dialog.dismiss()
}
private fun show() { private fun show() {
view = DeviceLinkingView(context, mode, this) view = DeviceLinkingView(context, mode, this)
dialog = AlertDialog.Builder(context).setView(view).show() dialog = AlertDialog.Builder(context).setView(view).show()
view.dismiss = { dismiss() } view.dismiss = { dismiss() }
startListening()
this.startListening() }
public fun dismiss() {
stopListening()
dialog.dismiss()
} }
// region Private functions
private fun startListening() { private fun startListening() {
DeviceLinkingSession.shared.startListeningForLinkingRequests() DeviceLinkingSession.shared.startListeningForLinkingRequests()
DeviceLinkingSession.shared.addListener(this) DeviceLinkingSession.shared.addListener(this)
@ -50,29 +48,20 @@ class DeviceLinkingDialog private constructor(private val context: Context, priv
DeviceLinkingSession.shared.stopListeningForLinkingRequests() DeviceLinkingSession.shared.stopListeningForLinkingRequests()
DeviceLinkingSession.shared.removeListener(this) DeviceLinkingSession.shared.removeListener(this)
} }
// endregion
// region Dialog View Delegate override fun sendPairingAuthorizedMessage(pairing: PairingAuthorisation): Boolean {
override fun authorise(pairing: PairingAuthorisation): Boolean {
val signedAuthorisation = pairing.sign(PairingAuthorisation.Type.GRANT, userPrivateKey) val signedAuthorisation = pairing.sign(PairingAuthorisation.Type.GRANT, userPrivateKey)
if (signedAuthorisation == null || signedAuthorisation.type != PairingAuthorisation.Type.GRANT) { if (signedAuthorisation == null || signedAuthorisation.type != PairingAuthorisation.Type.GRANT) {
Log.e("Loki", "Failed to sign grant authorisation") Log.d("Loki", "Failed to sign pairing authorization.")
return false return false
} }
retryIfNeeded(8) {
// Send authorisation message sendPairingAuthorisationMessage(context, pairing.secondaryDevicePublicKey, signedAuthorisation).get()
retryIfNeeded(3) {
sendAuthorisationMessage(context, pairing.secondaryDevicePublicKey, signedAuthorisation)
}.fail { }.fail {
Log.e("Loki", "Failed to send GRANT authorisation to ${pairing.secondaryDevicePublicKey}") Log.d("Loki", "Failed to send pairing authorization message to ${pairing.secondaryDevicePublicKey}.")
} }
// Add the auth to the database
DatabaseFactory.getLokiAPIDatabase(context).insertOrUpdatePairingAuthorisation(signedAuthorisation) DatabaseFactory.getLokiAPIDatabase(context).insertOrUpdatePairingAuthorisation(signedAuthorisation)
LokiStorageAPI.shared.updateUserDeviceMappings()
// Update the api
LokiStorageAPI.shared?.updateUserDeviceMappings()
return true return true
} }
@ -81,24 +70,17 @@ class DeviceLinkingDialog private constructor(private val context: Context, priv
} }
override fun handleDeviceLinkingDialogDismissed() { override fun handleDeviceLinkingDialogDismissed() {
// If we cancelled while we were listening for requests on main device, we need to remove any pre key bundles
if (mode == DeviceLinkingView.Mode.Master && view.pairingAuthorisation != null) { if (mode == DeviceLinkingView.Mode.Master && view.pairingAuthorisation != null) {
val authorisation = view.pairingAuthorisation!! val authorisation = view.pairingAuthorisation!!
// Remove pre key bundle from the requesting device
DatabaseFactory.getLokiPreKeyBundleDatabase(context).removePreKeyBundle(authorisation.secondaryDevicePublicKey) DatabaseFactory.getLokiPreKeyBundleDatabase(context).removePreKeyBundle(authorisation.secondaryDevicePublicKey)
} }
delegate?.handleDeviceLinkingDialogDismissed() delegate?.handleDeviceLinkingDialogDismissed()
} }
// endregion
// region Loki Device Session Listener
override fun requestUserAuthorization(authorisation: PairingAuthorisation) { override fun requestUserAuthorization(authorisation: PairingAuthorisation) {
Util.runOnMain { Util.runOnMain {
view.requestUserAuthorization(authorisation) view.requestUserAuthorization(authorisation)
} }
// Stop listening to any more requests
DeviceLinkingSession.shared.stopListeningForLinkingRequests() DeviceLinkingSession.shared.stopListeningForLinkingRequests()
} }
@ -106,9 +88,6 @@ class DeviceLinkingDialog private constructor(private val context: Context, priv
Util.runOnMain { Util.runOnMain {
view.onDeviceLinkAuthorized(authorisation) view.onDeviceLinkAuthorized(authorisation)
} }
// Stop listening to any more requests
DeviceLinkingSession.shared.stopListeningForLinkingRequests() DeviceLinkingSession.shared.stopListeningForLinkingRequests()
} }
// endregion
} }

View File

@ -1,12 +1,6 @@
package org.thoughtcrime.securesms.loki package org.thoughtcrime.securesms.loki
import org.whispersystems.signalservice.loki.api.PairingAuthorisation
interface DeviceLinkingDialogDelegate { interface DeviceLinkingDialogDelegate {
fun handleDeviceLinkAuthorized() { } fun handleDeviceLinkAuthorized() { }
fun handleDeviceLinkingDialogDismissed() { } fun handleDeviceLinkingDialogDismissed() { }
} }
interface DeviceLinkingViewDelegate: DeviceLinkingDialogDelegate {
fun authorise(pairing: PairingAuthorisation): Boolean { return false }
}

View File

@ -5,7 +5,6 @@ import android.graphics.Color
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.os.Handler import android.os.Handler
import android.util.AttributeSet import android.util.AttributeSet
import android.util.Log
import android.view.View import android.view.View
import android.widget.LinearLayout import android.widget.LinearLayout
import kotlinx.android.synthetic.main.view_device_linking.view.* import kotlinx.android.synthetic.main.view_device_linking.view.*
@ -77,29 +76,17 @@ class DeviceLinkingView private constructor(context: Context, attrs: AttributeSe
mnemonicTextView.text = MnemonicCodec(languageFileDirectory).encode(hexEncodedPublicKey).split(" ").slice(0 until 3).joinToString(" ") mnemonicTextView.text = MnemonicCodec(languageFileDirectory).encode(hexEncodedPublicKey).split(" ").slice(0 until 3).joinToString(" ")
} }
authorizeButton.visibility = View.GONE authorizeButton.visibility = View.GONE
authorizeButton.setOnClickListener { authorize() } authorizeButton.setOnClickListener { authorizePairing() }
cancelButton.setOnClickListener { cancel() } cancelButton.setOnClickListener { cancel() }
} }
// endregion // endregion
// region Device Linking // region Device Linking
fun requestUserAuthorization(authorisation: PairingAuthorisation) { fun requestUserAuthorization(authorisation: PairingAuthorisation) {
// To be called when a linking request has been received if (mode != Mode.Master) { throw IllegalStateException() }
if (mode != Mode.Master) { if (authorisation.type != PairingAuthorisation.Type.REQUEST) { throw IllegalStateException() }
Log.w("Loki", "Received request for pairing authorisation on a slave device") if (pairingAuthorisation != null) { return }
return pairingAuthorisation = authorisation
}
if (authorisation.type != PairingAuthorisation.Type.REQUEST) {
Log.w("Loki", "Received request for GRANT pairing authorisation! It shouldn't be possible!!")
return
}
if (this.pairingAuthorisation != null) {
Log.e("Loki", "Received request for another pairing authorisation when one was active")
return
}
spinner.visibility = View.GONE spinner.visibility = View.GONE
val titleTextViewLayoutParams = titleTextView.layoutParams as LayoutParams val titleTextViewLayoutParams = titleTextView.layoutParams as LayoutParams
titleTextViewLayoutParams.topMargin = toPx(16, resources) titleTextViewLayoutParams.topMargin = toPx(16, resources)
@ -110,25 +97,11 @@ class DeviceLinkingView private constructor(context: Context, attrs: AttributeSe
val hexEncodedPublicKey = authorisation.secondaryDevicePublicKey.removing05PrefixIfNeeded() val hexEncodedPublicKey = authorisation.secondaryDevicePublicKey.removing05PrefixIfNeeded()
mnemonicTextView.text = MnemonicCodec(languageFileDirectory).encode(hexEncodedPublicKey).split(" ").slice(0 until 3).joinToString(" ") mnemonicTextView.text = MnemonicCodec(languageFileDirectory).encode(hexEncodedPublicKey).split(" ").slice(0 until 3).joinToString(" ")
authorizeButton.visibility = View.VISIBLE authorizeButton.visibility = View.VISIBLE
this.pairingAuthorisation = authorisation
}
private fun authorize() {
if (pairingAuthorisation == null || mode != Mode.Master ) { return; }
// Pass authorisation to delegate and only dismiss if it succeeded
if (delegate.authorise(pairingAuthorisation!!)) {
delegate.handleDeviceLinkAuthorized()
dismiss?.invoke()
}
} }
fun onDeviceLinkAuthorized(authorisation: PairingAuthorisation) { fun onDeviceLinkAuthorized(authorisation: PairingAuthorisation) {
// To be called when a device link was accepted by the primary device if (mode != Mode.Slave || pairingAuthorisation != null) { return }
if (mode == Mode.Master || pairingAuthorisation != null) { return }
pairingAuthorisation = authorisation pairingAuthorisation = authorisation
spinner.visibility = View.GONE spinner.visibility = View.GONE
val titleTextViewLayoutParams = titleTextView.layoutParams as LayoutParams val titleTextViewLayoutParams = titleTextView.layoutParams as LayoutParams
titleTextViewLayoutParams.topMargin = toPx(8, resources) titleTextViewLayoutParams.topMargin = toPx(8, resources)
@ -142,7 +115,6 @@ class DeviceLinkingView private constructor(context: Context, attrs: AttributeSe
mnemonicTextView.visibility = View.GONE mnemonicTextView.visibility = View.GONE
buttonContainer.visibility = View.GONE buttonContainer.visibility = View.GONE
cancelButton.visibility = View.GONE cancelButton.visibility = View.GONE
Handler().postDelayed({ Handler().postDelayed({
delegate.handleDeviceLinkAuthorized() delegate.handleDeviceLinkAuthorized()
dismiss?.invoke() dismiss?.invoke()
@ -151,6 +123,14 @@ class DeviceLinkingView private constructor(context: Context, attrs: AttributeSe
// endregion // endregion
// region Interaction // region Interaction
private fun authorizePairing() {
if (pairingAuthorisation == null || mode != Mode.Master ) { return; }
if (delegate.sendPairingAuthorizedMessage(pairingAuthorisation!!)) {
delegate.handleDeviceLinkAuthorized()
dismiss?.invoke()
}
}
private fun cancel() { private fun cancel() {
delegate.handleDeviceLinkingDialogDismissed() delegate.handleDeviceLinkingDialogDismissed()
dismiss?.invoke() dismiss?.invoke()

View File

@ -0,0 +1,9 @@
package org.thoughtcrime.securesms.loki
import org.whispersystems.signalservice.loki.api.PairingAuthorisation
interface DeviceLinkingViewDelegate {
fun handleDeviceLinkAuthorized() { }
fun handleDeviceLinkingDialogDismissed() { }
fun sendPairingAuthorizedMessage(pairing: PairingAuthorisation): Boolean { return false }
}

View File

@ -1,8 +1,14 @@
package org.thoughtcrime.securesms.loki package org.thoughtcrime.securesms.loki
import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import android.os.Build import android.os.Build
import android.support.annotation.ColorRes import android.support.annotation.ColorRes
import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.recipients.Recipient
import org.whispersystems.signalservice.loki.api.LokiGroupChatAPI
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
import kotlin.math.roundToInt import kotlin.math.roundToInt
fun Resources.getColorWithID(@ColorRes id: Int, theme: Resources.Theme?): Int { fun Resources.getColorWithID(@ColorRes id: Int, theme: Resources.Theme?): Int {
@ -17,3 +23,19 @@ fun toPx(dp: Int, resources: Resources): Int {
val scale = resources.displayMetrics.density val scale = resources.displayMetrics.density
return (dp * scale).roundToInt() return (dp * scale).roundToInt()
} }
fun isGroupRecipient(recipient: String): Boolean {
return (LokiGroupChatAPI.publicChatServer == recipient)
}
fun getFriendPublicKeys(context: Context, devicePublicKeys: Set<String>): Set<String> {
val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context)
return devicePublicKeys.mapNotNull { device ->
val address = Address.fromSerialized(device)
val recipient = Recipient.from(context, address, false)
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(recipient)
if (threadID < 0) { return@mapNotNull null }
val friendRequestStatus = lokiThreadDatabase.getFriendRequestStatus(threadID)
if (friendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS) device else null
}.toSet()
}

View File

@ -47,14 +47,14 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
private val lastDeletionServerIDCacheIndex = "loki_api_last_deletion_server_id_cache_index" private val lastDeletionServerIDCacheIndex = "loki_api_last_deletion_server_id_cache_index"
private val lastDeletionServerID = "last_deletion_server_id" private val lastDeletionServerID = "last_deletion_server_id"
@JvmStatic val createLastDeletionServerIDTableCommand = "CREATE TABLE $lastDeletionServerIDCache ($lastDeletionServerIDCacheIndex STRING PRIMARY KEY, $lastDeletionServerID INTEGER DEFAULT 0);" @JvmStatic val createLastDeletionServerIDTableCommand = "CREATE TABLE $lastDeletionServerIDCache ($lastDeletionServerIDCacheIndex STRING PRIMARY KEY, $lastDeletionServerID INTEGER DEFAULT 0);"
// Pairing authorisation cache
// Authorisation private val pairingAuthorisationCache = "loki_pairing_authorisation_cache"
private val multiDeviceAuthTable = "loki_multi_device_authorisation" private val primaryDevicePublicKey = "primary_device"
private val primaryDevice = "primary_device" private val secondaryDevicePublicKey = "secondary_device"
private val secondaryDevice = "secondary_device"
private val requestSignature = "request_signature" private val requestSignature = "request_signature"
private val grantSignature = "grant_signature" private val grantSignature = "grant_signature"
@JvmStatic val createMultiDeviceAuthTableCommand = "CREATE TABLE $multiDeviceAuthTable($primaryDevice TEXT, $secondaryDevice TEXT, $requestSignature TEXT NULLABLE DEFAULT NULL, $grantSignature TEXT NULLABLE DEFAULT NULL, PRIMARY KEY ($primaryDevice, $secondaryDevice));" @JvmStatic val createPairingAuthorisationTableCommand = "CREATE TABLE $pairingAuthorisationCache ($primaryDevicePublicKey TEXT, $secondaryDevicePublicKey TEXT, " +
"$requestSignature TEXT NULLABLE DEFAULT NULL, $grantSignature TEXT NULLABLE DEFAULT NULL, PRIMARY KEY ($primaryDevicePublicKey, $secondaryDevicePublicKey));"
} }
override fun getSwarmCache(hexEncodedPublicKey: String): Set<LokiAPITarget>? { override fun getSwarmCache(hexEncodedPublicKey: String): Set<LokiAPITarget>? {
@ -154,9 +154,9 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
override fun getPairingAuthorisations(hexEncodedPublicKey: String): List<PairingAuthorisation> { override fun getPairingAuthorisations(hexEncodedPublicKey: String): List<PairingAuthorisation> {
val database = databaseHelper.readableDatabase val database = databaseHelper.readableDatabase
return database.getAll(multiDeviceAuthTable, "$primaryDevice = ? OR $secondaryDevice = ?", arrayOf(hexEncodedPublicKey, hexEncodedPublicKey)) { cursor -> return database.getAll(pairingAuthorisationCache, "$primaryDevicePublicKey = ? OR $secondaryDevicePublicKey = ?", arrayOf( hexEncodedPublicKey, hexEncodedPublicKey )) { cursor ->
val primaryDevicePubKey = cursor.getString(primaryDevice) val primaryDevicePubKey = cursor.getString(primaryDevicePublicKey)
val secondaryDevicePubKey = cursor.getString(secondaryDevice) val secondaryDevicePubKey = cursor.getString(secondaryDevicePublicKey)
val requestSignature: ByteArray? = if (cursor.isNull(cursor.getColumnIndexOrThrow(requestSignature))) null else cursor.getBase64EncodedData(requestSignature) val requestSignature: ByteArray? = if (cursor.isNull(cursor.getColumnIndexOrThrow(requestSignature))) null else cursor.getBase64EncodedData(requestSignature)
val grantSignature: ByteArray? = if (cursor.isNull(cursor.getColumnIndexOrThrow(grantSignature))) null else cursor.getBase64EncodedData(grantSignature) val grantSignature: ByteArray? = if (cursor.isNull(cursor.getColumnIndexOrThrow(grantSignature))) null else cursor.getBase64EncodedData(grantSignature)
PairingAuthorisation(primaryDevicePubKey, secondaryDevicePubKey, requestSignature, grantSignature) PairingAuthorisation(primaryDevicePubKey, secondaryDevicePubKey, requestSignature, grantSignature)
@ -166,16 +166,16 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
override fun insertOrUpdatePairingAuthorisation(authorisation: PairingAuthorisation) { override fun insertOrUpdatePairingAuthorisation(authorisation: PairingAuthorisation) {
val database = databaseHelper.writableDatabase val database = databaseHelper.writableDatabase
val values = ContentValues() val values = ContentValues()
values.put(primaryDevice, authorisation.primaryDevicePublicKey) values.put(primaryDevicePublicKey, authorisation.primaryDevicePublicKey)
values.put(secondaryDevice, authorisation.secondaryDevicePublicKey) values.put(secondaryDevicePublicKey, authorisation.secondaryDevicePublicKey)
if (authorisation.requestSignature != null) { values.put(requestSignature, Base64.encodeBytes(authorisation.requestSignature)) } if (authorisation.requestSignature != null) { values.put(requestSignature, Base64.encodeBytes(authorisation.requestSignature)) }
if (authorisation.grantSignature != null) { values.put(grantSignature, Base64.encodeBytes(authorisation.grantSignature)) } if (authorisation.grantSignature != null) { values.put(grantSignature, Base64.encodeBytes(authorisation.grantSignature)) }
database.insertOrUpdate(multiDeviceAuthTable, values, "$primaryDevice = ? AND $secondaryDevice = ?", arrayOf(authorisation.primaryDevicePublicKey, authorisation.secondaryDevicePublicKey)) database.insertOrUpdate(pairingAuthorisationCache, values, "$primaryDevicePublicKey = ? AND $secondaryDevicePublicKey = ?", arrayOf( authorisation.primaryDevicePublicKey, authorisation.secondaryDevicePublicKey ))
} }
override fun removePairingAuthorisations(hexEncodedPublicKey: String) { override fun removePairingAuthorisations(hexEncodedPublicKey: String) {
val database = databaseHelper.readableDatabase val database = databaseHelper.readableDatabase
database.delete(multiDeviceAuthTable, "$primaryDevice = ? OR $secondaryDevice = ?", arrayOf(hexEncodedPublicKey, hexEncodedPublicKey)) database.delete(pairingAuthorisationCache, "$primaryDevicePublicKey = ? OR $secondaryDevicePublicKey = ?", arrayOf( hexEncodedPublicKey, hexEncodedPublicKey ))
} }
} }

View File

@ -28,7 +28,7 @@ fun getAllDevices(context: Context, pubKey: String, storageAPI: LokiStorageAPI,
devices.remove(ourPubKey) devices.remove(ourPubKey)
} }
val friends = getFriends(context, devices) val friends = getFriendPublicKeys(context, devices)
for (device in devices) { for (device in devices) {
block(device, friends.contains(device), friends.count()) block(device, friends.contains(device), friends.count())
} }
@ -80,7 +80,7 @@ fun shouldAutomaticallyBecomeFriendsWithDevice(pubKey: String, context: Context)
return deferred.promise return deferred.promise
} }
fun sendAuthorisationMessage(context: Context, contactHexEncodedPublicKey: String, authorisation: PairingAuthorisation): Promise<Unit, Exception> { fun sendPairingAuthorisationMessage(context: Context, contactHexEncodedPublicKey: String, authorisation: PairingAuthorisation): Promise<Unit, Exception> {
val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender() val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender()
val address = SignalServiceAddress(contactHexEncodedPublicKey) val address = SignalServiceAddress(contactHexEncodedPublicKey)
val message = SignalServiceDataMessage.newBuilder().withBody("").withPairingAuthorisation(authorisation) val message = SignalServiceDataMessage.newBuilder().withBody("").withPairingAuthorisation(authorisation)

View File

@ -233,7 +233,7 @@ class SeedActivity : BaseActionBarActivity() {
// Send the request to the other user // Send the request to the other user
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
retryIfNeeded(3) { retryIfNeeded(3) {
sendAuthorisationMessage(this@SeedActivity, authorisation.primaryDevicePublicKey, authorisation).get() sendPairingAuthorisationMessage(this@SeedActivity, authorisation.primaryDevicePublicKey, authorisation).get()
}.failUi { }.failUi {
dialog.dismiss() dialog.dismiss()
resetRegistration() resetRegistration()

View File

@ -1,25 +0,0 @@
package org.thoughtcrime.securesms.loki
import android.content.Context
import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.recipients.Recipient
import org.whispersystems.signalservice.loki.api.LokiGroupChatAPI
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
fun isGroupChat(pubKey: String): Boolean {
return (LokiGroupChatAPI.publicChatServer == pubKey)
}
fun getFriends(context: Context, devices: Set<String>): Set<String> {
val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context)
return devices.mapNotNull { device ->
val address = Address.fromSerialized(device)
val recipient = Recipient.from(context, address, false)
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(recipient)
if (threadID < 0) { return@mapNotNull null }
if (lokiThreadDatabase.getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS) device else null
}.toSet()
}

View File

@ -20,11 +20,9 @@ import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
import org.thoughtcrime.securesms.jobs.MultiDeviceReadUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceReadUpdateJob;
import org.thoughtcrime.securesms.jobs.SendReadReceiptJob; import org.thoughtcrime.securesms.jobs.SendReadReceiptJob;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.MultiDeviceUtilKt; import org.thoughtcrime.securesms.loki.MultiDeviceUtilKt;
import org.thoughtcrime.securesms.service.ExpiringMessageManager; import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -91,20 +89,13 @@ public class MarkReadReceiver extends BroadcastReceiver {
for (Address address : addressMap.keySet()) { for (Address address : addressMap.keySet()) {
LokiStorageAPI storageAPI = LokiStorageAPI.Companion.getShared(); LokiStorageAPI storageAPI = LokiStorageAPI.Companion.getShared();
if (storageAPI == null) {
Log.w("Loki", "LokiStorageAPI is not initialized!");
return;
}
List<Long> timestamps = Stream.of(addressMap.get(address)).map(SyncMessageId::getTimetamp).toList(); List<Long> timestamps = Stream.of(addressMap.get(address)).map(SyncMessageId::getTimetamp).toList();
MultiDeviceUtilKt.getAllDevices(context, address.serialize(), storageAPI, (devicePubKey, isFriend, friendCount) -> { MultiDeviceUtilKt.getAllDevices(context, address.serialize(), storageAPI, (devicePublicKey, isFriend, friendCount) -> {
// Loki - This also prevents read receipts from being sent in group chats as they don't maintain a friend request status // Loki - This also prevents read receipts from being sent in group chats as they don't maintain a friend request status
if (isFriend) { if (isFriend) {
Address deviceAddress = Address.fromSerialized(devicePubKey); ApplicationContext.getInstance(context).getJobManager().add(new SendReadReceiptJob(Address.fromSerialized(devicePublicKey), timestamps));
ApplicationContext.getInstance(context)
.getJobManager()
.add(new SendReadReceiptJob(deviceAddress, timestamps));
} }
return Unit.INSTANCE; return Unit.INSTANCE;
}); });

View File

@ -42,8 +42,8 @@ import org.thoughtcrime.securesms.jobs.SmsSendJob;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository; import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil; import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.GeneralUtilitiesKt;
import org.thoughtcrime.securesms.loki.MultiDeviceUtilKt; import org.thoughtcrime.securesms.loki.MultiDeviceUtilKt;
import org.thoughtcrime.securesms.loki.UtilKt;
import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.push.AccountManagerFactory; import org.thoughtcrime.securesms.push.AccountManagerFactory;
@ -211,27 +211,26 @@ public class MessageSender {
LokiStorageAPI storageAPI = LokiStorageAPI.Companion.getShared(); LokiStorageAPI storageAPI = LokiStorageAPI.Companion.getShared();
JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
// Just send the message normally if the storage api is not set or if it's a group message // Just send the message normally if it's a group message
String recipientPubKey = recipient.getAddress().serialize(); String recipientPublicKey = recipient.getAddress().serialize();
if (storageAPI == null || UtilKt.isGroupChat(recipientPubKey)) { if (GeneralUtilitiesKt.isGroupRecipient(recipientPublicKey)) {
if (storageAPI == null) { Log.w("Loki", "LokiStorageAPI is not initialized!"); }
jobManager.add(new PushTextSendJob(messageId, recipient.getAddress())); jobManager.add(new PushTextSendJob(messageId, recipient.getAddress()));
return; return;
} }
MultiDeviceUtilKt.getAllDevices(context, recipientPubKey, storageAPI, (devicePubKey, isFriend, friendCount) -> { MultiDeviceUtilKt.getAllDevices(context, recipientPublicKey, storageAPI, (devicePublicKey, isFriend, friendCount) -> {
Address deviceAddress = Address.fromSerialized(devicePubKey); Address address = Address.fromSerialized(devicePublicKey);
long messageIdToUse = recipientPubKey.equals(devicePubKey) ? messageId : -1L; long messageIDToUse = recipientPublicKey.equals(devicePublicKey) ? messageId : -1L;
// Send a normal message to our friends
if (isFriend) { if (isFriend) {
jobManager.add(new PushTextSendJob(messageId, messageIdToUse, deviceAddress)); // Send a normal message if the user is friends with the recipient
jobManager.add(new PushTextSendJob(messageId, messageIDToUse, address));
} else { } else {
// Send friend requests to non friends // Send friend requests to non friends. If the user is friends with any
// If we're friends with one of the devices then send out a default friend request message // of the devices then send out a default friend request message.
boolean isFriendsWithAny = friendCount > 0; boolean isFriendsWithAny = (friendCount > 0);
String defaultFriendRequestMessage = isFriendsWithAny ? "This is a friend request for devices linked to " + recipientPubKey : null; String defaultFriendRequestMessage = isFriendsWithAny ? "Accept this friend request to enable messages to be synced across devices" : null;
jobManager.add(new PushTextSendJob(messageId, messageIdToUse, deviceAddress, true, defaultFriendRequestMessage)); jobManager.add(new PushTextSendJob(messageId, messageIDToUse, address, true, defaultFriendRequestMessage));
} }
return Unit.INSTANCE; return Unit.INSTANCE;
@ -242,26 +241,26 @@ public class MessageSender {
LokiStorageAPI storageAPI = LokiStorageAPI.Companion.getShared(); LokiStorageAPI storageAPI = LokiStorageAPI.Companion.getShared();
JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
// Just send the message normally if the storage api is not set or if it's a group message // Just send the message normally if it's a group message
String recipientPubKey = recipient.getAddress().serialize(); String recipientPublicKey = recipient.getAddress().serialize();
if (storageAPI == null || UtilKt.isGroupChat(recipientPubKey)) { if (GeneralUtilitiesKt.isGroupRecipient(recipientPublicKey)) {
if (storageAPI == null) { Log.w("Loki", "LokiStorageAPI is not initialized!"); }
PushMediaSendJob.enqueue(context, jobManager, messageId, recipient.getAddress()); PushMediaSendJob.enqueue(context, jobManager, messageId, recipient.getAddress());
return;
} }
MultiDeviceUtilKt.getAllDevices(context, recipientPubKey, storageAPI, (devicePubKey, isFriend, friendCount) -> { MultiDeviceUtilKt.getAllDevices(context, recipientPublicKey, storageAPI, (devicePublicKey, isFriend, friendCount) -> {
Address deviceAddress = Address.fromSerialized(devicePubKey); Address address = Address.fromSerialized(devicePublicKey);
long messageIdToUse = recipientPubKey.equals(devicePubKey) ? messageId : -1L; long messageIDToUse = recipientPublicKey.equals(devicePublicKey) ? messageId : -1L;
// Send a normal message to our friends
if (isFriend) { if (isFriend) {
PushMediaSendJob.enqueue(context, jobManager, messageId, messageIdToUse, deviceAddress); // Send a normal message if the user is friends with the recipient
PushMediaSendJob.enqueue(context, jobManager, messageId, messageIDToUse, address);
} else { } else {
// Send friend requests to non friends // Send friend requests to non friends. If the user is friends with any
// If we're friends with one of the devices then send out a default friend request message // of the devices then send out a default friend request message.
boolean isFriendsWithAny = friendCount > 0; boolean isFriendsWithAny = friendCount > 0;
String defaultFriendRequestMessage = isFriendsWithAny ? "This is a friend request for devices linked to " + recipientPubKey : null; String defaultFriendRequestMessage = isFriendsWithAny ? "Accept this friend request to enable messages to be synced across devices" : null;
PushMediaSendJob.enqueue(context, jobManager, messageId, messageIdToUse, deviceAddress, true, defaultFriendRequestMessage); PushMediaSendJob.enqueue(context, jobManager, messageId, messageIDToUse, address, true, defaultFriendRequestMessage);
} }
return Unit.INSTANCE; return Unit.INSTANCE;