From 443cdfa4ebc41675bfb1905c2024f3b6ee288f87 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Fri, 4 Sep 2020 15:55:32 +1000 Subject: [PATCH 1/4] enable push notification for shared sender keys --- .../loki/api/LokiPushNotificationManager.kt | 89 ++++++++++++++++++- .../loki/protocol/ClosedGroupsProtocol.kt | 13 ++- 2 files changed, 97 insertions(+), 5 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/api/LokiPushNotificationManager.kt b/src/org/thoughtcrime/securesms/loki/api/LokiPushNotificationManager.kt index 39a9c46078..e59502174d 100644 --- a/src/org/thoughtcrime/securesms/loki/api/LokiPushNotificationManager.kt +++ b/src/org/thoughtcrime/securesms/loki/api/LokiPushNotificationManager.kt @@ -2,11 +2,18 @@ package org.thoughtcrime.securesms.loki.api import android.content.Context import okhttp3.* +import org.thoughtcrime.securesms.database.DatabaseFactory +import org.thoughtcrime.securesms.jobmanager.Data +import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint +import org.thoughtcrime.securesms.jobs.BaseJob +import org.thoughtcrime.securesms.loki.protocol.ClosedGroupUpdateMessageSendJob import org.thoughtcrime.securesms.util.TextSecurePreferences import org.whispersystems.libsignal.logging.Log import org.whispersystems.signalservice.internal.util.JsonUtil import org.whispersystems.signalservice.loki.api.PushNotificationAcknowledgement import java.io.IOException +import java.lang.Exception +import java.util.concurrent.TimeUnit object LokiPushNotificationManager { private val connection = OkHttpClient() @@ -15,11 +22,14 @@ object LokiPushNotificationManager { PushNotificationAcknowledgement.shared.server } + public const val subscribe = "subscribe_closed_group" + public const val unsubscribe = "unsubscribe_closed_group" + private const val tokenExpirationInterval = 12 * 60 * 60 * 1000 @JvmStatic fun unregister(token: String, context: Context?) { - val parameters = mapOf( "token" to token ) + val parameters = mapOf("token" to token) val url = "$server/register" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body).build() @@ -30,7 +40,7 @@ object LokiPushNotificationManager { 200 -> { val bodyAsString = response.body()!!.string() val json = JsonUtil.fromJson(bodyAsString, Map::class.java) - val code = json?.get("code") as? Int + val code = json?.get("code") as? Int if (code != null && code != 0) { TextSecurePreferences.setIsUsingFCM(context, false) } else { @@ -44,6 +54,10 @@ object LokiPushNotificationManager { Log.d("Loki", "Couldn't disable FCM.") } }) + + for (closedGroup: String in DatabaseFactory.getSSKDatabase(context).getAllClosedGroupPublicKeys()) { + operateClosedGroup(closedGroup, TextSecurePreferences.getLocalNumber(context), context, unsubscribe) + } } @JvmStatic @@ -51,7 +65,7 @@ object LokiPushNotificationManager { val oldToken = TextSecurePreferences.getFCMToken(context) val lastUploadDate = TextSecurePreferences.getLastFCMUploadTime(context) if (!force && token == oldToken && System.currentTimeMillis() - lastUploadDate < tokenExpirationInterval) { return } - val parameters = mapOf( "token" to token, "pubKey" to publicKey ) + val parameters = mapOf("token" to token, "pubKey" to publicKey) val url = "$server/register" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body).build() @@ -62,7 +76,7 @@ object LokiPushNotificationManager { 200 -> { val bodyAsString = response.body()!!.string() val json = JsonUtil.fromJson(bodyAsString, Map::class.java) - val code = json?.get("code") as? Int + val code = json?.get("code") as? Int if (code != null && code != 0) { TextSecurePreferences.setIsUsingFCM(context, true) TextSecurePreferences.setFCMToken(context, token) @@ -78,5 +92,72 @@ object LokiPushNotificationManager { Log.d("Loki", "Couldn't register for FCM.") } }) + + for (closedGroup: String in DatabaseFactory.getSSKDatabase(context).getAllClosedGroupPublicKeys()) { + operateClosedGroup(closedGroup, publicKey, context, subscribe) + } + + } + + @JvmStatic + fun operateClosedGroup(closedGroupPublicKey: String, publicKey: String, context: Context?, operation: String) { + Log.d("Loki", "Start to notify PN server of closed group.") + if (!TextSecurePreferences.isUsingFCM(context)) { return } + val parameters = mapOf("closedGroupPublicKey" to closedGroupPublicKey, "pubKey" to publicKey) + val url = "$server/$operation" + val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) + val request = Request.Builder().url(url).post(body).build() + connection.newCall(request).enqueue(object : Callback { + + override fun onResponse(call: Call, response: Response) { + when (response.code()) { + 200 -> { + val bodyAsString = response.body()!!.string() + val json = JsonUtil.fromJson(bodyAsString, Map::class.java) + val code = json?.get("code") as? Int + if (code == null || code == 0) { + Log.d("Loki", "Couldn't subscribe/unsubscribe $closedGroupPublicKey due to error: ${json?.get("message") as? String ?: "null"}.") + } else { + Log.d("Loki", "Subscribe/unsubscribe success.") + } + } + } + } + + override fun onFailure(call: Call, exception: IOException) { + Log.d("Loki", "Couldn't subscribe/unsubscribe $closedGroupPublicKey.") + } + }) } } + +class ClosedGroupSubscribeJob private constructor(parameters: Parameters, private val closedGroupPublicKey: String) : BaseJob(parameters) { + + companion object { + const val KEY = "ClosedGroupSubscribeJob" + } + + constructor(closedGroupPublicKey: String) : this(Parameters.Builder() + .addConstraint(NetworkConstraint.KEY) + .setQueue(KEY) + .setLifespan(TimeUnit.DAYS.toMillis(1)) + .setMaxAttempts(1) + .build(), + closedGroupPublicKey) + + override fun serialize(): Data { + val builder = Data.Builder() + builder.putString("closedGroupPublicKey", closedGroupPublicKey) + return builder.build() + } + + override fun getFactoryKey(): String { return KEY } + + override fun onCanceled() { } + + public override fun onRun() { + LokiPushNotificationManager.operateClosedGroup(closedGroupPublicKey, TextSecurePreferences.getLocalNumber(context), context, LokiPushNotificationManager.subscribe) + } + + override fun onShouldRetry(e: Exception): Boolean { return false } +} diff --git a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt index 0841aaf2a6..aa5d282792 100644 --- a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt +++ b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt @@ -8,6 +8,8 @@ import nl.komponents.kovenant.deferred import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.database.Address import org.thoughtcrime.securesms.database.DatabaseFactory +import org.thoughtcrime.securesms.loki.api.ClosedGroupSubscribeJob +import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager import org.thoughtcrime.securesms.loki.utilities.recipient import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage import org.thoughtcrime.securesms.recipients.Recipient @@ -33,7 +35,7 @@ import java.io.IOException import java.util.* object ClosedGroupsProtocol { - val isSharedSenderKeysEnabled = false + val isSharedSenderKeysEnabled = true val groupSizeLimit = 10 public fun createClosedGroup(context: Context, name: String, members: Collection): Promise { @@ -70,6 +72,10 @@ object ClosedGroupsProtocol { } // Add the group to the user's set of public keys to poll for DatabaseFactory.getSSKDatabase(context).setClosedGroupPrivateKey(groupPublicKey, groupKeyPair.hexEncodedPrivateKey) + // Notify PN server + val jobForPN = ClosedGroupSubscribeJob(groupPublicKey) + jobForPN.setContext(context) + jobForPN.onRun() // Notify the user val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID) @@ -137,6 +143,8 @@ object ClosedGroupsProtocol { sskDatabase.removeClosedGroupPrivateKey(groupPublicKey) groupDB.setActive(groupID, false) groupDB.remove(groupID, Address.fromSerialized(userPublicKey)) + // Notify PN server + LokiPushNotificationManager.operateClosedGroup(groupPublicKey, userPublicKey, context, LokiPushNotificationManager.unsubscribe) } else { // Establish sessions if needed establishSessionsWithMembersIfNeeded(context, members) @@ -231,6 +239,7 @@ object ClosedGroupsProtocol { public fun handleNewClosedGroup(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdate, senderPublicKey: String) { // Prepare + val userPublicKey = TextSecurePreferences.getLocalNumber(context) val sskDatabase = DatabaseFactory.getSSKDatabase(context) // Unwrap the message val groupPublicKey = closedGroupUpdate.groupPublicKey.toByteArray().toHexString() @@ -258,6 +267,8 @@ object ClosedGroupsProtocol { insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins) // Establish sessions if needed establishSessionsWithMembersIfNeeded(context, members) + // Notify PN server + LokiPushNotificationManager.operateClosedGroup(groupPublicKey, userPublicKey, context, LokiPushNotificationManager.subscribe) } public fun handleClosedGroupUpdate(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdate, senderPublicKey: String) { From 557eecffd1f05c363eb9287aa9094345a09d1270 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Tue, 15 Sep 2020 16:57:50 +1000 Subject: [PATCH 2/4] Add missing unsubscribe case --- .../loki/api/LokiPushNotificationManager.kt | 79 +++++++------------ .../loki/protocol/ClosedGroupsProtocol.kt | 22 +++--- 2 files changed, 39 insertions(+), 62 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/api/LokiPushNotificationManager.kt b/src/org/thoughtcrime/securesms/loki/api/LokiPushNotificationManager.kt index e59502174d..59fa012e2d 100644 --- a/src/org/thoughtcrime/securesms/loki/api/LokiPushNotificationManager.kt +++ b/src/org/thoughtcrime/securesms/loki/api/LokiPushNotificationManager.kt @@ -17,19 +17,27 @@ import java.util.concurrent.TimeUnit object LokiPushNotificationManager { private val connection = OkHttpClient() + private val tokenExpirationInterval = 12 * 60 * 60 * 1000 private val server by lazy { PushNotificationAcknowledgement.shared.server } - public const val subscribe = "subscribe_closed_group" - public const val unsubscribe = "unsubscribe_closed_group" + enum class ClosedGroupOperation { + Subscribe, Unsubscribe; - private const val tokenExpirationInterval = 12 * 60 * 60 * 1000 + val rawValue: String + get() { + return when (this) { + Subscribe -> "subscribe_closed_group" + Unsubscribe -> "unsubscribe_closed_group" + } + } + } @JvmStatic - fun unregister(token: String, context: Context?) { - val parameters = mapOf("token" to token) + fun unregister(token: String, context: Context) { + val parameters = mapOf( "token" to token ) val url = "$server/register" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body).build() @@ -54,18 +62,20 @@ object LokiPushNotificationManager { Log.d("Loki", "Couldn't disable FCM.") } }) - - for (closedGroup: String in DatabaseFactory.getSSKDatabase(context).getAllClosedGroupPublicKeys()) { - operateClosedGroup(closedGroup, TextSecurePreferences.getLocalNumber(context), context, unsubscribe) + // Unsubscribe from all closed groups + val allClosedGroupPublicKeys = DatabaseFactory.getSSKDatabase(context).getAllClosedGroupPublicKeys() + val userPublicKey = TextSecurePreferences.getLocalNumber(context) + allClosedGroupPublicKeys.forEach { closedGroup -> + performOperation(context, ClosedGroupOperation.Unsubscribe, closedGroup, userPublicKey) } } @JvmStatic - fun register(token: String, publicKey: String, context: Context?, force: Boolean) { + fun register(token: String, publicKey: String, context: Context, force: Boolean) { val oldToken = TextSecurePreferences.getFCMToken(context) val lastUploadDate = TextSecurePreferences.getLastFCMUploadTime(context) if (!force && token == oldToken && System.currentTimeMillis() - lastUploadDate < tokenExpirationInterval) { return } - val parameters = mapOf("token" to token, "pubKey" to publicKey) + val parameters = mapOf( "token" to token, "pubKey" to publicKey ) val url = "$server/register" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body).build() @@ -92,19 +102,19 @@ object LokiPushNotificationManager { Log.d("Loki", "Couldn't register for FCM.") } }) - - for (closedGroup: String in DatabaseFactory.getSSKDatabase(context).getAllClosedGroupPublicKeys()) { - operateClosedGroup(closedGroup, publicKey, context, subscribe) + // Subscribe to all closed groups + val allClosedGroupPublicKeys = DatabaseFactory.getSSKDatabase(context).getAllClosedGroupPublicKeys() + allClosedGroupPublicKeys.forEach { closedGroup -> + performOperation(context, ClosedGroupOperation.Subscribe, closedGroup, publicKey) } - } @JvmStatic - fun operateClosedGroup(closedGroupPublicKey: String, publicKey: String, context: Context?, operation: String) { + fun performOperation(context: Context, operation: ClosedGroupOperation, closedGroupPublicKey: String, publicKey: String) { Log.d("Loki", "Start to notify PN server of closed group.") if (!TextSecurePreferences.isUsingFCM(context)) { return } val parameters = mapOf("closedGroupPublicKey" to closedGroupPublicKey, "pubKey" to publicKey) - val url = "$server/$operation" + val url = "$server/${operation.rawValue}" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body).build() connection.newCall(request).enqueue(object : Callback { @@ -116,48 +126,15 @@ object LokiPushNotificationManager { val json = JsonUtil.fromJson(bodyAsString, Map::class.java) val code = json?.get("code") as? Int if (code == null || code == 0) { - Log.d("Loki", "Couldn't subscribe/unsubscribe $closedGroupPublicKey due to error: ${json?.get("message") as? String ?: "null"}.") - } else { - Log.d("Loki", "Subscribe/unsubscribe success.") + Log.d("Loki", "Couldn't subscribe/unsubscribe to/from PNs for closed group with ID: $closedGroupPublicKey due to error: ${json?.get("message") as? String ?: "null"}.") } } } } override fun onFailure(call: Call, exception: IOException) { - Log.d("Loki", "Couldn't subscribe/unsubscribe $closedGroupPublicKey.") + Log.d("Loki", "Couldn't subscribe/unsubscribe to/from PNs for closed group with ID: $closedGroupPublicKey due to error: $exception.") } }) } } - -class ClosedGroupSubscribeJob private constructor(parameters: Parameters, private val closedGroupPublicKey: String) : BaseJob(parameters) { - - companion object { - const val KEY = "ClosedGroupSubscribeJob" - } - - constructor(closedGroupPublicKey: String) : this(Parameters.Builder() - .addConstraint(NetworkConstraint.KEY) - .setQueue(KEY) - .setLifespan(TimeUnit.DAYS.toMillis(1)) - .setMaxAttempts(1) - .build(), - closedGroupPublicKey) - - override fun serialize(): Data { - val builder = Data.Builder() - builder.putString("closedGroupPublicKey", closedGroupPublicKey) - return builder.build() - } - - override fun getFactoryKey(): String { return KEY } - - override fun onCanceled() { } - - public override fun onRun() { - LokiPushNotificationManager.operateClosedGroup(closedGroupPublicKey, TextSecurePreferences.getLocalNumber(context), context, LokiPushNotificationManager.subscribe) - } - - override fun onShouldRetry(e: Exception): Boolean { return false } -} diff --git a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt index aa5d282792..2ff6e2a137 100644 --- a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt +++ b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt @@ -8,8 +8,8 @@ import nl.komponents.kovenant.deferred import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.database.Address import org.thoughtcrime.securesms.database.DatabaseFactory -import org.thoughtcrime.securesms.loki.api.ClosedGroupSubscribeJob import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager +import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager.ClosedGroupOperation import org.thoughtcrime.securesms.loki.utilities.recipient import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage import org.thoughtcrime.securesms.recipients.Recipient @@ -35,7 +35,7 @@ import java.io.IOException import java.util.* object ClosedGroupsProtocol { - val isSharedSenderKeysEnabled = true + val isSharedSenderKeysEnabled = false val groupSizeLimit = 10 public fun createClosedGroup(context: Context, name: String, members: Collection): Promise { @@ -68,17 +68,15 @@ object ClosedGroupsProtocol { if (member == userPublicKey) { continue } val job = ClosedGroupUpdateMessageSendJob(member, closedGroupUpdateKind) job.setContext(context) - job.onRun() // Run the job immediately + job.onRun() // Run the job immediately to make all of this sync } // Add the group to the user's set of public keys to poll for DatabaseFactory.getSSKDatabase(context).setClosedGroupPrivateKey(groupPublicKey, groupKeyPair.hexEncodedPrivateKey) - // Notify PN server - val jobForPN = ClosedGroupSubscribeJob(groupPublicKey) - jobForPN.setContext(context) - jobForPN.onRun() // Notify the user val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID) + // Notify the PN server + LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey) // Fulfill the promise deferred.resolve(groupID) }.start() @@ -143,8 +141,8 @@ object ClosedGroupsProtocol { sskDatabase.removeClosedGroupPrivateKey(groupPublicKey) groupDB.setActive(groupID, false) groupDB.remove(groupID, Address.fromSerialized(userPublicKey)) - // Notify PN server - LokiPushNotificationManager.operateClosedGroup(groupPublicKey, userPublicKey, context, LokiPushNotificationManager.unsubscribe) + // Notify the PN server + LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Unsubscribe, groupPublicKey, userPublicKey) } else { // Establish sessions if needed establishSessionsWithMembersIfNeeded(context, members) @@ -267,8 +265,8 @@ object ClosedGroupsProtocol { insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins) // Establish sessions if needed establishSessionsWithMembersIfNeeded(context, members) - // Notify PN server - LokiPushNotificationManager.operateClosedGroup(groupPublicKey, userPublicKey, context, LokiPushNotificationManager.subscribe) + // Notify the PN server + LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey) } public fun handleClosedGroupUpdate(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdate, senderPublicKey: String) { @@ -314,6 +312,8 @@ object ClosedGroupsProtocol { sskDatabase.removeClosedGroupPrivateKey(groupPublicKey) groupDB.setActive(groupID, false) groupDB.remove(groupID, Address.fromSerialized(userPublicKey)) + // Notify the PN server + LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Unsubscribe, groupPublicKey, userPublicKey) } else { establishSessionsWithMembersIfNeeded(context, members) val userRatchet = SharedSenderKeysImplementation.shared.generateRatchet(groupPublicKey, userPublicKey) From 518e88bd0a9f312a79cbf2dee6eca307c43a39c2 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Tue, 15 Sep 2020 17:00:18 +1000 Subject: [PATCH 3/4] Clean --- .../securesms/loki/api/LokiPushNotificationManager.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/api/LokiPushNotificationManager.kt b/src/org/thoughtcrime/securesms/loki/api/LokiPushNotificationManager.kt index 59fa012e2d..ac81b2a284 100644 --- a/src/org/thoughtcrime/securesms/loki/api/LokiPushNotificationManager.kt +++ b/src/org/thoughtcrime/securesms/loki/api/LokiPushNotificationManager.kt @@ -111,9 +111,8 @@ object LokiPushNotificationManager { @JvmStatic fun performOperation(context: Context, operation: ClosedGroupOperation, closedGroupPublicKey: String, publicKey: String) { - Log.d("Loki", "Start to notify PN server of closed group.") if (!TextSecurePreferences.isUsingFCM(context)) { return } - val parameters = mapOf("closedGroupPublicKey" to closedGroupPublicKey, "pubKey" to publicKey) + val parameters = mapOf( "closedGroupPublicKey" to closedGroupPublicKey, "pubKey" to publicKey ) val url = "$server/${operation.rawValue}" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body).build() From c4ea3240e39a93b2e1d527e67cd9fc8511544128 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Tue, 15 Sep 2020 17:10:48 +1000 Subject: [PATCH 4/4] Handle non-200 response codes --- .../securesms/loki/api/LokiPushNotificationManager.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/org/thoughtcrime/securesms/loki/api/LokiPushNotificationManager.kt b/src/org/thoughtcrime/securesms/loki/api/LokiPushNotificationManager.kt index ac81b2a284..19f49bf67e 100644 --- a/src/org/thoughtcrime/securesms/loki/api/LokiPushNotificationManager.kt +++ b/src/org/thoughtcrime/securesms/loki/api/LokiPushNotificationManager.kt @@ -128,6 +128,9 @@ object LokiPushNotificationManager { Log.d("Loki", "Couldn't subscribe/unsubscribe to/from PNs for closed group with ID: $closedGroupPublicKey due to error: ${json?.get("message") as? String ?: "null"}.") } } + else -> { + Log.d("Loki", "Couldn't subscribe/unsubscribe to/from PNs for closed group with ID: $closedGroupPublicKey.") + } } }