mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 13:38:49 +03:00
Merge branch 'vector-im:develop' into develop
This commit is contained in:
commit
e1960e9593
278 changed files with 2721 additions and 1636 deletions
1
changelog.d/5151.misc
Normal file
1
changelog.d/5151.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Improve threads rendering in the main timeline
|
1
changelog.d/5953.misc
Normal file
1
changelog.d/5953.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Reformatted project code
|
|
@ -192,7 +192,7 @@ dependencies {
|
|||
implementation libs.apache.commonsImaging
|
||||
|
||||
// Phone number https://github.com/google/libphonenumber
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.47'
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.48'
|
||||
|
||||
testImplementation libs.tests.junit
|
||||
testImplementation 'org.robolectric:robolectric:4.7.3'
|
||||
|
|
|
@ -37,7 +37,10 @@ class PermalinkParserTest {
|
|||
Assert.assertTrue("Should be parsed as email invite but was ${parsedLink::class.java}", parsedLink is PermalinkData.RoomEmailInviteLink)
|
||||
parsedLink as PermalinkData.RoomEmailInviteLink
|
||||
Assert.assertEquals("!MRBNLPtFnMAazZVPMO:matrix.org", parsedLink.roomId)
|
||||
Assert.assertEquals("XmOwRZnSFabCRhTywFbJWKXWVNPysOpXIbroMGaUymqkJSvHeVKRsjHajwjCYdBsvGSvHauxbKfJmOxtXldtyLnyBMLKpBQCMzyYggrdapbVIceWZBtmslOQrXLABRoe", parsedLink.token)
|
||||
Assert.assertEquals(
|
||||
"XmOwRZnSFabCRhTywFbJWKXWVNPysOpXIbroMGaUymqkJSvHeVKRsjHajwjCYdBsvGSvHauxbKfJmOxtXldtyLnyBMLKpBQCMzyYggrdapbVIceWZBtmslOQrXLABRoe",
|
||||
parsedLink.token
|
||||
)
|
||||
Assert.assertEquals("vector.im", parsedLink.identityServer)
|
||||
Assert.assertEquals("Team2", parsedLink.roomName)
|
||||
Assert.assertEquals("hiphop5", parsedLink.inviterName)
|
||||
|
@ -45,7 +48,8 @@ class PermalinkParserTest {
|
|||
|
||||
@Test
|
||||
fun testParseLinkWIthEvent() {
|
||||
val rawInvite = "https://matrix.to/#/!OGEhHVWSdvArJzumhm:matrix.org/\$xuvJUVDJnwEeVjPx029rAOZ50difpmU_5gZk_T0jGfc?via=matrix.org&via=libera.chat&via=matrix.example.io"
|
||||
val rawInvite =
|
||||
"https://matrix.to/#/!OGEhHVWSdvArJzumhm:matrix.org/\$xuvJUVDJnwEeVjPx029rAOZ50difpmU_5gZk_T0jGfc?via=matrix.org&via=libera.chat&via=matrix.example.io"
|
||||
|
||||
val parsedLink = PermalinkParser.parse(rawInvite)
|
||||
Assert.assertTrue("Should be parsed as room link", parsedLink is PermalinkData.RoomLink)
|
||||
|
|
|
@ -21,5 +21,7 @@ import kotlinx.coroutines.asCoroutineDispatcher
|
|||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main,
|
||||
Executors.newSingleThreadExecutor().asCoroutineDispatcher())
|
||||
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(
|
||||
Main, Main, Main, Main,
|
||||
Executors.newSingleThreadExecutor().asCoroutineDispatcher()
|
||||
)
|
||||
|
|
|
@ -67,9 +67,11 @@ class DeactivateAccountTest : InstrumentedTest {
|
|||
val throwable = commonTestHelper.logAccountWithError(session.myUserId, TestConstants.PASSWORD)
|
||||
|
||||
// Test the error
|
||||
assertTrue(throwable is Failure.ServerError &&
|
||||
throwable.error.code == MatrixError.M_USER_DEACTIVATED &&
|
||||
throwable.error.message == "This account has been deactivated")
|
||||
assertTrue(
|
||||
throwable is Failure.ServerError &&
|
||||
throwable.error.code == MatrixError.M_USER_DEACTIVATED &&
|
||||
throwable.error.message == "This account has been deactivated"
|
||||
)
|
||||
|
||||
// Try to create an account with the deactivate account user id, it will fail (M_USER_IN_USE)
|
||||
val hs = commonTestHelper.createHomeServerConfig()
|
||||
|
@ -95,8 +97,10 @@ class DeactivateAccountTest : InstrumentedTest {
|
|||
|
||||
// Test the error
|
||||
accountCreationError.let {
|
||||
assertTrue(it is Failure.ServerError &&
|
||||
it.error.code == MatrixError.M_USER_IN_USE)
|
||||
assertTrue(
|
||||
it is Failure.ServerError &&
|
||||
it.error.code == MatrixError.M_USER_IN_USE
|
||||
)
|
||||
}
|
||||
|
||||
// No need to close the session, it has been deactivated
|
||||
|
|
|
@ -291,7 +291,8 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
|
|||
)
|
||||
)
|
||||
}
|
||||
}, it)
|
||||
}, it
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -308,7 +309,8 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
|
|||
requestID,
|
||||
roomId,
|
||||
bob.myUserId,
|
||||
bob.sessionParams.credentials.deviceId!!)
|
||||
bob.sessionParams.credentials.deviceId!!
|
||||
)
|
||||
|
||||
// we should reach SHOW SAS on both
|
||||
var alicePovTx: OutgoingSasVerificationTransaction? = null
|
||||
|
|
|
@ -29,15 +29,17 @@ import org.matrix.android.sdk.internal.raw.RawModule
|
|||
import org.matrix.android.sdk.internal.settings.SettingsModule
|
||||
import org.matrix.android.sdk.internal.util.system.SystemModule
|
||||
|
||||
@Component(modules = [
|
||||
TestModule::class,
|
||||
MatrixModule::class,
|
||||
NetworkModule::class,
|
||||
AuthModule::class,
|
||||
RawModule::class,
|
||||
SettingsModule::class,
|
||||
SystemModule::class
|
||||
])
|
||||
@Component(
|
||||
modules = [
|
||||
TestModule::class,
|
||||
MatrixModule::class,
|
||||
NetworkModule::class,
|
||||
AuthModule::class,
|
||||
RawModule::class,
|
||||
SettingsModule::class,
|
||||
SystemModule::class
|
||||
]
|
||||
)
|
||||
@MatrixScope
|
||||
internal interface TestMatrixComponent : MatrixComponent {
|
||||
|
||||
|
|
|
@ -76,9 +76,11 @@ class CryptoStoreTest : InstrumentedTest {
|
|||
}
|
||||
|
||||
val olmSession1 = OlmSession().apply {
|
||||
initOutboundSession(olmAccount1,
|
||||
initOutboundSession(
|
||||
olmAccount1,
|
||||
olmAccount1.identityKeys()[OlmAccount.JSON_KEY_IDENTITY_KEY],
|
||||
olmAccount1.oneTimeKeys()[OlmAccount.JSON_KEY_ONE_TIME_KEY]?.values?.first())
|
||||
olmAccount1.oneTimeKeys()[OlmAccount.JSON_KEY_ONE_TIME_KEY]?.values?.first()
|
||||
)
|
||||
}
|
||||
|
||||
val sessionId1 = olmSession1.sessionIdentifier()
|
||||
|
@ -93,9 +95,11 @@ class CryptoStoreTest : InstrumentedTest {
|
|||
}
|
||||
|
||||
val olmSession2 = OlmSession().apply {
|
||||
initOutboundSession(olmAccount2,
|
||||
initOutboundSession(
|
||||
olmAccount2,
|
||||
olmAccount2.identityKeys()[OlmAccount.JSON_KEY_IDENTITY_KEY],
|
||||
olmAccount2.oneTimeKeys()[OlmAccount.JSON_KEY_ONE_TIME_KEY]?.values?.first())
|
||||
olmAccount2.oneTimeKeys()[OlmAccount.JSON_KEY_ONE_TIME_KEY]?.values?.first()
|
||||
)
|
||||
}
|
||||
|
||||
val sessionId2 = olmSession2.sessionIdentifier()
|
||||
|
|
|
@ -299,11 +299,13 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
}
|
||||
|
||||
val importedResult = testHelper.doSync<ImportRoomKeysResult> {
|
||||
keysBackupService.restoreKeyBackupWithPassword(keyVersionResult!!,
|
||||
keysBackupService.restoreKeyBackupWithPassword(
|
||||
keyVersionResult!!,
|
||||
keyBackupPassword,
|
||||
null,
|
||||
null,
|
||||
null, it)
|
||||
null, it
|
||||
)
|
||||
}
|
||||
|
||||
assertEquals(3, importedResult.totalNumberOfKeys)
|
||||
|
|
|
@ -93,9 +93,11 @@ class ExportEncryptionTest {
|
|||
fail("## checkExportDecrypt1() failed : " + e.message)
|
||||
}
|
||||
|
||||
assertEquals("## checkExportDecrypt1() : expectedString $expectedString -- decodedString $decodedString",
|
||||
assertEquals(
|
||||
"## checkExportDecrypt1() : expectedString $expectedString -- decodedString $decodedString",
|
||||
expectedString,
|
||||
decodedString)
|
||||
decodedString
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -111,9 +113,11 @@ class ExportEncryptionTest {
|
|||
fail("## checkExportDecrypt2() failed : " + e.message)
|
||||
}
|
||||
|
||||
assertEquals("## checkExportDecrypt2() : expectedString $expectedString -- decodedString $decodedString",
|
||||
assertEquals(
|
||||
"## checkExportDecrypt2() : expectedString $expectedString -- decodedString $decodedString",
|
||||
expectedString,
|
||||
decodedString)
|
||||
decodedString
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -129,9 +133,11 @@ class ExportEncryptionTest {
|
|||
fail("## checkExportDecrypt3() failed : " + e.message)
|
||||
}
|
||||
|
||||
assertEquals("## checkExportDecrypt3() : expectedString $expectedString -- decodedString $decodedString",
|
||||
assertEquals(
|
||||
"## checkExportDecrypt3() : expectedString $expectedString -- decodedString $decodedString",
|
||||
expectedString,
|
||||
decodedString)
|
||||
decodedString
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -147,9 +153,11 @@ class ExportEncryptionTest {
|
|||
fail("## checkExportEncrypt1() failed : " + e.message)
|
||||
}
|
||||
|
||||
assertEquals("## checkExportEncrypt1() : expectedString $expectedString -- decodedString $decodedString",
|
||||
assertEquals(
|
||||
"## checkExportEncrypt1() : expectedString $expectedString -- decodedString $decodedString",
|
||||
expectedString,
|
||||
decodedString)
|
||||
decodedString
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -165,9 +173,11 @@ class ExportEncryptionTest {
|
|||
fail("## checkExportEncrypt2() failed : " + e.message)
|
||||
}
|
||||
|
||||
assertEquals("## checkExportEncrypt2() : expectedString $expectedString -- decodedString $decodedString",
|
||||
assertEquals(
|
||||
"## checkExportEncrypt2() : expectedString $expectedString -- decodedString $decodedString",
|
||||
expectedString,
|
||||
decodedString)
|
||||
decodedString
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -183,9 +193,11 @@ class ExportEncryptionTest {
|
|||
fail("## checkExportEncrypt3() failed : " + e.message)
|
||||
}
|
||||
|
||||
assertEquals("## checkExportEncrypt3() : expectedString $expectedString -- decodedString $decodedString",
|
||||
assertEquals(
|
||||
"## checkExportEncrypt3() : expectedString $expectedString -- decodedString $decodedString",
|
||||
expectedString,
|
||||
decodedString)
|
||||
decodedString
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -201,8 +213,10 @@ class ExportEncryptionTest {
|
|||
fail("## checkExportEncrypt4() failed : " + e.message)
|
||||
}
|
||||
|
||||
assertEquals("## checkExportEncrypt4() : expectedString $expectedString -- decodedString $decodedString",
|
||||
assertEquals(
|
||||
"## checkExportEncrypt4() : expectedString $expectedString -- decodedString $decodedString",
|
||||
expectedString,
|
||||
decodedString)
|
||||
decodedString
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -171,7 +171,10 @@ class UnwedgingTest : InstrumentedTest {
|
|||
// Let us wedge the session now. Set crypto state like after the first message
|
||||
Timber.i("## CRYPTO | testUnwedging: wedge the session now. Set crypto state like after the first message")
|
||||
|
||||
aliceCryptoStore.storeSession(OlmSessionWrapper(deserializeFromRealm<OlmSession>(oldSession)!!), bobSession.cryptoService().getMyDevice().identityKey()!!)
|
||||
aliceCryptoStore.storeSession(
|
||||
OlmSessionWrapper(deserializeFromRealm<OlmSession>(oldSession)!!),
|
||||
bobSession.cryptoService().getMyDevice().identityKey()!!
|
||||
)
|
||||
olmDevice.clearOlmSessionCache()
|
||||
Thread.sleep(6_000)
|
||||
|
||||
|
@ -218,7 +221,8 @@ class UnwedgingTest : InstrumentedTest {
|
|||
)
|
||||
)
|
||||
}
|
||||
}, it)
|
||||
}, it
|
||||
)
|
||||
}
|
||||
|
||||
// Wait until we received back the key
|
||||
|
|
|
@ -126,8 +126,16 @@ class XSigningTest : InstrumentedTest {
|
|||
assertNull("Alice should not see bob User key", bobKeysFromAlicePOV.userKey())
|
||||
assertNotNull("Alice can see bob SelfSigned key", bobKeysFromAlicePOV.selfSigningKey())
|
||||
|
||||
assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.masterKey()?.unpaddedBase64PublicKey, bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()?.masterKey()?.unpaddedBase64PublicKey)
|
||||
assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.selfSigningKey()?.unpaddedBase64PublicKey, bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()?.selfSigningKey()?.unpaddedBase64PublicKey)
|
||||
assertEquals(
|
||||
"Bob keys from alice pov should match",
|
||||
bobKeysFromAlicePOV.masterKey()?.unpaddedBase64PublicKey,
|
||||
bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()?.masterKey()?.unpaddedBase64PublicKey
|
||||
)
|
||||
assertEquals(
|
||||
"Bob keys from alice pov should match",
|
||||
bobKeysFromAlicePOV.selfSigningKey()?.unpaddedBase64PublicKey,
|
||||
bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()?.selfSigningKey()?.unpaddedBase64PublicKey
|
||||
)
|
||||
|
||||
assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted())
|
||||
|
||||
|
|
|
@ -145,7 +145,10 @@ class KeyShareTests : InstrumentedTest {
|
|||
Log.v("TEST", "Incoming request Session 1 (looking for $outGoingRequestId)")
|
||||
Log.v("TEST", "=========================")
|
||||
it.forEach { keyRequest ->
|
||||
Log.v("TEST", "[ts${keyRequest.localCreationTimestamp}] requestId ${keyRequest.requestId}, for sessionId ${keyRequest.requestBody?.sessionId} is ${keyRequest.state}")
|
||||
Log.v(
|
||||
"TEST",
|
||||
"[ts${keyRequest.localCreationTimestamp}] requestId ${keyRequest.requestId}, for sessionId ${keyRequest.requestBody?.sessionId} is ${keyRequest.state}"
|
||||
)
|
||||
}
|
||||
Log.v("TEST", "=========================")
|
||||
}
|
||||
|
@ -164,8 +167,10 @@ class KeyShareTests : InstrumentedTest {
|
|||
}
|
||||
|
||||
// Mark the device as trusted
|
||||
aliceSession.cryptoService().setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId,
|
||||
aliceSession2.sessionParams.deviceId ?: "")
|
||||
aliceSession.cryptoService().setDeviceVerification(
|
||||
DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId,
|
||||
aliceSession2.sessionParams.deviceId ?: ""
|
||||
)
|
||||
|
||||
// Re request
|
||||
aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
|
||||
|
@ -223,7 +228,8 @@ class KeyShareTests : InstrumentedTest {
|
|||
)
|
||||
)
|
||||
}
|
||||
}, it)
|
||||
}, it
|
||||
)
|
||||
}
|
||||
|
||||
// Also bootstrap keybackup on first session
|
||||
|
@ -282,8 +288,10 @@ class KeyShareTests : InstrumentedTest {
|
|||
})
|
||||
|
||||
val txId = "m.testVerif12"
|
||||
aliceVerificationService2.beginKeyVerification(VerificationMethod.SAS, aliceSession1.myUserId, aliceSession1.sessionParams.deviceId
|
||||
?: "", txId)
|
||||
aliceVerificationService2.beginKeyVerification(
|
||||
VerificationMethod.SAS, aliceSession1.myUserId, aliceSession1.sessionParams.deviceId
|
||||
?: "", txId
|
||||
)
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
|
@ -337,7 +345,8 @@ class KeyShareTests : InstrumentedTest {
|
|||
)
|
||||
)
|
||||
}
|
||||
}, it)
|
||||
}, it
|
||||
)
|
||||
}
|
||||
|
||||
// Create an encrypted room and send a couple of messages
|
||||
|
@ -371,7 +380,8 @@ class KeyShareTests : InstrumentedTest {
|
|||
)
|
||||
)
|
||||
}
|
||||
}, it)
|
||||
}, it
|
||||
)
|
||||
}
|
||||
|
||||
// Let alice invite bob
|
||||
|
|
|
@ -147,13 +147,15 @@ class WithHeldTests : InstrumentedTest {
|
|||
val aliceInterceptor = testHelper.getTestInterceptor(aliceSession)
|
||||
|
||||
// Simulate no OTK
|
||||
aliceInterceptor!!.addRule(MockOkHttpInterceptor.SimpleRule(
|
||||
"/keys/claim",
|
||||
200,
|
||||
"""
|
||||
aliceInterceptor!!.addRule(
|
||||
MockOkHttpInterceptor.SimpleRule(
|
||||
"/keys/claim",
|
||||
200,
|
||||
"""
|
||||
{ "one_time_keys" : {} }
|
||||
"""
|
||||
))
|
||||
)
|
||||
)
|
||||
Log.d("#TEST", "Recovery :${aliceSession.sessionParams.credentials.accessToken}")
|
||||
|
||||
val roomAlicePov = aliceSession.getRoom(testData.roomId)!!
|
||||
|
@ -184,7 +186,10 @@ class WithHeldTests : InstrumentedTest {
|
|||
|
||||
// Ensure that alice has marked the session to be shared with bob
|
||||
val sessionId = eventBobPOV!!.root.content.toModel<EncryptedEventContent>()!!.sessionId!!
|
||||
val chainIndex = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject(bobSession.myUserId, bobSession.sessionParams.credentials.deviceId)
|
||||
val chainIndex = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject(
|
||||
bobSession.myUserId,
|
||||
bobSession.sessionParams.credentials.deviceId
|
||||
)
|
||||
|
||||
Assert.assertEquals("Alice should have marked bob's device for this session", 0, chainIndex)
|
||||
// Add a new device for bob
|
||||
|
@ -202,7 +207,10 @@ class WithHeldTests : InstrumentedTest {
|
|||
}
|
||||
}
|
||||
|
||||
val chainIndex2 = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject(bobSecondSession.myUserId, bobSecondSession.sessionParams.credentials.deviceId)
|
||||
val chainIndex2 = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject(
|
||||
bobSecondSession.myUserId,
|
||||
bobSecondSession.sessionParams.credentials.deviceId
|
||||
)
|
||||
|
||||
Assert.assertEquals("Alice should have marked bob's device for this session", 1, chainIndex2)
|
||||
|
||||
|
|
|
@ -54,9 +54,11 @@ class KeysBackupPasswordTest : InstrumentedTest {
|
|||
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
|
||||
|
||||
// Reverse operation
|
||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(PASSWORD,
|
||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(
|
||||
PASSWORD,
|
||||
generatePrivateKeyResult.salt,
|
||||
generatePrivateKeyResult.iterations)
|
||||
generatePrivateKeyResult.iterations
|
||||
)
|
||||
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
|
||||
assertArrayEquals(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
|
||||
|
@ -102,9 +104,11 @@ class KeysBackupPasswordTest : InstrumentedTest {
|
|||
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
|
||||
|
||||
// Reverse operation, with bad password
|
||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(BAD_PASSWORD,
|
||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(
|
||||
BAD_PASSWORD,
|
||||
generatePrivateKeyResult.salt,
|
||||
generatePrivateKeyResult.iterations)
|
||||
generatePrivateKeyResult.iterations
|
||||
)
|
||||
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
|
||||
assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
|
||||
|
@ -122,9 +126,11 @@ class KeysBackupPasswordTest : InstrumentedTest {
|
|||
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
|
||||
|
||||
// Reverse operation, with bad iteration
|
||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(PASSWORD,
|
||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(
|
||||
PASSWORD,
|
||||
generatePrivateKeyResult.salt,
|
||||
500_001)
|
||||
500_001
|
||||
)
|
||||
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
|
||||
assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
|
||||
|
@ -142,9 +148,11 @@ class KeysBackupPasswordTest : InstrumentedTest {
|
|||
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
|
||||
|
||||
// Reverse operation, with bad iteration
|
||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(PASSWORD,
|
||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(
|
||||
PASSWORD,
|
||||
BAD_SALT,
|
||||
generatePrivateKeyResult.iterations)
|
||||
generatePrivateKeyResult.iterations
|
||||
)
|
||||
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
|
||||
assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
|
||||
|
@ -168,7 +176,8 @@ class KeysBackupPasswordTest : InstrumentedTest {
|
|||
116.toByte(), 224.toByte(), 229.toByte(), 224.toByte(), 9.toByte(), 3.toByte(), 178.toByte(), 162.toByte(),
|
||||
120.toByte(), 23.toByte(), 108.toByte(), 218.toByte(), 22.toByte(), 61.toByte(), 241.toByte(), 200.toByte(),
|
||||
235.toByte(), 173.toByte(), 236.toByte(), 100.toByte(), 115.toByte(), 247.toByte(), 33.toByte(), 132.toByte(),
|
||||
195.toByte(), 154.toByte(), 64.toByte(), 158.toByte(), 184.toByte(), 148.toByte(), 20.toByte(), 85.toByte())
|
||||
195.toByte(), 154.toByte(), 64.toByte(), 158.toByte(), 184.toByte(), 148.toByte(), 20.toByte(), 85.toByte()
|
||||
)
|
||||
|
||||
assertArrayEquals(privateKeyBytes, retrievedPrivateKey)
|
||||
}
|
||||
|
|
|
@ -272,10 +272,12 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertNotNull(decryption)
|
||||
// - Check decryptKeyBackupData() returns stg
|
||||
val sessionData = keysBackup
|
||||
.decryptKeyBackupData(keyBackupData,
|
||||
.decryptKeyBackupData(
|
||||
keyBackupData,
|
||||
session.olmInboundGroupSession!!.sessionIdentifier(),
|
||||
cryptoTestData.roomId,
|
||||
decryption!!)
|
||||
decryption!!
|
||||
)
|
||||
assertNotNull(sessionData)
|
||||
// - Compare the decrypted megolm key with the original one
|
||||
keysBackupTestHelper.assertKeysEquals(session.exportKeys(), sessionData)
|
||||
|
@ -297,7 +299,8 @@ class KeysBackupTest : InstrumentedTest {
|
|||
|
||||
// - Restore the e2e backup from the homeserver
|
||||
val importRoomKeysResult = testHelper.doSync<ImportRoomKeysResult> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
||||
null,
|
||||
null,
|
||||
|
@ -680,7 +683,8 @@ class KeysBackupTest : InstrumentedTest {
|
|||
val steps = ArrayList<StepProgressListener.Step>()
|
||||
|
||||
val importRoomKeysResult = testHelper.doSync<ImportRoomKeysResult> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
password,
|
||||
null,
|
||||
null,
|
||||
|
@ -771,7 +775,8 @@ class KeysBackupTest : InstrumentedTest {
|
|||
|
||||
// - Restore the e2e backup with the recovery key.
|
||||
val importRoomKeysResult = testHelper.doSync<ImportRoomKeysResult> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
||||
null,
|
||||
null,
|
||||
|
@ -1055,7 +1060,11 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertFalse(keysBackup2.isEnabled)
|
||||
|
||||
// - Validate the old device from the new one
|
||||
aliceSession2.cryptoService().setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession2.myUserId, oldDeviceId)
|
||||
aliceSession2.cryptoService().setDeviceVerification(
|
||||
DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
|
||||
aliceSession2.myUserId,
|
||||
oldDeviceId
|
||||
)
|
||||
|
||||
// -> Backup should automatically enable on the new device
|
||||
val latch4 = CountDownLatch(1)
|
||||
|
|
|
@ -88,10 +88,12 @@ internal class KeysBackupTestHelper(
|
|||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
|
||||
return KeysBackupScenarioData(cryptoTestData,
|
||||
return KeysBackupScenarioData(
|
||||
cryptoTestData,
|
||||
aliceKeys,
|
||||
prepareKeysBackupDataResult,
|
||||
aliceSession2)
|
||||
aliceSession2
|
||||
)
|
||||
}
|
||||
|
||||
fun prepareAndCreateKeysBackupData(keysBackup: KeysBackupService,
|
||||
|
|
|
@ -207,14 +207,16 @@ class QuadSTests : InstrumentedTest {
|
|||
|
||||
// Assert that can decrypt with both keys
|
||||
testHelper.runBlockingTest {
|
||||
aliceSession.sharedSecretStorageService().getSecret("my.secret",
|
||||
aliceSession.sharedSecretStorageService().getSecret(
|
||||
"my.secret",
|
||||
keyId1,
|
||||
RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)!!
|
||||
)
|
||||
}
|
||||
|
||||
testHelper.runBlockingTest {
|
||||
aliceSession.sharedSecretStorageService().getSecret("my.secret",
|
||||
aliceSession.sharedSecretStorageService().getSecret(
|
||||
"my.secret",
|
||||
keyId2,
|
||||
RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!!
|
||||
)
|
||||
|
@ -245,13 +247,15 @@ class QuadSTests : InstrumentedTest {
|
|||
|
||||
testHelper.runBlockingTest {
|
||||
try {
|
||||
aliceSession.sharedSecretStorageService().getSecret("my.secret",
|
||||
aliceSession.sharedSecretStorageService().getSecret(
|
||||
"my.secret",
|
||||
keyId1,
|
||||
RawBytesKeySpec.fromPassphrase(
|
||||
"A bad passphrase",
|
||||
key1Info.content?.passphrase?.salt ?: "",
|
||||
key1Info.content?.passphrase?.iterations ?: 0,
|
||||
null)
|
||||
null
|
||||
)
|
||||
)
|
||||
} catch (throwable: Throwable) {
|
||||
assert(throwable is SharedSecretStorageError.BadMac)
|
||||
|
@ -260,13 +264,15 @@ class QuadSTests : InstrumentedTest {
|
|||
|
||||
// Now try with correct key
|
||||
testHelper.runBlockingTest {
|
||||
aliceSession.sharedSecretStorageService().getSecret("my.secret",
|
||||
aliceSession.sharedSecretStorageService().getSecret(
|
||||
"my.secret",
|
||||
keyId1,
|
||||
RawBytesKeySpec.fromPassphrase(
|
||||
passphrase,
|
||||
key1Info.content?.passphrase?.salt ?: "",
|
||||
key1Info.content?.passphrase?.iterations ?: 0,
|
||||
null)
|
||||
null
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -321,7 +327,8 @@ class QuadSTests : InstrumentedTest {
|
|||
keyId,
|
||||
passphrase,
|
||||
emptyKeySigner,
|
||||
null)
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId")
|
||||
|
|
|
@ -75,10 +75,12 @@ class SASTest : InstrumentedTest {
|
|||
}
|
||||
bobVerificationService.addListener(bobListener)
|
||||
|
||||
val txID = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS,
|
||||
val txID = aliceVerificationService.beginKeyVerification(
|
||||
VerificationMethod.SAS,
|
||||
bobSession.myUserId,
|
||||
bobSession.cryptoService().getMyDevice().deviceId,
|
||||
null)
|
||||
null
|
||||
)
|
||||
assertNotNull("Alice should have a started transaction", txID)
|
||||
|
||||
val aliceKeyTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID!!)
|
||||
|
@ -467,8 +469,10 @@ class SASTest : InstrumentedTest {
|
|||
val aliceTx = aliceVerificationService.getExistingTransaction(bobUserId, verificationSAS!!) as SASDefaultVerificationTransaction
|
||||
val bobTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, verificationSAS) as SASDefaultVerificationTransaction
|
||||
|
||||
assertEquals("Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL),
|
||||
bobTx.getShortCodeRepresentation(SasMode.DECIMAL))
|
||||
assertEquals(
|
||||
"Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL),
|
||||
bobTx.getShortCodeRepresentation(SasMode.DECIMAL)
|
||||
)
|
||||
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
|
@ -544,7 +548,8 @@ class SASTest : InstrumentedTest {
|
|||
|
||||
// Assert that devices are verified
|
||||
val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(bobUserId, bobDeviceId)
|
||||
val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? = bobSession.cryptoService().getDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyDevice().deviceId)
|
||||
val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? =
|
||||
bobSession.cryptoService().getDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyDevice().deviceId)
|
||||
|
||||
assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified)
|
||||
assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
|
||||
|
@ -611,14 +616,16 @@ class SASTest : InstrumentedTest {
|
|||
requestID!!,
|
||||
cryptoTestData.roomId,
|
||||
bobSession.myUserId,
|
||||
bobSession.sessionParams.deviceId!!)
|
||||
bobSession.sessionParams.deviceId!!
|
||||
)
|
||||
|
||||
bobVerificationService.beginKeyVerificationInDMs(
|
||||
VerificationMethod.SAS,
|
||||
requestID!!,
|
||||
cryptoTestData.roomId,
|
||||
aliceSession.myUserId,
|
||||
aliceSession.sessionParams.deviceId!!)
|
||||
aliceSession.sessionParams.deviceId!!
|
||||
)
|
||||
|
||||
// we should reach SHOW SAS on both
|
||||
var alicePovTx: SasVerificationTransaction?
|
||||
|
|
|
@ -37,7 +37,8 @@ class QrCodeTest : InstrumentedTest {
|
|||
sharedSecret = "MTIzNDU2Nzg"
|
||||
)
|
||||
|
||||
private val value1 = "MATRIX\u0002\u0000\u0000\u000DMaTransaction\u0092Ñ0qCú²íq\u0087á®\u0013à\u0098\u0091\u000DÇéoÃ\"_²lq]hC«¢UMynd¤Ù.ô\u0091XäÏ\u0094ê\u008B«\u009Døl\u000F¿+\u008CË\u0014¤®õÁ\u008BA¥12345678"
|
||||
private val value1 =
|
||||
"MATRIX\u0002\u0000\u0000\u000DMaTransaction\u0092Ñ0qCú²íq\u0087á®\u0013à\u0098\u0091\u000DÇéoÃ\"_²lq]hC«¢UMynd¤Ù.ô\u0091XäÏ\u0094ê\u008B«\u009Døl\u000F¿+\u008CË\u0014¤®õÁ\u008BA¥12345678"
|
||||
|
||||
private val qrCode2 = QrCodeData.SelfVerifyingMasterKeyTrusted(
|
||||
transactionId = "MaTransaction",
|
||||
|
@ -46,7 +47,8 @@ class QrCodeTest : InstrumentedTest {
|
|||
sharedSecret = "MTIzNDU2Nzg"
|
||||
)
|
||||
|
||||
private val value2 = "MATRIX\u0002\u0001\u0000\u000DMaTransaction\u0092Ñ0qCú²íq\u0087á®\u0013à\u0098\u0091\u000DÇéoÃ\"_²lq]hC«¢UMynd¤Ù.ô\u0091XäÏ\u0094ê\u008B«\u009Døl\u000F¿+\u008CË\u0014¤®õÁ\u008BA¥12345678"
|
||||
private val value2 =
|
||||
"MATRIX\u0002\u0001\u0000\u000DMaTransaction\u0092Ñ0qCú²íq\u0087á®\u0013à\u0098\u0091\u000DÇéoÃ\"_²lq]hC«¢UMynd¤Ù.ô\u0091XäÏ\u0094ê\u008B«\u009Døl\u000F¿+\u008CË\u0014¤®õÁ\u008BA¥12345678"
|
||||
|
||||
private val qrCode3 = QrCodeData.SelfVerifyingMasterKeyNotTrusted(
|
||||
transactionId = "MaTransaction",
|
||||
|
@ -55,7 +57,8 @@ class QrCodeTest : InstrumentedTest {
|
|||
sharedSecret = "MTIzNDU2Nzg"
|
||||
)
|
||||
|
||||
private val value3 = "MATRIX\u0002\u0002\u0000\u000DMaTransactionMynd¤Ù.ô\u0091XäÏ\u0094ê\u008B«\u009Døl\u000F¿+\u008CË\u0014¤®õÁ\u008BA¥\u0092Ñ0qCú²íq\u0087á®\u0013à\u0098\u0091\u000DÇéoÃ\"_²lq]hC«¢U12345678"
|
||||
private val value3 =
|
||||
"MATRIX\u0002\u0002\u0000\u000DMaTransactionMynd¤Ù.ô\u0091XäÏ\u0094ê\u008B«\u009Døl\u000F¿+\u008CË\u0014¤®õÁ\u008BA¥\u0092Ñ0qCú²íq\u0087á®\u0013à\u0098\u0091\u000DÇéoÃ\"_²lq]hC«¢U12345678"
|
||||
|
||||
private val sharedSecretByteArray = "12345678".toByteArray(Charsets.ISO_8859_1)
|
||||
|
||||
|
|
|
@ -175,7 +175,8 @@ class VerificationTest : InstrumentedTest {
|
|||
)
|
||||
)
|
||||
}
|
||||
}, callback)
|
||||
}, callback
|
||||
)
|
||||
}
|
||||
|
||||
testHelper.doSync<Unit> { callback ->
|
||||
|
@ -191,7 +192,8 @@ class VerificationTest : InstrumentedTest {
|
|||
)
|
||||
)
|
||||
}
|
||||
}, callback)
|
||||
}, callback
|
||||
)
|
||||
}
|
||||
|
||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||
|
|
|
@ -71,10 +71,12 @@ class MarkdownParserTest : InstrumentedTest {
|
|||
testIdentity("")
|
||||
testIdentity("a")
|
||||
testIdentity("1")
|
||||
testIdentity("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et " +
|
||||
"dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea com" +
|
||||
"modo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pari" +
|
||||
"atur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
|
||||
testIdentity(
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et " +
|
||||
"dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea com" +
|
||||
"modo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pari" +
|
||||
"atur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -294,16 +296,20 @@ class MarkdownParserTest : InstrumentedTest {
|
|||
"$markdownPattern$name$markdownPattern"
|
||||
.let {
|
||||
markdownParser.parse(it)
|
||||
.expect(expectedText = it,
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name</$htmlExpectedTag>")
|
||||
.expect(
|
||||
expectedText = it,
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name</$htmlExpectedTag>"
|
||||
)
|
||||
}
|
||||
|
||||
// Test twice the same tag
|
||||
"$markdownPattern$name$markdownPattern and $markdownPattern$name bis$markdownPattern"
|
||||
.let {
|
||||
markdownParser.parse(it)
|
||||
.expect(expectedText = it,
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name</$htmlExpectedTag> and <$htmlExpectedTag>$name bis</$htmlExpectedTag>")
|
||||
.expect(
|
||||
expectedText = it,
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name</$htmlExpectedTag> and <$htmlExpectedTag>$name bis</$htmlExpectedTag>"
|
||||
)
|
||||
}
|
||||
|
||||
val textBefore = "a"
|
||||
|
@ -313,48 +319,60 @@ class MarkdownParserTest : InstrumentedTest {
|
|||
"$textBefore$markdownPattern$name$markdownPattern"
|
||||
.let {
|
||||
markdownParser.parse(it)
|
||||
.expect(expectedText = it,
|
||||
expectedFormattedText = "$textBefore<$htmlExpectedTag>$name</$htmlExpectedTag>")
|
||||
.expect(
|
||||
expectedText = it,
|
||||
expectedFormattedText = "$textBefore<$htmlExpectedTag>$name</$htmlExpectedTag>"
|
||||
)
|
||||
}
|
||||
|
||||
// With text before and space
|
||||
"$textBefore $markdownPattern$name$markdownPattern"
|
||||
.let {
|
||||
markdownParser.parse(it)
|
||||
.expect(expectedText = it,
|
||||
expectedFormattedText = "$textBefore <$htmlExpectedTag>$name</$htmlExpectedTag>")
|
||||
.expect(
|
||||
expectedText = it,
|
||||
expectedFormattedText = "$textBefore <$htmlExpectedTag>$name</$htmlExpectedTag>"
|
||||
)
|
||||
}
|
||||
|
||||
// With sticked text after
|
||||
"$markdownPattern$name$markdownPattern$textAfter"
|
||||
.let {
|
||||
markdownParser.parse(it)
|
||||
.expect(expectedText = it,
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name</$htmlExpectedTag>$textAfter")
|
||||
.expect(
|
||||
expectedText = it,
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name</$htmlExpectedTag>$textAfter"
|
||||
)
|
||||
}
|
||||
|
||||
// With space and text after
|
||||
"$markdownPattern$name$markdownPattern $textAfter"
|
||||
.let {
|
||||
markdownParser.parse(it)
|
||||
.expect(expectedText = it,
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name</$htmlExpectedTag> $textAfter")
|
||||
.expect(
|
||||
expectedText = it,
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name</$htmlExpectedTag> $textAfter"
|
||||
)
|
||||
}
|
||||
|
||||
// With sticked text before and text after
|
||||
"$textBefore$markdownPattern$name$markdownPattern$textAfter"
|
||||
.let {
|
||||
markdownParser.parse(it)
|
||||
.expect(expectedText = it,
|
||||
expectedFormattedText = "a<$htmlExpectedTag>$name</$htmlExpectedTag>$textAfter")
|
||||
.expect(
|
||||
expectedText = it,
|
||||
expectedFormattedText = "a<$htmlExpectedTag>$name</$htmlExpectedTag>$textAfter"
|
||||
)
|
||||
}
|
||||
|
||||
// With text before and after, with spaces
|
||||
"$textBefore $markdownPattern$name$markdownPattern $textAfter"
|
||||
.let {
|
||||
markdownParser.parse(it)
|
||||
.expect(expectedText = it,
|
||||
expectedFormattedText = "$textBefore <$htmlExpectedTag>$name</$htmlExpectedTag> $textAfter")
|
||||
.expect(
|
||||
expectedText = it,
|
||||
expectedFormattedText = "$textBefore <$htmlExpectedTag>$name</$htmlExpectedTag> $textAfter"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -366,16 +384,20 @@ class MarkdownParserTest : InstrumentedTest {
|
|||
"$markdownPattern$name\n$name$markdownPattern"
|
||||
.let {
|
||||
markdownParser.parse(it)
|
||||
.expect(expectedText = it,
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name$softBreak$name</$htmlExpectedTag>")
|
||||
.expect(
|
||||
expectedText = it,
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name$softBreak$name</$htmlExpectedTag>"
|
||||
)
|
||||
}
|
||||
|
||||
// With new line between two blocks
|
||||
"$markdownPattern$name$markdownPattern\n$markdownPattern$name$markdownPattern"
|
||||
.let {
|
||||
markdownParser.parse(it)
|
||||
.expect(expectedText = it,
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name</$htmlExpectedTag><br /><$htmlExpectedTag>$name</$htmlExpectedTag>")
|
||||
.expect(
|
||||
expectedText = it,
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name</$htmlExpectedTag><br /><$htmlExpectedTag>$name</$htmlExpectedTag>"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,27 +39,35 @@ internal class JsonCanonicalizerTest : InstrumentedTest {
|
|||
"""{"a":["c":"b","d":"e"]}""",
|
||||
"""{"a":["d":"b","c":"e"]}"""
|
||||
).forEach {
|
||||
assertEquals(it,
|
||||
JsonCanonicalizer.canonicalize(it))
|
||||
assertEquals(
|
||||
it,
|
||||
JsonCanonicalizer.canonicalize(it)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun reorderTest() {
|
||||
assertEquals("""{"a":true,"b":false}""",
|
||||
JsonCanonicalizer.canonicalize("""{"b":false,"a":true}"""))
|
||||
assertEquals(
|
||||
"""{"a":true,"b":false}""",
|
||||
JsonCanonicalizer.canonicalize("""{"b":false,"a":true}""")
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun realSampleTest() {
|
||||
assertEquals("""{"algorithms":["m.megolm.v1.aes-sha2","m.olm.v1.curve25519-aes-sha2"],"device_id":"VSCUNFSOUI","keys":{"curve25519:VSCUNFSOUI":"utyOjnhiQ73qNhi9HlN0OgWIowe5gthTS8r0r9TcJ3o","ed25519:VSCUNFSOUI":"qNhEt+Yggaajet0hX\/FjTRLfySgs65ldYyomm7PIx6U"},"user_id":"@benoitx:matrix.org"}""",
|
||||
JsonCanonicalizer.canonicalize("""{"algorithms":["m.megolm.v1.aes-sha2","m.olm.v1.curve25519-aes-sha2"],"device_id":"VSCUNFSOUI","user_id":"@benoitx:matrix.org","keys":{"curve25519:VSCUNFSOUI":"utyOjnhiQ73qNhi9HlN0OgWIowe5gthTS8r0r9TcJ3o","ed25519:VSCUNFSOUI":"qNhEt+Yggaajet0hX/FjTRLfySgs65ldYyomm7PIx6U"}}"""))
|
||||
assertEquals(
|
||||
"""{"algorithms":["m.megolm.v1.aes-sha2","m.olm.v1.curve25519-aes-sha2"],"device_id":"VSCUNFSOUI","keys":{"curve25519:VSCUNFSOUI":"utyOjnhiQ73qNhi9HlN0OgWIowe5gthTS8r0r9TcJ3o","ed25519:VSCUNFSOUI":"qNhEt+Yggaajet0hX\/FjTRLfySgs65ldYyomm7PIx6U"},"user_id":"@benoitx:matrix.org"}""",
|
||||
JsonCanonicalizer.canonicalize("""{"algorithms":["m.megolm.v1.aes-sha2","m.olm.v1.curve25519-aes-sha2"],"device_id":"VSCUNFSOUI","user_id":"@benoitx:matrix.org","keys":{"curve25519:VSCUNFSOUI":"utyOjnhiQ73qNhi9HlN0OgWIowe5gthTS8r0r9TcJ3o","ed25519:VSCUNFSOUI":"qNhEt+Yggaajet0hX/FjTRLfySgs65ldYyomm7PIx6U"}}""")
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun doubleQuoteTest() {
|
||||
assertEquals("{\"a\":\"\\\"\"}",
|
||||
JsonCanonicalizer.canonicalize("{\"a\":\"\\\"\"}"))
|
||||
assertEquals(
|
||||
"{\"a\":\"\\\"\"}",
|
||||
JsonCanonicalizer.canonicalize("{\"a\":\"\\\"\"}")
|
||||
)
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
|
@ -68,38 +76,52 @@ internal class JsonCanonicalizerTest : InstrumentedTest {
|
|||
|
||||
@Test
|
||||
fun matrixOrg001Test() {
|
||||
assertEquals("""{}""",
|
||||
JsonCanonicalizer.canonicalize("""{}"""))
|
||||
assertEquals(
|
||||
"""{}""",
|
||||
JsonCanonicalizer.canonicalize("""{}""")
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun matrixOrg002Test() {
|
||||
assertEquals("""{"one":1,"two":"Two"}""",
|
||||
JsonCanonicalizer.canonicalize("""{
|
||||
assertEquals(
|
||||
"""{"one":1,"two":"Two"}""",
|
||||
JsonCanonicalizer.canonicalize(
|
||||
"""{
|
||||
"one": 1,
|
||||
"two": "Two"
|
||||
}"""))
|
||||
}"""
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun matrixOrg003Test() {
|
||||
assertEquals("""{"a":"1","b":"2"}""",
|
||||
JsonCanonicalizer.canonicalize("""{
|
||||
assertEquals(
|
||||
"""{"a":"1","b":"2"}""",
|
||||
JsonCanonicalizer.canonicalize(
|
||||
"""{
|
||||
"b": "2",
|
||||
"a": "1"
|
||||
}"""))
|
||||
}"""
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun matrixOrg004Test() {
|
||||
assertEquals("""{"a":"1","b":"2"}""",
|
||||
JsonCanonicalizer.canonicalize("""{"b":"2","a":"1"}"""))
|
||||
assertEquals(
|
||||
"""{"a":"1","b":"2"}""",
|
||||
JsonCanonicalizer.canonicalize("""{"b":"2","a":"1"}""")
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun matrixOrg005Test() {
|
||||
assertEquals("""{"auth":{"mxid":"@john.doe:example.com","profile":{"display_name":"John Doe","three_pids":[{"address":"john.doe@example.org","medium":"email"},{"address":"123456789","medium":"msisdn"}]},"success":true}}""",
|
||||
JsonCanonicalizer.canonicalize("""{
|
||||
assertEquals(
|
||||
"""{"auth":{"mxid":"@john.doe:example.com","profile":{"display_name":"John Doe","three_pids":[{"address":"john.doe@example.org","medium":"email"},{"address":"123456789","medium":"msisdn"}]},"success":true}}""",
|
||||
JsonCanonicalizer.canonicalize(
|
||||
"""{
|
||||
"auth": {
|
||||
"success": true,
|
||||
"mxid": "@john.doe:example.com",
|
||||
|
@ -117,37 +139,53 @@ internal class JsonCanonicalizerTest : InstrumentedTest {
|
|||
]
|
||||
}
|
||||
}
|
||||
}"""))
|
||||
}"""
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun matrixOrg006Test() {
|
||||
assertEquals("""{"a":"日本語"}""",
|
||||
JsonCanonicalizer.canonicalize("""{
|
||||
assertEquals(
|
||||
"""{"a":"日本語"}""",
|
||||
JsonCanonicalizer.canonicalize(
|
||||
"""{
|
||||
"a": "日本語"
|
||||
}"""))
|
||||
}"""
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun matrixOrg007Test() {
|
||||
assertEquals("""{"日":1,"本":2}""",
|
||||
JsonCanonicalizer.canonicalize("""{
|
||||
assertEquals(
|
||||
"""{"日":1,"本":2}""",
|
||||
JsonCanonicalizer.canonicalize(
|
||||
"""{
|
||||
"本": 2,
|
||||
"日": 1
|
||||
}"""))
|
||||
}"""
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun matrixOrg008Test() {
|
||||
assertEquals("""{"a":"日"}""",
|
||||
JsonCanonicalizer.canonicalize("{\"a\": \"\u65E5\"}"))
|
||||
assertEquals(
|
||||
"""{"a":"日"}""",
|
||||
JsonCanonicalizer.canonicalize("{\"a\": \"\u65E5\"}")
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun matrixOrg009Test() {
|
||||
assertEquals("""{"a":null}""",
|
||||
JsonCanonicalizer.canonicalize("""{
|
||||
assertEquals(
|
||||
"""{"a":null}""",
|
||||
JsonCanonicalizer.canonicalize(
|
||||
"""{
|
||||
"a": null
|
||||
}"""))
|
||||
}"""
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,9 +26,18 @@ class StringOrderTest {
|
|||
|
||||
@Test
|
||||
fun testbasing() {
|
||||
assertEquals("a", StringOrderUtils.baseToString(StringOrderUtils.stringToBase("a", StringOrderUtils.DEFAULT_ALPHABET), StringOrderUtils.DEFAULT_ALPHABET))
|
||||
assertEquals("element", StringOrderUtils.baseToString(StringOrderUtils.stringToBase("element", StringOrderUtils.DEFAULT_ALPHABET), StringOrderUtils.DEFAULT_ALPHABET))
|
||||
assertEquals("matrix", StringOrderUtils.baseToString(StringOrderUtils.stringToBase("matrix", StringOrderUtils.DEFAULT_ALPHABET), StringOrderUtils.DEFAULT_ALPHABET))
|
||||
assertEquals(
|
||||
"a",
|
||||
StringOrderUtils.baseToString(StringOrderUtils.stringToBase("a", StringOrderUtils.DEFAULT_ALPHABET), StringOrderUtils.DEFAULT_ALPHABET)
|
||||
)
|
||||
assertEquals(
|
||||
"element",
|
||||
StringOrderUtils.baseToString(StringOrderUtils.stringToBase("element", StringOrderUtils.DEFAULT_ALPHABET), StringOrderUtils.DEFAULT_ALPHABET)
|
||||
)
|
||||
assertEquals(
|
||||
"matrix",
|
||||
StringOrderUtils.baseToString(StringOrderUtils.stringToBase("matrix", StringOrderUtils.DEFAULT_ALPHABET), StringOrderUtils.DEFAULT_ALPHABET)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -59,7 +59,8 @@ class ThreadMessagingTest : InstrumentedTest {
|
|||
val sentMessages = commonTestHelper.sendTextMessage(
|
||||
room = aliceRoom,
|
||||
message = textMessage,
|
||||
nbOfMessages = 1)
|
||||
nbOfMessages = 1
|
||||
)
|
||||
|
||||
val initMessage = sentMessages.first()
|
||||
|
||||
|
@ -73,7 +74,8 @@ class ThreadMessagingTest : InstrumentedTest {
|
|||
room = aliceRoom,
|
||||
message = "Reply In the above thread",
|
||||
numberOfMessages = 1,
|
||||
rootThreadEventId = initMessage.root.eventId.orEmpty())
|
||||
rootThreadEventId = initMessage.root.eventId.orEmpty()
|
||||
)
|
||||
|
||||
val replyInThread = repliesInThread.first()
|
||||
replyInThread.root.isThread().shouldBeTrue()
|
||||
|
@ -116,7 +118,8 @@ class ThreadMessagingTest : InstrumentedTest {
|
|||
val sentMessages = commonTestHelper.sendTextMessage(
|
||||
room = aliceRoom,
|
||||
message = textMessage,
|
||||
nbOfMessages = 1)
|
||||
nbOfMessages = 1
|
||||
)
|
||||
|
||||
val initMessage = sentMessages.first()
|
||||
|
||||
|
@ -134,7 +137,8 @@ class ThreadMessagingTest : InstrumentedTest {
|
|||
room = bobRoom,
|
||||
message = "Reply In the above thread",
|
||||
numberOfMessages = 1,
|
||||
rootThreadEventId = initMessage.root.eventId.orEmpty())
|
||||
rootThreadEventId = initMessage.root.eventId.orEmpty()
|
||||
)
|
||||
|
||||
val replyInThread = repliesInThread.first()
|
||||
replyInThread.root.isThread().shouldBeTrue()
|
||||
|
@ -190,7 +194,8 @@ class ThreadMessagingTest : InstrumentedTest {
|
|||
val sentMessages = commonTestHelper.sendTextMessage(
|
||||
room = aliceRoom,
|
||||
message = textMessage,
|
||||
nbOfMessages = 5)
|
||||
nbOfMessages = 5
|
||||
)
|
||||
|
||||
sentMessages.forEach {
|
||||
it.root.isThread().shouldBeFalse()
|
||||
|
@ -206,7 +211,8 @@ class ThreadMessagingTest : InstrumentedTest {
|
|||
room = aliceRoom,
|
||||
message = "Reply In the above thread",
|
||||
numberOfMessages = 40,
|
||||
rootThreadEventId = selectedInitMessage.root.eventId.orEmpty())
|
||||
rootThreadEventId = selectedInitMessage.root.eventId.orEmpty()
|
||||
)
|
||||
|
||||
repliesInThread.forEach {
|
||||
it.root.isThread().shouldBeTrue()
|
||||
|
@ -253,7 +259,8 @@ class ThreadMessagingTest : InstrumentedTest {
|
|||
val sentMessages = commonTestHelper.sendTextMessage(
|
||||
room = aliceRoom,
|
||||
message = textMessage,
|
||||
nbOfMessages = 5)
|
||||
nbOfMessages = 5
|
||||
)
|
||||
|
||||
sentMessages.forEach {
|
||||
it.root.isThread().shouldBeFalse()
|
||||
|
@ -270,7 +277,8 @@ class ThreadMessagingTest : InstrumentedTest {
|
|||
room = aliceRoom,
|
||||
message = "Alice reply In the above second thread message",
|
||||
numberOfMessages = 35,
|
||||
rootThreadEventId = secondMessage.root.eventId.orEmpty())
|
||||
rootThreadEventId = secondMessage.root.eventId.orEmpty()
|
||||
)
|
||||
|
||||
// Let's reply in timeline to that message from another user
|
||||
val bobSession = cryptoTestData.secondSession!!
|
||||
|
@ -282,14 +290,16 @@ class ThreadMessagingTest : InstrumentedTest {
|
|||
room = bobRoom,
|
||||
message = "Bob reply In the above first thread message",
|
||||
numberOfMessages = 42,
|
||||
rootThreadEventId = firstMessage.root.eventId.orEmpty())
|
||||
rootThreadEventId = firstMessage.root.eventId.orEmpty()
|
||||
)
|
||||
|
||||
// Bob will also reply in second thread 5 times
|
||||
val bobThreadRepliesInSecondMessage = commonTestHelper.replyInThreadMessage(
|
||||
room = bobRoom,
|
||||
message = "Another Bob reply In the above second thread message",
|
||||
numberOfMessages = 20,
|
||||
rootThreadEventId = secondMessage.root.eventId.orEmpty())
|
||||
rootThreadEventId = secondMessage.root.eventId.orEmpty()
|
||||
)
|
||||
|
||||
aliceThreadRepliesInSecondMessage.forEach {
|
||||
it.root.isThread().shouldBeTrue()
|
||||
|
|
|
@ -68,7 +68,8 @@ internal class ChunkEntityTest : InstrumentedTest {
|
|||
roomId = ROOM_ID,
|
||||
eventEntity = fakeEvent,
|
||||
direction = PaginationDirection.FORWARDS,
|
||||
roomMemberContentsByUser = emptyMap())
|
||||
roomMemberContentsByUser = emptyMap()
|
||||
)
|
||||
chunk.timelineEvents.size shouldBeEqualTo 1
|
||||
}
|
||||
}
|
||||
|
@ -84,12 +85,14 @@ internal class ChunkEntityTest : InstrumentedTest {
|
|||
roomId = ROOM_ID,
|
||||
eventEntity = fakeEvent,
|
||||
direction = PaginationDirection.FORWARDS,
|
||||
roomMemberContentsByUser = emptyMap())
|
||||
roomMemberContentsByUser = emptyMap()
|
||||
)
|
||||
chunk.addTimelineEvent(
|
||||
roomId = ROOM_ID,
|
||||
eventEntity = fakeEvent,
|
||||
direction = PaginationDirection.FORWARDS,
|
||||
roomMemberContentsByUser = emptyMap())
|
||||
roomMemberContentsByUser = emptyMap()
|
||||
)
|
||||
chunk.timelineEvents.size shouldBeEqualTo 1
|
||||
}
|
||||
}
|
||||
|
@ -162,7 +165,8 @@ internal class ChunkEntityTest : InstrumentedTest {
|
|||
roomId = roomId,
|
||||
eventEntity = fakeEvent,
|
||||
direction = direction,
|
||||
roomMemberContentsByUser = emptyMap())
|
||||
roomMemberContentsByUser = emptyMap()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -71,7 +71,8 @@ class TimelineForwardPaginationTest : InstrumentedTest {
|
|||
val sentMessages = commonTestHelper.sendTextMessage(
|
||||
roomFromAlicePOV,
|
||||
message,
|
||||
numberOfMessagesToSend)
|
||||
numberOfMessagesToSend
|
||||
)
|
||||
|
||||
// Alice clear the cache and restart the sync
|
||||
commonTestHelper.clearCacheAndSync(aliceSession)
|
||||
|
|
|
@ -94,7 +94,8 @@ class TimelinePreviousLastForwardTest : InstrumentedTest {
|
|||
val firstMessageFromAliceId = commonTestHelper.sendTextMessage(
|
||||
roomFromAlicePOV,
|
||||
firstMessage,
|
||||
30)
|
||||
30
|
||||
)
|
||||
.last()
|
||||
.eventId
|
||||
|
||||
|
@ -130,7 +131,8 @@ class TimelinePreviousLastForwardTest : InstrumentedTest {
|
|||
commonTestHelper.sendTextMessage(
|
||||
roomFromAlicePOV,
|
||||
secondMessage,
|
||||
30)
|
||||
30
|
||||
)
|
||||
|
||||
// Bob start to sync
|
||||
bobSession.startSync(true)
|
||||
|
|
|
@ -64,7 +64,8 @@ class TimelineSimpleBackPaginationTest : InstrumentedTest {
|
|||
commonTestHelper.sendTextMessage(
|
||||
roomFromAlicePOV,
|
||||
message,
|
||||
numberOfMessagesToSent)
|
||||
numberOfMessagesToSent
|
||||
)
|
||||
|
||||
val bobTimeline = roomFromBobPOV.timelineService().createTimeline(null, TimelineSettings(30))
|
||||
bobTimeline.start()
|
||||
|
|
|
@ -85,7 +85,8 @@ class SearchMessagesTest : InstrumentedTest {
|
|||
commonTestHelper.sendTextMessage(
|
||||
roomFromAlicePOV,
|
||||
MESSAGE,
|
||||
2)
|
||||
2
|
||||
)
|
||||
|
||||
val data = commonTestHelper.runBlockingTest {
|
||||
block.invoke(cryptoTestData)
|
||||
|
|
|
@ -177,21 +177,27 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
val commonTestHelper = CommonTestHelper(context())
|
||||
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
|
||||
|
||||
val spaceAInfo = createPublicSpace(session, "SpaceA", listOf(
|
||||
val spaceAInfo = createPublicSpace(
|
||||
session, "SpaceA", listOf(
|
||||
Triple("A1", true /*auto-join*/, true/*canonical*/),
|
||||
Triple("A2", true, true)
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
/* val spaceBInfo = */ createPublicSpace(session, "SpaceB", listOf(
|
||||
/* val spaceBInfo = */ createPublicSpace(
|
||||
session, "SpaceB", listOf(
|
||||
Triple("B1", true /*auto-join*/, true/*canonical*/),
|
||||
Triple("B2", true, true),
|
||||
Triple("B3", true, true)
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
val spaceCInfo = createPublicSpace(session, "SpaceC", listOf(
|
||||
val spaceCInfo = createPublicSpace(
|
||||
session, "SpaceC", listOf(
|
||||
Triple("C1", true /*auto-join*/, true/*canonical*/),
|
||||
Triple("C2", true, true)
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
// add C as a subspace of A
|
||||
val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
|
||||
|
@ -254,15 +260,19 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
val commonTestHelper = CommonTestHelper(context())
|
||||
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
|
||||
|
||||
val spaceAInfo = createPublicSpace(session, "SpaceA", listOf(
|
||||
val spaceAInfo = createPublicSpace(
|
||||
session, "SpaceA", listOf(
|
||||
Triple("A1", true /*auto-join*/, true/*canonical*/),
|
||||
Triple("A2", true, true)
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
val spaceCInfo = createPublicSpace(session, "SpaceC", listOf(
|
||||
val spaceCInfo = createPublicSpace(
|
||||
session, "SpaceC", listOf(
|
||||
Triple("C1", true /*auto-join*/, true/*canonical*/),
|
||||
Triple("C2", true, true)
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
// add C as a subspace of A
|
||||
val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
|
||||
|
@ -296,16 +306,20 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
val commonTestHelper = CommonTestHelper(context())
|
||||
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
|
||||
|
||||
val spaceAInfo = createPublicSpace(session, "SpaceA", listOf(
|
||||
val spaceAInfo = createPublicSpace(
|
||||
session, "SpaceA", listOf(
|
||||
Triple("A1", true /*auto-join*/, true/*canonical*/),
|
||||
Triple("A2", true, true)
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
val spaceBInfo = createPublicSpace(session, "SpaceB", listOf(
|
||||
val spaceBInfo = createPublicSpace(
|
||||
session, "SpaceB", listOf(
|
||||
Triple("B1", true /*auto-join*/, true/*canonical*/),
|
||||
Triple("B2", true, true),
|
||||
Triple("B3", true, true)
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
// add B as a subspace of A
|
||||
val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
|
||||
|
@ -315,10 +329,12 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
session.spaceService().setSpaceParent(spaceBInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
|
||||
}
|
||||
|
||||
val spaceCInfo = createPublicSpace(session, "SpaceC", listOf(
|
||||
val spaceCInfo = createPublicSpace(
|
||||
session, "SpaceC", listOf(
|
||||
Triple("C1", true /*auto-join*/, true/*canonical*/),
|
||||
Triple("C2", true, true)
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
|
||||
|
@ -446,21 +462,27 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
val commonTestHelper = CommonTestHelper(context())
|
||||
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
|
||||
|
||||
/* val spaceAInfo = */ createPublicSpace(session, "SpaceA", listOf(
|
||||
/* val spaceAInfo = */ createPublicSpace(
|
||||
session, "SpaceA", listOf(
|
||||
Triple("A1", true /*auto-join*/, true/*canonical*/),
|
||||
Triple("A2", true, true)
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
val spaceBInfo = createPublicSpace(session, "SpaceB", listOf(
|
||||
val spaceBInfo = createPublicSpace(
|
||||
session, "SpaceB", listOf(
|
||||
Triple("B1", true /*auto-join*/, true/*canonical*/),
|
||||
Triple("B2", true, true),
|
||||
Triple("B3", true, true)
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
val spaceCInfo = createPublicSpace(session, "SpaceC", listOf(
|
||||
val spaceCInfo = createPublicSpace(
|
||||
session, "SpaceC", listOf(
|
||||
Triple("C1", true /*auto-join*/, true/*canonical*/),
|
||||
Triple("C2", true, true)
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
|
||||
|
||||
|
@ -494,10 +516,12 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
val aliceSession = commonTestHelper.createAccount("Alice", SessionTestParams(true))
|
||||
val bobSession = commonTestHelper.createAccount("Bib", SessionTestParams(true))
|
||||
|
||||
val spaceAInfo = createPrivateSpace(aliceSession, "Private Space A", listOf(
|
||||
val spaceAInfo = createPrivateSpace(
|
||||
aliceSession, "Private Space A", listOf(
|
||||
Triple("General", true /*suggested*/, true/*canonical*/),
|
||||
Triple("Random", true, true)
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
commonTestHelper.runBlockingTest {
|
||||
aliceSession.getRoom(spaceAInfo.spaceId)!!.membershipService().invite(bobSession.myUserId, null)
|
||||
|
|
|
@ -28,8 +28,12 @@ internal class MathsHtmlNodeRenderer(private val context: HtmlNodeRendererContex
|
|||
val display = node.javaClass == DisplayMaths::class.java
|
||||
val contents = node.firstChild // should be the only child
|
||||
val latex = (contents as Text).literal
|
||||
val attributes = context.extendAttributes(node, if (display) "div" else "span", Collections.singletonMap("data-mx-maths",
|
||||
latex))
|
||||
val attributes = context.extendAttributes(
|
||||
node, if (display) "div" else "span", Collections.singletonMap(
|
||||
"data-mx-maths",
|
||||
latex
|
||||
)
|
||||
)
|
||||
html.tag(if (display) "div" else "span", attributes)
|
||||
html.tag("code")
|
||||
context.render(contents)
|
||||
|
|
|
@ -132,9 +132,11 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
|||
val matrixConfiguration = (appContext as MatrixConfiguration.Provider).providesMatrixConfiguration()
|
||||
instance = Matrix(appContext, matrixConfiguration)
|
||||
} else {
|
||||
throw IllegalStateException("Matrix is not initialized properly." +
|
||||
" If you want to manage your own Matrix instance use Matrix.createInstance" +
|
||||
" otherwise you should call Matrix.initialize or let your application implement MatrixConfiguration.Provider.")
|
||||
throw IllegalStateException(
|
||||
"Matrix is not initialized properly." +
|
||||
" If you want to manage your own Matrix instance use Matrix.createInstance" +
|
||||
" otherwise you should call Matrix.initialize or let your application implement MatrixConfiguration.Provider."
|
||||
)
|
||||
}
|
||||
}
|
||||
return instance
|
||||
|
|
|
@ -102,12 +102,14 @@ fun TermPolicies.toLocalizedLoginTerms(userLanguage: String,
|
|||
}
|
||||
}
|
||||
|
||||
result.add(LocalizedFlowDataLoginTerms(
|
||||
policyName = localizedFlowDataLoginTermsPolicyName,
|
||||
version = localizedFlowDataLoginTermsVersion,
|
||||
localizedUrl = localizedFlowDataLoginTermsLocalizedUrl,
|
||||
localizedName = localizedFlowDataLoginTermsLocalizedName
|
||||
))
|
||||
result.add(
|
||||
LocalizedFlowDataLoginTerms(
|
||||
policyName = localizedFlowDataLoginTermsPolicyName,
|
||||
version = localizedFlowDataLoginTermsVersion,
|
||||
localizedUrl = localizedFlowDataLoginTermsLocalizedUrl,
|
||||
localizedName = localizedFlowDataLoginTermsLocalizedName
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -88,8 +88,10 @@ fun RegistrationFlowResponse.toFlowResult(): FlowResult {
|
|||
val isMandatory = flows?.all { type in it.stages.orEmpty() } == true
|
||||
|
||||
val stage = when (type) {
|
||||
LoginFlowTypes.RECAPTCHA -> Stage.ReCaptcha(isMandatory, ((params?.get(type) as? Map<*, *>)?.get("public_key") as? String)
|
||||
?: "")
|
||||
LoginFlowTypes.RECAPTCHA -> Stage.ReCaptcha(
|
||||
isMandatory, ((params?.get(type) as? Map<*, *>)?.get("public_key") as? String)
|
||||
?: ""
|
||||
)
|
||||
LoginFlowTypes.DUMMY -> Stage.Dummy(isMandatory)
|
||||
LoginFlowTypes.TERMS -> Stage.Terms(isMandatory, params?.get(type) as? TermPolicies ?: emptyMap<String, String>())
|
||||
LoginFlowTypes.EMAIL_IDENTITY -> Stage.Email(isMandatory)
|
||||
|
|
|
@ -93,7 +93,8 @@ data class CryptoCrossSigningKey(
|
|||
userId = userId,
|
||||
usages = listOf(usage.value),
|
||||
keys = mapOf("ed25519:$b64key" to b64key),
|
||||
signatures = signMap)
|
||||
signatures = signMap
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,7 +68,8 @@ interface FileService {
|
|||
mxcUrl = messageContent.getFileUrl(),
|
||||
fileName = messageContent.getFileName(),
|
||||
mimeType = messageContent.mimeType,
|
||||
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt())
|
||||
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt()
|
||||
)
|
||||
|
||||
/**
|
||||
* Use this URI and pass it to intent using flag Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
|
|
|
@ -379,9 +379,11 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
throw MatrixIdFailure.InvalidMatrixId
|
||||
}
|
||||
|
||||
return getWellknownTask.execute(GetWellknownTask.Params(
|
||||
domain = matrixId.getDomain(),
|
||||
homeServerConnectionConfig = homeServerConnectionConfig)
|
||||
return getWellknownTask.execute(
|
||||
GetWellknownTask.Params(
|
||||
domain = matrixId.getDomain(),
|
||||
homeServerConnectionConfig = homeServerConnectionConfig
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -390,13 +392,15 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
password: String,
|
||||
initialDeviceName: String,
|
||||
deviceId: String?): Session {
|
||||
return directLoginTask.execute(DirectLoginTask.Params(
|
||||
homeServerConnectionConfig = homeServerConnectionConfig,
|
||||
userId = matrixId,
|
||||
password = password,
|
||||
deviceName = initialDeviceName,
|
||||
deviceId = deviceId
|
||||
))
|
||||
return directLoginTask.execute(
|
||||
DirectLoginTask.Params(
|
||||
homeServerConnectionConfig = homeServerConnectionConfig,
|
||||
userId = matrixId,
|
||||
password = password,
|
||||
deviceName = initialDeviceName,
|
||||
deviceId = deviceId
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {
|
||||
|
|
|
@ -21,9 +21,11 @@ import io.realm.annotations.RealmModule
|
|||
/**
|
||||
* Realm module for authentication classes
|
||||
*/
|
||||
@RealmModule(library = true,
|
||||
@RealmModule(
|
||||
library = true,
|
||||
classes = [
|
||||
SessionParamsEntity::class,
|
||||
PendingSessionEntity::class
|
||||
])
|
||||
]
|
||||
)
|
||||
internal class AuthRealmModule
|
||||
|
|
|
@ -44,7 +44,8 @@ internal class PendingSessionMapper @Inject constructor(moshi: Moshi) {
|
|||
resetPasswordData = resetPasswordData,
|
||||
currentSession = entity.currentSession,
|
||||
isRegistrationStarted = entity.isRegistrationStarted,
|
||||
currentThreePidData = threePidData)
|
||||
currentThreePidData = threePidData
|
||||
)
|
||||
}
|
||||
|
||||
fun map(sessionData: PendingSessionData?): PendingSessionEntity? {
|
||||
|
|
|
@ -54,6 +54,7 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
|
|||
sessionParams.userId,
|
||||
credentialsJson,
|
||||
homeServerConnectionConfigJson,
|
||||
sessionParams.isTokenValid)
|
||||
sessionParams.isTokenValid
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,21 +120,25 @@ internal class DefaultRegistrationWizard(
|
|||
RegisterAddThreePidTask.Params(
|
||||
threePid,
|
||||
pendingSessionData.clientSecret,
|
||||
pendingSessionData.sendAttempt))
|
||||
pendingSessionData.sendAttempt
|
||||
)
|
||||
)
|
||||
|
||||
pendingSessionData = pendingSessionData.copy(sendAttempt = pendingSessionData.sendAttempt + 1)
|
||||
.also { pendingSessionStore.savePendingSessionData(it) }
|
||||
|
||||
val params = RegistrationParams(
|
||||
auth = if (threePid is RegisterThreePid.Email) {
|
||||
AuthParams.createForEmailIdentity(safeSession,
|
||||
AuthParams.createForEmailIdentity(
|
||||
safeSession,
|
||||
ThreePidCredentials(
|
||||
clientSecret = pendingSessionData.clientSecret,
|
||||
sid = response.sid
|
||||
)
|
||||
)
|
||||
} else {
|
||||
AuthParams.createForMsisdnIdentity(safeSession,
|
||||
AuthParams.createForMsisdnIdentity(
|
||||
safeSession,
|
||||
ThreePidCredentials(
|
||||
clientSecret = pendingSessionData.clientSecret,
|
||||
sid = response.sid
|
||||
|
|
|
@ -712,8 +712,10 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
}.foldToCallback(callback)
|
||||
} else {
|
||||
val algorithm = getEncryptionAlgorithm(roomId)
|
||||
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON,
|
||||
algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON)
|
||||
val reason = String.format(
|
||||
MXCryptoError.UNABLE_TO_ENCRYPT_REASON,
|
||||
algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON
|
||||
)
|
||||
Timber.tag(loggerTag.value).e("encryptEventContent() : failed $reason")
|
||||
callback.onFailure(Failure.CryptoError(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_ENCRYPT, reason)))
|
||||
}
|
||||
|
|
|
@ -137,10 +137,14 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
|
|||
olmDevice.verifySignature(fingerprint, oneTimeKey.signalableJSONDictionary(), signature)
|
||||
isVerified = true
|
||||
} catch (e: Exception) {
|
||||
Timber.tag(loggerTag.value).d(e, "verifyKeyAndStartSession() : Verify error for otk: ${oneTimeKey.signalableJSONDictionary()}," +
|
||||
" signature:$signature fingerprint:$fingerprint")
|
||||
Timber.tag(loggerTag.value).e("verifyKeyAndStartSession() : Verify error for ${deviceInfo.userId}|${deviceInfo.deviceId} " +
|
||||
" - signable json ${oneTimeKey.signalableJSONDictionary()}")
|
||||
Timber.tag(loggerTag.value).d(
|
||||
e, "verifyKeyAndStartSession() : Verify error for otk: ${oneTimeKey.signalableJSONDictionary()}," +
|
||||
" signature:$signature fingerprint:$fingerprint"
|
||||
)
|
||||
Timber.tag(loggerTag.value).e(
|
||||
"verifyKeyAndStartSession() : Verify error for ${deviceInfo.userId}|${deviceInfo.deviceId} " +
|
||||
" - signable json ${oneTimeKey.signalableJSONDictionary()}"
|
||||
)
|
||||
errorMessage = e.message
|
||||
}
|
||||
|
||||
|
|
|
@ -96,11 +96,13 @@ internal class MXMegolmDecryption(private val userId: String,
|
|||
}
|
||||
|
||||
return runCatching {
|
||||
olmDevice.decryptGroupMessage(encryptedEventContent.ciphertext,
|
||||
olmDevice.decryptGroupMessage(
|
||||
encryptedEventContent.ciphertext,
|
||||
event.roomId,
|
||||
timeline,
|
||||
encryptedEventContent.sessionId,
|
||||
encryptedEventContent.senderKey)
|
||||
encryptedEventContent.senderKey
|
||||
)
|
||||
}
|
||||
.fold(
|
||||
{ olmDecryptionResult ->
|
||||
|
@ -132,9 +134,11 @@ internal class MXMegolmDecryption(private val userId: String,
|
|||
requestKeysForEvent(event, true)
|
||||
}
|
||||
// Encapsulate as withHeld exception
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.KEYS_WITHHELD,
|
||||
throw MXCryptoError.Base(
|
||||
MXCryptoError.ErrorType.KEYS_WITHHELD,
|
||||
withHeldInfo.code?.value ?: "",
|
||||
withHeldInfo.reason)
|
||||
withHeldInfo.reason
|
||||
)
|
||||
}
|
||||
|
||||
if (requestKeysOnFail) {
|
||||
|
@ -144,7 +148,8 @@ internal class MXMegolmDecryption(private val userId: String,
|
|||
throw MXCryptoError.Base(
|
||||
MXCryptoError.ErrorType.UNKNOWN_MESSAGE_INDEX,
|
||||
"UNKNOWN_MESSAGE_INDEX",
|
||||
null)
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
val reason = String.format(MXCryptoError.OLM_REASON, throwable.olmException.message)
|
||||
|
@ -153,7 +158,8 @@ internal class MXMegolmDecryption(private val userId: String,
|
|||
throw MXCryptoError.Base(
|
||||
MXCryptoError.ErrorType.OLM,
|
||||
reason,
|
||||
detailedReason)
|
||||
detailedReason
|
||||
)
|
||||
}
|
||||
if (throwable is MXCryptoError.Base) {
|
||||
if (
|
||||
|
@ -166,9 +172,11 @@ internal class MXMegolmDecryption(private val userId: String,
|
|||
requestKeysForEvent(event, true)
|
||||
}
|
||||
// Encapsulate as withHeld exception
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.KEYS_WITHHELD,
|
||||
throw MXCryptoError.Base(
|
||||
MXCryptoError.ErrorType.KEYS_WITHHELD,
|
||||
withHeldInfo.code?.value ?: "",
|
||||
withHeldInfo.reason)
|
||||
withHeldInfo.reason
|
||||
)
|
||||
} else {
|
||||
// This is un-used in Matrix Android SDK2, not sure if needed
|
||||
// addEventToPendingList(event, timeline)
|
||||
|
@ -298,13 +306,15 @@ internal class MXMegolmDecryption(private val userId: String,
|
|||
}
|
||||
|
||||
Timber.tag(loggerTag.value).i("onRoomKeyEvent addInboundGroupSession ${roomKeyContent.sessionId}")
|
||||
val added = olmDevice.addInboundGroupSession(roomKeyContent.sessionId,
|
||||
val added = olmDevice.addInboundGroupSession(
|
||||
roomKeyContent.sessionId,
|
||||
roomKeyContent.sessionKey,
|
||||
roomKeyContent.roomId,
|
||||
senderKey,
|
||||
forwardingCurve25519KeyChain,
|
||||
keysClaimed,
|
||||
exportFormat)
|
||||
exportFormat
|
||||
)
|
||||
|
||||
if (added) {
|
||||
defaultKeysBackupService.maybeBackupKeys()
|
||||
|
|
|
@ -56,6 +56,7 @@ internal class MXMegolmDecryptionFactory @Inject constructor(
|
|||
sendToDeviceTask,
|
||||
coroutineDispatchers,
|
||||
cryptoCoroutineScope,
|
||||
eventsManager)
|
||||
eventsManager
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -337,8 +337,9 @@ internal class MXMegolmEncryption(
|
|||
sessionId: String,
|
||||
senderKey: String?,
|
||||
code: WithHeldCode) {
|
||||
Timber.tag(loggerTag.value).d("notifyKeyWithHeld() :sending withheld for session:$sessionId and code $code to" +
|
||||
" ${targets.joinToString { "${it.userId}|${it.deviceId}" }}"
|
||||
Timber.tag(loggerTag.value).d(
|
||||
"notifyKeyWithHeld() :sending withheld for session:$sessionId and code $code to" +
|
||||
" ${targets.joinToString { "${it.userId}|${it.deviceId}" }}"
|
||||
)
|
||||
val withHeldContent = RoomKeyWithHeldContent(
|
||||
roomId = roomId,
|
||||
|
|
|
@ -36,6 +36,7 @@ internal class SharedWithHelper(
|
|||
userId = deviceInfo.userId,
|
||||
deviceId = deviceInfo.deviceId,
|
||||
deviceIdentityKey = deviceInfo.identityKey() ?: "",
|
||||
chainIndex = chainIndex)
|
||||
chainIndex = chainIndex
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,20 +45,26 @@ internal class MXOlmDecryption(
|
|||
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
||||
val olmEventContent = event.content.toModel<OlmEventContent>() ?: run {
|
||||
Timber.tag(loggerTag.value).e("## decryptEvent() : bad event format")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_EVENT_FORMAT,
|
||||
MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON)
|
||||
throw MXCryptoError.Base(
|
||||
MXCryptoError.ErrorType.BAD_EVENT_FORMAT,
|
||||
MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON
|
||||
)
|
||||
}
|
||||
|
||||
val cipherText = olmEventContent.ciphertext ?: run {
|
||||
Timber.tag(loggerTag.value).e("## decryptEvent() : missing cipher text")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_CIPHER_TEXT,
|
||||
MXCryptoError.MISSING_CIPHER_TEXT_REASON)
|
||||
throw MXCryptoError.Base(
|
||||
MXCryptoError.ErrorType.MISSING_CIPHER_TEXT,
|
||||
MXCryptoError.MISSING_CIPHER_TEXT_REASON
|
||||
)
|
||||
}
|
||||
|
||||
val senderKey = olmEventContent.senderKey ?: run {
|
||||
Timber.tag(loggerTag.value).e("## decryptEvent() : missing sender key")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY,
|
||||
MXCryptoError.MISSING_SENDER_KEY_TEXT_REASON)
|
||||
throw MXCryptoError.Base(
|
||||
MXCryptoError.ErrorType.MISSING_SENDER_KEY,
|
||||
MXCryptoError.MISSING_SENDER_KEY_TEXT_REASON
|
||||
)
|
||||
}
|
||||
|
||||
val messageAny = cipherText[olmDevice.deviceCurve25519Key] ?: run {
|
||||
|
@ -98,52 +104,70 @@ internal class MXOlmDecryption(
|
|||
}
|
||||
|
||||
if (olmPayloadContent.recipient != userId) {
|
||||
Timber.tag(loggerTag.value).e("## decryptEvent() : Event ${event.eventId}:" +
|
||||
" Intended recipient ${olmPayloadContent.recipient} does not match our id $userId")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT,
|
||||
String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient))
|
||||
Timber.tag(loggerTag.value).e(
|
||||
"## decryptEvent() : Event ${event.eventId}:" +
|
||||
" Intended recipient ${olmPayloadContent.recipient} does not match our id $userId"
|
||||
)
|
||||
throw MXCryptoError.Base(
|
||||
MXCryptoError.ErrorType.BAD_RECIPIENT,
|
||||
String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient)
|
||||
)
|
||||
}
|
||||
|
||||
val recipientKeys = olmPayloadContent.recipientKeys ?: run {
|
||||
Timber.tag(loggerTag.value).e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'recipient_keys'" +
|
||||
" property; cannot prevent unknown-key attack")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
|
||||
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys"))
|
||||
Timber.tag(loggerTag.value).e(
|
||||
"## decryptEvent() : Olm event (id=${event.eventId}) contains no 'recipient_keys'" +
|
||||
" property; cannot prevent unknown-key attack"
|
||||
)
|
||||
throw MXCryptoError.Base(
|
||||
MXCryptoError.ErrorType.MISSING_PROPERTY,
|
||||
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys")
|
||||
)
|
||||
}
|
||||
|
||||
val ed25519 = recipientKeys["ed25519"]
|
||||
|
||||
if (ed25519 != olmDevice.deviceEd25519Key) {
|
||||
Timber.tag(loggerTag.value).e("## decryptEvent() : Event ${event.eventId}: Intended recipient ed25519 key $ed25519 did not match ours")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT_KEY,
|
||||
MXCryptoError.BAD_RECIPIENT_KEY_REASON)
|
||||
throw MXCryptoError.Base(
|
||||
MXCryptoError.ErrorType.BAD_RECIPIENT_KEY,
|
||||
MXCryptoError.BAD_RECIPIENT_KEY_REASON
|
||||
)
|
||||
}
|
||||
|
||||
if (olmPayloadContent.sender.isNullOrBlank()) {
|
||||
Timber.tag(loggerTag.value)
|
||||
.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'sender' property; cannot prevent unknown-key attack")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
|
||||
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender"))
|
||||
throw MXCryptoError.Base(
|
||||
MXCryptoError.ErrorType.MISSING_PROPERTY,
|
||||
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender")
|
||||
)
|
||||
}
|
||||
|
||||
if (olmPayloadContent.sender != event.senderId) {
|
||||
Timber.tag(loggerTag.value)
|
||||
.e("Event ${event.eventId}: sender ${olmPayloadContent.sender} does not match reported sender ${event.senderId}")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.FORWARDED_MESSAGE,
|
||||
String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender))
|
||||
throw MXCryptoError.Base(
|
||||
MXCryptoError.ErrorType.FORWARDED_MESSAGE,
|
||||
String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender)
|
||||
)
|
||||
}
|
||||
|
||||
if (olmPayloadContent.roomId != event.roomId) {
|
||||
Timber.tag(loggerTag.value)
|
||||
.e("## decryptEvent() : Event ${event.eventId}: room ${olmPayloadContent.roomId} does not match reported room ${event.roomId}")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ROOM,
|
||||
String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.roomId))
|
||||
throw MXCryptoError.Base(
|
||||
MXCryptoError.ErrorType.BAD_ROOM,
|
||||
String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.roomId)
|
||||
)
|
||||
}
|
||||
|
||||
val keys = olmPayloadContent.keys ?: run {
|
||||
Timber.tag(loggerTag.value).e("## decryptEvent failed : null keys")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT,
|
||||
MXCryptoError.MISSING_CIPHER_TEXT_REASON)
|
||||
throw MXCryptoError.Base(
|
||||
MXCryptoError.ErrorType.UNABLE_TO_DECRYPT,
|
||||
MXCryptoError.MISSING_CIPHER_TEXT_REASON
|
||||
)
|
||||
}
|
||||
|
||||
return MXEventDecryptionResult(
|
||||
|
|
|
@ -26,6 +26,7 @@ internal class MXOlmDecryptionFactory @Inject constructor(private val olmDevice:
|
|||
fun create(): MXOlmDecryption {
|
||||
return MXOlmDecryption(
|
||||
olmDevice,
|
||||
userId)
|
||||
userId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ internal class MXOlmEncryptionFactory @Inject constructor(private val olmDevice:
|
|||
cryptoStore,
|
||||
messageEncrypter,
|
||||
deviceListManager,
|
||||
ensureOlmSessionsForUsersAction)
|
||||
ensureOlmSessionsForUsersAction
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -566,8 +566,10 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
}
|
||||
|
||||
// Sign the other MasterKey with our UserSigning key
|
||||
val newSignature = JsonCanonicalizer.getCanonicalJson(Map::class.java,
|
||||
otherMasterKeys.signalableJSONDictionary()).let { userPkSigning?.sign(it) }
|
||||
val newSignature = JsonCanonicalizer.getCanonicalJson(
|
||||
Map::class.java,
|
||||
otherMasterKeys.signalableJSONDictionary()
|
||||
).let { userPkSigning?.sign(it) }
|
||||
|
||||
if (newSignature == null) {
|
||||
// race??
|
||||
|
@ -684,7 +686,8 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
val otherSSKSignature = otherDevice.signatures?.get(otherUserId)?.get("ed25519:${otherKeys.selfSigningKey()?.unpaddedBase64PublicKey}")
|
||||
?: return legacyFallbackTrust(
|
||||
locallyTrusted,
|
||||
DeviceTrustResult.MissingDeviceSignature(otherDeviceId, otherKeys.selfSigningKey()
|
||||
DeviceTrustResult.MissingDeviceSignature(
|
||||
otherDeviceId, otherKeys.selfSigningKey()
|
||||
?.unpaddedBase64PublicKey
|
||||
?: ""
|
||||
)
|
||||
|
@ -733,7 +736,8 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
val otherSSKSignature = otherDevice.signatures?.get(otherKeys.userId)?.get("ed25519:${otherKeys.selfSigningKey()?.unpaddedBase64PublicKey}")
|
||||
?: return legacyFallbackTrust(
|
||||
locallyTrusted,
|
||||
DeviceTrustResult.MissingDeviceSignature(otherDevice.deviceId, otherKeys.selfSigningKey()
|
||||
DeviceTrustResult.MissingDeviceSignature(
|
||||
otherDevice.deviceId, otherKeys.selfSigningKey()
|
||||
?.unpaddedBase64PublicKey
|
||||
?: ""
|
||||
)
|
||||
|
|
|
@ -516,7 +516,8 @@ internal class DefaultKeysBackupService @Inject constructor(
|
|||
UpdateKeysBackupVersionBody(
|
||||
algorithm = keysBackupVersion.algorithm,
|
||||
authData = newMegolmBackupAuthDataWithNewSignature.toJsonDict(),
|
||||
version = keysBackupVersion.version)
|
||||
version = keysBackupVersion.version
|
||||
)
|
||||
}
|
||||
|
||||
// And send it to the homeserver
|
||||
|
@ -719,14 +720,18 @@ internal class DefaultKeysBackupService @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
Timber.v("restoreKeysWithRecoveryKey: Decrypted ${sessionsData.size} keys out" +
|
||||
" of $sessionsFromHsCount from the backup store on the homeserver")
|
||||
Timber.v(
|
||||
"restoreKeysWithRecoveryKey: Decrypted ${sessionsData.size} keys out" +
|
||||
" of $sessionsFromHsCount from the backup store on the homeserver"
|
||||
)
|
||||
|
||||
// Do not trigger a backup for them if they come from the backup version we are using
|
||||
val backUp = keysVersionResult.version != keysBackupVersion?.version
|
||||
if (backUp) {
|
||||
Timber.v("restoreKeysWithRecoveryKey: Those keys will be backed up" +
|
||||
" to backup version: ${keysBackupVersion?.version}")
|
||||
Timber.v(
|
||||
"restoreKeysWithRecoveryKey: Those keys will be backed up" +
|
||||
" to backup version: ${keysBackupVersion?.version}"
|
||||
)
|
||||
}
|
||||
|
||||
// Import them into the crypto store
|
||||
|
@ -801,11 +806,15 @@ internal class DefaultKeysBackupService @Inject constructor(
|
|||
// Get key for the room and for the session
|
||||
val data = getRoomSessionDataTask.execute(GetRoomSessionDataTask.Params(roomId, sessionId, version))
|
||||
// Convert to KeysBackupData
|
||||
KeysBackupData(mutableMapOf(
|
||||
roomId to RoomKeysBackupData(mutableMapOf(
|
||||
sessionId to data
|
||||
))
|
||||
))
|
||||
KeysBackupData(
|
||||
mutableMapOf(
|
||||
roomId to RoomKeysBackupData(
|
||||
mutableMapOf(
|
||||
sessionId to data
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
} else if (roomId != null) {
|
||||
// Get all keys for the room
|
||||
val data = getRoomSessionsDataTask.execute(GetRoomSessionsDataTask.Params(roomId, version))
|
||||
|
@ -1326,7 +1335,8 @@ internal class DefaultKeysBackupService @Inject constructor(
|
|||
"sender_key" to sessionData.senderKey,
|
||||
"sender_claimed_keys" to sessionData.senderClaimedKeys,
|
||||
"forwarding_curve25519_key_chain" to (sessionData.forwardingCurve25519KeyChain.orEmpty()),
|
||||
"session_key" to sessionData.sessionKey)
|
||||
"session_key" to sessionData.sessionKey
|
||||
)
|
||||
|
||||
val json = MoshiProvider.providesMoshi()
|
||||
.adapter(Map::class.java)
|
||||
|
@ -1354,7 +1364,8 @@ internal class DefaultKeysBackupService @Inject constructor(
|
|||
sessionData = mapOf(
|
||||
"ciphertext" to encryptedSessionBackupData.mCipherText,
|
||||
"mac" to encryptedSessionBackupData.mMac,
|
||||
"ephemeral" to encryptedSessionBackupData.mEphemeralKey)
|
||||
"ephemeral" to encryptedSessionBackupData.mEphemeralKey
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,8 @@ internal class DefaultDeleteRoomSessionDataTask @Inject constructor(
|
|||
roomKeysApi.deleteRoomSessionData(
|
||||
params.roomId,
|
||||
params.sessionId,
|
||||
params.version)
|
||||
params.version
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,8 @@ internal class DefaultDeleteRoomSessionsDataTask @Inject constructor(
|
|||
return executeRequest(globalErrorReceiver) {
|
||||
roomKeysApi.deleteRoomSessionsData(
|
||||
params.roomId,
|
||||
params.version)
|
||||
params.version
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,8 @@ internal class DefaultGetRoomSessionDataTask @Inject constructor(
|
|||
roomKeysApi.getRoomSessionData(
|
||||
params.roomId,
|
||||
params.sessionId,
|
||||
params.version)
|
||||
params.version
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,8 @@ internal class DefaultGetRoomSessionsDataTask @Inject constructor(
|
|||
return executeRequest(globalErrorReceiver) {
|
||||
roomKeysApi.getRoomSessionsData(
|
||||
params.roomId,
|
||||
params.version)
|
||||
params.version
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,8 @@ internal class DefaultStoreRoomSessionDataTask @Inject constructor(
|
|||
params.roomId,
|
||||
params.sessionId,
|
||||
params.version,
|
||||
params.keyBackupData)
|
||||
params.keyBackupData
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,8 @@ internal class DefaultStoreRoomSessionsDataTask @Inject constructor(
|
|||
roomKeysApi.storeRoomSessionsData(
|
||||
params.roomId,
|
||||
params.version,
|
||||
params.roomKeysBackupData)
|
||||
params.roomKeysBackupData
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,8 @@ internal class DefaultStoreSessionsDataTask @Inject constructor(
|
|||
return executeRequest(globalErrorReceiver) {
|
||||
roomKeysApi.storeSessionsData(
|
||||
params.version,
|
||||
params.keysBackupData)
|
||||
params.keysBackupData
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -213,7 +213,8 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
|||
secretKey.privateKey,
|
||||
ByteArray(32) { 0.toByte() },
|
||||
secretName.toByteArray(),
|
||||
64)
|
||||
64
|
||||
)
|
||||
|
||||
// The first 32 bytes are used as the AES key, and the next 32 bytes are used as the MAC key
|
||||
val aesKey = pseudoRandomKey.copyOfRange(0, 32)
|
||||
|
@ -255,7 +256,8 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
|||
secretKey.privateKey,
|
||||
ByteArray(32) { 0.toByte() },
|
||||
secretName.toByteArray(),
|
||||
64)
|
||||
64
|
||||
)
|
||||
|
||||
// The first 32 bytes are used as the AES key, and the next 32 bytes are used as the MAC key
|
||||
val aesKey = pseudoRandomKey.copyOfRange(0, 32)
|
||||
|
|
|
@ -38,7 +38,8 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.WithHeldSessionEnti
|
|||
/**
|
||||
* Realm module for Crypto store classes
|
||||
*/
|
||||
@RealmModule(library = true,
|
||||
@RealmModule(
|
||||
library = true,
|
||||
classes = [
|
||||
CryptoMetadataEntity::class,
|
||||
CryptoRoomEntity::class,
|
||||
|
@ -57,5 +58,6 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.WithHeldSessionEnti
|
|||
WithHeldSessionEntity::class,
|
||||
SharedSessionEntity::class,
|
||||
OutboundGroupSessionInfoEntity::class
|
||||
])
|
||||
]
|
||||
)
|
||||
internal class RealmCryptoStoreModule
|
||||
|
|
|
@ -27,11 +27,13 @@ import javax.inject.Inject
|
|||
|
||||
internal class CrossSigningKeysMapper @Inject constructor(moshi: Moshi) {
|
||||
|
||||
private val signaturesAdapter = moshi.adapter<Map<String, Map<String, String>>>(Types.newParameterizedType(
|
||||
Map::class.java,
|
||||
String::class.java,
|
||||
Any::class.java
|
||||
))
|
||||
private val signaturesAdapter = moshi.adapter<Map<String, Map<String, String>>>(
|
||||
Types.newParameterizedType(
|
||||
Map::class.java,
|
||||
String::class.java,
|
||||
Any::class.java
|
||||
)
|
||||
)
|
||||
|
||||
fun update(keyInfo: KeyInfoEntity, cryptoCrossSigningKey: CryptoCrossSigningKey) {
|
||||
// update signatures?
|
||||
|
|
|
@ -72,16 +72,20 @@ internal class MigrateCryptoTo004(realm: DynamicRealm) : RealmMigrator(realm, 4)
|
|||
?.addField(CryptoMetadataEntityFields.X_SIGN_SELF_SIGNED_PRIVATE_KEY, String::class.java)
|
||||
|
||||
val moshi = Moshi.Builder().add(SerializeNulls.JSON_ADAPTER_FACTORY).build()
|
||||
val listMigrationAdapter = moshi.adapter<List<String>>(Types.newParameterizedType(
|
||||
List::class.java,
|
||||
String::class.java,
|
||||
Any::class.java
|
||||
))
|
||||
val mapMigrationAdapter = moshi.adapter<JsonDict>(Types.newParameterizedType(
|
||||
Map::class.java,
|
||||
String::class.java,
|
||||
Any::class.java
|
||||
))
|
||||
val listMigrationAdapter = moshi.adapter<List<String>>(
|
||||
Types.newParameterizedType(
|
||||
List::class.java,
|
||||
String::class.java,
|
||||
Any::class.java
|
||||
)
|
||||
)
|
||||
val mapMigrationAdapter = moshi.adapter<JsonDict>(
|
||||
Types.newParameterizedType(
|
||||
Map::class.java,
|
||||
String::class.java,
|
||||
Any::class.java
|
||||
)
|
||||
)
|
||||
|
||||
realm.schema.get("DeviceInfoEntity")
|
||||
?.addField(DeviceInfoEntityFields.USER_ID, String::class.java)
|
||||
|
|
|
@ -27,21 +27,27 @@ import timber.log.Timber
|
|||
internal object CryptoMapper {
|
||||
|
||||
private val moshi = Moshi.Builder().add(SerializeNulls.JSON_ADAPTER_FACTORY).build()
|
||||
private val listMigrationAdapter = moshi.adapter<List<String>>(Types.newParameterizedType(
|
||||
List::class.java,
|
||||
String::class.java,
|
||||
Any::class.java
|
||||
))
|
||||
private val mapMigrationAdapter = moshi.adapter<JsonDict>(Types.newParameterizedType(
|
||||
Map::class.java,
|
||||
String::class.java,
|
||||
Any::class.java
|
||||
))
|
||||
private val mapOfStringMigrationAdapter = moshi.adapter<Map<String, Map<String, String>>>(Types.newParameterizedType(
|
||||
Map::class.java,
|
||||
String::class.java,
|
||||
Any::class.java
|
||||
))
|
||||
private val listMigrationAdapter = moshi.adapter<List<String>>(
|
||||
Types.newParameterizedType(
|
||||
List::class.java,
|
||||
String::class.java,
|
||||
Any::class.java
|
||||
)
|
||||
)
|
||||
private val mapMigrationAdapter = moshi.adapter<JsonDict>(
|
||||
Types.newParameterizedType(
|
||||
Map::class.java,
|
||||
String::class.java,
|
||||
Any::class.java
|
||||
)
|
||||
)
|
||||
private val mapOfStringMigrationAdapter = moshi.adapter<Map<String, Map<String, String>>>(
|
||||
Types.newParameterizedType(
|
||||
Map::class.java,
|
||||
String::class.java,
|
||||
Any::class.java
|
||||
)
|
||||
)
|
||||
|
||||
internal fun mapToEntity(deviceInfo: CryptoDeviceInfo): DeviceInfoEntity {
|
||||
return DeviceInfoEntity(primaryKey = DeviceInfoEntity.createPrimaryKey(deviceInfo.userId, deviceInfo.deviceId))
|
||||
|
@ -91,11 +97,13 @@ internal object CryptoMapper {
|
|||
},
|
||||
keys = deviceInfoEntity.keysMapJson?.let {
|
||||
try {
|
||||
moshi.adapter<Map<String, String>>(Types.newParameterizedType(
|
||||
Map::class.java,
|
||||
String::class.java,
|
||||
Any::class.java
|
||||
)).fromJson(it)
|
||||
moshi.adapter<Map<String, String>>(
|
||||
Types.newParameterizedType(
|
||||
Map::class.java,
|
||||
String::class.java,
|
||||
Any::class.java
|
||||
)
|
||||
).fromJson(it)
|
||||
} catch (failure: Throwable) {
|
||||
Timber.e(failure)
|
||||
null
|
||||
|
|
|
@ -73,11 +73,13 @@ internal class DefaultSendEventTask @Inject constructor(
|
|||
@Throws
|
||||
private suspend fun handleEncryption(params: SendEventTask.Params): Event {
|
||||
if (params.encrypt && !params.event.isEncrypted()) {
|
||||
return encryptEventTask.execute(EncryptEventTask.Params(
|
||||
params.event.roomId ?: "",
|
||||
params.event,
|
||||
listOf("m.relates_to")
|
||||
))
|
||||
return encryptEventTask.execute(
|
||||
EncryptEventTask.Params(
|
||||
params.event.roomId ?: "",
|
||||
params.event,
|
||||
listOf("m.relates_to")
|
||||
)
|
||||
)
|
||||
}
|
||||
return params.event
|
||||
}
|
||||
|
|
|
@ -64,11 +64,13 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
|
|||
private suspend fun handleEncryption(params: SendVerificationMessageTask.Params): Event {
|
||||
if (cryptoSessionInfoProvider.isRoomEncrypted(params.event.roomId ?: "")) {
|
||||
try {
|
||||
return encryptEventTask.execute(EncryptEventTask.Params(
|
||||
params.event.roomId ?: "",
|
||||
params.event,
|
||||
listOf("m.relates_to")
|
||||
))
|
||||
return encryptEventTask.execute(
|
||||
EncryptEventTask.Params(
|
||||
params.event.roomId ?: "",
|
||||
params.event,
|
||||
listOf("m.relates_to")
|
||||
)
|
||||
)
|
||||
} catch (throwable: Throwable) {
|
||||
// We said it's ok to send verification request in clear
|
||||
}
|
||||
|
|
|
@ -53,7 +53,8 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
|
|||
transactionId,
|
||||
otherUserID,
|
||||
null,
|
||||
isIncoming = true),
|
||||
isIncoming = true
|
||||
),
|
||||
IncomingSasVerificationTransaction {
|
||||
|
||||
override val uxState: IncomingSasVerificationTransaction.UxState
|
||||
|
|
|
@ -50,7 +50,8 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
|
|||
transactionId,
|
||||
otherUserId,
|
||||
otherDeviceId,
|
||||
isIncoming = false),
|
||||
isIncoming = false
|
||||
),
|
||||
OutgoingSasVerificationTransaction {
|
||||
|
||||
override val uxState: OutgoingSasVerificationTransaction.UxState
|
||||
|
|
|
@ -105,8 +105,10 @@ internal abstract class DefaultVerificationTransaction(
|
|||
|
||||
private fun setDeviceVerified(userId: String, deviceId: String) {
|
||||
// TODO should not override cross sign status
|
||||
setDeviceVerificationAction.handle(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
|
||||
setDeviceVerificationAction.handle(
|
||||
DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
|
||||
userId,
|
||||
deviceId)
|
||||
deviceId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,7 +58,8 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
transactionId,
|
||||
otherUserId,
|
||||
otherDeviceId,
|
||||
isIncoming),
|
||||
isIncoming
|
||||
),
|
||||
SasVerificationTransaction {
|
||||
|
||||
companion object {
|
||||
|
@ -297,9 +298,11 @@ internal abstract class SASDefaultVerificationTransaction(
|
|||
return
|
||||
}
|
||||
|
||||
trust(otherMasterKeyIsVerified,
|
||||
trust(
|
||||
otherMasterKeyIsVerified,
|
||||
verifiedDevices,
|
||||
eventuallyMarkMyMasterKeyAsTrusted = otherMasterKey?.trustLevel?.isVerified() == false)
|
||||
eventuallyMarkMyMasterKeyAsTrusted = otherMasterKey?.trustLevel?.isVerified() == false
|
||||
)
|
||||
}
|
||||
|
||||
override fun cancel() {
|
||||
|
|
|
@ -54,7 +54,8 @@ internal class DefaultQrCodeVerificationTransaction(
|
|||
transactionId,
|
||||
otherUserId,
|
||||
otherDeviceId,
|
||||
isIncoming),
|
||||
isIncoming
|
||||
),
|
||||
QrCodeVerificationTransaction {
|
||||
|
||||
override val qrCodeText: String?
|
||||
|
|
|
@ -56,7 +56,8 @@ internal sealed class QrCodeData(
|
|||
transactionId,
|
||||
userMasterCrossSigningPublicKey,
|
||||
otherUserMasterCrossSigningPublicKey,
|
||||
sharedSecret)
|
||||
sharedSecret
|
||||
)
|
||||
|
||||
/**
|
||||
* self-verifying in which the current device does trust the master key
|
||||
|
@ -77,7 +78,8 @@ internal sealed class QrCodeData(
|
|||
transactionId,
|
||||
userMasterCrossSigningPublicKey,
|
||||
otherDeviceKey,
|
||||
sharedSecret)
|
||||
sharedSecret
|
||||
)
|
||||
|
||||
/**
|
||||
* self-verifying in which the current device does not yet trust the master key
|
||||
|
@ -98,5 +100,6 @@ internal sealed class QrCodeData(
|
|||
transactionId,
|
||||
deviceKey,
|
||||
userMasterCrossSigningPublicKey,
|
||||
sharedSecret)
|
||||
sharedSecret
|
||||
)
|
||||
}
|
||||
|
|
|
@ -207,8 +207,10 @@ internal fun List<TimelineEvent>.mapEventsWithEdition(realm: Realm, roomId: Stri
|
|||
?.eventId
|
||||
?.let { editedEventId ->
|
||||
TimelineEventEntity.where(realm, roomId, eventId = editedEventId).findFirst()?.let { editedEvent ->
|
||||
it.root.threadDetails = it.root.threadDetails?.copy(lastRootThreadEdition = editedEvent.root?.asDomain()?.getDecryptedTextSummary()
|
||||
?: "(edited)")
|
||||
it.root.threadDetails = it.root.threadDetails?.copy(
|
||||
lastRootThreadEdition = editedEvent.root?.asDomain()?.getDecryptedTextSummary()
|
||||
?: "(edited)"
|
||||
)
|
||||
it
|
||||
} ?: it
|
||||
} ?: it
|
||||
|
@ -341,7 +343,8 @@ internal fun updateNotificationsNew(roomId: String, realm: Realm, currentUserId:
|
|||
realm = realm,
|
||||
roomId = roomId,
|
||||
rootThreadEventId = eventId,
|
||||
senderId = currentUserId)
|
||||
senderId = currentUserId
|
||||
)
|
||||
val rootThreadEventEntity = EventEntity.where(realm, eventId).findFirst()
|
||||
|
||||
if (isUserParticipating) {
|
||||
|
|
|
@ -24,7 +24,8 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit
|
|||
/**
|
||||
* Realm module for Session
|
||||
*/
|
||||
@RealmModule(library = true,
|
||||
@RealmModule(
|
||||
library = true,
|
||||
classes = [
|
||||
ChunkEntity::class,
|
||||
EventEntity::class,
|
||||
|
@ -71,5 +72,6 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit
|
|||
SpaceParentSummaryEntity::class,
|
||||
UserPresenceEntity::class,
|
||||
ThreadSummaryEntity::class
|
||||
])
|
||||
]
|
||||
)
|
||||
internal class SessionRealmModule
|
||||
|
|
|
@ -43,15 +43,17 @@ import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory
|
|||
import org.matrix.olm.OlmManager
|
||||
import java.io.File
|
||||
|
||||
@Component(modules = [
|
||||
MatrixModule::class,
|
||||
NetworkModule::class,
|
||||
AuthModule::class,
|
||||
RawModule::class,
|
||||
SettingsModule::class,
|
||||
SystemModule::class,
|
||||
NoOpTestModule::class
|
||||
])
|
||||
@Component(
|
||||
modules = [
|
||||
MatrixModule::class,
|
||||
NetworkModule::class,
|
||||
AuthModule::class,
|
||||
RawModule::class,
|
||||
SettingsModule::class,
|
||||
SystemModule::class,
|
||||
NoOpTestModule::class
|
||||
]
|
||||
)
|
||||
@MatrixScope
|
||||
internal interface MatrixComponent {
|
||||
|
||||
|
|
|
@ -36,7 +36,8 @@ internal object MatrixModule {
|
|||
@Provides
|
||||
@MatrixScope
|
||||
fun providesMatrixCoroutineDispatchers(): MatrixCoroutineDispatchers {
|
||||
return MatrixCoroutineDispatchers(io = Dispatchers.IO,
|
||||
return MatrixCoroutineDispatchers(
|
||||
io = Dispatchers.IO,
|
||||
computation = Dispatchers.Default,
|
||||
main = Dispatchers.Main,
|
||||
crypto = createBackgroundHandler("Crypto_Thread").asCoroutineDispatcher(),
|
||||
|
|
|
@ -46,17 +46,18 @@ internal object MoshiProvider {
|
|||
.add(TlsVersionMoshiAdapter())
|
||||
// Use addLast here so we can inject a SplitLazyRoomSyncJsonAdapter later to override the default parsing.
|
||||
.addLast(DefaultLazyRoomSyncEphemeralJsonAdapter())
|
||||
.add(RuntimeJsonAdapterFactory.of(MessageContent::class.java, "msgtype", MessageDefaultContent::class.java)
|
||||
.registerSubtype(MessageTextContent::class.java, MessageType.MSGTYPE_TEXT)
|
||||
.registerSubtype(MessageNoticeContent::class.java, MessageType.MSGTYPE_NOTICE)
|
||||
.registerSubtype(MessageEmoteContent::class.java, MessageType.MSGTYPE_EMOTE)
|
||||
.registerSubtype(MessageAudioContent::class.java, MessageType.MSGTYPE_AUDIO)
|
||||
.registerSubtype(MessageImageContent::class.java, MessageType.MSGTYPE_IMAGE)
|
||||
.registerSubtype(MessageVideoContent::class.java, MessageType.MSGTYPE_VIDEO)
|
||||
.registerSubtype(MessageLocationContent::class.java, MessageType.MSGTYPE_LOCATION)
|
||||
.registerSubtype(MessageFileContent::class.java, MessageType.MSGTYPE_FILE)
|
||||
.registerSubtype(MessageVerificationRequestContent::class.java, MessageType.MSGTYPE_VERIFICATION_REQUEST)
|
||||
.registerSubtype(MessagePollResponseContent::class.java, MessageType.MSGTYPE_POLL_RESPONSE)
|
||||
.add(
|
||||
RuntimeJsonAdapterFactory.of(MessageContent::class.java, "msgtype", MessageDefaultContent::class.java)
|
||||
.registerSubtype(MessageTextContent::class.java, MessageType.MSGTYPE_TEXT)
|
||||
.registerSubtype(MessageNoticeContent::class.java, MessageType.MSGTYPE_NOTICE)
|
||||
.registerSubtype(MessageEmoteContent::class.java, MessageType.MSGTYPE_EMOTE)
|
||||
.registerSubtype(MessageAudioContent::class.java, MessageType.MSGTYPE_AUDIO)
|
||||
.registerSubtype(MessageImageContent::class.java, MessageType.MSGTYPE_IMAGE)
|
||||
.registerSubtype(MessageVideoContent::class.java, MessageType.MSGTYPE_VIDEO)
|
||||
.registerSubtype(MessageLocationContent::class.java, MessageType.MSGTYPE_LOCATION)
|
||||
.registerSubtype(MessageFileContent::class.java, MessageType.MSGTYPE_FILE)
|
||||
.registerSubtype(MessageVerificationRequestContent::class.java, MessageType.MSGTYPE_VERIFICATION_REQUEST)
|
||||
.registerSubtype(MessagePollResponseContent::class.java, MessageType.MSGTYPE_POLL_RESPONSE)
|
||||
)
|
||||
.add(SerializeNulls.JSON_ADAPTER_FACTORY)
|
||||
.build()
|
||||
|
|
|
@ -84,9 +84,11 @@ internal class WorkManagerProvider @Inject constructor(
|
|||
if (workInfo?.state?.isFinished == true) {
|
||||
checkWorkerLiveState.removeObserver(this)
|
||||
if (workInfo.state == WorkInfo.State.FAILED) {
|
||||
throw RuntimeException("MatrixWorkerFactory is not being set on your worker configuration.\n" +
|
||||
"Makes sure to add it to a DelegatingWorkerFactory if you have your own factory or use it directly.\n" +
|
||||
"You can grab the instance through the Matrix class.")
|
||||
throw RuntimeException(
|
||||
"MatrixWorkerFactory is not being set on your worker configuration.\n" +
|
||||
"Makes sure to add it to a DelegatingWorkerFactory if you have your own factory or use it directly.\n" +
|
||||
"You can grab the instance through the Matrix class."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,10 +76,12 @@ class WellKnown {
|
|||
if (apiUrl != null &&
|
||||
apiUrl.startsWith("https://") &&
|
||||
uiUrl!!.startsWith("https://")) {
|
||||
managers.add(WellKnownManagerConfig(
|
||||
apiUrl = apiUrl,
|
||||
uiUrl = uiUrl
|
||||
))
|
||||
managers.add(
|
||||
WellKnownManagerConfig(
|
||||
apiUrl = apiUrl,
|
||||
uiUrl = uiUrl
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,8 +63,10 @@ internal class RuntimeJsonAdapterFactory<T>(
|
|||
}
|
||||
val fallbackAdapter = moshi.adapter<Any>(fallbackType)
|
||||
val objectJsonAdapter = moshi.adapter(Any::class.java)
|
||||
return RuntimeJsonAdapter(labelKey, labelToAdapter, typeToLabel,
|
||||
objectJsonAdapter, fallbackAdapter).nullSafe()
|
||||
return RuntimeJsonAdapter(
|
||||
labelKey, labelToAdapter, typeToLabel,
|
||||
objectJsonAdapter, fallbackAdapter
|
||||
).nullSafe()
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
|
@ -77,8 +79,10 @@ internal class RuntimeJsonAdapterFactory<T>(
|
|||
override fun fromJson(reader: JsonReader): Any? {
|
||||
val peekedToken = reader.peek()
|
||||
if (peekedToken != JsonReader.Token.BEGIN_OBJECT) {
|
||||
throw JsonDataException("Expected BEGIN_OBJECT but was " + peekedToken +
|
||||
" at path " + reader.path)
|
||||
throw JsonDataException(
|
||||
"Expected BEGIN_OBJECT but was " + peekedToken +
|
||||
" at path " + reader.path
|
||||
)
|
||||
}
|
||||
val jsonValue = reader.readJsonValue()
|
||||
val jsonObject = jsonValue as Map<String, Any>?
|
||||
|
@ -91,13 +95,15 @@ internal class RuntimeJsonAdapterFactory<T>(
|
|||
override fun toJson(writer: JsonWriter, value: Any?) {
|
||||
val type: Class<*> = value!!.javaClass
|
||||
val label = typeToLabel[type]
|
||||
?: throw IllegalArgumentException("Expected one of " +
|
||||
typeToLabel.keys +
|
||||
" but found " +
|
||||
value +
|
||||
", a " +
|
||||
value.javaClass +
|
||||
". Register this subtype.")
|
||||
?: throw IllegalArgumentException(
|
||||
"Expected one of " +
|
||||
typeToLabel.keys +
|
||||
" but found " +
|
||||
value +
|
||||
", a " +
|
||||
value.javaClass +
|
||||
". Register this subtype."
|
||||
)
|
||||
val adapter = labelToAdapter[label]!!
|
||||
val jsonValue = adapter.toJsonValue(value) as Map<String, Any>?
|
||||
val valueWithLabel: MutableMap<String, Any> = LinkedHashMap(1 + jsonValue!!.size)
|
||||
|
|
|
@ -35,8 +35,10 @@ internal fun RealmQuery<RoomSummaryEntity>.process(sortOrder: RoomSortOrder): Re
|
|||
arrayOf(
|
||||
RoomSummaryEntityFields.IS_FAVOURITE,
|
||||
RoomSummaryEntityFields.IS_LOW_PRIORITY,
|
||||
RoomSummaryEntityFields.LAST_ACTIVITY_TIME),
|
||||
arrayOf(Sort.DESCENDING, Sort.ASCENDING, Sort.DESCENDING))
|
||||
RoomSummaryEntityFields.LAST_ACTIVITY_TIME
|
||||
),
|
||||
arrayOf(Sort.DESCENDING, Sort.ASCENDING, Sort.DESCENDING)
|
||||
)
|
||||
}
|
||||
RoomSortOrder.NONE -> {
|
||||
}
|
||||
|
|
|
@ -23,9 +23,11 @@ import org.matrix.android.sdk.internal.database.model.RawCacheEntity
|
|||
/**
|
||||
* Realm module for global classes
|
||||
*/
|
||||
@RealmModule(library = true,
|
||||
@RealmModule(
|
||||
library = true,
|
||||
classes = [
|
||||
RawCacheEntity::class,
|
||||
KnownServerUrlEntity::class
|
||||
])
|
||||
]
|
||||
)
|
||||
internal class GlobalRealmModule
|
||||
|
|
|
@ -68,7 +68,8 @@ import org.matrix.android.sdk.internal.session.widgets.WidgetModule
|
|||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||
import org.matrix.android.sdk.internal.util.system.SystemModule
|
||||
|
||||
@Component(dependencies = [MatrixComponent::class],
|
||||
@Component(
|
||||
dependencies = [MatrixComponent::class],
|
||||
modules = [
|
||||
SessionModule::class,
|
||||
RoomModule::class,
|
||||
|
|
|
@ -289,12 +289,14 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
|||
|
||||
val uploadThumbnailResult = dealWithThumbnail(params)
|
||||
|
||||
handleSuccess(params,
|
||||
handleSuccess(
|
||||
params,
|
||||
contentUploadResponse.contentUri,
|
||||
uploadedFileEncryptedFileInfo,
|
||||
uploadThumbnailResult?.uploadedThumbnailUrl,
|
||||
uploadThumbnailResult?.uploadedThumbnailEncryptedFileInfo,
|
||||
newAttachmentAttributes)
|
||||
newAttachmentAttributes
|
||||
)
|
||||
} catch (t: Throwable) {
|
||||
Timber.e(t, "## ERROR ${t.localizedMessage}")
|
||||
handleFailure(params, t)
|
||||
|
|
|
@ -21,9 +21,11 @@ import io.realm.annotations.RealmModule
|
|||
/**
|
||||
* Realm module for content scanner classes
|
||||
*/
|
||||
@RealmModule(library = true,
|
||||
@RealmModule(
|
||||
library = true,
|
||||
classes = [
|
||||
ContentScannerInfoEntity::class,
|
||||
ContentScanResultEntity::class
|
||||
])
|
||||
]
|
||||
)
|
||||
internal class ContentScannerRealmModule
|
||||
|
|
|
@ -94,10 +94,12 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
|
|||
}.getOrNull()
|
||||
|
||||
val wellknownResult = runCatching {
|
||||
getWellknownTask.execute(GetWellknownTask.Params(
|
||||
domain = userId.getDomain(),
|
||||
homeServerConnectionConfig = homeServerConnectionConfig
|
||||
))
|
||||
getWellknownTask.execute(
|
||||
GetWellknownTask.Params(
|
||||
domain = userId.getDomain(),
|
||||
homeServerConnectionConfig = homeServerConnectionConfig
|
||||
)
|
||||
)
|
||||
}.getOrNull()
|
||||
|
||||
insertInDb(capabilities, mediaConfig, versions, wellknownResult)
|
||||
|
|
|
@ -218,9 +218,11 @@ internal class DefaultIdentityService @Inject constructor(
|
|||
listeners.toList().forEach { tryOrNull { it.onIdentityServerChange() } }
|
||||
}
|
||||
|
||||
updateUserAccountDataTask.execute(UpdateUserAccountDataTask.IdentityParams(
|
||||
identityContent = IdentityServerContent(baseUrl = url)
|
||||
))
|
||||
updateUserAccountDataTask.execute(
|
||||
UpdateUserAccountDataTask.IdentityParams(
|
||||
identityContent = IdentityServerContent(baseUrl = url)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun getUserConsent(): Boolean {
|
||||
|
@ -297,11 +299,13 @@ internal class DefaultIdentityService @Inject constructor(
|
|||
}
|
||||
|
||||
override suspend fun sign3pidInvitation(identiyServer: String, token: String, secret: String): SignInvitationResult {
|
||||
return sign3pidInvitationTask.execute(Sign3pidInvitationTask.Params(
|
||||
url = identiyServer,
|
||||
token = token,
|
||||
privateKey = secret
|
||||
))
|
||||
return sign3pidInvitationTask.execute(
|
||||
Sign3pidInvitationTask.Params(
|
||||
url = identiyServer,
|
||||
token = token,
|
||||
privateKey = secret
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun addListener(listener: IdentityServiceListener) {
|
||||
|
|
|
@ -83,11 +83,13 @@ internal class DefaultIdentityBulkLookupTask @Inject constructor(
|
|||
return try {
|
||||
LookUpData(hashedAddresses,
|
||||
executeRequest(null) {
|
||||
identityAPI.lookup(IdentityLookUpParams(
|
||||
hashedAddresses,
|
||||
IdentityHashDetailResponse.ALGORITHM_SHA256,
|
||||
hashDetailResponse.pepper
|
||||
))
|
||||
identityAPI.lookup(
|
||||
IdentityLookUpParams(
|
||||
hashedAddresses,
|
||||
IdentityHashDetailResponse.ALGORITHM_SHA256,
|
||||
hashDetailResponse.pepper
|
||||
)
|
||||
)
|
||||
})
|
||||
} catch (failure: Throwable) {
|
||||
// Catch invalid hash pepper and retry
|
||||
|
@ -117,8 +119,10 @@ internal class DefaultIdentityBulkLookupTask @Inject constructor(
|
|||
return withOlmUtility { olmUtility ->
|
||||
threePids.map { threePid ->
|
||||
base64ToBase64Url(
|
||||
olmUtility.sha256(threePid.value.lowercase(Locale.ROOT) +
|
||||
" " + threePid.toMedium() + " " + pepper)
|
||||
olmUtility.sha256(
|
||||
threePid.value.lowercase(Locale.ROOT) +
|
||||
" " + threePid.toMedium() + " " + pepper
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,18 +57,22 @@ internal class DefaultIdentityRequestTokenForBindingTask @Inject constructor(
|
|||
|
||||
val tokenResponse = executeRequest(null) {
|
||||
when (params.threePid) {
|
||||
is ThreePid.Email -> identityAPI.requestTokenToBindEmail(IdentityRequestTokenForEmailBody(
|
||||
clientSecret = clientSecret,
|
||||
sendAttempt = sendAttempt,
|
||||
email = params.threePid.email
|
||||
))
|
||||
is ThreePid.Email -> identityAPI.requestTokenToBindEmail(
|
||||
IdentityRequestTokenForEmailBody(
|
||||
clientSecret = clientSecret,
|
||||
sendAttempt = sendAttempt,
|
||||
email = params.threePid.email
|
||||
)
|
||||
)
|
||||
is ThreePid.Msisdn -> {
|
||||
identityAPI.requestTokenToBindMsisdn(IdentityRequestTokenForMsisdnBody(
|
||||
clientSecret = clientSecret,
|
||||
sendAttempt = sendAttempt,
|
||||
phoneNumber = params.threePid.msisdn,
|
||||
countryCode = params.threePid.getCountryCode()
|
||||
))
|
||||
identityAPI.requestTokenToBindMsisdn(
|
||||
IdentityRequestTokenForMsisdnBody(
|
||||
clientSecret = clientSecret,
|
||||
sendAttempt = sendAttempt,
|
||||
phoneNumber = params.threePid.msisdn,
|
||||
countryCode = params.threePid.getCountryCode()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,8 @@ internal class DefaultIdentitySubmitTokenForBindingTask @Inject constructor(
|
|||
clientSecret = identityPendingBinding.clientSecret,
|
||||
sid = identityPendingBinding.sid,
|
||||
token = params.token
|
||||
))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (!tokenResponse.isSuccess()) {
|
||||
|
|
|
@ -21,9 +21,11 @@ import io.realm.annotations.RealmModule
|
|||
/**
|
||||
* Realm module for identity server classes
|
||||
*/
|
||||
@RealmModule(library = true,
|
||||
@RealmModule(
|
||||
library = true,
|
||||
classes = [
|
||||
IdentityDataEntity::class,
|
||||
IdentityPendingBindingEntity::class
|
||||
])
|
||||
]
|
||||
)
|
||||
internal class IdentityRealmModule
|
||||
|
|
|
@ -50,7 +50,8 @@ internal class DefaultBindThreePidsTask @Inject constructor(private val profileA
|
|||
identityServerUrlWithoutProtocol = identityServerUrlWithoutProtocol,
|
||||
identityServerAccessToken = identityServerAccessToken,
|
||||
sid = identityPendingBinding.sid
|
||||
))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Binding is over, cleanup the store
|
||||
|
|
|
@ -135,21 +135,25 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
|
|||
override suspend fun finalizeAddingThreePid(threePid: ThreePid,
|
||||
userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) {
|
||||
finalizeAddingThreePidTask
|
||||
.execute(FinalizeAddingThreePidTask.Params(
|
||||
threePid = threePid,
|
||||
userInteractiveAuthInterceptor = userInteractiveAuthInterceptor,
|
||||
userWantsToCancel = false
|
||||
))
|
||||
.execute(
|
||||
FinalizeAddingThreePidTask.Params(
|
||||
threePid = threePid,
|
||||
userInteractiveAuthInterceptor = userInteractiveAuthInterceptor,
|
||||
userWantsToCancel = false
|
||||
)
|
||||
)
|
||||
refreshThreePids()
|
||||
}
|
||||
|
||||
override suspend fun cancelAddingThreePid(threePid: ThreePid) {
|
||||
finalizeAddingThreePidTask
|
||||
.execute(FinalizeAddingThreePidTask.Params(
|
||||
threePid = threePid,
|
||||
userInteractiveAuthInterceptor = null,
|
||||
userWantsToCancel = true
|
||||
))
|
||||
.execute(
|
||||
FinalizeAddingThreePidTask.Params(
|
||||
threePid = threePid,
|
||||
userInteractiveAuthInterceptor = null,
|
||||
userWantsToCancel = true
|
||||
)
|
||||
)
|
||||
refreshThreePids()
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,8 @@ internal class DefaultUnbindThreePidsTask @Inject constructor(private val profil
|
|||
identityServerUrlWithoutProtocol,
|
||||
params.threePid.toMedium(),
|
||||
params.threePid.value
|
||||
))
|
||||
)
|
||||
)
|
||||
}.isSuccess()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,17 +85,19 @@ internal class DefaultPushersService @Inject constructor(
|
|||
deviceDisplayName: String,
|
||||
append: Boolean) {
|
||||
addPusherTask.execute(
|
||||
AddPusherTask.Params(JsonPusher(
|
||||
pushKey = email,
|
||||
kind = Pusher.KIND_EMAIL,
|
||||
appId = Pusher.APP_ID_EMAIL,
|
||||
profileTag = "",
|
||||
lang = lang,
|
||||
appDisplayName = appDisplayName,
|
||||
deviceDisplayName = deviceDisplayName,
|
||||
data = JsonPusherData(brand = emailBranding),
|
||||
append = append
|
||||
))
|
||||
AddPusherTask.Params(
|
||||
JsonPusher(
|
||||
pushKey = email,
|
||||
kind = Pusher.KIND_EMAIL,
|
||||
appId = Pusher.APP_ID_EMAIL,
|
||||
profileTag = "",
|
||||
lang = lang,
|
||||
appDisplayName = appDisplayName,
|
||||
deviceDisplayName = deviceDisplayName,
|
||||
data = JsonPusherData(brand = emailBranding),
|
||||
append = append
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -67,8 +67,10 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
|
|||
}.filter {
|
||||
it.senderId != userId
|
||||
}
|
||||
Timber.v("[PushRules] Found ${allEvents.size} out of ${(newJoinEvents + inviteEvents).size}" +
|
||||
" to check for push rules with ${params.rules.size} rules")
|
||||
Timber.v(
|
||||
"[PushRules] Found ${allEvents.size} out of ${(newJoinEvents + inviteEvents).size}" +
|
||||
" to check for push rules with ${params.rules.size} rules"
|
||||
)
|
||||
val matchedEvents = allEvents.mapNotNull { event ->
|
||||
pushRuleFinder.fulfilledBingRule(event, params.rules)?.let {
|
||||
Timber.v("[PushRules] Rule $it match for event ${event.eventId}")
|
||||
|
|
|
@ -116,7 +116,8 @@ internal class CreateRoomBodyBuilder @Inject constructor(
|
|||
fileUploader.uploadFromUri(
|
||||
uri = avatarUri,
|
||||
filename = UUID.randomUUID().toString(),
|
||||
mimeType = MimeTypes.Jpeg)
|
||||
mimeType = MimeTypes.Jpeg
|
||||
)
|
||||
}
|
||||
}?.let { response ->
|
||||
Event(
|
||||
|
|
|
@ -83,7 +83,8 @@ internal class RoomMemberEventHandler @Inject constructor(
|
|||
roomMember,
|
||||
// When an update is happening, insertOrUpdate replace existing values with null if they are not provided,
|
||||
// but we want to preserve presence record value and not replace it with null
|
||||
getExistingPresenceState(realm, roomId, userId))
|
||||
getExistingPresenceState(realm, roomId, userId)
|
||||
)
|
||||
realm.insertOrUpdate(roomMemberEntity)
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue