mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-16 02:05:06 +08:00
Fix / Save account data after update (local echo)
This commit is contained in:
parent
def01cca8f
commit
64647cb465
@ -33,6 +33,7 @@ import im.vector.matrix.android.common.CryptoTestHelper
|
||||
import im.vector.matrix.android.common.SessionTestParams
|
||||
import im.vector.matrix.android.common.TestConstants
|
||||
import im.vector.matrix.android.common.TestMatrixCallback
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding
|
||||
import im.vector.matrix.android.internal.crypto.secrets.DefaultSharedSecureStorage
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@ -164,9 +165,9 @@ class QuadSTests : InstrumentedTest {
|
||||
TestMatrixCallback(storeCountDownLatch)
|
||||
)
|
||||
|
||||
val secretAccountData = assertAccountData(aliceSession,"secret.of.life" )
|
||||
val secretAccountData = assertAccountData(aliceSession, "secret.of.life")
|
||||
|
||||
val encryptedContent = secretAccountData.content.get("encrypted") as? Map<*,*>
|
||||
val encryptedContent = secretAccountData.content.get("encrypted") as? Map<*, *>
|
||||
Assert.assertNotNull("Element should be encrypted", encryptedContent)
|
||||
Assert.assertNotNull("Secret should be encrypted with default key", encryptedContent?.get(keyId))
|
||||
|
||||
@ -182,12 +183,12 @@ class QuadSTests : InstrumentedTest {
|
||||
var decryptedSecret: String? = null
|
||||
|
||||
val decryptCountDownLatch = CountDownLatch(1)
|
||||
aliceSession.sharedSecretStorageService.getSecret("secret.of.life" ,
|
||||
null, //default key
|
||||
aliceSession.sharedSecretStorageService.getSecret("secret.of.life",
|
||||
null, //default key
|
||||
keySpec!!,
|
||||
object : MatrixCallback<String> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
fail("Fail to decrypt -> " +failure.localizedMessage)
|
||||
fail("Fail to decrypt -> " + failure.localizedMessage)
|
||||
decryptCountDownLatch.countDown()
|
||||
}
|
||||
|
||||
@ -202,7 +203,136 @@ class QuadSTests : InstrumentedTest {
|
||||
|
||||
Assert.assertEquals("Secret mismatch", clearSecret, decryptedSecret)
|
||||
mTestHelper.signout(aliceSession)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_SetDefaultLocalEcho() {
|
||||
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
|
||||
val quadS = aliceSession.sharedSecretStorageService
|
||||
|
||||
val emptyKeySigner = object : KeySigner {
|
||||
override fun sign(canonicalJson: String): Map<String, Map<String, String>>? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
val TEST_KEY_ID = "my.test.Key"
|
||||
|
||||
val countDownLatch = CountDownLatch(1)
|
||||
quadS.generateKey(TEST_KEY_ID, "Test Key", emptyKeySigner,
|
||||
TestMatrixCallback(countDownLatch))
|
||||
|
||||
mTestHelper.await(countDownLatch)
|
||||
|
||||
//Test that we don't need to wait for an account data sync to access directly the keyid from DB
|
||||
val defaultLatch = CountDownLatch(1)
|
||||
quadS.setDefaultKey(TEST_KEY_ID, TestMatrixCallback(defaultLatch))
|
||||
mTestHelper.await(defaultLatch)
|
||||
|
||||
|
||||
mTestHelper.signout(aliceSession)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_StoreSecretWithMultipleKey() {
|
||||
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
val keyId1 = "Key.1"
|
||||
val key1Info = generatedSecret(aliceSession, keyId1, true)
|
||||
val keyId2 = "Key2"
|
||||
val key2Info = generatedSecret(aliceSession, keyId2, true)
|
||||
|
||||
val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
|
||||
|
||||
val storeLatch = CountDownLatch(1)
|
||||
aliceSession.sharedSecretStorageService.storeSecret(
|
||||
"my.secret",
|
||||
mySecretText.toByteArray().toBase64NoPadding(),
|
||||
listOf(keyId1, keyId2),
|
||||
TestMatrixCallback(storeLatch)
|
||||
)
|
||||
mTestHelper.await(storeLatch)
|
||||
|
||||
val accountDataEvent = aliceSession.getAccountData("my.secret")
|
||||
val encryptedContent = accountDataEvent?.content?.get("encrypted") as? Map<*, *>
|
||||
|
||||
Assert.assertEquals("Content should contains two encryptions", 2, encryptedContent?.keys?.size ?: 0)
|
||||
|
||||
Assert.assertNotNull(encryptedContent?.get(keyId1))
|
||||
Assert.assertNotNull(encryptedContent?.get(keyId2))
|
||||
|
||||
// Assert that can decrypt with both keys
|
||||
val decryptCountDownLatch = CountDownLatch(2)
|
||||
aliceSession.sharedSecretStorageService.getSecret("my.secret",
|
||||
keyId1,
|
||||
Curve25519AesSha2KeySpec.fromRecoveryKey(key1Info.recoveryKey)!!,
|
||||
TestMatrixCallback(decryptCountDownLatch)
|
||||
)
|
||||
|
||||
aliceSession.sharedSecretStorageService.getSecret("my.secret",
|
||||
keyId2,
|
||||
Curve25519AesSha2KeySpec.fromRecoveryKey(key2Info.recoveryKey)!!,
|
||||
TestMatrixCallback(decryptCountDownLatch)
|
||||
)
|
||||
|
||||
mTestHelper.await(decryptCountDownLatch)
|
||||
|
||||
mTestHelper.signout(aliceSession)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_GetSecretWithBadPassphrase() {
|
||||
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
val keyId1 = "Key.1"
|
||||
val passphrase = "The good pass phrase"
|
||||
val key1Info = generatedSecretFromPassphrase(aliceSession, passphrase, keyId1, true)
|
||||
|
||||
val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
|
||||
|
||||
val storeLatch = CountDownLatch(1)
|
||||
aliceSession.sharedSecretStorageService.storeSecret(
|
||||
"my.secret",
|
||||
mySecretText.toByteArray().toBase64NoPadding(),
|
||||
listOf(keyId1),
|
||||
TestMatrixCallback(storeLatch)
|
||||
)
|
||||
mTestHelper.await(storeLatch)
|
||||
|
||||
val decryptCountDownLatch = CountDownLatch(2)
|
||||
aliceSession.sharedSecretStorageService.getSecret("my.secret",
|
||||
keyId1,
|
||||
Curve25519AesSha2KeySpec.fromPassphrase(
|
||||
"A bad passphrase",
|
||||
key1Info.content?.passphrase?.salt ?: "",
|
||||
key1Info.content?.passphrase?.iterations ?: 0,
|
||||
null),
|
||||
object : MatrixCallback<String> {
|
||||
override fun onSuccess(data: String) {
|
||||
decryptCountDownLatch.countDown()
|
||||
fail("Should not be able to decrypt")
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
Assert.assertTrue(true)
|
||||
decryptCountDownLatch.countDown()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Now try with correct key
|
||||
aliceSession.sharedSecretStorageService.getSecret("my.secret",
|
||||
keyId1,
|
||||
Curve25519AesSha2KeySpec.fromPassphrase(
|
||||
passphrase,
|
||||
key1Info.content?.passphrase?.salt ?: "",
|
||||
key1Info.content?.passphrase?.iterations ?: 0,
|
||||
null),
|
||||
TestMatrixCallback(decryptCountDownLatch)
|
||||
)
|
||||
|
||||
mTestHelper.await(decryptCountDownLatch)
|
||||
|
||||
mTestHelper.signout(aliceSession)
|
||||
}
|
||||
|
||||
private fun assertAccountData(session: Session, type: String): UserAccountDataEvent {
|
||||
@ -268,4 +398,50 @@ class QuadSTests : InstrumentedTest {
|
||||
|
||||
return creationInfo!!
|
||||
}
|
||||
|
||||
private fun generatedSecretFromPassphrase(session: Session, passphrase: String, keyId: String, asDefault: Boolean = true): SSSSKeyCreationInfo {
|
||||
|
||||
val quadS = session.sharedSecretStorageService
|
||||
|
||||
val emptyKeySigner = object : KeySigner {
|
||||
override fun sign(canonicalJson: String): Map<String, Map<String, String>>? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
var creationInfo: SSSSKeyCreationInfo? = null
|
||||
|
||||
val generateLatch = CountDownLatch(1)
|
||||
|
||||
quadS.generateKeyWithPassphrase(keyId, keyId,
|
||||
passphrase,
|
||||
emptyKeySigner,
|
||||
null,
|
||||
object : MatrixCallback<SSSSKeyCreationInfo> {
|
||||
|
||||
override fun onSuccess(data: SSSSKeyCreationInfo) {
|
||||
creationInfo = data
|
||||
generateLatch.countDown()
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
Assert.fail("onFailure " + failure.localizedMessage)
|
||||
generateLatch.countDown()
|
||||
}
|
||||
})
|
||||
|
||||
mTestHelper.await(generateLatch)
|
||||
|
||||
Assert.assertNotNull(creationInfo)
|
||||
|
||||
assertAccountData(session, "m.secret_storage.key.$keyId")
|
||||
if (asDefault) {
|
||||
val setDefaultLatch = CountDownLatch(1)
|
||||
quadS.setDefaultKey(keyId, TestMatrixCallback(setDefaultLatch))
|
||||
mTestHelper.await(setDefaultLatch)
|
||||
assertAccountData(session, DefaultSharedSecureStorage.DEFAULT_KEY_ID)
|
||||
}
|
||||
|
||||
return creationInfo!!
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.api.session.accountdata
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
|
||||
@ -31,5 +32,5 @@ interface AccountDataService {
|
||||
|
||||
fun getLiveAccountData(filterType: List<String>): LiveData<List<UserAccountDataEvent>>
|
||||
|
||||
fun updateAccountData(type: String, data: Any, callback: MatrixCallback<Unit>? = null)
|
||||
fun updateAccountData(type: String, content: Content, callback: MatrixCallback<Unit>? = null)
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.crypto.secrets
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||
import im.vector.matrix.android.api.session.accountdata.AccountDataService
|
||||
import im.vector.matrix.android.api.session.events.model.toContent
|
||||
import im.vector.matrix.android.api.session.securestorage.Curve25519AesSha2KeySpec
|
||||
import im.vector.matrix.android.api.session.securestorage.EncryptedSecretContent
|
||||
import im.vector.matrix.android.api.session.securestorage.KeyInfo
|
||||
@ -82,7 +83,7 @@ internal class DefaultSharedSecureStorage @Inject constructor(
|
||||
|
||||
accountDataService.updateAccountData(
|
||||
"$KEY_ID_BASE.$keyId",
|
||||
signedContent,
|
||||
signedContent.toContent(),
|
||||
object : MatrixCallback<Unit> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
callback.onFailure(failure)
|
||||
@ -136,7 +137,7 @@ internal class DefaultSharedSecureStorage @Inject constructor(
|
||||
|
||||
accountDataService.updateAccountData(
|
||||
"$KEY_ID_BASE.$keyId",
|
||||
signedContent,
|
||||
signedContent.toContent(),
|
||||
object : MatrixCallback<Unit> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
callback.onFailure(failure)
|
||||
@ -254,7 +255,7 @@ internal class DefaultSharedSecureStorage @Inject constructor(
|
||||
|
||||
accountDataService.updateAccountData(
|
||||
type = name,
|
||||
data = mapOf(
|
||||
content = mapOf(
|
||||
"encrypted" to encryptedContents
|
||||
),
|
||||
callback = callback
|
||||
|
@ -210,7 +210,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleGenericAccountData(realm: Realm, type: String, content: Content?) {
|
||||
fun handleGenericAccountData(realm: Realm, type: String, content: Content?) {
|
||||
val existing = realm.where<UserAccountDataEntity>().equalTo(UserAccountDataEntityFields.TYPE, type)
|
||||
.findFirst()
|
||||
if (existing != null) {
|
||||
|
@ -21,6 +21,7 @@ import androidx.lifecycle.Transformations
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.accountdata.AccountDataService
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.api.util.toOptional
|
||||
@ -28,6 +29,7 @@ import im.vector.matrix.android.internal.database.model.UserAccountDataEntity
|
||||
import im.vector.matrix.android.internal.database.model.UserAccountDataEntityFields
|
||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
import im.vector.matrix.android.internal.di.SessionId
|
||||
import im.vector.matrix.android.internal.session.sync.UserAccountDataSyncHandler
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
@ -37,6 +39,7 @@ internal class DefaultAccountDataService @Inject constructor(
|
||||
private val monarchy: Monarchy,
|
||||
@SessionId private val sessionId: String,
|
||||
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
|
||||
private val userAccountDataSyncHandler: UserAccountDataSyncHandler,
|
||||
private val taskExecutor: TaskExecutor
|
||||
) : AccountDataService {
|
||||
|
||||
@ -87,13 +90,24 @@ internal class DefaultAccountDataService @Inject constructor(
|
||||
})
|
||||
}
|
||||
|
||||
override fun updateAccountData(type: String, data: Any, callback: MatrixCallback<Unit>?) {
|
||||
override fun updateAccountData(type: String, content: Content, callback: MatrixCallback<Unit>?) {
|
||||
updateUserAccountDataTask.configureWith(UpdateUserAccountDataTask.AnyParams(
|
||||
type = type,
|
||||
any = data
|
||||
any = content
|
||||
)) {
|
||||
this.retryCount = 5
|
||||
callback?.let { this.callback = it }
|
||||
this.callback = object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
userAccountDataSyncHandler.handleGenericAccountData(realm, type, content)
|
||||
}
|
||||
callback?.onSuccess(data)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
callback?.onFailure(failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.session.user.accountdata
|
||||
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.session.sync.UserAccountDataSyncHandler
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.BreadcrumbsContent
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
|
Loading…
Reference in New Issue
Block a user