mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-27 12:05:22 +00:00
feat: testnet clearing network data on delete and differentiating dialogs
This commit is contained in:
parent
11f64a1d1a
commit
fdc042e6d4
@ -42,12 +42,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
|||||||
return TextSecurePreferences.getLocalNumber(context)
|
return TextSecurePreferences.getLocalNumber(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getUserKeyPair(): Pair<String, ByteArray>? {
|
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return null
|
|
||||||
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
|
|
||||||
return Pair(userPublicKey, userPrivateKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getUserX25519KeyPair(): ECKeyPair {
|
override fun getUserX25519KeyPair(): ECKeyPair {
|
||||||
return DatabaseFactory.getLokiAPIDatabase(context).getUserX25519KeyPair()
|
return DatabaseFactory.getLokiAPIDatabase(context).getUserX25519KeyPair()
|
||||||
}
|
}
|
||||||
|
@ -92,6 +92,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
helpTranslateButton.setOnClickListener { helpTranslate() }
|
helpTranslateButton.setOnClickListener { helpTranslate() }
|
||||||
seedButton.setOnClickListener { showSeed() }
|
seedButton.setOnClickListener { showSeed() }
|
||||||
clearAllDataButton.setOnClickListener { clearAllData() }
|
clearAllDataButton.setOnClickListener { clearAllData() }
|
||||||
|
clearAllDataAndNetworkButton.setOnClickListener { clearAllDataIncludingNetwork() }
|
||||||
versionTextView.text = String.format(getString(R.string.version_s), "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
|
versionTextView.text = String.format(getString(R.string.version_s), "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,8 +303,13 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun clearAllData() {
|
private fun clearAllData() {
|
||||||
ClearAllDataDialog().show(supportFragmentManager, "Clear All Data Dialog")
|
ClearAllDataDialog(deleteNetworkMessages = false).show(supportFragmentManager, "Clear All Data Dialog")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun clearAllDataIncludingNetwork() {
|
||||||
|
ClearAllDataDialog(deleteNetworkMessages = true).show(supportFragmentManager, "Clear All Data Dialog")
|
||||||
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
private inner class DisplayNameEditActionModeCallback: ActionMode.Callback {
|
private inner class DisplayNameEditActionModeCallback: ActionMode.Callback {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package org.thoughtcrime.securesms.loki.dialogs
|
package org.thoughtcrime.securesms.loki.dialogs
|
||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
|
import android.content.DialogInterface
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@ -11,21 +12,22 @@ import androidx.fragment.app.DialogFragment
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import kotlinx.android.synthetic.main.dialog_clear_all_data.*
|
import kotlinx.android.synthetic.main.dialog_clear_all_data.*
|
||||||
import kotlinx.android.synthetic.main.dialog_clear_all_data.view.*
|
import kotlinx.android.synthetic.main.dialog_clear_all_data.view.*
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
|
import nl.komponents.kovenant.Promise
|
||||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
import org.session.libsession.snode.SnodeAPI
|
import org.session.libsession.snode.SnodeAPI
|
||||||
import org.session.libsession.snode.SnodeDeleteMessage
|
|
||||||
import org.session.libsession.utilities.KeyPairUtilities
|
import org.session.libsession.utilities.KeyPairUtilities
|
||||||
|
import org.session.libsignal.utilities.Log
|
||||||
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
|
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
class ClearAllDataDialog : DialogFragment() {
|
class ClearAllDataDialog(val deleteNetworkMessages: Boolean) : DialogFragment() {
|
||||||
|
|
||||||
var clearJob: Job? = null
|
var clearJob: Job? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
updateUI()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
@ -40,41 +42,73 @@ class ClearAllDataDialog : DialogFragment() {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateUI() {
|
override fun onDismiss(dialog: DialogInterface) {
|
||||||
if (clearJob?.isActive == true) {
|
super.onDismiss(dialog)
|
||||||
// clear background job is running, prevent interaction
|
|
||||||
dialog?.let { view ->
|
|
||||||
view.cancelButton.isVisible = false
|
|
||||||
view.clearAllDataButton.isVisible = false
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
dialog?.let { view ->
|
override fun onStart() {
|
||||||
view.cancelButton.isVisible = false
|
super.onStart()
|
||||||
view.clearAllDataButton.isVisible = false
|
isCancelable = false
|
||||||
|
dialog?.setCanceledOnTouchOutside(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateUI(isLoading: Boolean) {
|
||||||
|
dialog?.let { view ->
|
||||||
|
view.cancelButton.isVisible = !isLoading
|
||||||
|
view.clearAllDataButton.isVisible = !isLoading
|
||||||
|
view.progressBar.isVisible = isLoading
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clearAllData() {
|
private fun clearAllData() {
|
||||||
if (KeyPairUtilities.hasV2KeyPair(requireContext())) {
|
if (KeyPairUtilities.hasV2KeyPair(requireContext())) {
|
||||||
clearJob = lifecycleScope.launch {
|
clearJob = lifecycleScope.launch(Dispatchers.IO) {
|
||||||
delay(5_000)
|
withContext(Dispatchers.Main) {
|
||||||
|
updateUI(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!deleteNetworkMessages) {
|
||||||
|
try {
|
||||||
|
MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(requireContext()).get()
|
||||||
|
ApplicationContext.getInstance(context).clearAllData(false)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("Loki", "Failed to force sync", e)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
// finish
|
// finish
|
||||||
val userPublicKey = MessagingModuleConfiguration.shared.storage.getUserPublicKey()
|
val promises = SnodeAPI.deleteAllMessages(requireContext()).get()
|
||||||
|
|
||||||
|
val rawResponses = promises.map {
|
||||||
val deleteMessage = SnodeDeleteMessage(userKey, System.currentTimeMillis(), )
|
try {
|
||||||
SnodeAPI.deleteAllMessages()
|
it.get()
|
||||||
// TODO: re-add the clear data here
|
} catch (e: Exception) {
|
||||||
//ApplicationContext.getInstance(context).clearAllData(false)
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: process the responses here
|
||||||
|
if (rawResponses.any { it == null || it["failed"] as? Boolean == true }) {
|
||||||
|
// didn't succeed (at least one)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
updateUI(false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// don't force sync because all the messages are deleted?
|
||||||
|
ApplicationContext.getInstance(context).clearAllData(false)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val dialog = AlertDialog.Builder(requireContext())
|
val dialog = AlertDialog.Builder(requireContext())
|
||||||
val message = "We’ve upgraded the way Session IDs are generated, so you will be unable to restore your current Session ID."
|
val message = "We’ve upgraded the way Session IDs are generated, so you will be unable to restore your current Session ID."
|
||||||
dialog.setMessage(message)
|
dialog.setMessage(message)
|
||||||
dialog.setPositiveButton("Yes") { _, _ ->
|
dialog.setPositiveButton("Yes") { _, _ ->
|
||||||
// TODO: re-add the clear data here
|
ApplicationContext.getInstance(context).clearAllData(false)
|
||||||
// ApplicationContext.getInstance(context).clearAllData(false)
|
|
||||||
}
|
}
|
||||||
dialog.setNegativeButton("Cancel") { _, _ ->
|
dialog.setNegativeButton("Cancel") { _, _ ->
|
||||||
// Do nothing
|
// Do nothing
|
||||||
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.loki.protocol
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.google.protobuf.ByteString
|
import com.google.protobuf.ByteString
|
||||||
|
import nl.komponents.kovenant.Promise
|
||||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
import org.session.libsession.messaging.messages.Destination
|
import org.session.libsession.messaging.messages.Destination
|
||||||
import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
||||||
@ -28,16 +29,17 @@ object MultiDeviceProtocol {
|
|||||||
TextSecurePreferences.setLastConfigurationSyncTime(context, now)
|
TextSecurePreferences.setLastConfigurationSyncTime(context, now)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun forceSyncConfigurationNowIfNeeded(context: Context) {
|
fun forceSyncConfigurationNowIfNeeded(context: Context): Promise<Unit, Exception> {
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return Promise.ofSuccess(Unit)
|
||||||
val contacts = ContactUtilities.getAllContacts(context).filter { recipient ->
|
val contacts = ContactUtilities.getAllContacts(context).filter { recipient ->
|
||||||
!recipient.isGroupRecipient && !recipient.isBlocked && !recipient.name.isNullOrEmpty() && !recipient.isLocalNumber && recipient.address.serialize().isNotEmpty()
|
!recipient.isGroupRecipient && !recipient.isBlocked && !recipient.name.isNullOrEmpty() && !recipient.isLocalNumber && recipient.address.serialize().isNotEmpty()
|
||||||
}.map { recipient ->
|
}.map { recipient ->
|
||||||
ConfigurationMessage.Contact(recipient.address.serialize(), recipient.name!!, recipient.profileAvatar, recipient.profileKey)
|
ConfigurationMessage.Contact(recipient.address.serialize(), recipient.name!!, recipient.profileAvatar, recipient.profileKey)
|
||||||
}
|
}
|
||||||
val configurationMessage = ConfigurationMessage.getCurrent(contacts) ?: return
|
val configurationMessage = ConfigurationMessage.getCurrent(contacts) ?: return Promise.ofSuccess(Unit)
|
||||||
MessageSender.send(configurationMessage, Destination.from(Address.fromSerialized(userPublicKey)))
|
val promise = MessageSender.send(configurationMessage, Destination.from(Address.fromSerialized(userPublicKey)))
|
||||||
TextSecurePreferences.setLastConfigurationSyncTime(context, System.currentTimeMillis())
|
TextSecurePreferences.setLastConfigurationSyncTime(context, System.currentTimeMillis())
|
||||||
|
return promise
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -185,6 +185,16 @@
|
|||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:text="@string/activity_settings_clear_all_data_button_title" />
|
android:text="@string/activity_settings_clear_all_data_button_title" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/clearAllDataAndNetworkButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/setting_button_height"
|
||||||
|
android:background="@drawable/setting_button_background"
|
||||||
|
android:textColor="@color/destructive"
|
||||||
|
android:textSize="@dimen/medium_font_size"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/activity_settings_clear_all_data_and_network_button_title" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:background="@drawable/default_dialog_background_inset"
|
android:background="@drawable/default_dialog_background_inset"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
@ -36,6 +37,7 @@
|
|||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
tools:visibility="gone"
|
||||||
style="@style/Widget.Session.Button.Dialog.Unimportant"
|
style="@style/Widget.Session.Button.Dialog.Unimportant"
|
||||||
android:id="@+id/cancelButton"
|
android:id="@+id/cancelButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
@ -44,6 +46,7 @@
|
|||||||
android:text="@string/cancel" />
|
android:text="@string/cancel" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
tools:visibility="gone"
|
||||||
style="@style/Widget.Session.Button.Dialog.Destructive"
|
style="@style/Widget.Session.Button.Dialog.Destructive"
|
||||||
android:id="@+id/clearAllDataButton"
|
android:id="@+id/clearAllDataButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
@ -52,6 +55,17 @@
|
|||||||
android:layout_marginStart="@dimen/medium_spacing"
|
android:layout_marginStart="@dimen/medium_spacing"
|
||||||
android:text="@string/delete" />
|
android:text="@string/delete" />
|
||||||
|
|
||||||
|
<com.github.ybq.android.spinkit.SpinKitView
|
||||||
|
style="@style/SpinKitView.Small.ThreeBounce"
|
||||||
|
android:layout_marginVertical="@dimen/small_spacing"
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="@dimen/small_button_height"
|
||||||
|
android:layout_weight="1"
|
||||||
|
app:SpinKit_Color="@color/accent"
|
||||||
|
android:visibility="gone"
|
||||||
|
/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
@ -755,6 +755,7 @@
|
|||||||
<string name="activity_settings_invite_button_title">Invite</string>
|
<string name="activity_settings_invite_button_title">Invite</string>
|
||||||
<string name="activity_settings_recovery_phrase_button_title">Recovery Phrase</string>
|
<string name="activity_settings_recovery_phrase_button_title">Recovery Phrase</string>
|
||||||
<string name="activity_settings_clear_all_data_button_title">Clear Data</string>
|
<string name="activity_settings_clear_all_data_button_title">Clear Data</string>
|
||||||
|
<string name="activity_settings_clear_all_data_and_network_button_title">Clear Data Including Network</string>
|
||||||
<string name="activity_settings_help_translate_session">Help us Translate Session</string>
|
<string name="activity_settings_help_translate_session">Help us Translate Session</string>
|
||||||
|
|
||||||
<string name="activity_notification_settings_title">Notifications</string>
|
<string name="activity_notification_settings_title">Notifications</string>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<domain-config cleartextTrafficPermitted="true">
|
<domain-config cleartextTrafficPermitted="true">
|
||||||
<domain includeSubdomains="true">127.0.0.1</domain>
|
<domain includeSubdomains="true">127.0.0.1</domain>
|
||||||
</domain-config>
|
</domain-config>
|
||||||
<domain-config cleartextTrafficPermitted="false">
|
<domain-config cleartextTrafficPermitted="true">
|
||||||
<domain includeSubdomains="false">public.loki.foundation</domain>
|
<domain includeSubdomains="false">public.loki.foundation</domain>
|
||||||
<trust-anchors>
|
<trust-anchors>
|
||||||
<certificates src="@raw/lf_session_cert"/>
|
<certificates src="@raw/lf_session_cert"/>
|
||||||
|
@ -28,7 +28,6 @@ interface StorageProtocol {
|
|||||||
|
|
||||||
// General
|
// General
|
||||||
fun getUserPublicKey(): String?
|
fun getUserPublicKey(): String?
|
||||||
fun getUserKeyPair(): Pair<String, ByteArray>?
|
|
||||||
fun getUserX25519KeyPair(): ECKeyPair
|
fun getUserX25519KeyPair(): ECKeyPair
|
||||||
fun getUserDisplayName(): String?
|
fun getUserDisplayName(): String?
|
||||||
fun getUserProfileKey(): ByteArray?
|
fun getUserProfileKey(): ByteArray?
|
||||||
|
@ -172,9 +172,9 @@ object OpenGroupAPIV2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun requestNewAuthToken(room: String, server: String): Promise<String, Exception> {
|
fun requestNewAuthToken(room: String, server: String): Promise<String, Exception> {
|
||||||
val (publicKey, privateKey) = MessagingModuleConfiguration.shared.storage.getUserKeyPair()
|
val (publicKey, privateKey) = MessagingModuleConfiguration.shared.storage.getUserX25519KeyPair().let { it.publicKey.serialize() to it.privateKey.serialize() }
|
||||||
?: return Promise.ofFail(Error.Generic)
|
?: return Promise.ofFail(Error.Generic)
|
||||||
val queryParameters = mutableMapOf( "public_key" to publicKey )
|
val queryParameters = mutableMapOf( "public_key" to publicKey.toHexString() )
|
||||||
val request = Request(GET, room, server, "auth_token_challenge", queryParameters, isAuthRequired = false, parameters = null)
|
val request = Request(GET, room, server, "auth_token_challenge", queryParameters, isAuthRequired = false, parameters = null)
|
||||||
return send(request).map { json ->
|
return send(request).map { json ->
|
||||||
val challenge = json["challenge"] as? Map<*, *> ?: throw Error.ParsingFailed
|
val challenge = json["challenge"] as? Map<*, *> ?: throw Error.ParsingFailed
|
||||||
|
@ -6,6 +6,7 @@ import org.session.libsignal.protos.SignalServiceProtos
|
|||||||
import org.session.libsignal.utilities.Base64
|
import org.session.libsignal.utilities.Base64
|
||||||
import org.session.libsignal.utilities.Base64.decode
|
import org.session.libsignal.utilities.Base64.decode
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
|
import org.session.libsignal.utilities.toHexString
|
||||||
import org.whispersystems.curve25519.Curve25519
|
import org.whispersystems.curve25519.Curve25519
|
||||||
|
|
||||||
data class OpenGroupMessageV2(
|
data class OpenGroupMessageV2(
|
||||||
@ -45,10 +46,10 @@ data class OpenGroupMessageV2(
|
|||||||
|
|
||||||
fun sign(): OpenGroupMessageV2? {
|
fun sign(): OpenGroupMessageV2? {
|
||||||
if (base64EncodedData.isEmpty()) return null
|
if (base64EncodedData.isEmpty()) return null
|
||||||
val (publicKey, privateKey) = MessagingModuleConfiguration.shared.storage.getUserKeyPair() ?: return null
|
val (publicKey, privateKey) = MessagingModuleConfiguration.shared.storage.getUserX25519KeyPair().let { it.publicKey to it.privateKey }
|
||||||
if (sender != publicKey) return null
|
if (sender != publicKey.serialize().toHexString()) return null
|
||||||
val signature = try {
|
val signature = try {
|
||||||
curve.calculateSignature(privateKey, decode(base64EncodedData))
|
curve.calculateSignature(privateKey.serialize(), decode(base64EncodedData))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w("Loki", "Couldn't sign open group message.", e)
|
Log.w("Loki", "Couldn't sign open group message.", e)
|
||||||
return null
|
return null
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
package org.session.libsession.snode
|
package org.session.libsession.snode
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import com.goterl.lazysodium.LazySodiumAndroid
|
import com.goterl.lazysodium.LazySodiumAndroid
|
||||||
import com.goterl.lazysodium.SodiumAndroid
|
import com.goterl.lazysodium.SodiumAndroid
|
||||||
@ -9,18 +10,25 @@ import com.goterl.lazysodium.exceptions.SodiumException
|
|||||||
import com.goterl.lazysodium.interfaces.GenericHash
|
import com.goterl.lazysodium.interfaces.GenericHash
|
||||||
import com.goterl.lazysodium.interfaces.PwHash
|
import com.goterl.lazysodium.interfaces.PwHash
|
||||||
import com.goterl.lazysodium.interfaces.SecretBox
|
import com.goterl.lazysodium.interfaces.SecretBox
|
||||||
|
import com.goterl.lazysodium.interfaces.Sign
|
||||||
import com.goterl.lazysodium.utils.Key
|
import com.goterl.lazysodium.utils.Key
|
||||||
import nl.komponents.kovenant.*
|
import nl.komponents.kovenant.*
|
||||||
import nl.komponents.kovenant.functional.bind
|
import nl.komponents.kovenant.functional.bind
|
||||||
import nl.komponents.kovenant.functional.map
|
import nl.komponents.kovenant.functional.map
|
||||||
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
import org.session.libsession.messaging.utilities.MessageWrapper
|
import org.session.libsession.messaging.utilities.MessageWrapper
|
||||||
|
import org.session.libsession.utilities.IdentityKeyUtil
|
||||||
|
import org.session.libsession.utilities.KeyPairUtilities
|
||||||
import org.session.libsignal.crypto.getRandomElement
|
import org.session.libsignal.crypto.getRandomElement
|
||||||
import org.session.libsignal.database.LokiAPIDatabaseProtocol
|
import org.session.libsignal.database.LokiAPIDatabaseProtocol
|
||||||
import org.session.libsignal.protos.SignalServiceProtos
|
import org.session.libsignal.protos.SignalServiceProtos
|
||||||
import org.session.libsignal.utilities.*
|
import org.session.libsignal.utilities.*
|
||||||
import org.session.libsignal.utilities.Base64
|
import org.session.libsignal.utilities.Base64
|
||||||
|
import org.whispersystems.curve25519.Curve25519
|
||||||
|
import java.nio.charset.Charset
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.Pair
|
||||||
|
|
||||||
object SnodeAPI {
|
object SnodeAPI {
|
||||||
private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) }
|
private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) }
|
||||||
@ -52,7 +60,7 @@ object SnodeAPI {
|
|||||||
private val targetSwarmSnodeCount = 2
|
private val targetSwarmSnodeCount = 2
|
||||||
private val useOnionRequests = true
|
private val useOnionRequests = true
|
||||||
|
|
||||||
internal val useTestnet = false
|
internal val useTestnet = true
|
||||||
|
|
||||||
// Error
|
// Error
|
||||||
internal sealed class Error(val description: String) : Exception(description) {
|
internal sealed class Error(val description: String) : Exception(description) {
|
||||||
@ -283,6 +291,13 @@ object SnodeAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getNetworkTime(snode: Snode): Promise<Pair<Snode,Long>, Exception> {
|
||||||
|
return invoke(Snode.Method.Info, snode, null, emptyMap()).map { rawResponse ->
|
||||||
|
val timestamp = rawResponse["timestamp"] as Long
|
||||||
|
snode to timestamp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun sendMessage(message: SnodeMessage): Promise<Set<RawResponsePromise>, Exception> {
|
fun sendMessage(message: SnodeMessage): Promise<Set<RawResponsePromise>, Exception> {
|
||||||
val destination = if (useTestnet) message.recipient.removing05PrefixIfNeeded() else message.recipient
|
val destination = if (useTestnet) message.recipient.removing05PrefixIfNeeded() else message.recipient
|
||||||
return retryIfNeeded(maxRetryCount) {
|
return retryIfNeeded(maxRetryCount) {
|
||||||
@ -306,15 +321,30 @@ object SnodeAPI {
|
|||||||
* - "signature": signature of ( PUBKEY_HEX || TIMESTAMP || DELETEDHASH[0] || ... || DELETEDHASH[N] ), signed
|
* - "signature": signature of ( PUBKEY_HEX || TIMESTAMP || DELETEDHASH[0] || ... || DELETEDHASH[N] ), signed
|
||||||
* by the node's ed25519 pubkey.
|
* by the node's ed25519 pubkey.
|
||||||
*/
|
*/
|
||||||
fun deleteAllMessages(deleteMessage: SnodeDeleteMessage): Promise<Set<RawResponsePromise>, Exception> {
|
fun deleteAllMessages(context: Context): Promise<Set<RawResponsePromise>, Exception> {
|
||||||
|
|
||||||
|
return retryIfNeeded(1) {
|
||||||
// considerations: timestamp off in retrying logic, not being able to re-sign with latest timestamp? do we just not retry this as it will be synchronous
|
// considerations: timestamp off in retrying logic, not being able to re-sign with latest timestamp? do we just not retry this as it will be synchronous
|
||||||
val destination = if (useTestnet) deleteMessage.pubKey.removing05PrefixIfNeeded() else deleteMessage.pubKey
|
val ed = KeyPairUtilities.getUserED25519KeyPair(context) ?: return@retryIfNeeded Promise.ofFail(Error.Generic)
|
||||||
return retryIfNeeded(maxRetryCount) {
|
val xPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey
|
||||||
|
val userKeyPair = MessagingModuleConfiguration.shared.storage.getUserX25519KeyPair()
|
||||||
|
val userPublicKey = MessagingModuleConfiguration.shared.storage.getUserPublicKey() ?: return@retryIfNeeded Promise.ofFail(Error.Generic)
|
||||||
|
|
||||||
|
val destination = if (useTestnet) userPublicKey.removing05PrefixIfNeeded() else userPublicKey
|
||||||
|
|
||||||
getTargetSnodes(destination).map { swarm ->
|
getTargetSnodes(destination).map { swarm ->
|
||||||
swarm.map { snode ->
|
swarm.map { snode ->
|
||||||
|
retryIfNeeded(1) {
|
||||||
|
getNetworkTime(snode).bind { (_, timestamp) ->
|
||||||
|
val signature = ByteArray(Sign.BYTES)
|
||||||
|
val data = Snode.Method.DeleteAll.rawValue.toByteArray() + timestamp.toString().toByteArray()
|
||||||
|
val signed = sodium.cryptoSignDetached(signature, data, data.size.toLong(), xPrivateKey.serialize())
|
||||||
|
val deleteMessage = SnodeDeleteMessage(userPublicKey, timestamp, Base64.encodeBytes(signature))
|
||||||
val parameters = deleteMessage.toJSON()
|
val parameters = deleteMessage.toJSON()
|
||||||
retryIfNeeded(maxRetryCount) {
|
invoke(Snode.Method.DeleteAll, snode, destination, parameters).fail { e ->
|
||||||
invoke(Snode.Method.DeleteAll, snode, destination, parameters)
|
Log.e("Loki", "Failed to clear data",e)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}.toSet()
|
}.toSet()
|
||||||
}
|
}
|
||||||
|
@ -14,15 +14,15 @@ data class SnodeDeleteMessage(
|
|||||||
*/
|
*/
|
||||||
val timestamp: Long,
|
val timestamp: Long,
|
||||||
/**
|
/**
|
||||||
* an Ed25519 signature of ( "delete_all" || timestamp ), signed by the ed25519
|
* a Base64-encoded signature of ( "delete_all" || timestamp ), signed by the pubKey
|
||||||
*/
|
*/
|
||||||
val signature: String,
|
val signature: String,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
internal fun toJSON(): Map<String, String> {
|
internal fun toJSON(): Map<String, Any> {
|
||||||
return mapOf(
|
return mapOf(
|
||||||
"pubkey" to if (SnodeAPI.useTestnet) pubKey.removing05PrefixIfNeeded() else pubKey,
|
"pubkey" to pubKey,
|
||||||
"timestamp" to timestamp.toString(),
|
"timestamp" to timestamp,
|
||||||
"signature" to signature
|
"signature" to signature
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ class Snode(val address: String, val port: Int, val publicKeySet: KeySet?) {
|
|||||||
GetMessages("retrieve"),
|
GetMessages("retrieve"),
|
||||||
SendMessage("store"),
|
SendMessage("store"),
|
||||||
OxenDaemonRPCCall("oxend_request"),
|
OxenDaemonRPCCall("oxend_request"),
|
||||||
|
Info("info"),
|
||||||
DeleteAll("delete_all")
|
DeleteAll("delete_all")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user