mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-06 05:22:39 +00:00
...
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
package org.thoughtcrime.securesms.notifications
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.goterl.lazysodium.LazySodiumAndroid
|
||||
import com.goterl.lazysodium.SodiumAndroid
|
||||
import com.goterl.lazysodium.interfaces.AEAD
|
||||
import com.goterl.lazysodium.utils.Key
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.session.libsession.messaging.jobs.BatchMessageReceiveJob
|
||||
import org.session.libsession.messaging.jobs.JobQueue
|
||||
import org.session.libsession.messaging.jobs.MessageReceiveParameters
|
||||
import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationMetadata
|
||||
import org.session.libsession.messaging.utilities.MessageWrapper
|
||||
import org.session.libsession.messaging.utilities.SodiumUtilities
|
||||
import org.session.libsession.utilities.bencode.Bencode
|
||||
import org.session.libsession.utilities.bencode.BencodeList
|
||||
import org.session.libsession.utilities.bencode.BencodeString
|
||||
import org.session.libsignal.utilities.Base64
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val TAG = "PushHandler"
|
||||
|
||||
class PushHandler @Inject constructor(@ApplicationContext val context: Context) {
|
||||
private val sodium = LazySodiumAndroid(SodiumAndroid())
|
||||
|
||||
fun onPush(dataMap: Map<String, String>) {
|
||||
val data: ByteArray? = if (dataMap.containsKey("spns")) {
|
||||
// this is a v2 push notification
|
||||
try {
|
||||
decrypt(Base64.decode(dataMap["enc_payload"]))
|
||||
} catch(e: Exception) {
|
||||
Log.e(TAG, "Invalid push notification: ${e.message}")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// old v1 push notification; we still need this for receiving legacy closed group notifications
|
||||
dataMap.get("ENCRYPTED_DATA")?.let(Base64::decode)
|
||||
}
|
||||
data?.let { onPush(data) } ?: onPush()
|
||||
|
||||
}
|
||||
|
||||
fun onPush() {
|
||||
Log.d(TAG, "Failed to decode data for message.")
|
||||
val builder = NotificationCompat.Builder(context, NotificationChannels.OTHER)
|
||||
.setSmallIcon(network.loki.messenger.R.drawable.ic_notification)
|
||||
.setColor(context.getColor(network.loki.messenger.R.color.textsecure_primary))
|
||||
.setContentTitle("Session")
|
||||
.setContentText("You've got a new message.")
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setAutoCancel(true)
|
||||
NotificationManagerCompat.from(context).notify(11111, builder.build())
|
||||
}
|
||||
|
||||
fun onPush(data: ByteArray) {
|
||||
try {
|
||||
val envelopeAsData = MessageWrapper.unwrap(data).toByteArray()
|
||||
val job = BatchMessageReceiveJob(listOf(MessageReceiveParameters(envelopeAsData)), null)
|
||||
JobQueue.shared.add(job)
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "Failed to unwrap data for message due to error: $e.")
|
||||
}
|
||||
}
|
||||
|
||||
fun decrypt(encPayload: ByteArray): ByteArray? {
|
||||
Log.d(TAG, "decrypt() called")
|
||||
|
||||
val encKey = getOrCreateNotificationKey()
|
||||
val nonce = encPayload.take(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray()
|
||||
val payload = encPayload.drop(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray()
|
||||
val padded = SodiumUtilities.decrypt(payload, encKey.asBytes, nonce)
|
||||
?: error("Failed to decrypt push notification")
|
||||
val decrypted = padded.dropLastWhile { it.toInt() == 0 }.toByteArray()
|
||||
val bencoded = Bencode.Decoder(decrypted)
|
||||
val expectedList = (bencoded.decode() as? BencodeList)?.values
|
||||
?: error("Failed to decode bencoded list from payload")
|
||||
|
||||
val metadataJson = (expectedList[0] as? BencodeString)?.value ?: error("no metadata")
|
||||
val metadata: PushNotificationMetadata = Json.decodeFromString(String(metadataJson))
|
||||
|
||||
val content: ByteArray? = if (expectedList.size >= 2) (expectedList[1] as? BencodeString)?.value else null
|
||||
// null content is valid only if we got a "data_too_long" flag
|
||||
if (content == null)
|
||||
check(metadata.data_too_long) { "missing message data, but no too-long flag" }
|
||||
else
|
||||
check(metadata.data_len == content.size) { "wrong message data size" }
|
||||
|
||||
Log.d(TAG, "Received push for ${metadata.account}/${metadata.namespace}, msg ${metadata.msg_hash}, ${metadata.data_len}B")
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
fun getOrCreateNotificationKey(): Key {
|
||||
if (IdentityKeyUtil.retrieve(context, IdentityKeyUtil.NOTIFICATION_KEY) == null) {
|
||||
// generate the key and store it
|
||||
val key = sodium.keygen(AEAD.Method.XCHACHA20_POLY1305_IETF)
|
||||
IdentityKeyUtil.save(context, IdentityKeyUtil.NOTIFICATION_KEY, key.asHexString)
|
||||
}
|
||||
return Key.fromHexString(IdentityKeyUtil.retrieve(context, IdentityKeyUtil.NOTIFICATION_KEY))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.thoughtcrime.securesms.notifications
|
||||
|
||||
import android.content.Context
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
|
||||
class ExpiryManager(
|
||||
private val context: Context,
|
||||
private val interval: Int = 12 * 60 * 60 * 1000
|
||||
) {
|
||||
fun isExpired() = currentTime() > time + interval
|
||||
|
||||
fun markTime() {
|
||||
time = currentTime()
|
||||
}
|
||||
|
||||
fun clearTime() {
|
||||
time = 0
|
||||
}
|
||||
|
||||
private var time
|
||||
get() = TextSecurePreferences.getLastFCMUploadTime(context)
|
||||
set(value) = TextSecurePreferences.setLastFCMUploadTime(context, value)
|
||||
|
||||
private fun currentTime() = System.currentTimeMillis()
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.thoughtcrime.securesms.notifications
|
||||
|
||||
import android.content.Context
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
|
||||
class FcmTokenManager(
|
||||
private val context: Context,
|
||||
private val expiryManager: ExpiryManager
|
||||
) {
|
||||
val isUsingFCM get() = TextSecurePreferences.isUsingFCM(context)
|
||||
|
||||
var fcmToken
|
||||
get() = TextSecurePreferences.getFCMToken(context)
|
||||
set(value) {
|
||||
TextSecurePreferences.setFCMToken(context, value)
|
||||
if (value != null) markTime() else clearTime()
|
||||
}
|
||||
|
||||
val requiresUnregister get() = fcmToken != null
|
||||
|
||||
private fun clearTime() = expiryManager.clearTime()
|
||||
private fun markTime() = expiryManager.markTime()
|
||||
private fun isExpired() = expiryManager.isExpired()
|
||||
|
||||
fun isInvalid(): Boolean = fcmToken == null || isExpired()
|
||||
}
|
||||
Reference in New Issue
Block a user