diff --git a/CHANGES.md b/CHANGES.md index 0b0666e754..15153bb20e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,17 +7,17 @@ Features ✨: Improvements 🙌: - The initial sync is now handled by a foreground service - Render aliases and canonical alias change in the timeline - - Fix autocompletion issues and add support for rooms and groups - Introduce developer mode in the settings (#745, #796) - Improve devices list screen - Add settings for rageshake sensibility - Fix autocompletion issues and add support for rooms, groups, and emoji (#780) - Show skip to bottom FAB while scrolling down (#752) + - Enable encryption on a room, SDK part (#212) Other changes: - Change the way RiotX identifies a session to allow the SDK to support several sessions with the same user (#800) - Exclude play-services-oss-licenses library from F-Droid build (#814) - - Email domain can be limited on some homeserver, i18n of the displayed error (#754) + - Email domain can be limited on some homeservers, i18n of the displayed error (#754) Bugfix 🐛: - Fix crash when opening room creation screen from the room filtering screen diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/account/AccountCreationTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/account/AccountCreationTest.kt new file mode 100644 index 0000000000..c44ac9c47b --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/account/AccountCreationTest.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.account + +import im.vector.matrix.android.InstrumentedTest +import im.vector.matrix.android.common.CommonTestHelper +import im.vector.matrix.android.common.CryptoTestHelper +import im.vector.matrix.android.common.SessionTestParams +import im.vector.matrix.android.common.TestConstants +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.runners.MethodSorters + +@RunWith(JUnit4::class) +@FixMethodOrder(MethodSorters.JVM) +class AccountCreationTest : InstrumentedTest { + + private val commonTestHelper = CommonTestHelper(context()) + private val cryptoTestHelper = CryptoTestHelper(commonTestHelper) + + @Test + fun createAccountTest() { + val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true)) + + commonTestHelper.signout(session) + + session.close() + } + + @Test + fun createAccountAndLoginAgainTest() { + val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true)) + + // Log again to the same account + val session2 = commonTestHelper.logIntoAccount(session.myUserId, SessionTestParams(withInitialSync = true)) + + session.close() + session2.close() + } + + @Test + fun simpleE2eTest() { + val res = cryptoTestHelper.doE2ETestWithAliceInARoom() + + res.close() + } +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt new file mode 100644 index 0000000000..b16c865765 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt @@ -0,0 +1,278 @@ +/* + * Copyright 2016 OpenMarket Ltd + * Copyright 2018 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.common + +import android.content.Context +import android.net.Uri +import im.vector.matrix.android.api.Matrix +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.MatrixConfiguration +import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig +import im.vector.matrix.android.api.auth.data.LoginFlowResult +import im.vector.matrix.android.api.auth.registration.RegistrationResult +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.room.Room +import im.vector.matrix.android.api.session.room.timeline.Timeline +import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import im.vector.matrix.android.api.session.room.timeline.TimelineSettings +import org.junit.Assert.* +import java.util.* +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +/** + * This class exposes methods to be used in common cases + * Registration, login, Sync, Sending messages... + */ +class CommonTestHelper(context: Context) { + + val matrix: Matrix + + init { + Matrix.initialize(context, MatrixConfiguration("TestFlavor")) + + matrix = Matrix.getInstance(context) + } + + fun createAccount(userNamePrefix: String, testParams: SessionTestParams): Session { + return createAccount(userNamePrefix, TestConstants.PASSWORD, testParams) + } + + fun logIntoAccount(userId: String, testParams: SessionTestParams): Session { + return logIntoAccount(userId, TestConstants.PASSWORD, testParams) + } + + /** + * Create a Home server configuration, with Http connection allowed for test + */ + fun createHomeServerConfig(): HomeServerConnectionConfig { + return HomeServerConnectionConfig.Builder() + .withHomeServerUri(Uri.parse(TestConstants.TESTS_HOME_SERVER_URL)) + .build() + } + + /** + * This methods init the event stream and check for initial sync + * + * @param session the session to sync + */ + fun syncSession(session: Session) { + // val lock = CountDownLatch(1) + + // val observer = androidx.lifecycle.Observer { syncState -> + // if (syncState is SyncState.Idle) { + // lock.countDown() + // } + // } + + // TODO observe? + // while (session.syncState().value !is SyncState.Idle) { + // sleep(100) + // } + + session.open() + session.startSync(true) + // await(lock) + // session.syncState().removeObserver(observer) + } + + /** + * Sends text messages in a room + * + * @param room the room where to send the messages + * @param message the message to send + * @param nbOfMessages the number of time the message will be sent + */ + fun sendTextMessage(room: Room, message: String, nbOfMessages: Int): List { + val sentEvents = ArrayList(nbOfMessages) + val latch = CountDownLatch(nbOfMessages) + val onEventSentListener = object : Timeline.Listener { + override fun onTimelineFailure(throwable: Throwable) { + } + + override fun onTimelineUpdated(snapshot: List) { + // TODO Count only new messages? + if (snapshot.count { it.root.type == EventType.MESSAGE } == nbOfMessages) { + sentEvents.addAll(snapshot.filter { it.root.type == EventType.MESSAGE }) + latch.countDown() + } + } + } + val timeline = room.createTimeline(null, TimelineSettings(10)) + timeline.addListener(onEventSentListener) + for (i in 0 until nbOfMessages) { + room.sendTextMessage(message + " #" + (i + 1)) + } + await(latch) + timeline.removeListener(onEventSentListener) + + // Check that all events has been created + assertEquals(nbOfMessages.toLong(), sentEvents.size.toLong()) + + return sentEvents + } + + // PRIVATE METHODS ***************************************************************************** + + /** + * Creates a unique account + * + * @param userNamePrefix the user name prefix + * @param password the password + * @param testParams test params about the session + * @return the session associated with the newly created account + */ + private fun createAccount(userNamePrefix: String, + password: String, + testParams: SessionTestParams): Session { + val session = createAccountAndSync( + userNamePrefix + "_" + System.currentTimeMillis() + UUID.randomUUID(), + password, + testParams + ) + assertNotNull(session) + return session + } + + /** + * Logs into an existing account + * + * @param userId the userId to log in + * @param password the password to log in + * @param testParams test params about the session + * @return the session associated with the existing account + */ + private fun logIntoAccount(userId: String, + password: String, + testParams: SessionTestParams): Session { + val session = logAccountAndSync(userId, password, testParams) + assertNotNull(session) + return session + } + + /** + * Create an account and a dedicated session + * + * @param userName the account username + * @param password the password + * @param sessionTestParams parameters for the test + */ + private fun createAccountAndSync(userName: String, + password: String, + sessionTestParams: SessionTestParams): Session { + val hs = createHomeServerConfig() + + doSync { + matrix.authenticationService + .getLoginFlow(hs, it) + } + + doSync { + matrix.authenticationService + .getRegistrationWizard() + .createAccount(userName, password, null, it) + } + + // Preform dummy step + val registrationResult = doSync { + matrix.authenticationService + .getRegistrationWizard() + .dummy(it) + } + + assertTrue(registrationResult is RegistrationResult.Success) + val session = (registrationResult as RegistrationResult.Success).session + if (sessionTestParams.withInitialSync) { + syncSession(session) + } + + return session + } + + /** + * Start an account login + * + * @param userName the account username + * @param password the password + * @param sessionTestParams session test params + */ + private fun logAccountAndSync(userName: String, + password: String, + sessionTestParams: SessionTestParams): Session { + val hs = createHomeServerConfig() + + doSync { + matrix.authenticationService + .getLoginFlow(hs, it) + } + + val session = doSync { + matrix.authenticationService + .getLoginWizard() + .login(userName, password, "myDevice", it) + } + + if (sessionTestParams.withInitialSync) { + syncSession(session) + } + + return session + } + + /** + * Await for a latch and ensure the result is true + * + * @param latch + * @throws InterruptedException + */ + fun await(latch: CountDownLatch) { + assertTrue(latch.await(TestConstants.timeOutMillis, TimeUnit.MILLISECONDS)) + } + + // Transform a method with a MatrixCallback to a synchronous method + inline fun doSync(block: (MatrixCallback) -> Unit): T { + val lock = CountDownLatch(1) + var result: T? = null + + val callback = object : TestMatrixCallback(lock) { + override fun onSuccess(data: T) { + result = data + super.onSuccess(data) + } + } + + block.invoke(callback) + + await(lock) + + assertNotNull(result) + return result!! + } + + /** + * Clear all provided sessions + */ + fun Iterable.close() = forEach { it.close() } + + fun signout(session: Session) { + val lock = CountDownLatch(1) + session.signOut(true, object : TestMatrixCallback(lock) {}) + await(lock) + } +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestData.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestData.kt new file mode 100644 index 0000000000..8ad9f1ec6f --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestData.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2018 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.common + +import im.vector.matrix.android.api.session.Session + +data class CryptoTestData(val firstSession: Session, + val roomId: String, + val secondSession: Session? = null, + val thirdSession: Session? = null) { + + fun close() { + firstSession.close() + secondSession?.close() + secondSession?.close() + } +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt new file mode 100644 index 0000000000..df45249265 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt @@ -0,0 +1,335 @@ +/* + * Copyright 2018 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.common + +import android.os.SystemClock +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.toContent +import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams +import im.vector.matrix.android.api.session.room.timeline.Timeline +import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import im.vector.matrix.android.api.session.room.timeline.TimelineSettings +import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP +import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupAuthData +import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo +import org.junit.Assert.* +import java.util.* +import java.util.concurrent.CountDownLatch + +class CryptoTestHelper(val mTestHelper: CommonTestHelper) { + + val messagesFromAlice: List = Arrays.asList("0 - Hello I'm Alice!", "4 - Go!") + val messagesFromBob: List = Arrays.asList("1 - Hello I'm Bob!", "2 - Isn't life grand?", "3 - Let's go to the opera.") + + val defaultSessionParams = SessionTestParams(true) + + /** + * @return alice session + */ + fun doE2ETestWithAliceInARoom(): CryptoTestData { + val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams) + + var roomId: String? = null + val lock1 = CountDownLatch(1) + + aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }, object : TestMatrixCallback(lock1) { + override fun onSuccess(data: String) { + roomId = data + super.onSuccess(data) + } + }) + + mTestHelper.await(lock1) + assertNotNull(roomId) + + val room = aliceSession.getRoom(roomId!!)!! + + val lock2 = CountDownLatch(1) + room.enableEncryptionWithAlgorithm(MXCRYPTO_ALGORITHM_MEGOLM, object : TestMatrixCallback(lock2) {}) + mTestHelper.await(lock2) + + return CryptoTestData(aliceSession, roomId!!) + } + + /** + * @return alice and bob sessions + */ + fun doE2ETestWithAliceAndBobInARoom(): CryptoTestData { + val statuses = HashMap() + + val cryptoTestData = doE2ETestWithAliceInARoom() + val aliceSession = cryptoTestData.firstSession + val aliceRoomId = cryptoTestData.roomId + + val room = aliceSession.getRoom(aliceRoomId)!! + + val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams) + + val lock1 = CountDownLatch(2) + +// val bobEventListener = object : MXEventListener() { +// override fun onNewRoom(roomId: String) { +// if (TextUtils.equals(roomId, aliceRoomId)) { +// if (!statuses.containsKey("onNewRoom")) { +// statuses["onNewRoom"] = "onNewRoom" +// lock1.countDown() +// } +// } +// } +// } +// +// bobSession.dataHandler.addListener(bobEventListener) + + room.invite(bobSession.myUserId, callback = object : TestMatrixCallback(lock1) { + override fun onSuccess(data: Unit) { + statuses["invite"] = "invite" + super.onSuccess(data) + } + }) + + mTestHelper.await(lock1) + + assertTrue(statuses.containsKey("invite") && statuses.containsKey("onNewRoom")) + +// bobSession.dataHandler.removeListener(bobEventListener) + + val lock2 = CountDownLatch(2) + + bobSession.joinRoom(aliceRoomId, callback = TestMatrixCallback(lock2)) + +// room.addEventListener(object : MXEventListener() { +// override fun onLiveEvent(event: Event, roomState: RoomState) { +// if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_STATE_ROOM_MEMBER)) { +// val contentToConsider = event.contentAsJsonObject +// val member = JsonUtils.toRoomMember(contentToConsider) +// +// if (TextUtils.equals(member.membership, RoomMember.MEMBERSHIP_JOIN)) { +// statuses["AliceJoin"] = "AliceJoin" +// lock2.countDown() +// } +// } +// } +// }) + + mTestHelper.await(lock2) + + // Ensure bob can send messages to the room +// val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!! +// assertNotNull(roomFromBobPOV.powerLevels) +// assertTrue(roomFromBobPOV.powerLevels.maySendMessage(bobSession.myUserId)) + + assertTrue(statuses.toString() + "", statuses.containsKey("AliceJoin")) + +// bobSession.dataHandler.removeListener(bobEventListener) + + return CryptoTestData(aliceSession, aliceRoomId, bobSession) + } + + /** + * @return Alice, Bob and Sam session + */ + fun doE2ETestWithAliceAndBobAndSamInARoom(): CryptoTestData { + val statuses = HashMap() + + val cryptoTestData = doE2ETestWithAliceAndBobInARoom() + val aliceSession = cryptoTestData.firstSession + val aliceRoomId = cryptoTestData.roomId + + val room = aliceSession.getRoom(aliceRoomId)!! + + val samSession = mTestHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams) + + val lock1 = CountDownLatch(2) + +// val samEventListener = object : MXEventListener() { +// override fun onNewRoom(roomId: String) { +// if (TextUtils.equals(roomId, aliceRoomId)) { +// if (!statuses.containsKey("onNewRoom")) { +// statuses["onNewRoom"] = "onNewRoom" +// lock1.countDown() +// } +// } +// } +// } +// +// samSession.dataHandler.addListener(samEventListener) + + room.invite(samSession.myUserId, null, object : TestMatrixCallback(lock1) { + override fun onSuccess(data: Unit) { + statuses["invite"] = "invite" + super.onSuccess(data) + } + }) + + mTestHelper.await(lock1) + + assertTrue(statuses.containsKey("invite") && statuses.containsKey("onNewRoom")) + +// samSession.dataHandler.removeListener(samEventListener) + + val lock2 = CountDownLatch(1) + + samSession.joinRoom(aliceRoomId, null, object : TestMatrixCallback(lock2) { + override fun onSuccess(data: Unit) { + statuses["joinRoom"] = "joinRoom" + super.onSuccess(data) + } + }) + + mTestHelper.await(lock2) + assertTrue(statuses.containsKey("joinRoom")) + + // wait the initial sync + SystemClock.sleep(1000) + +// samSession.dataHandler.removeListener(samEventListener) + + return CryptoTestData(aliceSession, aliceRoomId, cryptoTestData.secondSession, samSession) + } + + /** + * @return Alice and Bob sessions + */ + fun doE2ETestWithAliceAndBobInARoomWithEncryptedMessages(): CryptoTestData { + val cryptoTestData = doE2ETestWithAliceAndBobInARoom() + val aliceSession = cryptoTestData.firstSession + val aliceRoomId = cryptoTestData.roomId + val bobSession = cryptoTestData.secondSession!! + + bobSession.setWarnOnUnknownDevices(false) + + aliceSession.setWarnOnUnknownDevices(false) + + val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!! + val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!! + + var lock = CountDownLatch(1) + + val bobEventsListener = object : Timeline.Listener { + override fun onTimelineFailure(throwable: Throwable) { + } + + override fun onTimelineUpdated(snapshot: List) { + val size = snapshot.filter { it.root.senderId != bobSession.myUserId && it.root.getClearType() == EventType.MESSAGE } + .size + + if (size == 3) { + lock.countDown() + } + } + } + + val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(10)) + bobTimeline.addListener(bobEventsListener) + + val results = HashMap() + + // bobSession.dataHandler.addListener(object : MXEventListener() { + // override fun onToDeviceEvent(event: Event) { + // results["onToDeviceEvent"] = event + // lock.countDown() + // } + // }) + + // Alice sends a message + roomFromAlicePOV.sendTextMessage(messagesFromAlice[0]) + assertTrue(results.containsKey("onToDeviceEvent")) +// assertEquals(1, messagesReceivedByBobCount) + + // Bob send a message + lock = CountDownLatch(1) + roomFromBobPOV.sendTextMessage(messagesFromBob[0]) + // android does not echo the messages sent from itself +// messagesReceivedByBobCount++ + mTestHelper.await(lock) +// assertEquals(2, messagesReceivedByBobCount) + + // Bob send a message + lock = CountDownLatch(1) + roomFromBobPOV.sendTextMessage(messagesFromBob[1]) + // android does not echo the messages sent from itself +// messagesReceivedByBobCount++ + mTestHelper.await(lock) +// assertEquals(3, messagesReceivedByBobCount) + + // Bob send a message + lock = CountDownLatch(1) + roomFromBobPOV.sendTextMessage(messagesFromBob[2]) + // android does not echo the messages sent from itself +// messagesReceivedByBobCount++ + mTestHelper.await(lock) +// assertEquals(4, messagesReceivedByBobCount) + + // Alice sends a message + lock = CountDownLatch(2) + roomFromAlicePOV.sendTextMessage(messagesFromAlice[1]) + mTestHelper.await(lock) +// assertEquals(5, messagesReceivedByBobCount) + + bobTimeline.removeListener(bobEventsListener) + + return cryptoTestData + } + + fun checkEncryptedEvent(event: Event, roomId: String, clearMessage: String, senderSession: Session) { + assertEquals(EventType.ENCRYPTED, event.type) + assertNotNull(event.content) + + val eventWireContent = event.content.toContent() + assertNotNull(eventWireContent) + + assertNull(eventWireContent.get("body")) + assertEquals(MXCRYPTO_ALGORITHM_MEGOLM, eventWireContent.get("algorithm")) + + assertNotNull(eventWireContent.get("ciphertext")) + assertNotNull(eventWireContent.get("session_id")) + assertNotNull(eventWireContent.get("sender_key")) + + assertEquals(senderSession.sessionParams.credentials.deviceId, eventWireContent.get("device_id")) + + assertNotNull(event.eventId) + assertEquals(roomId, event.roomId) + assertEquals(EventType.MESSAGE, event.getClearType()) + // TODO assertTrue(event.getAge() < 10000) + + val eventContent = event.toContent() + assertNotNull(eventContent) + assertEquals(clearMessage, eventContent.get("body")) + assertEquals(senderSession.myUserId, event.senderId) + } + + fun createFakeMegolmBackupAuthData(): MegolmBackupAuthData { + return MegolmBackupAuthData( + publicKey = "abcdefg", + signatures = HashMap>().apply { + this["something"] = HashMap().apply { + this["ed25519:something"] = "hijklmnop" + } + } + ) + } + + fun createFakeMegolmBackupCreationInfo(): MegolmBackupCreationInfo { + return MegolmBackupCreationInfo().apply { + algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP + authData = createFakeMegolmBackupAuthData() + } + } +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/MockOkHttpInterceptor.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/MockOkHttpInterceptor.kt new file mode 100644 index 0000000000..a1f95424a6 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/MockOkHttpInterceptor.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package im.vector.matrix.android.common + +import okhttp3.Interceptor +import okhttp3.Protocol +import okhttp3.Request +import okhttp3.Response +import okhttp3.ResponseBody.Companion.toResponseBody +import javax.net.ssl.HttpsURLConnection + +/** + * Allows to intercept network requests for test purpose by + * - re-writing the response + * - changing the response code (200/404/etc..). + * - Test delays.. + * + * Basic usage: + * + * val mockInterceptor = MockOkHttpInterceptor() + * mockInterceptor.addRule(MockOkHttpInterceptor.SimpleRule(".well-known/matrix/client", 200, "{}")) + * + * RestHttpClientFactoryProvider.defaultProvider = RestClientHttpClientFactory(mockInterceptor) + * AutoDiscovery().findClientConfig("matrix.org", ) + * + */ +class MockOkHttpInterceptor : Interceptor { + + private var rules: ArrayList = ArrayList() + + fun addRule(rule: Rule) { + rules.add(rule) + } + + override fun intercept(chain: Interceptor.Chain): Response { + val originalRequest = chain.request() + + rules.forEach { rule -> + if (originalRequest.url.toString().contains(rule.match)) { + rule.process(originalRequest)?.let { + return it + } + } + } + + return chain.proceed(originalRequest) + } + + abstract class Rule(val match: String) { + abstract fun process(originalRequest: Request): Response? + } + + /** + * Simple rule that reply with the given body for any request that matches the match param + */ + class SimpleRule(match: String, + private val code: Int = HttpsURLConnection.HTTP_OK, + private val body: String = "{}") : Rule(match) { + + override fun process(originalRequest: Request): Response? { + return Response.Builder() + .protocol(Protocol.HTTP_1_1) + .request(originalRequest) + .message("mocked answer") + .body(body.toResponseBody(null)) + .code(code) + .build() + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionId.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/SessionTestParams.kt similarity index 66% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionId.kt rename to matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/SessionTestParams.kt index 2f39806aa5..7d1d23e951 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionId.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/SessionTestParams.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 New Vector Ltd + * Copyright 2018 New Vector Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,10 +14,6 @@ * limitations under the License. */ -package im.vector.matrix.android.internal.auth +package im.vector.matrix.android.common -import im.vector.matrix.android.internal.util.md5 - -internal fun createSessionId(userId: String, deviceId: String?): String { - return (if (deviceId.isNullOrBlank()) userId else "$userId|$deviceId").md5() -} +data class SessionTestParams @JvmOverloads constructor(val withInitialSync: Boolean = false) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestAssertUtil.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestAssertUtil.kt new file mode 100644 index 0000000000..2a62165210 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestAssertUtil.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2018 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.common + +import org.junit.Assert.* + +/** + * Compare two lists and their content + */ +fun assertListEquals(list1: List?, list2: List?) { + if (list1 == null) { + assertNull(list2) + } else { + assertNotNull(list2) + + assertEquals("List sizes must match", list1.size, list2!!.size) + + for (i in list1.indices) { + assertEquals("Elements at index $i are not equal", list1[i], list2[i]) + } + } +} + +/** + * Compare two maps and their content + */ +fun assertDictEquals(dict1: Map?, dict2: Map?) { + if (dict1 == null) { + assertNull(dict2) + } else { + assertNotNull(dict2) + + assertEquals("Map sizes must match", dict1.size, dict2!!.size) + + for (i in dict1.keys) { + assertEquals("Values for key $i are not equal", dict1[i], dict2[i]) + } + } +} + +/** + * Compare two byte arrays content. + * Note that if the arrays have not the same size, it also fails. + */ +fun assertByteArrayNotEqual(a1: ByteArray, a2: ByteArray) { + if (a1.size != a2.size) { + fail("Arrays have not the same size.") + } + + for (index in a1.indices) { + if (a1[index] != a2[index]) { + // Difference found! + return + } + } + + fail("Arrays are equals.") +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestConstants.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestConstants.kt new file mode 100644 index 0000000000..60cc87d330 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestConstants.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2018 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.common + +import android.os.Debug + +object TestConstants { + + const val TESTS_HOME_SERVER_URL = "http://10.0.2.2:8080" + + // Time out to use when waiting for server response. 60s + private const val AWAIT_TIME_OUT_MILLIS = 60000 + + // Time out to use when waiting for server response, when the debugger is connected. 10 minutes + private const val AWAIT_TIME_OUT_WITH_DEBUGGER_MILLIS = 10 * 60000 + + const val USER_ALICE = "Alice" + const val USER_BOB = "Bob" + const val USER_SAM = "Sam" + + const val PASSWORD = "password" + + val timeOutMillis: Long + get() = if (Debug.isDebuggerConnected()) { + // Wait more + AWAIT_TIME_OUT_WITH_DEBUGGER_MILLIS.toLong() + } else { + AWAIT_TIME_OUT_MILLIS.toLong() + } +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestMatrixCallback.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestMatrixCallback.kt new file mode 100644 index 0000000000..c04777440b --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestMatrixCallback.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2018 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.common + +import androidx.annotation.CallSuper +import im.vector.matrix.android.api.MatrixCallback +import org.junit.Assert.fail +import timber.log.Timber +import java.util.concurrent.CountDownLatch + +/** + * Simple implementation of MatrixCallback, which count down the CountDownLatch on each API callback + * @param onlySuccessful true to fail if an error occurs. This is the default behavior + * @param + */ +open class TestMatrixCallback(private val countDownLatch: CountDownLatch, + private val onlySuccessful: Boolean = true) : MatrixCallback { + + @CallSuper + override fun onSuccess(data: T) { + countDownLatch.countDown() + } + + @CallSuper + override fun onFailure(failure: Throwable) { + Timber.e(failure, "TestApiCallback") + + if (onlySuccessful) { + fail("onFailure " + failure.localizedMessage) + } + + countDownLatch.countDown() + } +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/AttachmentEncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/AttachmentEncryptionTest.kt new file mode 100644 index 0000000000..84b3f24191 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/AttachmentEncryptionTest.kt @@ -0,0 +1,148 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.crypto + +import android.os.MemoryFile +import android.util.Base64 +import androidx.test.ext.junit.runners.AndroidJUnit4 +import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments +import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo +import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileKey +import org.junit.Assert.* +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import java.io.ByteArrayInputStream +import java.io.InputStream + +/** + * Unit tests AttachmentEncryptionTest. + */ +@Suppress("SpellCheckingInspection") +@RunWith(AndroidJUnit4::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class AttachmentEncryptionTest { + + private fun checkDecryption(input: String, encryptedFileInfo: EncryptedFileInfo): String { + val `in` = Base64.decode(input, Base64.DEFAULT) + + val inputStream: InputStream + + inputStream = if (`in`.isEmpty()) { + ByteArrayInputStream(`in`) + } else { + val memoryFile = MemoryFile("file" + System.currentTimeMillis(), `in`.size) + memoryFile.outputStream.write(`in`) + memoryFile.inputStream + } + + val decryptedStream = MXEncryptedAttachments.decryptAttachment(inputStream, encryptedFileInfo) + + assertNotNull(decryptedStream) + + inputStream.close() + + val buffer = ByteArray(100) + + val len = decryptedStream!!.read(buffer) + + decryptedStream.close() + + return Base64.encodeToString(buffer, 0, len, Base64.DEFAULT).replace("\n".toRegex(), "").replace("=".toRegex(), "") + } + + @Test + fun checkDecrypt1() { + val encryptedFileInfo = EncryptedFileInfo( + v = "v2", + hashes = mapOf("sha256" to "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU"), + key = EncryptedFileKey( + alg = "A256CTR", + k = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + key_ops = listOf("encrypt", "decrypt"), + kty = "oct", + ext = true + ), + iv = "AAAAAAAAAAAAAAAAAAAAAA", + url = "dummyUrl" + ) + + assertEquals("", checkDecryption("", encryptedFileInfo)) + } + + @Test + fun checkDecrypt2() { + val encryptedFileInfo = EncryptedFileInfo( + v = "v2", + hashes = mapOf("sha256" to "YzF08lARDdOCzJpzuSwsjTNlQc4pHxpdHcXiD/wpK6k"), + key = EncryptedFileKey( + alg = "A256CTR", + k = "__________________________________________8", + key_ops = listOf("encrypt", "decrypt"), + kty = "oct", + ext = true + ), + iv = "//////////8AAAAAAAAAAA", + url = "dummyUrl" + ) + + assertEquals("SGVsbG8sIFdvcmxk", checkDecryption("5xJZTt5cQicm+9f4", encryptedFileInfo)) + } + + @Test + fun checkDecrypt3() { + val encryptedFileInfo = EncryptedFileInfo( + v = "v2", + hashes = mapOf("sha256" to "IOq7/dHHB+mfHfxlRY5XMeCWEwTPmlf4cJcgrkf6fVU"), + key = EncryptedFileKey( + alg = "A256CTR", + k = "__________________________________________8", + key_ops = listOf("encrypt", "decrypt"), + kty = "oct", + ext = true + ), + iv = "//////////8AAAAAAAAAAA", + url = "dummyUrl" + ) + + assertEquals("YWxwaGFudW1lcmljYWxseWFscGhhbnVtZXJpY2FsbHlhbHBoYW51bWVyaWNhbGx5YWxwaGFudW1lcmljYWxseQ", + checkDecryption("zhtFStAeFx0s+9L/sSQO+WQMtldqYEHqTxMduJrCIpnkyer09kxJJuA4K+adQE4w+7jZe/vR9kIcqj9rOhDR8Q", + encryptedFileInfo)) + } + + @Test + fun checkDecrypt4() { + val encryptedFileInfo = EncryptedFileInfo( + v = "v2", + hashes = mapOf("sha256" to "LYG/orOViuFwovJpv2YMLSsmVKwLt7pY3f8SYM7KU5E"), + key = EncryptedFileKey( + alg = "A256CTR", + k = "__________________________________________8", + key_ops = listOf("encrypt", "decrypt"), + kty = "oct", + ext = true + ), + iv = "/////////////////////w", + url = "dummyUrl" + ) + + assertNotEquals("YWxwaGFudW1lcmljYWxseWFscGhhbnVtZXJpY2FsbHlhbHBoYW51bWVyaWNhbGx5YWxwaGFudW1lcmljYWxseQ", + checkDecryption("tJVNBVJ/vl36UQt4Y5e5m84bRUrQHhcdLPvS/7EkDvlkDLZXamBB6k8THbiawiKZ5Mnq9PZMSSbgOCvmnUBOMA", + encryptedFileInfo)) + } +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ExportEncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ExportEncryptionTest.kt new file mode 100644 index 0000000000..89ed3c2e65 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ExportEncryptionTest.kt @@ -0,0 +1,206 @@ +/* + * Copyright 2018 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.crypto + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Assert.* +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters + +/** + * Unit tests ExportEncryptionTest. + */ +@RunWith(AndroidJUnit4::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class ExportEncryptionTest { + + @Test + fun checkExportError1() { + val password = "password" + val input = "-----" + var failed = false + + try { + MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password) + } catch (e: Exception) { + failed = true + } + + assertTrue(failed) + } + + @Test + fun checkExportError2() { + val password = "password" + val input = "-----BEGIN MEGOLM SESSION DATA-----\n" + "-----" + var failed = false + + try { + MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password) + } catch (e: Exception) { + failed = true + } + + assertTrue(failed) + } + + @Test + fun checkExportError3() { + val password = "password" + val input = "-----BEGIN MEGOLM SESSION DATA-----\n" + + " AXNhbHRzYWx0c2FsdHNhbHSIiIiIiIiIiIiIiIiIiIiIAAAACmIRUW2OjZ3L2l6j9h0lHlV3M2dx\n" + + " cissyYBxjsfsAn\n" + + " -----END MEGOLM SESSION DATA-----" + var failed = false + + try { + MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password) + } catch (e: Exception) { + failed = true + } + + assertTrue(failed) + } + + @Test + fun checkExportDecrypt1() { + val password = "password" + val input = "-----BEGIN MEGOLM SESSION DATA-----\nAXNhbHRzYWx0c2FsdHNhbHSIiIiIiIiIiIiIiIiIiIiIAAAACmIRUW2OjZ3L2l6j9h0lHlV3M2dx\n" + "cissyYBxjsfsAndErh065A8=\n-----END MEGOLM SESSION DATA-----" + val expectedString = "plain" + + var decodedString: String? = null + try { + decodedString = MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password) + } catch (e: Exception) { + fail("## checkExportDecrypt1() failed : " + e.message) + } + + assertEquals("## checkExportDecrypt1() : expectedString $expectedString -- decodedString $decodedString", + expectedString, + decodedString) + } + + @Test + fun checkExportDecrypt2() { + val password = "betterpassword" + val input = "-----BEGIN MEGOLM SESSION DATA-----\nAW1vcmVzYWx0bW9yZXNhbHT//////////wAAAAAAAAAAAAAD6KyBpe1Niv5M5NPm4ZATsJo5nghk\n" + "KYu63a0YQ5DRhUWEKk7CcMkrKnAUiZny\n-----END MEGOLM SESSION DATA-----" + val expectedString = "Hello, World" + + var decodedString: String? = null + try { + decodedString = MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password) + } catch (e: Exception) { + fail("## checkExportDecrypt2() failed : " + e.message) + } + + assertEquals("## checkExportDecrypt2() : expectedString $expectedString -- decodedString $decodedString", + expectedString, + decodedString) + } + + @Test + fun checkExportDecrypt3() { + val password = "SWORDFISH" + val input = "-----BEGIN MEGOLM SESSION DATA-----\nAXllc3NhbHR5Z29vZG5lc3P//////////wAAAAAAAAAAAAAD6OIW+Je7gwvjd4kYrb+49gKCfExw\n" + "MgJBMD4mrhLkmgAngwR1pHjbWXaoGybtiAYr0moQ93GrBQsCzPbvl82rZhaXO3iH5uHo/RCEpOqp\nPgg29363BGR+/Ripq/VCLKGNbw==\n-----END MEGOLM SESSION DATA-----" + val expectedString = "alphanumericallyalphanumericallyalphanumericallyalphanumerically" + + var decodedString: String? = null + try { + decodedString = MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password) + } catch (e: Exception) { + fail("## checkExportDecrypt3() failed : " + e.message) + } + + assertEquals("## checkExportDecrypt3() : expectedString $expectedString -- decodedString $decodedString", + expectedString, + decodedString) + } + + @Test + fun checkExportEncrypt1() { + val password = "password" + val expectedString = "plain" + var decodedString: String? = null + + try { + decodedString = MXMegolmExportEncryption + .decryptMegolmKeyFile(MXMegolmExportEncryption.encryptMegolmKeyFile(expectedString, password, 1000), password) + } catch (e: Exception) { + fail("## checkExportEncrypt1() failed : " + e.message) + } + + assertEquals("## checkExportEncrypt1() : expectedString $expectedString -- decodedString $decodedString", + expectedString, + decodedString) + } + + @Test + fun checkExportEncrypt2() { + val password = "betterpassword" + val expectedString = "Hello, World" + var decodedString: String? = null + + try { + decodedString = MXMegolmExportEncryption + .decryptMegolmKeyFile(MXMegolmExportEncryption.encryptMegolmKeyFile(expectedString, password, 1000), password) + } catch (e: Exception) { + fail("## checkExportEncrypt2() failed : " + e.message) + } + + assertEquals("## checkExportEncrypt2() : expectedString $expectedString -- decodedString $decodedString", + expectedString, + decodedString) + } + + @Test + fun checkExportEncrypt3() { + val password = "SWORDFISH" + val expectedString = "alphanumericallyalphanumericallyalphanumericallyalphanumerically" + var decodedString: String? = null + + try { + decodedString = MXMegolmExportEncryption + .decryptMegolmKeyFile(MXMegolmExportEncryption.encryptMegolmKeyFile(expectedString, password, 1000), password) + } catch (e: Exception) { + fail("## checkExportEncrypt3() failed : " + e.message) + } + + assertEquals("## checkExportEncrypt3() : expectedString $expectedString -- decodedString $decodedString", + expectedString, + decodedString) + } + + @Test + fun checkExportEncrypt4() { + val password = "passwordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpassword" + "passwordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpassword" + val expectedString = "alphanumericallyalphanumericallyalphanumericallyalphanumerically" + var decodedString: String? = null + + try { + decodedString = MXMegolmExportEncryption + .decryptMegolmKeyFile(MXMegolmExportEncryption.encryptMegolmKeyFile(expectedString, password, 1000), password) + } catch (e: Exception) { + fail("## checkExportEncrypt4() failed : " + e.message) + } + + assertEquals("## checkExportEncrypt4() : expectedString $expectedString -- decodedString $decodedString", + expectedString, + decodedString) + } +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupPasswordTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupPasswordTest.kt new file mode 100644 index 0000000000..53e68383ee --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupPasswordTest.kt @@ -0,0 +1,178 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.crypto.keysbackup + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import im.vector.matrix.android.InstrumentedTest +import im.vector.matrix.android.api.listeners.ProgressListener +import im.vector.matrix.android.common.assertByteArrayNotEqual +import org.junit.Assert.* +import org.junit.Before +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.matrix.olm.OlmManager +import org.matrix.olm.OlmPkDecryption + +@RunWith(AndroidJUnit4::class) +@FixMethodOrder(MethodSorters.JVM) +class KeysBackupPasswordTest : InstrumentedTest { + + @Before + fun ensureLibLoaded() { + OlmManager() + } + + /** + * Check KeysBackupPassword utilities + */ + @Test + fun passwordConverter_ok() { + val generatePrivateKeyResult = generatePrivateKeyWithPassword(PASSWORD, null) + + assertEquals(32, generatePrivateKeyResult.salt.length) + assertEquals(500_000, generatePrivateKeyResult.iterations) + assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size) + + // Reverse operation + val retrievedPrivateKey = retrievePrivateKeyWithPassword(PASSWORD, + generatePrivateKeyResult.salt, + generatePrivateKeyResult.iterations) + + assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size) + assertArrayEquals(generatePrivateKeyResult.privateKey, retrievedPrivateKey) + } + + /** + * Check generatePrivateKeyWithPassword progress listener behavior + */ + @Test + fun passwordConverter_progress_ok() { + val progressValues = ArrayList(101) + var lastTotal = 0 + + generatePrivateKeyWithPassword(PASSWORD, object : ProgressListener { + override fun onProgress(progress: Int, total: Int) { + if (!progressValues.contains(progress)) { + progressValues.add(progress) + } + + lastTotal = total + } + }) + + assertEquals(100, lastTotal) + + // Ensure all values are here + assertEquals(101, progressValues.size) + + for (i in 0..100) { + assertTrue(progressValues[i] == i) + } + } + + /** + * Check KeysBackupPassword utilities, with bad password + */ + @Test + fun passwordConverter_badPassword_ok() { + val generatePrivateKeyResult = generatePrivateKeyWithPassword(PASSWORD, null) + + assertEquals(32, generatePrivateKeyResult.salt.length) + assertEquals(500_000, generatePrivateKeyResult.iterations) + assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size) + + // Reverse operation, with bad password + val retrievedPrivateKey = retrievePrivateKeyWithPassword(BAD_PASSWORD, + generatePrivateKeyResult.salt, + generatePrivateKeyResult.iterations) + + assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size) + assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey) + } + + /** + * Check KeysBackupPassword utilities, with bad password + */ + @Test + fun passwordConverter_badIteration_ok() { + val generatePrivateKeyResult = generatePrivateKeyWithPassword(PASSWORD, null) + + assertEquals(32, generatePrivateKeyResult.salt.length) + assertEquals(500_000, generatePrivateKeyResult.iterations) + assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size) + + // Reverse operation, with bad iteration + val retrievedPrivateKey = retrievePrivateKeyWithPassword(PASSWORD, + generatePrivateKeyResult.salt, + 500_001) + + assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size) + assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey) + } + + /** + * Check KeysBackupPassword utilities, with bad salt + */ + @Test + fun passwordConverter_badSalt_ok() { + val generatePrivateKeyResult = generatePrivateKeyWithPassword(PASSWORD, null) + + assertEquals(32, generatePrivateKeyResult.salt.length) + assertEquals(500_000, generatePrivateKeyResult.iterations) + assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size) + + // Reverse operation, with bad iteration + val retrievedPrivateKey = retrievePrivateKeyWithPassword(PASSWORD, + BAD_SALT, + generatePrivateKeyResult.iterations) + + assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size) + assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey) + } + + /** + * Check [retrievePrivateKeyWithPassword] with data coming from another platform (RiotWeb). + */ + @Test + fun passwordConverter_crossPlatform_ok() { + val password = "This is a passphrase!" + val salt = "TO0lxhQ9aYgGfMsclVWPIAublg8h9Nlu" + val iteration = 500_000 + + val retrievedPrivateKey = retrievePrivateKeyWithPassword(password, salt, iteration) + + assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size) + + // Data from RiotWeb + val privateKeyBytes = byteArrayOf( + 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()) + + assertArrayEquals(privateKeyBytes, retrievedPrivateKey) + } + + companion object { + private const val PASSWORD = "password" + private const val BAD_PASSWORD = "passw0rd" + + private const val BAD_SALT = "AA0lxhQ9aYgGfMsclVWPIAublg8h9Nlu" + } +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupTest.kt new file mode 100644 index 0000000000..15deebdab1 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupTest.kt @@ -0,0 +1,1417 @@ +/* + * Copyright 2018 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.crypto.keysbackup + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import im.vector.matrix.android.InstrumentedTest +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.listeners.ProgressListener +import im.vector.matrix.android.api.listeners.StepProgressListener +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService +import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState +import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener +import im.vector.matrix.android.common.* +import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP +import im.vector.matrix.android.internal.crypto.MegolmSessionData +import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest +import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust +import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo +import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion +import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult +import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult +import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo +import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper +import org.junit.Assert.* +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import java.util.* +import java.util.concurrent.CountDownLatch + +@RunWith(AndroidJUnit4::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class KeysBackupTest : InstrumentedTest { + + private val mTestHelper = CommonTestHelper(context()) + private val mCryptoTestHelper = CryptoTestHelper(mTestHelper) + + private val defaultSessionParams = SessionTestParams(withInitialSync = false) + private val defaultSessionParamsWithInitialSync = SessionTestParams(withInitialSync = true) + + /** + * - From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys + * - Check backup keys after having marked one as backed up + * - Reset keys backup markers + */ + @Test + fun roomKeysTest_testBackupStore_ok() { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + + // From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys + val cryptoStore = (cryptoTestData.firstSession.getKeysBackupService() as KeysBackup).store + val sessions = cryptoStore.inboundGroupSessionsToBackup(100) + val sessionsCount = sessions.size + + assertFalse(sessions.isEmpty()) + assertEquals(sessionsCount, cryptoTestData.firstSession.inboundGroupSessionsCount(false)) + assertEquals(0, cryptoTestData.firstSession.inboundGroupSessionsCount(true)) + + // - Check backup keys after having marked one as backed up + val session = sessions[0] + + cryptoStore.markBackupDoneForInboundGroupSessions(Collections.singletonList(session)) + + assertEquals(sessionsCount, cryptoTestData.firstSession.inboundGroupSessionsCount(false)) + assertEquals(1, cryptoTestData.firstSession.inboundGroupSessionsCount(true)) + + val sessions2 = cryptoStore.inboundGroupSessionsToBackup(100) + assertEquals(sessionsCount - 1, sessions2.size) + + // - Reset keys backup markers + cryptoStore.resetBackupMarkers() + + val sessions3 = cryptoStore.inboundGroupSessionsToBackup(100) + assertEquals(sessionsCount, sessions3.size) + assertEquals(sessionsCount, cryptoTestData.firstSession.inboundGroupSessionsCount(false)) + assertEquals(0, cryptoTestData.firstSession.inboundGroupSessionsCount(true)) + } + + /** + * Check that prepareKeysBackupVersionWithPassword returns valid data + */ + @Test + fun prepareKeysBackupVersionTest() { + val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams) + + assertNotNull(bobSession.getKeysBackupService()) + + val keysBackup = bobSession.getKeysBackupService() + + val stateObserver = StateObserver(keysBackup) + + assertFalse(keysBackup.isEnabled) + + val latch = CountDownLatch(1) + + keysBackup.prepareKeysBackupVersion(null, null, object : MatrixCallback { + override fun onSuccess(data: MegolmBackupCreationInfo) { + assertNotNull(data) + + assertEquals(MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, data.algorithm) + assertNotNull(data.authData) + assertNotNull(data.authData!!.publicKey) + assertNotNull(data.authData!!.signatures) + assertNotNull(data.recoveryKey) + + latch.countDown() + } + + override fun onFailure(failure: Throwable) { + fail(failure.localizedMessage) + + latch.countDown() + } + }) + mTestHelper.await(latch) + + stateObserver.stopAndCheckStates(null) + bobSession.close() + } + + /** + * Test creating a keys backup version and check that createKeysBackupVersion() returns valid data + */ + @Test + fun createKeysBackupVersionTest() { + val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams) + + val keysBackup = bobSession.getKeysBackupService() + + val stateObserver = StateObserver(keysBackup) + + assertFalse(keysBackup.isEnabled) + + var megolmBackupCreationInfo: MegolmBackupCreationInfo? = null + val latch = CountDownLatch(1) + keysBackup.prepareKeysBackupVersion(null, null, object : MatrixCallback { + override fun onSuccess(data: MegolmBackupCreationInfo) { + megolmBackupCreationInfo = data + + latch.countDown() + } + + override fun onFailure(failure: Throwable) { + fail(failure.localizedMessage) + + latch.countDown() + } + }) + mTestHelper.await(latch) + + assertNotNull(megolmBackupCreationInfo) + + assertFalse(keysBackup.isEnabled) + + val latch2 = CountDownLatch(1) + + // Create the version + keysBackup.createKeysBackupVersion(megolmBackupCreationInfo!!, object : TestMatrixCallback(latch2) { + override fun onSuccess(data: KeysVersion) { + assertNotNull(data) + assertNotNull(data.version) + + super.onSuccess(data) + } + }) + mTestHelper.await(latch2) + + // Backup must be enable now + assertTrue(keysBackup.isEnabled) + + stateObserver.stopAndCheckStates(null) + bobSession.close() + } + + /** + * - Check that createKeysBackupVersion() launches the backup + * - Check the backup completes + */ + @Test + fun backupAfterCreateKeysBackupVersionTest() { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + + val keysBackup = cryptoTestData.firstSession.getKeysBackupService() + + val latch = CountDownLatch(1) + + assertEquals(2, cryptoTestData.firstSession.inboundGroupSessionsCount(false)) + assertEquals(0, cryptoTestData.firstSession.inboundGroupSessionsCount(true)) + + val stateObserver = StateObserver(keysBackup, latch, 5) + + prepareAndCreateKeysBackupData(keysBackup) + + mTestHelper.await(latch) + + val nbOfKeys = cryptoTestData.firstSession.inboundGroupSessionsCount(false) + val backedUpKeys = cryptoTestData.firstSession.inboundGroupSessionsCount(true) + + assertEquals(2, nbOfKeys) + assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys) + + // Check the several backup state changes + stateObserver.stopAndCheckStates( + listOf( + KeysBackupState.Enabling, + KeysBackupState.ReadyToBackUp, + KeysBackupState.WillBackUp, + KeysBackupState.BackingUp, + KeysBackupState.ReadyToBackUp + ) + ) + cryptoTestData.close() + } + + /** + * Check that backupAllGroupSessions() returns valid data + */ + @Test + fun backupAllGroupSessionsTest() { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + + val keysBackup = cryptoTestData.firstSession.getKeysBackupService() + + val stateObserver = StateObserver(keysBackup) + + prepareAndCreateKeysBackupData(keysBackup) + + // Check that backupAllGroupSessions returns valid data + val nbOfKeys = cryptoTestData.firstSession.inboundGroupSessionsCount(false) + + assertEquals(2, nbOfKeys) + + val latch = CountDownLatch(1) + + var lastBackedUpKeysProgress = 0 + + keysBackup.backupAllGroupSessions(object : ProgressListener { + override fun onProgress(progress: Int, total: Int) { + assertEquals(nbOfKeys, total) + lastBackedUpKeysProgress = progress + } + }, TestMatrixCallback(latch)) + + mTestHelper.await(latch) + assertEquals(nbOfKeys, lastBackedUpKeysProgress) + + val backedUpKeys = cryptoTestData.firstSession.inboundGroupSessionsCount(true) + + assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys) + + stateObserver.stopAndCheckStates(null) + cryptoTestData.close() + } + + /** + * Check encryption and decryption of megolm keys in the backup. + * - Pick a megolm key + * - Check [MXKeyBackup encryptGroupSession] returns stg + * - Check [MXKeyBackup pkDecryptionFromRecoveryKey] is able to create a OLMPkDecryption + * - Check [MXKeyBackup decryptKeyBackupData] returns stg + * - Compare the decrypted megolm key with the original one + */ + @Test + fun testEncryptAndDecryptKeysBackupData() { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + + val keysBackup = cryptoTestData.firstSession.getKeysBackupService() as KeysBackup + + val stateObserver = StateObserver(keysBackup) + + // - Pick a megolm key + val session = keysBackup.store.inboundGroupSessionsToBackup(1)[0] + + val keyBackupCreationInfo = prepareAndCreateKeysBackupData(keysBackup).megolmBackupCreationInfo + + // - Check encryptGroupSession() returns stg + val keyBackupData = keysBackup.encryptGroupSession(session) + assertNotNull(keyBackupData) + assertNotNull(keyBackupData.sessionData) + + // - Check pkDecryptionFromRecoveryKey() is able to create a OlmPkDecryption + val decryption = keysBackup.pkDecryptionFromRecoveryKey(keyBackupCreationInfo.recoveryKey) + assertNotNull(decryption) + // - Check decryptKeyBackupData() returns stg + val sessionData = keysBackup.decryptKeyBackupData(keyBackupData, session.olmInboundGroupSession!!.sessionIdentifier(), cryptoTestData.roomId, decryption!!) + assertNotNull(sessionData) + // - Compare the decrypted megolm key with the original one + assertKeysEquals(session.exportKeys(), sessionData) + + stateObserver.stopAndCheckStates(null) + cryptoTestData.close() + } + + /** + * - Do an e2e backup to the homeserver with a recovery key + * - Log Alice on a new device + * - Restore the e2e backup from the homeserver with the recovery key + * - Restore must be successful + */ + @Test + fun restoreKeysBackupTest() { + val testData = createKeysBackupScenarioWithPassword(null) + + // - Restore the e2e backup from the homeserver + val latch2 = CountDownLatch(1) + var importRoomKeysResult: ImportRoomKeysResult? = null + testData.aliceSession2.getKeysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.getKeysBackupService().keysBackupVersion!!, + testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, + null, + null, + null, + object : TestMatrixCallback(latch2) { + override fun onSuccess(data: ImportRoomKeysResult) { + importRoomKeysResult = data + super.onSuccess(data) + } + } + ) + mTestHelper.await(latch2) + + checkRestoreSuccess(testData, importRoomKeysResult!!.totalNumberOfKeys, importRoomKeysResult!!.successfullyNumberOfImportedKeys) + + testData.cryptoTestData.close() + } + + /** + * + * This is the same as `testRestoreKeyBackup` but this test checks that pending key + * share requests are cancelled. + * + * - Do an e2e backup to the homeserver with a recovery key + * - Log Alice on a new device + * - *** Check the SDK sent key share requests + * - Restore the e2e backup from the homeserver with the recovery key + * - Restore must be successful + * - *** There must be no more pending key share requests + */ + @Test + fun restoreKeysBackupAndKeyShareRequestTest() { + val testData = createKeysBackupScenarioWithPassword(null) + + // - Check the SDK sent key share requests + val cryptoStore2 = (testData.aliceSession2.getKeysBackupService() as KeysBackup).store + val unsentRequest = cryptoStore2 + .getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.UNSENT)) + val sentRequest = cryptoStore2 + .getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.SENT)) + + // Request is either sent or unsent + assertTrue(unsentRequest != null || sentRequest != null) + + // - Restore the e2e backup from the homeserver + val latch2 = CountDownLatch(1) + var importRoomKeysResult: ImportRoomKeysResult? = null + testData.aliceSession2.getKeysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.getKeysBackupService().keysBackupVersion!!, + testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, + null, + null, + null, + object : TestMatrixCallback(latch2) { + override fun onSuccess(data: ImportRoomKeysResult) { + importRoomKeysResult = data + super.onSuccess(data) + } + } + ) + mTestHelper.await(latch2) + + checkRestoreSuccess(testData, importRoomKeysResult!!.totalNumberOfKeys, importRoomKeysResult!!.successfullyNumberOfImportedKeys) + + // - There must be no more pending key share requests + val unsentRequestAfterRestoration = cryptoStore2 + .getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.UNSENT)) + val sentRequestAfterRestoration = cryptoStore2 + .getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.SENT)) + + // Request is either sent or unsent + assertTrue(unsentRequestAfterRestoration == null && sentRequestAfterRestoration == null) + + testData.cryptoTestData.close() + } + + /** + * - Do an e2e backup to the homeserver with a recovery key + * - And log Alice on a new device + * - The new device must see the previous backup as not trusted + * - Trust the backup from the new device + * - Backup must be enabled on the new device + * - Retrieve the last version from the server + * - It must be the same + * - It must be trusted and must have with 2 signatures now + */ + @Test + fun trustKeyBackupVersionTest() { + // - Do an e2e backup to the homeserver with a recovery key + // - And log Alice on a new device + val testData = createKeysBackupScenarioWithPassword(null) + + val stateObserver = StateObserver(testData.aliceSession2.getKeysBackupService()) + + // - The new device must see the previous backup as not trusted + assertNotNull(testData.aliceSession2.getKeysBackupService().keysBackupVersion) + assertFalse(testData.aliceSession2.getKeysBackupService().isEnabled) + assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.getKeysBackupService().state) + + // - Trust the backup from the new device + val latch = CountDownLatch(1) + testData.aliceSession2.getKeysBackupService().trustKeysBackupVersion( + testData.aliceSession2.getKeysBackupService().keysBackupVersion!!, + true, + TestMatrixCallback(latch) + ) + mTestHelper.await(latch) + + // Wait for backup state to be ReadyToBackUp + waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp) + + // - Backup must be enabled on the new device, on the same version + assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.getKeysBackupService().keysBackupVersion?.version) + assertTrue(testData.aliceSession2.getKeysBackupService().isEnabled) + + // - Retrieve the last version from the server + val latch2 = CountDownLatch(1) + var keysVersionResult: KeysVersionResult? = null + testData.aliceSession2.getKeysBackupService().getCurrentVersion( + object : TestMatrixCallback(latch2) { + override fun onSuccess(data: KeysVersionResult?) { + keysVersionResult = data + super.onSuccess(data) + } + } + ) + mTestHelper.await(latch2) + + // - It must be the same + assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) + + val latch3 = CountDownLatch(1) + var keysBackupVersionTrust: KeysBackupVersionTrust? = null + testData.aliceSession2.getKeysBackupService().getKeysBackupTrust(keysVersionResult!!, + object : TestMatrixCallback(latch3) { + override fun onSuccess(data: KeysBackupVersionTrust) { + keysBackupVersionTrust = data + super.onSuccess(data) + } + }) + mTestHelper.await(latch3) + + // - It must be trusted and must have 2 signatures now + assertTrue(keysBackupVersionTrust!!.usable) + assertEquals(2, keysBackupVersionTrust!!.signatures.size) + + stateObserver.stopAndCheckStates(null) + testData.cryptoTestData.close() + } + + /** + * - Do an e2e backup to the homeserver with a recovery key + * - And log Alice on a new device + * - The new device must see the previous backup as not trusted + * - Trust the backup from the new device with the recovery key + * - Backup must be enabled on the new device + * - Retrieve the last version from the server + * - It must be the same + * - It must be trusted and must have with 2 signatures now + */ + @Test + fun trustKeyBackupVersionWithRecoveryKeyTest() { + // - Do an e2e backup to the homeserver with a recovery key + // - And log Alice on a new device + val testData = createKeysBackupScenarioWithPassword(null) + + val stateObserver = StateObserver(testData.aliceSession2.getKeysBackupService()) + + // - The new device must see the previous backup as not trusted + assertNotNull(testData.aliceSession2.getKeysBackupService().keysBackupVersion) + assertFalse(testData.aliceSession2.getKeysBackupService().isEnabled) + assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.getKeysBackupService().state) + + // - Trust the backup from the new device with the recovery key + val latch = CountDownLatch(1) + testData.aliceSession2.getKeysBackupService().trustKeysBackupVersionWithRecoveryKey( + testData.aliceSession2.getKeysBackupService().keysBackupVersion!!, + testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, + TestMatrixCallback(latch) + ) + mTestHelper.await(latch) + + // Wait for backup state to be ReadyToBackUp + waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp) + + // - Backup must be enabled on the new device, on the same version + assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.getKeysBackupService().keysBackupVersion?.version) + assertTrue(testData.aliceSession2.getKeysBackupService().isEnabled) + + // - Retrieve the last version from the server + val latch2 = CountDownLatch(1) + var keysVersionResult: KeysVersionResult? = null + testData.aliceSession2.getKeysBackupService().getCurrentVersion( + object : TestMatrixCallback(latch2) { + override fun onSuccess(data: KeysVersionResult?) { + keysVersionResult = data + super.onSuccess(data) + } + } + ) + mTestHelper.await(latch2) + + // - It must be the same + assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) + + val latch3 = CountDownLatch(1) + var keysBackupVersionTrust: KeysBackupVersionTrust? = null + testData.aliceSession2.getKeysBackupService().getKeysBackupTrust(keysVersionResult!!, + object : TestMatrixCallback(latch3) { + override fun onSuccess(data: KeysBackupVersionTrust) { + keysBackupVersionTrust = data + super.onSuccess(data) + } + }) + mTestHelper.await(latch3) + + // - It must be trusted and must have 2 signatures now + assertTrue(keysBackupVersionTrust!!.usable) + assertEquals(2, keysBackupVersionTrust!!.signatures.size) + + stateObserver.stopAndCheckStates(null) + testData.cryptoTestData.close() + } + + /** + * - Do an e2e backup to the homeserver with a recovery key + * - And log Alice on a new device + * - The new device must see the previous backup as not trusted + * - Try to trust the backup from the new device with a wrong recovery key + * - It must fail + * - The backup must still be untrusted and disabled + */ + @Test + fun trustKeyBackupVersionWithWrongRecoveryKeyTest() { + // - Do an e2e backup to the homeserver with a recovery key + // - And log Alice on a new device + val testData = createKeysBackupScenarioWithPassword(null) + + val stateObserver = StateObserver(testData.aliceSession2.getKeysBackupService()) + + // - The new device must see the previous backup as not trusted + assertNotNull(testData.aliceSession2.getKeysBackupService().keysBackupVersion) + assertFalse(testData.aliceSession2.getKeysBackupService().isEnabled) + assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.getKeysBackupService().state) + + // - Try to trust the backup from the new device with a wrong recovery key + val latch = CountDownLatch(1) + testData.aliceSession2.getKeysBackupService().trustKeysBackupVersionWithRecoveryKey( + testData.aliceSession2.getKeysBackupService().keysBackupVersion!!, + "Bad recovery key", + TestMatrixCallback(latch, false) + ) + mTestHelper.await(latch) + + // - The new device must still see the previous backup as not trusted + assertNotNull(testData.aliceSession2.getKeysBackupService().keysBackupVersion) + assertFalse(testData.aliceSession2.getKeysBackupService().isEnabled) + assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.getKeysBackupService().state) + + stateObserver.stopAndCheckStates(null) + testData.cryptoTestData.close() + } + + /** + * - Do an e2e backup to the homeserver with a password + * - And log Alice on a new device + * - The new device must see the previous backup as not trusted + * - Trust the backup from the new device with the password + * - Backup must be enabled on the new device + * - Retrieve the last version from the server + * - It must be the same + * - It must be trusted and must have with 2 signatures now + */ + @Test + fun trustKeyBackupVersionWithPasswordTest() { + val password = "Password" + + // - Do an e2e backup to the homeserver with a password + // - And log Alice on a new device + val testData = createKeysBackupScenarioWithPassword(password) + + val stateObserver = StateObserver(testData.aliceSession2.getKeysBackupService()) + + // - The new device must see the previous backup as not trusted + assertNotNull(testData.aliceSession2.getKeysBackupService().keysBackupVersion) + assertFalse(testData.aliceSession2.getKeysBackupService().isEnabled) + assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.getKeysBackupService().state) + + // - Trust the backup from the new device with the password + val latch = CountDownLatch(1) + testData.aliceSession2.getKeysBackupService().trustKeysBackupVersionWithPassphrase( + testData.aliceSession2.getKeysBackupService().keysBackupVersion!!, + password, + TestMatrixCallback(latch) + ) + mTestHelper.await(latch) + + // Wait for backup state to be ReadyToBackUp + waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp) + + // - Backup must be enabled on the new device, on the same version + assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.getKeysBackupService().keysBackupVersion?.version) + assertTrue(testData.aliceSession2.getKeysBackupService().isEnabled) + + // - Retrieve the last version from the server + val latch2 = CountDownLatch(1) + var keysVersionResult: KeysVersionResult? = null + testData.aliceSession2.getKeysBackupService().getCurrentVersion( + object : TestMatrixCallback(latch2) { + override fun onSuccess(data: KeysVersionResult?) { + keysVersionResult = data + super.onSuccess(data) + } + } + ) + mTestHelper.await(latch2) + + // - It must be the same + assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) + + val latch3 = CountDownLatch(1) + var keysBackupVersionTrust: KeysBackupVersionTrust? = null + testData.aliceSession2.getKeysBackupService().getKeysBackupTrust(keysVersionResult!!, + object : TestMatrixCallback(latch3) { + override fun onSuccess(data: KeysBackupVersionTrust) { + keysBackupVersionTrust = data + super.onSuccess(data) + } + }) + mTestHelper.await(latch3) + + // - It must be trusted and must have 2 signatures now + assertTrue(keysBackupVersionTrust!!.usable) + assertEquals(2, keysBackupVersionTrust!!.signatures.size) + + stateObserver.stopAndCheckStates(null) + testData.cryptoTestData.close() + } + + /** + * - Do an e2e backup to the homeserver with a password + * - And log Alice on a new device + * - The new device must see the previous backup as not trusted + * - Try to trust the backup from the new device with a wrong password + * - It must fail + * - The backup must still be untrusted and disabled + */ + @Test + fun trustKeyBackupVersionWithWrongPasswordTest() { + val password = "Password" + val badPassword = "Bad Password" + + // - Do an e2e backup to the homeserver with a password + // - And log Alice on a new device + val testData = createKeysBackupScenarioWithPassword(password) + + val stateObserver = StateObserver(testData.aliceSession2.getKeysBackupService()) + + // - The new device must see the previous backup as not trusted + assertNotNull(testData.aliceSession2.getKeysBackupService().keysBackupVersion) + assertFalse(testData.aliceSession2.getKeysBackupService().isEnabled) + assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.getKeysBackupService().state) + + // - Try to trust the backup from the new device with a wrong password + val latch = CountDownLatch(1) + testData.aliceSession2.getKeysBackupService().trustKeysBackupVersionWithPassphrase( + testData.aliceSession2.getKeysBackupService().keysBackupVersion!!, + badPassword, + TestMatrixCallback(latch, false) + ) + mTestHelper.await(latch) + + // - The new device must still see the previous backup as not trusted + assertNotNull(testData.aliceSession2.getKeysBackupService().keysBackupVersion) + assertFalse(testData.aliceSession2.getKeysBackupService().isEnabled) + assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.getKeysBackupService().state) + + stateObserver.stopAndCheckStates(null) + testData.cryptoTestData.close() + } + + /** + * - Do an e2e backup to the homeserver with a recovery key + * - Log Alice on a new device + * - Try to restore the e2e backup with a wrong recovery key + * - It must fail + */ + @Test + fun restoreKeysBackupWithAWrongRecoveryKeyTest() { + val testData = createKeysBackupScenarioWithPassword(null) + + // - Try to restore the e2e backup with a wrong recovery key + val latch2 = CountDownLatch(1) + var importRoomKeysResult: ImportRoomKeysResult? = null + testData.aliceSession2.getKeysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.getKeysBackupService().keysBackupVersion!!, + "EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d", + null, + null, + null, + object : TestMatrixCallback(latch2, false) { + override fun onSuccess(data: ImportRoomKeysResult) { + importRoomKeysResult = data + super.onSuccess(data) + } + } + ) + mTestHelper.await(latch2) + + // onSuccess may not have been called + assertNull(importRoomKeysResult) + + testData.cryptoTestData.close() + } + + /** + * - Do an e2e backup to the homeserver with a password + * - Log Alice on a new device + * - Restore the e2e backup with the password + * - Restore must be successful + */ + @Test + fun testBackupWithPassword() { + val password = "password" + + val testData = createKeysBackupScenarioWithPassword(password) + + // - Restore the e2e backup with the password + val latch2 = CountDownLatch(1) + var importRoomKeysResult: ImportRoomKeysResult? = null + val steps = ArrayList() + + testData.aliceSession2.getKeysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.getKeysBackupService().keysBackupVersion!!, + password, + null, + null, + object : StepProgressListener { + override fun onStepProgress(step: StepProgressListener.Step) { + steps.add(step) + } + }, + object : TestMatrixCallback(latch2) { + override fun onSuccess(data: ImportRoomKeysResult) { + importRoomKeysResult = data + super.onSuccess(data) + } + } + ) + mTestHelper.await(latch2) + + // Check steps + assertEquals(105, steps.size) + + for (i in 0..100) { + assertTrue(steps[i] is StepProgressListener.Step.ComputingKey) + assertEquals(i, (steps[i] as StepProgressListener.Step.ComputingKey).progress) + assertEquals(100, (steps[i] as StepProgressListener.Step.ComputingKey).total) + } + + assertTrue(steps[101] is StepProgressListener.Step.DownloadingKey) + + // 2 Keys to import, value will be 0%, 50%, 100% + for (i in 102..104) { + assertTrue(steps[i] is StepProgressListener.Step.ImportingKey) + assertEquals(100, (steps[i] as StepProgressListener.Step.ImportingKey).total) + } + + assertEquals(0, (steps[102] as StepProgressListener.Step.ImportingKey).progress) + assertEquals(50, (steps[103] as StepProgressListener.Step.ImportingKey).progress) + assertEquals(100, (steps[104] as StepProgressListener.Step.ImportingKey).progress) + + checkRestoreSuccess(testData, importRoomKeysResult!!.totalNumberOfKeys, importRoomKeysResult!!.successfullyNumberOfImportedKeys) + + testData.cryptoTestData.close() + } + + /** + * - Do an e2e backup to the homeserver with a password + * - Log Alice on a new device + * - Try to restore the e2e backup with a wrong password + * - It must fail + */ + @Test + fun restoreKeysBackupWithAWrongPasswordTest() { + val password = "password" + val wrongPassword = "passw0rd" + + val testData = createKeysBackupScenarioWithPassword(password) + + // - Try to restore the e2e backup with a wrong password + val latch2 = CountDownLatch(1) + var importRoomKeysResult: ImportRoomKeysResult? = null + testData.aliceSession2.getKeysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.getKeysBackupService().keysBackupVersion!!, + wrongPassword, + null, + null, + null, + object : TestMatrixCallback(latch2, false) { + override fun onSuccess(data: ImportRoomKeysResult) { + importRoomKeysResult = data + super.onSuccess(data) + } + } + ) + mTestHelper.await(latch2) + + // onSuccess may not have been called + assertNull(importRoomKeysResult) + + testData.cryptoTestData.close() + } + + /** + * - Do an e2e backup to the homeserver with a password + * - Log Alice on a new device + * - Restore the e2e backup with the recovery key. + * - Restore must be successful + */ + @Test + fun testUseRecoveryKeyToRestoreAPasswordBasedKeysBackup() { + val password = "password" + + val testData = createKeysBackupScenarioWithPassword(password) + + // - Restore the e2e backup with the recovery key. + val latch2 = CountDownLatch(1) + var importRoomKeysResult: ImportRoomKeysResult? = null + testData.aliceSession2.getKeysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.getKeysBackupService().keysBackupVersion!!, + testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, + null, + null, + null, + object : TestMatrixCallback(latch2) { + override fun onSuccess(data: ImportRoomKeysResult) { + importRoomKeysResult = data + super.onSuccess(data) + } + } + ) + mTestHelper.await(latch2) + + checkRestoreSuccess(testData, importRoomKeysResult!!.totalNumberOfKeys, importRoomKeysResult!!.successfullyNumberOfImportedKeys) + + testData.cryptoTestData.close() + } + + /** + * - Do an e2e backup to the homeserver with a recovery key + * - And log Alice on a new device + * - Try to restore the e2e backup with a password + * - It must fail + */ + @Test + fun testUsePasswordToRestoreARecoveryKeyBasedKeysBackup() { + val testData = createKeysBackupScenarioWithPassword(null) + + // - Try to restore the e2e backup with a password + val latch2 = CountDownLatch(1) + var importRoomKeysResult: ImportRoomKeysResult? = null + testData.aliceSession2.getKeysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.getKeysBackupService().keysBackupVersion!!, + "password", + null, + null, + null, + object : TestMatrixCallback(latch2, false) { + override fun onSuccess(data: ImportRoomKeysResult) { + importRoomKeysResult = data + super.onSuccess(data) + } + } + ) + mTestHelper.await(latch2) + + // onSuccess may not have been called + assertNull(importRoomKeysResult) + + testData.cryptoTestData.close() + } + + /** + * - Create a backup version + * - Check the returned KeysVersionResult is trusted + */ + @Test + fun testIsKeysBackupTrusted() { + // - Create a backup version + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + + val keysBackup = cryptoTestData.firstSession.getKeysBackupService() + + val stateObserver = StateObserver(keysBackup) + + // - Do an e2e backup to the homeserver + prepareAndCreateKeysBackupData(keysBackup) + + // Get key backup version from the home server + var keysVersionResult: KeysVersionResult? = null + val lock = CountDownLatch(1) + keysBackup.getCurrentVersion(object : TestMatrixCallback(lock) { + override fun onSuccess(data: KeysVersionResult?) { + keysVersionResult = data + super.onSuccess(data) + } + }) + mTestHelper.await(lock) + + assertNotNull(keysVersionResult) + + // - Check the returned KeyBackupVersion is trusted + val latch = CountDownLatch(1) + var keysBackupVersionTrust: KeysBackupVersionTrust? = null + keysBackup.getKeysBackupTrust(keysVersionResult!!, object : MatrixCallback { + override fun onSuccess(data: KeysBackupVersionTrust) { + keysBackupVersionTrust = data + latch.countDown() + } + + override fun onFailure(failure: Throwable) { + super.onFailure(failure) + latch.countDown() + } + }) + mTestHelper.await(latch) + + assertNotNull(keysBackupVersionTrust) + assertTrue(keysBackupVersionTrust!!.usable) + assertEquals(1, keysBackupVersionTrust!!.signatures.size) + + val signature = keysBackupVersionTrust!!.signatures[0] + assertTrue(signature.valid) + assertNotNull(signature.device) + assertEquals(cryptoTestData.firstSession.getMyDevice().deviceId, signature.deviceId) + assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.credentials.deviceId) + + stateObserver.stopAndCheckStates(null) + cryptoTestData.close() + } + + /** + * Check backup starts automatically if there is an existing and compatible backup + * version on the homeserver. + * - Create a backup version + * - Restart alice session + * -> The new alice session must back up to the same version + */ + @Test + fun testCheckAndStartKeysBackupWhenRestartingAMatrixSession() { + // - Create a backup version + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + + val keysBackup = cryptoTestData.firstSession.getKeysBackupService() + + val stateObserver = StateObserver(keysBackup) + + assertFalse(keysBackup.isEnabled) + + val keyBackupCreationInfo = prepareAndCreateKeysBackupData(keysBackup) + + assertTrue(keysBackup.isEnabled) + + // - Restart alice session + // - Log Alice on a new device + val aliceSession2 = mTestHelper.logIntoAccount(cryptoTestData.firstSession.myUserId, defaultSessionParamsWithInitialSync) + + cryptoTestData.close() + + val keysBackup2 = aliceSession2.getKeysBackupService() + + val stateObserver2 = StateObserver(keysBackup2) + + // -> The new alice session must back up to the same version + val latch = CountDownLatch(1) + var count = 0 + keysBackup2.addListener(object : KeysBackupStateListener { + override fun onStateChange(newState: KeysBackupState) { + // Check the backup completes + if (keysBackup.state == KeysBackupState.ReadyToBackUp) { + count++ + + if (count == 2) { + // Remove itself from the list of listeners + keysBackup.removeListener(this) + + latch.countDown() + } + } + } + }) + mTestHelper.await(latch) + + assertEquals(keyBackupCreationInfo.version, keysBackup2.currentBackupVersion) + + stateObserver.stopAndCheckStates(null) + stateObserver2.stopAndCheckStates(null) + aliceSession2.close() + } + + /** + * Check WrongBackUpVersion state + * + * - Make alice back up her keys to her homeserver + * - Create a new backup with fake data on the homeserver + * - Make alice back up all her keys again + * -> That must fail and her backup state must be WrongBackUpVersion + */ + @Test + fun testBackupWhenAnotherBackupWasCreated() { + // - Create a backup version + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + + val keysBackup = cryptoTestData.firstSession.getKeysBackupService() + + val stateObserver = StateObserver(keysBackup) + + assertFalse(keysBackup.isEnabled) + + // Wait for keys backup to be finished + val latch0 = CountDownLatch(1) + var count = 0 + keysBackup.addListener(object : KeysBackupStateListener { + override fun onStateChange(newState: KeysBackupState) { + // Check the backup completes + if (newState == KeysBackupState.ReadyToBackUp) { + count++ + + if (count == 2) { + // Remove itself from the list of listeners + keysBackup.removeListener(this) + + latch0.countDown() + } + } + } + }) + + // - Make alice back up her keys to her homeserver + prepareAndCreateKeysBackupData(keysBackup) + + assertTrue(keysBackup.isEnabled) + + mTestHelper.await(latch0) + + // - Create a new backup with fake data on the homeserver, directly using the rest client + val latch = CountDownLatch(1) + + val megolmBackupCreationInfo = mCryptoTestHelper.createFakeMegolmBackupCreationInfo() + (keysBackup as KeysBackup).createFakeKeysBackupVersion(megolmBackupCreationInfo, TestMatrixCallback(latch)) + mTestHelper.await(latch) + + // Reset the store backup status for keys + (cryptoTestData.firstSession.getKeysBackupService() as KeysBackup).store.resetBackupMarkers() + + // - Make alice back up all her keys again + val latch2 = CountDownLatch(1) + keysBackup.backupAllGroupSessions(object : ProgressListener { + override fun onProgress(progress: Int, total: Int) { + } + }, TestMatrixCallback(latch2, false)) + mTestHelper.await(latch2) + + // -> That must fail and her backup state must be WrongBackUpVersion + assertEquals(KeysBackupState.WrongBackUpVersion, keysBackup.state) + assertFalse(keysBackup.isEnabled) + + stateObserver.stopAndCheckStates(null) + cryptoTestData.close() + } + + /** + * - Do an e2e backup to the homeserver + * - Log Alice on a new device + * - Post a message to have a new megolm session + * - Try to backup all + * -> It must fail. Backup state must be NotTrusted + * - Validate the old device from the new one + * -> Backup should automatically enable on the new device + * -> It must use the same backup version + * - Try to backup all again + * -> It must success + */ + @Test + fun testBackupAfterVerifyingADevice() { + // - Create a backup version + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + + val keysBackup = cryptoTestData.firstSession.getKeysBackupService() + + val stateObserver = StateObserver(keysBackup) + + // - Make alice back up her keys to her homeserver + prepareAndCreateKeysBackupData(keysBackup) + + // Wait for keys backup to finish by asking again to backup keys. + val latch = CountDownLatch(1) + keysBackup.backupAllGroupSessions(object : ProgressListener { + override fun onProgress(progress: Int, total: Int) { + } + }, TestMatrixCallback(latch)) + mTestHelper.await(latch) + + val oldDeviceId = cryptoTestData.firstSession.sessionParams.credentials.deviceId!! + val oldKeyBackupVersion = keysBackup.currentBackupVersion + val aliceUserId = cryptoTestData.firstSession.myUserId + + // Close first Alice session, else they will share the same Crypto store and the test fails. + cryptoTestData.firstSession.close() + + // - Log Alice on a new device + val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, defaultSessionParamsWithInitialSync) + + // - Post a message to have a new megolm session + aliceSession2.setWarnOnUnknownDevices(false) + + val room2 = aliceSession2.getRoom(cryptoTestData.roomId)!! + + mTestHelper.sendTextMessage(room2, "New key", 1) + + // - Try to backup all in aliceSession2, it must fail + val keysBackup2 = aliceSession2.getKeysBackupService() + + val stateObserver2 = StateObserver(keysBackup2) + + var isSuccessful = false + val latch2 = CountDownLatch(1) + keysBackup2.backupAllGroupSessions(object : ProgressListener { + override fun onProgress(progress: Int, total: Int) { + } + }, object : TestMatrixCallback(latch2, false) { + override fun onSuccess(data: Unit) { + isSuccessful = true + super.onSuccess(data) + } + }) + mTestHelper.await(latch2) + + assertFalse(isSuccessful) + + // Backup state must be NotTrusted + assertEquals(KeysBackupState.NotTrusted, keysBackup2.state) + assertFalse(keysBackup2.isEnabled) + + // - Validate the old device from the new one + aliceSession2.setDeviceVerification(MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED, oldDeviceId, aliceSession2.myUserId) + + // -> Backup should automatically enable on the new device + val latch4 = CountDownLatch(1) + keysBackup2.addListener(object : KeysBackupStateListener { + override fun onStateChange(newState: KeysBackupState) { + // Check the backup completes + if (keysBackup2.state == KeysBackupState.ReadyToBackUp) { + // Remove itself from the list of listeners + keysBackup2.removeListener(this) + + latch4.countDown() + } + } + }) + mTestHelper.await(latch4) + + // -> It must use the same backup version + assertEquals(oldKeyBackupVersion, aliceSession2.getKeysBackupService().currentBackupVersion) + + val latch5 = CountDownLatch(1) + aliceSession2.getKeysBackupService().backupAllGroupSessions(null, TestMatrixCallback(latch5)) + mTestHelper.await(latch5) + + // -> It must success + assertTrue(aliceSession2.getKeysBackupService().isEnabled) + + stateObserver.stopAndCheckStates(null) + stateObserver2.stopAndCheckStates(null) + aliceSession2.close() + cryptoTestData.close() + } + + /** + * - Do an e2e backup to the homeserver with a recovery key + * - Delete the backup + */ + @Test + fun deleteKeysBackupTest() { + // - Create a backup version + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + + val keysBackup = cryptoTestData.firstSession.getKeysBackupService() + + val stateObserver = StateObserver(keysBackup) + + assertFalse(keysBackup.isEnabled) + + val keyBackupCreationInfo = prepareAndCreateKeysBackupData(keysBackup) + + assertTrue(keysBackup.isEnabled) + + val latch = CountDownLatch(1) + + // Delete the backup + keysBackup.deleteBackup(keyBackupCreationInfo.version, TestMatrixCallback(latch)) + + mTestHelper.await(latch) + + // Backup is now disabled + assertFalse(keysBackup.isEnabled) + + stateObserver.stopAndCheckStates(null) + cryptoTestData.close() + } + + /* ========================================================================================== + * Private + * ========================================================================================== */ + + /** + * As KeysBackup is doing asynchronous call to update its internal state, this method help to wait for the + * KeysBackup object to be in the specified state + */ + private fun waitForKeysBackupToBeInState(session: Session, state: KeysBackupState) { + // If already in the wanted state, return + if (session.getKeysBackupService().state == state) { + return + } + + // Else observe state changes + val latch = CountDownLatch(1) + + session.getKeysBackupService().addListener(object : KeysBackupStateListener { + override fun onStateChange(newState: KeysBackupState) { + if (newState == state) { + session.getKeysBackupService().removeListener(this) + latch.countDown() + } + } + }) + + mTestHelper.await(latch) + } + + private data class PrepareKeysBackupDataResult(val megolmBackupCreationInfo: MegolmBackupCreationInfo, + val version: String) + + private fun prepareAndCreateKeysBackupData(keysBackup: KeysBackupService, + password: String? = null): PrepareKeysBackupDataResult { + val stateObserver = StateObserver(keysBackup) + + var megolmBackupCreationInfo: MegolmBackupCreationInfo? = null + val latch = CountDownLatch(1) + keysBackup.prepareKeysBackupVersion(password, null, object : MatrixCallback { + override fun onSuccess(data: MegolmBackupCreationInfo) { + megolmBackupCreationInfo = data + + latch.countDown() + } + + override fun onFailure(failure: Throwable) { + fail(failure.localizedMessage) + + latch.countDown() + } + }) + mTestHelper.await(latch) + + assertNotNull(megolmBackupCreationInfo) + + assertFalse(keysBackup.isEnabled) + + val latch2 = CountDownLatch(1) + + // Create the version + var version: String? = null + keysBackup.createKeysBackupVersion(megolmBackupCreationInfo!!, object : TestMatrixCallback(latch2) { + override fun onSuccess(data: KeysVersion) { + assertNotNull(data) + assertNotNull(data.version) + + version = data.version + + super.onSuccess(data) + } + }) + mTestHelper.await(latch2) + + // Backup must be enable now + assertTrue(keysBackup.isEnabled) + assertNotNull(version) + + stateObserver.stopAndCheckStates(null) + return PrepareKeysBackupDataResult(megolmBackupCreationInfo!!, version!!) + } + + private fun assertKeysEquals(keys1: MegolmSessionData?, keys2: MegolmSessionData?) { + assertNotNull(keys1) + assertNotNull(keys2) + + assertEquals(keys1?.algorithm, keys2?.algorithm) + assertEquals(keys1?.roomId, keys2?.roomId) + // No need to compare the shortcut + // assertEquals(keys1?.sender_claimed_ed25519_key, keys2?.sender_claimed_ed25519_key) + assertEquals(keys1?.senderKey, keys2?.senderKey) + assertEquals(keys1?.sessionId, keys2?.sessionId) + assertEquals(keys1?.sessionKey, keys2?.sessionKey) + + assertListEquals(keys1?.forwardingCurve25519KeyChain, keys2?.forwardingCurve25519KeyChain) + assertDictEquals(keys1?.senderClaimedKeys, keys2?.senderClaimedKeys) + } + + /** + * Data class to store result of [createKeysBackupScenarioWithPassword] + */ + private data class KeysBackupScenarioData(val cryptoTestData: CryptoTestData, + val aliceKeys: List, + val prepareKeysBackupDataResult: PrepareKeysBackupDataResult, + val aliceSession2: Session) + + /** + * Common initial condition + * - Do an e2e backup to the homeserver + * - Log Alice on a new device, and wait for its keysBackup object to be ready (in state NotTrusted) + * + * @param password optional password + */ + private fun createKeysBackupScenarioWithPassword(password: String?): KeysBackupScenarioData { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() + + val cryptoStore = (cryptoTestData.firstSession.getKeysBackupService() as KeysBackup).store + val keysBackup = cryptoTestData.firstSession.getKeysBackupService() + + val stateObserver = StateObserver(keysBackup) + + val aliceKeys = cryptoStore.inboundGroupSessionsToBackup(100) + + // - Do an e2e backup to the homeserver + val prepareKeysBackupDataResult = prepareAndCreateKeysBackupData(keysBackup, password) + + val latch = CountDownLatch(1) + var lastProgress = 0 + var lastTotal = 0 + keysBackup.backupAllGroupSessions(object : ProgressListener { + override fun onProgress(progress: Int, total: Int) { + lastProgress = progress + lastTotal = total + } + }, TestMatrixCallback(latch)) + mTestHelper.await(latch) + + assertEquals(2, lastProgress) + assertEquals(2, lastTotal) + + val aliceUserId = cryptoTestData.firstSession.myUserId + + // Logout first Alice session, else they will share the same Crypto store and some tests may fail. + val latch2 = CountDownLatch(1) + cryptoTestData.firstSession.signOut(true, TestMatrixCallback(latch2)) + mTestHelper.await(latch2) + + // - Log Alice on a new device + val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, defaultSessionParamsWithInitialSync) + + // Test check: aliceSession2 has no keys at login + assertEquals(0, aliceSession2.inboundGroupSessionsCount(false)) + + // Wait for backup state to be NotTrusted + waitForKeysBackupToBeInState(aliceSession2, KeysBackupState.NotTrusted) + + stateObserver.stopAndCheckStates(null) + + return KeysBackupScenarioData(cryptoTestData, + aliceKeys, + prepareKeysBackupDataResult, + aliceSession2) + } + + /** + * Common restore success check after [createKeysBackupScenarioWithPassword]: + * - Imported keys number must be correct + * - The new device must have the same count of megolm keys + * - Alice must have the same keys on both devices + */ + private fun checkRestoreSuccess(testData: KeysBackupScenarioData, + total: Int, + imported: Int) { + // - Imported keys number must be correct + assertEquals(testData.aliceKeys.size, total) + assertEquals(total, imported) + + // - The new device must have the same count of megolm keys + assertEquals(testData.aliceKeys.size, testData.aliceSession2.inboundGroupSessionsCount(false)) + + // - Alice must have the same keys on both devices + for (aliceKey1 in testData.aliceKeys) { + val aliceKey2 = (testData.aliceSession2.getKeysBackupService() as KeysBackup).store + .getInboundGroupSession(aliceKey1.olmInboundGroupSession!!.sessionIdentifier(), aliceKey1.senderKey!!) + assertNotNull(aliceKey2) + assertKeysEquals(aliceKey1.exportKeys(), aliceKey2!!.exportKeys()) + } + } +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/StateObserver.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/StateObserver.kt new file mode 100644 index 0000000000..3f2e33d73b --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/StateObserver.kt @@ -0,0 +1,104 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.crypto.keysbackup + +import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService +import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState +import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import java.util.concurrent.CountDownLatch + +/** + * This class observe the state change of a KeysBackup object and provide a method to check the several state change + * It checks all state transitions and detected forbidden transition + */ +internal class StateObserver(private val keysBackup: KeysBackupService, + private val latch: CountDownLatch? = null, + private val expectedStateChange: Int = -1) : KeysBackupStateListener { + + private val allowedStateTransitions = listOf( + KeysBackupState.BackingUp to KeysBackupState.ReadyToBackUp, + KeysBackupState.BackingUp to KeysBackupState.WrongBackUpVersion, + + KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.Disabled, + KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.NotTrusted, + KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.ReadyToBackUp, + KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.Unknown, + KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.WrongBackUpVersion, + + KeysBackupState.Disabled to KeysBackupState.Enabling, + + KeysBackupState.Enabling to KeysBackupState.Disabled, + KeysBackupState.Enabling to KeysBackupState.ReadyToBackUp, + + KeysBackupState.NotTrusted to KeysBackupState.CheckingBackUpOnHomeserver, + // This transition happens when we trust the device + KeysBackupState.NotTrusted to KeysBackupState.ReadyToBackUp, + + KeysBackupState.ReadyToBackUp to KeysBackupState.WillBackUp, + + KeysBackupState.Unknown to KeysBackupState.CheckingBackUpOnHomeserver, + + KeysBackupState.WillBackUp to KeysBackupState.BackingUp, + + KeysBackupState.WrongBackUpVersion to KeysBackupState.CheckingBackUpOnHomeserver, + + // FIXME These transitions are observed during test, and I'm not sure they should occur. Don't have time to investigate now + KeysBackupState.ReadyToBackUp to KeysBackupState.BackingUp, + KeysBackupState.ReadyToBackUp to KeysBackupState.ReadyToBackUp, + KeysBackupState.WillBackUp to KeysBackupState.ReadyToBackUp, + KeysBackupState.WillBackUp to KeysBackupState.Unknown + ) + + private val stateList = ArrayList() + private var lastTransitionError: String? = null + + init { + keysBackup.addListener(this) + } + + // TODO Make expectedStates mandatory to enforce test + fun stopAndCheckStates(expectedStates: List?) { + keysBackup.removeListener(this) + + expectedStates?.let { + assertEquals(it.size, stateList.size) + + for (i in it.indices) { + assertEquals("The state $i is not correct. states: " + stateList.joinToString(separator = " "), it[i], stateList[i]) + } + } + + assertNull("states: " + stateList.joinToString(separator = " "), lastTransitionError) + } + + override fun onStateChange(newState: KeysBackupState) { + stateList.add(newState) + + // Check that state transition is valid + if (stateList.size >= 2 + && !allowedStateTransitions.contains(stateList[stateList.size - 2] to newState)) { + // Forbidden transition detected + lastTransitionError = "Forbidden transition detected from " + stateList[stateList.size - 2] + " to " + newState + } + + if (expectedStateChange == stateList.size) { + latch?.countDown() + } + } +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt new file mode 100644 index 0000000000..c05523f009 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt @@ -0,0 +1,525 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.crypto.verification + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import im.vector.matrix.android.InstrumentedTest +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.crypto.sas.* +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.common.CommonTestHelper +import im.vector.matrix.android.common.CryptoTestHelper +import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo +import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap +import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept +import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationCancel +import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart +import org.junit.Assert.* +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import java.util.* +import java.util.concurrent.CountDownLatch + +@RunWith(AndroidJUnit4::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class SASTest : InstrumentedTest { + private val mTestHelper = CommonTestHelper(context()) + private val mCryptoTestHelper = CryptoTestHelper(mTestHelper) + + @Test + fun test_aliceStartThenAliceCancel() { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + + val aliceSession = cryptoTestData.firstSession + val bobSession = cryptoTestData.secondSession + + val aliceSasMgr = aliceSession.getSasVerificationService() + val bobSasMgr = bobSession!!.getSasVerificationService() + + val bobTxCreatedLatch = CountDownLatch(1) + val bobListener = object : SasVerificationService.SasVerificationListener { + override fun transactionCreated(tx: SasVerificationTransaction) {} + + override fun transactionUpdated(tx: SasVerificationTransaction) { + bobTxCreatedLatch.countDown() + } + + override fun markedAsManuallyVerified(userId: String, deviceId: String) {} + } + bobSasMgr.addListener(bobListener) + + val txID = aliceSasMgr.beginKeyVerificationSAS(bobSession.myUserId, bobSession.getMyDevice().deviceId) + assertNotNull("Alice should have a started transaction", txID) + + val aliceKeyTx = aliceSasMgr.getExistingTransaction(bobSession.myUserId, txID!!) + assertNotNull("Alice should have a started transaction", aliceKeyTx) + + mTestHelper.await(bobTxCreatedLatch) + bobSasMgr.removeListener(bobListener) + + val bobKeyTx = bobSasMgr.getExistingTransaction(aliceSession.myUserId, txID) + + assertNotNull("Bob should have started verif transaction", bobKeyTx) + assertTrue(bobKeyTx is SASVerificationTransaction) + assertNotNull("Bob should have starting a SAS transaction", bobKeyTx) + assertTrue(aliceKeyTx is SASVerificationTransaction) + assertEquals("Alice and Bob have same transaction id", aliceKeyTx!!.transactionId, bobKeyTx!!.transactionId) + + val aliceSasTx = aliceKeyTx as SASVerificationTransaction? + val bobSasTx = bobKeyTx as SASVerificationTransaction? + + assertEquals("Alice state should be started", SasVerificationTxState.Started, aliceSasTx!!.state) + assertEquals("Bob state should be started by alice", SasVerificationTxState.OnStarted, bobSasTx!!.state) + + // Let's cancel from alice side + val cancelLatch = CountDownLatch(1) + + val bobListener2 = object : SasVerificationService.SasVerificationListener { + override fun transactionCreated(tx: SasVerificationTransaction) {} + + override fun transactionUpdated(tx: SasVerificationTransaction) { + if (tx.transactionId == txID) { + if ((tx as SASVerificationTransaction).state === SasVerificationTxState.OnCancelled) { + cancelLatch.countDown() + } + } + } + + override fun markedAsManuallyVerified(userId: String, deviceId: String) {} + } + bobSasMgr.addListener(bobListener2) + + aliceSasTx.cancel(CancelCode.User) + mTestHelper.await(cancelLatch) + + assertEquals("Should be cancelled on alice side", + SasVerificationTxState.Cancelled, aliceSasTx.state) + assertEquals("Should be cancelled on bob side", + SasVerificationTxState.OnCancelled, bobSasTx.state) + + assertEquals("Should be User cancelled on alice side", + CancelCode.User, aliceSasTx.cancelledReason) + assertEquals("Should be User cancelled on bob side", + CancelCode.User, aliceSasTx.cancelledReason) + + assertNull(bobSasMgr.getExistingTransaction(aliceSession.myUserId, txID)) + assertNull(aliceSasMgr.getExistingTransaction(bobSession.myUserId, txID)) + + cryptoTestData.close() + } + + @Test + fun test_key_agreement_protocols_must_include_curve25519() { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + + val bobSession = cryptoTestData.secondSession!! + + val protocols = listOf("meh_dont_know") + val tid = "00000000" + + // Bob should receive a cancel + var canceledToDeviceEvent: Event? = null + val cancelLatch = CountDownLatch(1) + // TODO bobSession!!.dataHandler.addListener(object : MXEventListener() { + // TODO override fun onToDeviceEvent(event: Event?) { + // TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) { + // TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) { + // TODO canceledToDeviceEvent = event + // TODO cancelLatch.countDown() + // TODO } + // TODO } + // TODO } + // TODO }) + + val aliceSession = cryptoTestData.firstSession + val aliceUserID = aliceSession.myUserId + val aliceDevice = aliceSession.getMyDevice().deviceId + + val aliceListener = object : SasVerificationService.SasVerificationListener { + override fun transactionCreated(tx: SasVerificationTransaction) {} + + override fun transactionUpdated(tx: SasVerificationTransaction) { + if ((tx as IncomingSASVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) { + (tx as IncomingSASVerificationTransaction).performAccept() + } + } + + override fun markedAsManuallyVerified(userId: String, deviceId: String) {} + } + aliceSession.getSasVerificationService().addListener(aliceListener) + + fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, protocols = protocols) + + mTestHelper.await(cancelLatch) + + val cancelReq = canceledToDeviceEvent!!.content.toModel()!! + assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code) + + cryptoTestData.close() + } + + @Test + fun test_key_agreement_macs_Must_include_hmac_sha256() { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + + val bobSession = cryptoTestData.secondSession!! + + val mac = listOf("shaBit") + val tid = "00000000" + + // Bob should receive a cancel + var canceledToDeviceEvent: Event? = null + val cancelLatch = CountDownLatch(1) + // TODO bobSession!!.dataHandler.addListener(object : MXEventListener() { + // TODO override fun onToDeviceEvent(event: Event?) { + // TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) { + // TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) { + // TODO canceledToDeviceEvent = event + // TODO cancelLatch.countDown() + // TODO } + // TODO } + // TODO } + // TODO }) + + val aliceSession = cryptoTestData.firstSession + val aliceUserID = aliceSession.myUserId + val aliceDevice = aliceSession.getMyDevice().deviceId + + fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, mac = mac) + + mTestHelper.await(cancelLatch) + + val cancelReq = canceledToDeviceEvent!!.content.toModel()!! + assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code) + + cryptoTestData.close() + } + + @Test + fun test_key_agreement_short_code_include_decimal() { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + + val bobSession = cryptoTestData.secondSession!! + + val codes = listOf("bin", "foo", "bar") + val tid = "00000000" + + // Bob should receive a cancel + var canceledToDeviceEvent: Event? = null + val cancelLatch = CountDownLatch(1) + // TODO bobSession!!.dataHandler.addListener(object : MXEventListener() { + // TODO override fun onToDeviceEvent(event: Event?) { + // TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) { + // TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) { + // TODO canceledToDeviceEvent = event + // TODO cancelLatch.countDown() + // TODO } + // TODO } + // TODO } + // TODO }) + + val aliceSession = cryptoTestData.firstSession + val aliceUserID = aliceSession.myUserId + val aliceDevice = aliceSession.getMyDevice().deviceId + + fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, codes = codes) + + mTestHelper.await(cancelLatch) + + val cancelReq = canceledToDeviceEvent!!.content.toModel()!! + assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code) + + cryptoTestData.close() + } + + private fun fakeBobStart(bobSession: Session, + aliceUserID: String?, + aliceDevice: String?, + tid: String, + protocols: List = SASVerificationTransaction.KNOWN_AGREEMENT_PROTOCOLS, + hashes: List = SASVerificationTransaction.KNOWN_HASHES, + mac: List = SASVerificationTransaction.KNOWN_MACS, + codes: List = SASVerificationTransaction.KNOWN_SHORT_CODES) { + val startMessage = KeyVerificationStart() + startMessage.fromDevice = bobSession.getMyDevice().deviceId + startMessage.method = KeyVerificationStart.VERIF_METHOD_SAS + startMessage.transactionID = tid + startMessage.keyAgreementProtocols = protocols + startMessage.hashes = hashes + startMessage.messageAuthenticationCodes = mac + startMessage.shortAuthenticationStrings = codes + + val contentMap = MXUsersDevicesMap() + contentMap.setObject(aliceUserID, aliceDevice, startMessage) + + // TODO val sendLatch = CountDownLatch(1) + // TODO bobSession.cryptoRestClient.sendToDevice( + // TODO EventType.KEY_VERIFICATION_START, + // TODO contentMap, + // TODO tid, + // TODO TestMatrixCallback(sendLatch) + // TODO ) + } + + // any two devices may only have at most one key verification in flight at a time. + // If a device has two verifications in progress with the same device, then it should cancel both verifications. + @Test + fun test_aliceStartTwoRequests() { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + + val aliceSession = cryptoTestData.firstSession + val bobSession = cryptoTestData.secondSession + + val aliceSasMgr = aliceSession.getSasVerificationService() + + val aliceCreatedLatch = CountDownLatch(2) + val aliceCancelledLatch = CountDownLatch(2) + val createdTx = ArrayList() + val aliceListener = object : SasVerificationService.SasVerificationListener { + override fun transactionCreated(tx: SasVerificationTransaction) { + createdTx.add(tx as SASVerificationTransaction) + aliceCreatedLatch.countDown() + } + + override fun transactionUpdated(tx: SasVerificationTransaction) { + if ((tx as SASVerificationTransaction).state === SasVerificationTxState.OnCancelled) { + aliceCancelledLatch.countDown() + } + } + + override fun markedAsManuallyVerified(userId: String, deviceId: String) {} + } + aliceSasMgr.addListener(aliceListener) + + val bobUserId = bobSession!!.myUserId + val bobDeviceId = bobSession.getMyDevice().deviceId + aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId) + aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId) + + mTestHelper.await(aliceCreatedLatch) + mTestHelper.await(aliceCancelledLatch) + + cryptoTestData.close() + } + + /** + * Test that when alice starts a 'correct' request, bob agrees. + */ + @Test + fun test_aliceAndBobAgreement() { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + + val aliceSession = cryptoTestData.firstSession + val bobSession = cryptoTestData.secondSession + + val aliceSasMgr = aliceSession.getSasVerificationService() + val bobSasMgr = bobSession!!.getSasVerificationService() + + var accepted: KeyVerificationAccept? = null + var startReq: KeyVerificationStart? = null + + val aliceAcceptedLatch = CountDownLatch(1) + val aliceListener = object : SasVerificationService.SasVerificationListener { + override fun markedAsManuallyVerified(userId: String, deviceId: String) {} + + override fun transactionCreated(tx: SasVerificationTransaction) {} + + override fun transactionUpdated(tx: SasVerificationTransaction) { + if ((tx as SASVerificationTransaction).state === SasVerificationTxState.OnAccepted) { + val at = tx as SASVerificationTransaction + accepted = at.accepted + startReq = at.startReq + aliceAcceptedLatch.countDown() + } + } + } + aliceSasMgr.addListener(aliceListener) + + val bobListener = object : SasVerificationService.SasVerificationListener { + override fun transactionCreated(tx: SasVerificationTransaction) {} + + override fun transactionUpdated(tx: SasVerificationTransaction) { + if ((tx as IncomingSASVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) { + val at = tx as IncomingSASVerificationTransaction + at.performAccept() + } + } + + override fun markedAsManuallyVerified(userId: String, deviceId: String) {} + } + bobSasMgr.addListener(bobListener) + + val bobUserId = bobSession.myUserId + val bobDeviceId = bobSession.getMyDevice().deviceId + aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId) + mTestHelper.await(aliceAcceptedLatch) + + assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false) + + // check that agreement is valid + assertTrue("Agreed Protocol should be Valid", accepted!!.isValid()) + assertTrue("Agreed Protocol should be known by alice", startReq!!.keyAgreementProtocols!!.contains(accepted!!.keyAgreementProtocol)) + assertTrue("Hash should be known by alice", startReq!!.hashes!!.contains(accepted!!.hash)) + assertTrue("Hash should be known by alice", startReq!!.messageAuthenticationCodes!!.contains(accepted!!.messageAuthenticationCode)) + + accepted!!.shortAuthenticationStrings?.forEach { + assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings!!.contains(it)) + } + + cryptoTestData.close() + } + + @Test + fun test_aliceAndBobSASCode() { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + + val aliceSession = cryptoTestData.firstSession + val bobSession = cryptoTestData.secondSession + + val aliceSasMgr = aliceSession.getSasVerificationService() + val bobSasMgr = bobSession!!.getSasVerificationService() + + val aliceSASLatch = CountDownLatch(1) + val aliceListener = object : SasVerificationService.SasVerificationListener { + override fun transactionCreated(tx: SasVerificationTransaction) {} + + override fun transactionUpdated(tx: SasVerificationTransaction) { + val uxState = (tx as OutgoingSASVerificationRequest).uxState + when (uxState) { + OutgoingSasVerificationRequest.UxState.SHOW_SAS -> { + aliceSASLatch.countDown() + } + else -> Unit + } + } + + override fun markedAsManuallyVerified(userId: String, deviceId: String) {} + } + aliceSasMgr.addListener(aliceListener) + + val bobSASLatch = CountDownLatch(1) + val bobListener = object : SasVerificationService.SasVerificationListener { + override fun transactionCreated(tx: SasVerificationTransaction) {} + + override fun transactionUpdated(tx: SasVerificationTransaction) { + val uxState = (tx as IncomingSASVerificationTransaction).uxState + when (uxState) { + IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> { + tx.performAccept() + } + else -> Unit + } + if (uxState === IncomingSasVerificationTransaction.UxState.SHOW_SAS) { + bobSASLatch.countDown() + } + } + + override fun markedAsManuallyVerified(userId: String, deviceId: String) {} + } + bobSasMgr.addListener(bobListener) + + val bobUserId = bobSession.myUserId + val bobDeviceId = bobSession.getMyDevice().deviceId + val verificationSAS = aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId) + mTestHelper.await(aliceSASLatch) + mTestHelper.await(bobSASLatch) + + val aliceTx = aliceSasMgr.getExistingTransaction(bobUserId, verificationSAS!!) as SASVerificationTransaction + val bobTx = bobSasMgr.getExistingTransaction(aliceSession.myUserId, verificationSAS) as SASVerificationTransaction + + assertEquals("Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL), + bobTx.getShortCodeRepresentation(SasMode.DECIMAL)) + + cryptoTestData.close() + } + + @Test + fun test_happyPath() { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + + val aliceSession = cryptoTestData.firstSession + val bobSession = cryptoTestData.secondSession + + val aliceSasMgr = aliceSession.getSasVerificationService() + val bobSasMgr = bobSession!!.getSasVerificationService() + + val aliceSASLatch = CountDownLatch(1) + val aliceListener = object : SasVerificationService.SasVerificationListener { + override fun transactionCreated(tx: SasVerificationTransaction) {} + + override fun transactionUpdated(tx: SasVerificationTransaction) { + val uxState = (tx as OutgoingSASVerificationRequest).uxState + when (uxState) { + OutgoingSasVerificationRequest.UxState.SHOW_SAS -> { + tx.userHasVerifiedShortCode() + } + OutgoingSasVerificationRequest.UxState.VERIFIED -> { + aliceSASLatch.countDown() + } + else -> Unit + } + } + + override fun markedAsManuallyVerified(userId: String, deviceId: String) {} + } + aliceSasMgr.addListener(aliceListener) + + val bobSASLatch = CountDownLatch(1) + val bobListener = object : SasVerificationService.SasVerificationListener { + override fun transactionCreated(tx: SasVerificationTransaction) {} + + override fun transactionUpdated(tx: SasVerificationTransaction) { + val uxState = (tx as IncomingSASVerificationTransaction).uxState + when (uxState) { + IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> { + tx.performAccept() + } + IncomingSasVerificationTransaction.UxState.SHOW_SAS -> { + tx.userHasVerifiedShortCode() + } + IncomingSasVerificationTransaction.UxState.VERIFIED -> { + bobSASLatch.countDown() + } + else -> Unit + } + } + + override fun markedAsManuallyVerified(userId: String, deviceId: String) {} + } + bobSasMgr.addListener(bobListener) + + val bobUserId = bobSession.myUserId + val bobDeviceId = bobSession.getMyDevice().deviceId + aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId) + mTestHelper.await(aliceSASLatch) + mTestHelper.await(bobSASLatch) + + // Assert that devices are verified + val bobDeviceInfoFromAlicePOV: MXDeviceInfo? = aliceSession.getDeviceInfo(bobUserId, bobDeviceId) + val aliceDeviceInfoFromBobPOV: MXDeviceInfo? = bobSession.getDeviceInfo(aliceSession.myUserId, aliceSession.getMyDevice().deviceId) + + // latch wait a bit again + Thread.sleep(1000) + + 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) + cryptoTestData.close() + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/Credentials.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/Credentials.kt index cf0302166f..72affe24bb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/Credentials.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/Credentials.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.api.auth.data import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import im.vector.matrix.android.internal.util.md5 /** * This data class hold credentials user data. @@ -34,3 +35,7 @@ data class Credentials( // Optional data that may contain info to override home server and/or identity server @Json(name = "well_known") val wellKnown: WellKnown? = null ) + +internal fun Credentials.sessionId(): String { + return (if (deviceId.isNullOrBlank()) userId else "$userId|$deviceId").md5() +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt index 1c73d4c5d1..339e6ac4a8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt @@ -73,6 +73,11 @@ interface Session : val myUserId: String get() = sessionParams.credentials.userId + /** + * The sessionId + */ + val sessionId: String + /** * This method allow to open a session. It does start some service on the background. */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt index 22bf564a8a..903df0cace 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt @@ -25,7 +25,6 @@ object EventType { const val MESSAGE = "m.room.message" const val STICKER = "m.sticker" const val ENCRYPTED = "m.room.encrypted" - const val ENCRYPTION = "m.room.encryption" const val FEEDBACK = "m.room.message.feedback" const val TYPING = "m.typing" const val REDACTION = "m.room.redaction" @@ -54,6 +53,7 @@ object EventType { const val STATE_ROOM_HISTORY_VISIBILITY = "m.room.history_visibility" const val STATE_ROOM_RELATED_GROUPS = "m.room.related_groups" const val STATE_ROOM_PINNED_EVENT = "m.room.pinned_events" + const val STATE_ROOM_ENCRYPTION = "m.room.encryption" // Call Events diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/crypto/RoomCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/crypto/RoomCryptoService.kt index f8c15fde47..124b2aef17 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/crypto/RoomCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/crypto/RoomCryptoService.kt @@ -16,6 +16,8 @@ package im.vector.matrix.android.api.session.room.crypto +import im.vector.matrix.android.api.MatrixCallback + interface RoomCryptoService { fun isEncrypted(): Boolean @@ -23,4 +25,6 @@ interface RoomCryptoService { fun encryptionAlgorithm(): String? fun shouldEncryptForInvitedMembers(): Boolean + + fun enableEncryptionWithAlgorithm(algorithm: String, callback: MatrixCallback) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt index bc1e941698..dbdd5b5a34 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt @@ -125,7 +125,7 @@ class CreateRoomParams { val contentMap = HashMap() contentMap["algorithm"] = algorithm - val algoEvent = Event(type = EventType.ENCRYPTION, + val algoEvent = Event(type = EventType.STATE_ROOM_ENCRYPTION, stateKey = "", content = contentMap.toContent() ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/state/StateService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/state/StateService.kt index 06f4a9c7ee..3372eb874c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/state/StateService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/state/StateService.kt @@ -26,5 +26,10 @@ interface StateService { */ fun updateTopic(topic: String, callback: MatrixCallback) + /** + * Enable encryption of the room + */ + fun enableEncryption(algorithm: String, callback: MatrixCallback) + fun getStateEvent(eventType: String): Event? } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/signout/SignOutService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/signout/SignOutService.kt index 76ca9291ec..3fb086ac45 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/signout/SignOutService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/signout/SignOutService.kt @@ -40,8 +40,8 @@ interface SignOutService { /** * Sign out, and release the session, clear all the session data, including crypto data - * @param sigOutFromHomeserver true if the sign out request has to be done + * @param signOutFromHomeserver true if the sign out request has to be done */ - fun signOut(sigOutFromHomeserver: Boolean, + fun signOut(signOutFromHomeserver: Boolean, callback: MatrixCallback): Cancelable } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/SessionManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/SessionManager.kt index c813a6813f..918f5f2f55 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/SessionManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/SessionManager.kt @@ -17,6 +17,7 @@ package im.vector.matrix.android.internal import im.vector.matrix.android.api.auth.data.SessionParams +import im.vector.matrix.android.api.auth.data.sessionId import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.di.MatrixComponent @@ -29,10 +30,11 @@ import javax.inject.Inject internal class SessionManager @Inject constructor(private val matrixComponent: MatrixComponent, private val sessionParamsStore: SessionParamsStore) { + // SessionId -> SessionComponent private val sessionComponents = HashMap() - fun getSessionComponent(userId: String): SessionComponent? { - val sessionParams = sessionParamsStore.get(userId) ?: return null + fun getSessionComponent(sessionId: String): SessionComponent? { + val sessionParams = sessionParamsStore.get(sessionId) ?: return null return getOrCreateSessionComponent(sessionParams) } @@ -40,17 +42,17 @@ internal class SessionManager @Inject constructor(private val matrixComponent: M return getOrCreateSessionComponent(sessionParams).session() } - fun releaseSession(userId: String) { - if (sessionComponents.containsKey(userId).not()) { - throw RuntimeException("You don't have a session for the user $userId") + fun releaseSession(sessionId: String) { + if (sessionComponents.containsKey(sessionId).not()) { + throw RuntimeException("You don't have a session for id $sessionId") } - sessionComponents.remove(userId)?.also { + sessionComponents.remove(sessionId)?.also { it.session().close() } } private fun getOrCreateSessionComponent(sessionParams: SessionParams): SessionComponent { - return sessionComponents.getOrPut(sessionParams.credentials.userId) { + return sessionComponents.getOrPut(sessionParams.credentials.sessionId()) { DaggerSessionComponent .factory() .create(matrixComponent, sessionParams) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt index 93349f4bbc..d5dd7e2959 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt @@ -45,14 +45,15 @@ import okhttp3.OkHttpClient import javax.inject.Inject import javax.net.ssl.HttpsURLConnection -internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated - private val okHttpClient: Lazy, - private val retrofitFactory: RetrofitFactory, - private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val sessionParamsStore: SessionParamsStore, - private val sessionManager: SessionManager, - private val sessionCreator: SessionCreator, - private val pendingSessionStore: PendingSessionStore +internal class DefaultAuthenticationService @Inject constructor( + @Unauthenticated + private val okHttpClient: Lazy, + private val retrofitFactory: RetrofitFactory, + private val coroutineDispatchers: MatrixCoroutineDispatchers, + private val sessionParamsStore: SessionParamsStore, + private val sessionManager: SessionManager, + private val sessionCreator: SessionCreator, + private val pendingSessionStore: PendingSessionStore ) : AuthenticationService { private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData() @@ -112,7 +113,7 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated // First check the homeserver version runCatching { - executeRequest { + executeRequest(null) { apiCall = authAPI.versions() } } @@ -141,7 +142,7 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated val authAPI = buildAuthAPI(homeServerConnectionConfig) // Ok, try to get the config.json file of a RiotWeb client - val riotConfig = executeRequest { + val riotConfig = executeRequest(null) { apiCall = authAPI.getRiotConfig() } @@ -153,7 +154,7 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig) - val versions = executeRequest { + val versions = executeRequest(null) { apiCall = newAuthAPI.versions() } @@ -167,7 +168,7 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated private suspend fun getLoginFlowResult(authAPI: AuthAPI, versions: Versions, homeServerUrl: String): LoginFlowResult { return if (versions.isSupportedBySdk()) { // Get the login flow - val loginFlowResponse = executeRequest { + val loginFlowResponse = executeRequest(null) { apiCall = authAPI.getLoginFlows() } LoginFlowResult.Success(loginFlowResponse, versions.isLoginAndRegistrationSupportedBySdk(), homeServerUrl) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionParamsStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionParamsStore.kt index 57c22b0053..f99b95c2b3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionParamsStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionParamsStore.kt @@ -21,7 +21,7 @@ import im.vector.matrix.android.api.auth.data.SessionParams internal interface SessionParamsStore { - fun get(userId: String): SessionParams? + fun get(sessionId: String): SessionParams? fun getLast(): SessionParams? @@ -29,11 +29,11 @@ internal interface SessionParamsStore { suspend fun save(sessionParams: SessionParams) - suspend fun setTokenInvalid(userId: String) + suspend fun setTokenInvalid(sessionId: String) suspend fun updateCredentials(newCredentials: Credentials) - suspend fun delete(userId: String) + suspend fun delete(sessionId: String) suspend fun deleteAll() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/AuthRealmMigration.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/AuthRealmMigration.kt index 7d7f8cc22c..e5e77cb14a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/AuthRealmMigration.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/AuthRealmMigration.kt @@ -17,7 +17,7 @@ package im.vector.matrix.android.internal.auth.db import im.vector.matrix.android.api.auth.data.Credentials -import im.vector.matrix.android.internal.auth.createSessionId +import im.vector.matrix.android.api.auth.data.sessionId import im.vector.matrix.android.internal.di.MoshiProvider import io.realm.DynamicRealm import io.realm.RealmMigration @@ -71,14 +71,13 @@ internal object AuthRealmMigration : RealmMigration { ?.addField(SessionParamsEntityFields.SESSION_ID, String::class.java) ?.setRequired(SessionParamsEntityFields.SESSION_ID, true) ?.transform { - val userId = it.getString(SessionParamsEntityFields.USER_ID) val credentialsJson = it.getString(SessionParamsEntityFields.CREDENTIALS_JSON) val credentials = MoshiProvider.providesMoshi() .adapter(Credentials::class.java) .fromJson(credentialsJson) - it.set(SessionParamsEntityFields.SESSION_ID, createSessionId(userId, credentials?.deviceId)) + it.set(SessionParamsEntityFields.SESSION_ID, credentials!!.sessionId()) } ?.addPrimaryKey(SessionParamsEntityFields.SESSION_ID) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/RealmSessionParamsStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/RealmSessionParamsStore.kt index a4774c632a..9491d5737c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/RealmSessionParamsStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/RealmSessionParamsStore.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.auth.db import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.SessionParams +import im.vector.matrix.android.api.auth.data.sessionId import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.database.awaitTransaction import im.vector.matrix.android.internal.di.AuthDatabase @@ -42,11 +43,11 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S } } - override fun get(userId: String): SessionParams? { + override fun get(sessionId: String): SessionParams? { return Realm.getInstance(realmConfiguration).use { realm -> realm .where(SessionParamsEntity::class.java) - .equalTo(SessionParamsEntityFields.USER_ID, userId) + .equalTo(SessionParamsEntityFields.SESSION_ID, sessionId) .findAll() .map { mapper.map(it) } .firstOrNull() @@ -76,17 +77,17 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S } } - override suspend fun setTokenInvalid(userId: String) { + override suspend fun setTokenInvalid(sessionId: String) { awaitTransaction(realmConfiguration) { realm -> val currentSessionParams = realm .where(SessionParamsEntity::class.java) - .equalTo(SessionParamsEntityFields.USER_ID, userId) + .equalTo(SessionParamsEntityFields.SESSION_ID, sessionId) .findAll() .firstOrNull() if (currentSessionParams == null) { // Should not happen - "Session param not found for user $userId" + "Session param not found for id $sessionId" .let { Timber.w(it) } .also { error(it) } } else { @@ -99,14 +100,14 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S awaitTransaction(realmConfiguration) { realm -> val currentSessionParams = realm .where(SessionParamsEntity::class.java) - .equalTo(SessionParamsEntityFields.USER_ID, newCredentials.userId) + .equalTo(SessionParamsEntityFields.SESSION_ID, newCredentials.sessionId()) .findAll() .map { mapper.map(it) } .firstOrNull() if (currentSessionParams == null) { // Should not happen - "Session param not found for user ${newCredentials.userId}" + "Session param not found for id ${newCredentials.sessionId()}" .let { Timber.w(it) } .also { error(it) } } else { @@ -123,10 +124,10 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S } } - override suspend fun delete(userId: String) { + override suspend fun delete(sessionId: String) { awaitTransaction(realmConfiguration) { it.where(SessionParamsEntity::class.java) - .equalTo(SessionParamsEntityFields.USER_ID, userId) + .equalTo(SessionParamsEntityFields.SESSION_ID, sessionId) .findAll() .deleteAllFromRealm() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/SessionParamsMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/SessionParamsMapper.kt index d4ba1eb818..ebd50a6924 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/SessionParamsMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/SessionParamsMapper.kt @@ -20,7 +20,7 @@ import com.squareup.moshi.Moshi import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig import im.vector.matrix.android.api.auth.data.SessionParams -import im.vector.matrix.android.internal.auth.createSessionId +import im.vector.matrix.android.api.auth.data.sessionId import javax.inject.Inject internal class SessionParamsMapper @Inject constructor(moshi: Moshi) { @@ -50,7 +50,7 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) { return null } return SessionParamsEntity( - createSessionId(sessionParams.credentials.userId, sessionParams.credentials.deviceId), + sessionParams.credentials.sessionId(), sessionParams.credentials.userId, credentialsJson, homeServerConnectionConfigJson, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/login/DefaultLoginWizard.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/login/DefaultLoginWizard.kt index b847773682..4d98ddcf08 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/login/DefaultLoginWizard.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/login/DefaultLoginWizard.kt @@ -72,7 +72,7 @@ internal class DefaultLoginWizard( } else { PasswordLoginParams.userIdentifier(login, password, deviceName) } - val credentials = executeRequest { + val credentials = executeRequest(null) { apiCall = authAPI.login(loginParams) } @@ -95,7 +95,7 @@ internal class DefaultLoginWizard( pendingSessionData = pendingSessionData.copy(sendAttempt = pendingSessionData.sendAttempt + 1) .also { pendingSessionStore.savePendingSessionData(it) } - val result = executeRequest { + val result = executeRequest(null) { apiCall = authAPI.resetPassword(AddThreePidRegistrationParams.from(param)) } @@ -120,7 +120,7 @@ internal class DefaultLoginWizard( resetPasswordData.newPassword ) - executeRequest { + executeRequest(null) { apiCall = authAPI.resetPasswordMailConfirmed(param) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/RegisterAddThreePidTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/RegisterAddThreePidTask.kt index 0246075153..c455ccf48c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/RegisterAddThreePidTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/RegisterAddThreePidTask.kt @@ -29,11 +29,12 @@ internal interface RegisterAddThreePidTask : Task { ) } -internal class DefaultRegisterTask(private val authAPI: AuthAPI) - : RegisterTask { +internal class DefaultRegisterTask( + private val authAPI: AuthAPI +) : RegisterTask { override suspend fun execute(params: RegisterTask.Params): Credentials { try { - return executeRequest { + return executeRequest(null) { apiCall = authAPI.register(params.registrationParams) } } catch (throwable: Throwable) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/ValidateCodeTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/ValidateCodeTask.kt index da75b839a6..30f9aaa705 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/ValidateCodeTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/ValidateCodeTask.kt @@ -27,11 +27,12 @@ internal interface ValidateCodeTask : Task onRoomEncryptionEvent(roomId, event) + event.getClearType() == EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) event.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event) } @@ -155,7 +155,7 @@ internal class DefaultCryptoService @Inject constructor( fun onLiveEvent(roomId: String, event: Event) { when { - event.getClearType() == EventType.ENCRYPTION -> onRoomEncryptionEvent(roomId, event) + event.getClearType() == EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) event.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event) } @@ -482,7 +482,7 @@ internal class DefaultCryptoService @Inject constructor( */ override fun isRoomEncrypted(roomId: String): Boolean { val encryptionEvent = monarchy.fetchCopied { - EventEntity.where(it, roomId = roomId, type = EventType.ENCRYPTION).findFirst() + EventEntity.where(it, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION).findFirst() } return encryptionEvent != null } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/SetDeviceVerificationAction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/SetDeviceVerificationAction.kt index 3eafa73fab..2d0c77c768 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/SetDeviceVerificationAction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/SetDeviceVerificationAction.kt @@ -22,9 +22,10 @@ import im.vector.matrix.android.internal.di.UserId import timber.log.Timber import javax.inject.Inject -internal class SetDeviceVerificationAction @Inject constructor(private val cryptoStore: IMXCryptoStore, - @UserId private val userId: String, - private val keysBackup: KeysBackup) { +internal class SetDeviceVerificationAction @Inject constructor( + private val cryptoStore: IMXCryptoStore, + @UserId private val userId: String, + private val keysBackup: KeysBackup) { fun handle(verificationStatus: Int, deviceId: String, userId: String) { val device = cryptoStore.getUserDevice(deviceId, userId) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt index 91b3d6b056..99267ee89c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt @@ -1221,7 +1221,7 @@ internal class KeysBackup @Inject constructor( // Do not stay in KeysBackupState.WrongBackUpVersion but check what is available on the homeserver checkAndStartKeysBackup() } - else -> + else -> // Come back to the ready state so that we will retry on the next received key keysBackupStateManager.state = KeysBackupState.ReadyToBackUp } @@ -1339,6 +1339,31 @@ internal class KeysBackup @Inject constructor( return sessionBackupData } + /* ========================================================================================== + * For test only + * ========================================================================================== */ + + // Direct access for test only + @VisibleForTesting + val store + get() = cryptoStore + + @VisibleForTesting + fun createFakeKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo, + callback: MatrixCallback) { + val createKeysBackupVersionBody = CreateKeysBackupVersionBody() + createKeysBackupVersionBody.algorithm = keysBackupCreationInfo.algorithm + @Suppress("UNCHECKED_CAST") + createKeysBackupVersionBody.authData = MoshiProvider.providesMoshi().adapter(Map::class.java) + .fromJson(keysBackupCreationInfo.authData?.toJsonString() ?: "") as JsonDict? + + createKeysBackupVersionTask + .configureWith(createKeysBackupVersionBody) { + this.callback = callback + } + .executeBy(taskExecutor) + } + companion object { // Maximum delay in ms in {@link maybeBackupKeys} private const val KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS = 10_000L diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt index 78ae0f7ea6..9b8183bd02 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt @@ -21,15 +21,18 @@ import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.CreateKeys import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface CreateKeysBackupVersionTask : Task -internal class DefaultCreateKeysBackupVersionTask @Inject constructor(private val roomKeysApi: RoomKeysApi) - : CreateKeysBackupVersionTask { +internal class DefaultCreateKeysBackupVersionTask @Inject constructor( + private val roomKeysApi: RoomKeysApi, + private val eventBus: EventBus +) : CreateKeysBackupVersionTask { override suspend fun execute(params: CreateKeysBackupVersionBody): KeysVersion { - return executeRequest { + return executeRequest(eventBus) { apiCall = roomKeysApi.createKeysBackupVersion(params) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt index 2b1f3df353..9712bb099b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.crypto.keysbackup.tasks import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface DeleteBackupTask : Task { @@ -27,11 +28,13 @@ internal interface DeleteBackupTask : Task { ) } -internal class DefaultDeleteBackupTask @Inject constructor(private val roomKeysApi: RoomKeysApi) - : DeleteBackupTask { +internal class DefaultDeleteBackupTask @Inject constructor( + private val roomKeysApi: RoomKeysApi, + private val eventBus: EventBus +) : DeleteBackupTask { override suspend fun execute(params: DeleteBackupTask.Params) { - return executeRequest { + return executeRequest(eventBus) { apiCall = roomKeysApi.deleteBackup(params.version) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt index ccb3645ef3..72173ec7f4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.crypto.keysbackup.tasks import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface DeleteRoomSessionDataTask : Task { @@ -29,11 +30,13 @@ internal interface DeleteRoomSessionDataTask : Task { @@ -28,11 +29,13 @@ internal interface DeleteRoomSessionsDataTask : Task { @@ -27,11 +28,13 @@ internal interface DeleteSessionsDataTask : Task -internal class DefaultGetKeysBackupLastVersionTask @Inject constructor(private val roomKeysApi: RoomKeysApi) - : GetKeysBackupLastVersionTask { +internal class DefaultGetKeysBackupLastVersionTask @Inject constructor( + private val roomKeysApi: RoomKeysApi, + private val eventBus: EventBus +) : GetKeysBackupLastVersionTask { override suspend fun execute(params: Unit): KeysVersionResult { - return executeRequest { + return executeRequest(eventBus) { apiCall = roomKeysApi.getKeysBackupLastVersion() } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt index ea0b9b9f3a..70cc7472a9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt @@ -20,15 +20,18 @@ import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetKeysBackupVersionTask : Task -internal class DefaultGetKeysBackupVersionTask @Inject constructor(private val roomKeysApi: RoomKeysApi) - : GetKeysBackupVersionTask { +internal class DefaultGetKeysBackupVersionTask @Inject constructor( + private val roomKeysApi: RoomKeysApi, + private val eventBus: EventBus +) : GetKeysBackupVersionTask { override suspend fun execute(params: String): KeysVersionResult { - return executeRequest { + return executeRequest(eventBus) { apiCall = roomKeysApi.getKeysBackupVersion(params) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt index a36850ba08..327836ed5f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt @@ -20,6 +20,7 @@ import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeyBackupData import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetRoomSessionDataTask : Task { @@ -30,11 +31,13 @@ internal interface GetRoomSessionDataTask : Task { @@ -29,11 +30,13 @@ internal interface GetRoomSessionsDataTask : Task { @@ -28,11 +29,13 @@ internal interface GetSessionsDataTask : Task { @@ -32,11 +33,13 @@ internal interface StoreRoomSessionDataTask : Task { @@ -31,11 +32,13 @@ internal interface StoreRoomSessionsDataTask : Task { @@ -30,11 +31,13 @@ internal interface StoreSessionsDataTask : Task { @@ -29,11 +30,13 @@ internal interface UpdateKeysBackupVersionTask : Task { val body = KeysClaimBody(oneTimeKeys = params.usersDevicesKeyTypesMap.map) - val keysClaimResponse = executeRequest { + val keysClaimResponse = executeRequest(eventBus) { apiCall = cryptoApi.claimOneTimeKeysForUsersDevices(body) } val map = MXUsersDevicesMap() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceTask.kt index 0e52c118d9..fbbaa0e0f7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceTask.kt @@ -23,6 +23,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceParams import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface DeleteDeviceTask : Task { @@ -31,12 +32,14 @@ internal interface DeleteDeviceTask : Task { ) } -internal class DefaultDeleteDeviceTask @Inject constructor(private val cryptoApi: CryptoApi) - : DeleteDeviceTask { +internal class DefaultDeleteDeviceTask @Inject constructor( + private val cryptoApi: CryptoApi, + private val eventBus: EventBus +) : DeleteDeviceTask { override suspend fun execute(params: DeleteDeviceTask.Params) { try { - executeRequest { + executeRequest(eventBus) { apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams()) } } catch (throwable: Throwable) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt index eb23f02275..19e0f6efb5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt @@ -23,6 +23,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceParams import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface DeleteDeviceWithUserPasswordTask : Task { @@ -33,12 +34,14 @@ internal interface DeleteDeviceWithUserPasswordTask : Task { @@ -31,8 +32,10 @@ internal interface DownloadKeysForUsersTask : Task() }.orEmpty() @@ -45,7 +48,7 @@ internal class DefaultDownloadKeysForUsers @Inject constructor(private val crypt body.token = params.token } - return executeRequest { + return executeRequest(eventBus) { apiCall = cryptoApi.downloadKeysForUsers(body) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetDeviceInfoTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetDeviceInfoTask.kt index f97e86a57d..9d9513b773 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetDeviceInfoTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetDeviceInfoTask.kt @@ -20,17 +20,20 @@ import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetDeviceInfoTask : Task { data class Params(val deviceId: String) } -internal class DefaultGetDeviceInfoTask @Inject constructor(private val cryptoApi: CryptoApi) - : GetDeviceInfoTask { +internal class DefaultGetDeviceInfoTask @Inject constructor( + private val cryptoApi: CryptoApi, + private val eventBus: EventBus +) : GetDeviceInfoTask { override suspend fun execute(params: GetDeviceInfoTask.Params): DeviceInfo { - return executeRequest { + return executeRequest(eventBus) { apiCall = cryptoApi.getDeviceInfo(params.deviceId) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetDevicesTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetDevicesTask.kt index d6e82adb4e..7a805f6a08 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetDevicesTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetDevicesTask.kt @@ -20,15 +20,18 @@ import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetDevicesTask : Task -internal class DefaultGetDevicesTask @Inject constructor(private val cryptoApi: CryptoApi) - : GetDevicesTask { +internal class DefaultGetDevicesTask @Inject constructor( + private val cryptoApi: CryptoApi, + private val eventBus: EventBus +) : GetDevicesTask { override suspend fun execute(params: Unit): DevicesListResponse { - return executeRequest { + return executeRequest(eventBus) { apiCall = cryptoApi.getDevices() } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetKeyChangesTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetKeyChangesTask.kt index 42c36bd1e7..84e2c293b9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetKeyChangesTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/GetKeyChangesTask.kt @@ -20,6 +20,7 @@ import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.model.rest.KeyChangesResponse import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetKeyChangesTask : Task { @@ -31,11 +32,13 @@ internal interface GetKeyChangesTask : Task { ) } -internal class DefaultSendToDeviceTask @Inject constructor(private val cryptoApi: CryptoApi) - : SendToDeviceTask { +internal class DefaultSendToDeviceTask @Inject constructor( + private val cryptoApi: CryptoApi, + private val eventBus: EventBus +) : SendToDeviceTask { override suspend fun execute(params: SendToDeviceTask.Params) { val sendToDeviceBody = SendToDeviceBody() sendToDeviceBody.messages = params.contentMap.map - return executeRequest { + return executeRequest(eventBus) { apiCall = cryptoApi.sendToDevice( params.eventType, params.transactionId ?: Random.nextInt(Integer.MAX_VALUE).toString(), diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SetDeviceNameTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SetDeviceNameTask.kt index 47f3050b88..74757c5cb3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SetDeviceNameTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SetDeviceNameTask.kt @@ -20,6 +20,7 @@ import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.model.rest.UpdateDeviceInfoBody import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface SetDeviceNameTask : Task { @@ -31,14 +32,16 @@ internal interface SetDeviceNameTask : Task { ) } -internal class DefaultSetDeviceNameTask @Inject constructor(private val cryptoApi: CryptoApi) - : SetDeviceNameTask { +internal class DefaultSetDeviceNameTask @Inject constructor( + private val cryptoApi: CryptoApi, + private val eventBus: EventBus +) : SetDeviceNameTask { override suspend fun execute(params: SetDeviceNameTask.Params) { val body = UpdateDeviceInfoBody( displayName = params.deviceName ) - return executeRequest { + return executeRequest(eventBus) { apiCall = cryptoApi.updateDeviceInfo(params.deviceId, body) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadKeysTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadKeysTask.kt index db05f473b1..d8bfe73eda 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadKeysTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadKeysTask.kt @@ -24,6 +24,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.convertToUTF8 +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface UploadKeysTask : Task { @@ -36,8 +37,10 @@ internal interface UploadKeysTask : Task executeRequest(block: Request.() -> Unit) = Request().apply(block).execute() +internal suspend inline fun executeRequest(eventBus: EventBus?, + block: Request.() -> Unit) = Request(eventBus).apply(block).execute() -internal class Request { +internal class Request(private val eventBus: EventBus?) { lateinit var apiCall: Call @@ -34,7 +36,7 @@ internal class Request { response.body() ?: throw IllegalStateException("The request returned a null body") } else { - throw response.toFailure() + throw response.toFailure(eventBus) } } catch (exception: Throwable) { throw when (exception) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt index 2018868059..f33ec2f88a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt @@ -74,18 +74,18 @@ internal suspend fun okhttp3.Call.awaitResponse(): okhttp3.Response { /** * Convert a retrofit Response to a Failure, and eventually parse errorBody to convert it to a MatrixError */ -internal fun Response.toFailure(): Failure { - return toFailure(errorBody(), code()) +internal fun Response.toFailure(eventBus: EventBus?): Failure { + return toFailure(errorBody(), code(), eventBus) } /** * Convert a okhttp3 Response to a Failure, and eventually parse errorBody to convert it to a MatrixError */ -internal fun okhttp3.Response.toFailure(): Failure { - return toFailure(body, code) +internal fun okhttp3.Response.toFailure(eventBus: EventBus?): Failure { + return toFailure(body, code, eventBus) } -private fun toFailure(errorBody: ResponseBody?, httpCode: Int): Failure { +private fun toFailure(errorBody: ResponseBody?, httpCode: Int, eventBus: EventBus?): Failure { if (errorBody == null) { return Failure.Unknown(RuntimeException("errorBody should not be null")) } @@ -100,11 +100,11 @@ private fun toFailure(errorBody: ResponseBody?, httpCode: Int): Failure { if (matrixError != null) { if (matrixError.code == MatrixError.M_CONSENT_NOT_GIVEN && !matrixError.consentUri.isNullOrBlank()) { // Also send this error to the bus, for a global management - EventBus.getDefault().post(GlobalError.ConsentNotGivenError(matrixError.consentUri)) + eventBus?.post(GlobalError.ConsentNotGivenError(matrixError.consentUri)) } else if (httpCode == HttpURLConnection.HTTP_UNAUTHORIZED /* 401 */ && matrixError.code == MatrixError.M_UNKNOWN_TOKEN) { // Also send this error to the bus, for a global management - EventBus.getDefault().post(GlobalError.InvalidToken(matrixError.isSoftLogout)) + eventBus?.post(GlobalError.InvalidToken(matrixError.isSoftLogout)) } return Failure.ServerError(matrixError, httpCode) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index b0bf70eb70..dc1a20802b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -17,7 +17,6 @@ package im.vector.matrix.android.internal.session import android.content.Context -import android.os.Looper import androidx.annotation.MainThread import androidx.lifecycle.LiveData import dagger.Lazy @@ -45,6 +44,7 @@ import im.vector.matrix.android.api.session.user.UserService import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.crypto.DefaultCryptoService import im.vector.matrix.android.internal.database.LiveEntityObserver +import im.vector.matrix.android.internal.di.SessionId import im.vector.matrix.android.internal.session.sync.SyncTaskSequencer import im.vector.matrix.android.internal.session.sync.SyncTokenStore import im.vector.matrix.android.internal.session.sync.job.SyncThread @@ -60,67 +60,70 @@ import javax.inject.Inject import javax.inject.Provider @SessionScope -internal class DefaultSession @Inject constructor(override val sessionParams: SessionParams, - private val context: Context, - private val liveEntityObservers: Set<@JvmSuppressWildcards LiveEntityObserver>, - private val sessionListeners: SessionListeners, - private val roomService: Lazy, - private val roomDirectoryService: Lazy, - private val groupService: Lazy, - private val userService: Lazy, - private val filterService: Lazy, - private val cacheService: Lazy, - private val signOutService: Lazy, - private val pushRuleService: Lazy, - private val pushersService: Lazy, - private val cryptoService: Lazy, - private val fileService: Lazy, - private val secureStorageService: Lazy, - private val syncThreadProvider: Provider, - private val contentUrlResolver: ContentUrlResolver, - private val syncTokenStore: SyncTokenStore, - private val syncTaskSequencer: SyncTaskSequencer, - private val sessionParamsStore: SessionParamsStore, - private val contentUploadProgressTracker: ContentUploadStateTracker, - private val initialSyncProgressService: Lazy, - private val homeServerCapabilitiesService: Lazy) +internal class DefaultSession @Inject constructor( + override val sessionParams: SessionParams, + private val context: Context, + private val eventBus: EventBus, + @SessionId + override val sessionId: String, + private val liveEntityObservers: Set<@JvmSuppressWildcards LiveEntityObserver>, + private val sessionListeners: SessionListeners, + private val roomService: Lazy, + private val roomDirectoryService: Lazy, + private val groupService: Lazy, + private val userService: Lazy, + private val filterService: Lazy, + private val cacheService: Lazy, + private val signOutService: Lazy, + private val pushRuleService: Lazy, + private val pushersService: Lazy, + private val cryptoService: Lazy, + private val fileService: Lazy, + private val secureStorageService: Lazy, + private val syncThreadProvider: Provider, + private val contentUrlResolver: ContentUrlResolver, + private val syncTokenStore: SyncTokenStore, + private val syncTaskSequencer: SyncTaskSequencer, + private val sessionParamsStore: SessionParamsStore, + private val contentUploadProgressTracker: ContentUploadStateTracker, + private val initialSyncProgressService: Lazy, + private val homeServerCapabilitiesService: Lazy) : Session, - RoomService by roomService.get(), - RoomDirectoryService by roomDirectoryService.get(), - GroupService by groupService.get(), - UserService by userService.get(), - CryptoService by cryptoService.get(), - SignOutService by signOutService.get(), - FilterService by filterService.get(), - PushRuleService by pushRuleService.get(), - PushersService by pushersService.get(), - FileService by fileService.get(), - InitialSyncProgressService by initialSyncProgressService.get(), - SecureStorageService by secureStorageService.get(), - HomeServerCapabilitiesService by homeServerCapabilitiesService.get() { + RoomService by roomService.get(), + RoomDirectoryService by roomDirectoryService.get(), + GroupService by groupService.get(), + UserService by userService.get(), + CryptoService by cryptoService.get(), + SignOutService by signOutService.get(), + FilterService by filterService.get(), + PushRuleService by pushRuleService.get(), + PushersService by pushersService.get(), + FileService by fileService.get(), + InitialSyncProgressService by initialSyncProgressService.get(), + SecureStorageService by secureStorageService.get(), + HomeServerCapabilitiesService by homeServerCapabilitiesService.get() { private var isOpen = false private var syncThread: SyncThread? = null override val isOpenable: Boolean - get() = sessionParamsStore.get(myUserId)?.isTokenValid ?: false + get() = sessionParamsStore.get(sessionId)?.isTokenValid ?: false @MainThread override fun open() { - assertMainThread() assert(!isOpen) isOpen = true liveEntityObservers.forEach { it.start() } - EventBus.getDefault().register(this) + eventBus.register(this) } override fun requireBackgroundSync() { - SyncWorker.requireBackgroundSync(context, myUserId) + SyncWorker.requireBackgroundSync(context, sessionId) } override fun startAutomaticBackgroundSync(repeatDelay: Long) { - SyncWorker.automaticallyBackgroundSync(context, myUserId, 0, repeatDelay) + SyncWorker.automaticallyBackgroundSync(context, sessionId, 0, repeatDelay) } override fun stopAnyBackgroundSync() { @@ -152,7 +155,7 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se liveEntityObservers.forEach { it.dispose() } cryptoService.get().close() isOpen = false - EventBus.getDefault().unregister(this) + eventBus.unregister(this) syncTaskSequencer.close() } @@ -180,10 +183,10 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se @Subscribe(threadMode = ThreadMode.MAIN) fun onGlobalError(globalError: GlobalError) { if (globalError is GlobalError.InvalidToken - && globalError.softLogout) { + && globalError.softLogout) { // Mark the token has invalid GlobalScope.launch(Dispatchers.IO) { - sessionParamsStore.setTokenInvalid(myUserId) + sessionParamsStore.setTokenInvalid(sessionId) } } @@ -201,12 +204,4 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se override fun removeListener(listener: Session.Listener) { sessionListeners.removeListener(listener) } - - // Private methods ***************************************************************************** - - private fun assertMainThread() { - if (Looper.getMainLooper().thread !== Thread.currentThread()) { - throw IllegalStateException("This method can only be called on the main thread!") - } - } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index 437a559ea1..2c72d93ebb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -26,11 +26,11 @@ import dagger.multibindings.IntoSet import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig import im.vector.matrix.android.api.auth.data.SessionParams +import im.vector.matrix.android.api.auth.data.sessionId import im.vector.matrix.android.api.session.InitialSyncProgressService import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService import im.vector.matrix.android.api.session.securestorage.SecureStorageService -import im.vector.matrix.android.internal.auth.createSessionId import im.vector.matrix.android.internal.database.LiveEntityObserver import im.vector.matrix.android.internal.database.SessionRealmConfigurationFactory import im.vector.matrix.android.internal.di.* @@ -47,6 +47,7 @@ import im.vector.matrix.android.internal.session.securestorage.DefaultSecureStor import im.vector.matrix.android.internal.util.md5 import io.realm.RealmConfiguration import okhttp3.OkHttpClient +import org.greenrobot.eventbus.EventBus import retrofit2.Retrofit import java.io.File @@ -87,7 +88,7 @@ internal abstract class SessionModule { @SessionId @Provides fun providesSessionId(credentials: Credentials): String { - return createSessionId(credentials.userId, credentials.deviceId) + return credentials.sessionId() } @JvmStatic @@ -154,6 +155,13 @@ internal abstract class SessionModule { return retrofitFactory .create(okHttpClient, sessionParams.homeServerConnectionConfig.homeServerUri.toString()) } + + @JvmStatic + @Provides + @SessionScope + fun providesEventBus(): EventBus { + return EventBus.builder().build() + } } @Binds diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt index 2f4e991e62..4071c9224f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt @@ -29,12 +29,14 @@ import okhttp3.Request import okhttp3.RequestBody import okhttp3.RequestBody.Companion.asRequestBody import okhttp3.RequestBody.Companion.toRequestBody +import org.greenrobot.eventbus.EventBus import java.io.File import java.io.IOException import javax.inject.Inject internal class FileUploader @Inject constructor(@Authenticated private val okHttpClient: OkHttpClient, + private val eventBus: EventBus, sessionParams: SessionParams, moshi: Moshi) { @@ -73,7 +75,7 @@ internal class FileUploader @Inject constructor(@Authenticated return okHttpClient.newCall(request).awaitResponse().use { response -> if (!response.isSuccessful) { - throw response.toFailure() + throw response.toFailure(eventBus) } else { response.body?.source()?.let { responseAdapter.fromJson(it) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt index a05edb7b0f..1725ef99aa 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt @@ -42,7 +42,7 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) : @JsonClass(generateAdapter = true) internal data class Params( - override val userId: String, + override val sessionId: String, val roomId: String, val event: Event, val attachment: ContentAttachmentData, @@ -64,7 +64,7 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) : return Result.success(inputData) } - val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() + val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success() sessionComponent.inject(this) val eventId = params.event.eventId ?: return Result.success() @@ -169,7 +169,7 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) : Timber.v("handleSuccess $attachmentUrl, work is stopped $isStopped") contentUploadStateTracker.setSuccess(params.event.eventId!!) val event = updateEvent(params.event, attachmentUrl, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo) - val sendParams = SendEventWorker.Params(params.userId, params.roomId, event) + val sendParams = SendEventWorker.Params(params.sessionId, params.roomId, event) return Result.success(WorkerParamsFactory.toData(sendParams)) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultSaveFilterTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultSaveFilterTask.kt index 08985bf17d..47c5e4a08a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultSaveFilterTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultSaveFilterTask.kt @@ -20,6 +20,7 @@ import im.vector.matrix.android.api.session.sync.FilterService import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject /** @@ -32,9 +33,11 @@ internal interface SaveFilterTask : Task { ) } -internal class DefaultSaveFilterTask @Inject constructor(@UserId private val userId: String, - private val filterAPI: FilterApi, - private val filterRepository: FilterRepository +internal class DefaultSaveFilterTask @Inject constructor( + @UserId private val userId: String, + private val filterAPI: FilterApi, + private val filterRepository: FilterRepository, + private val eventBus: EventBus ) : SaveFilterTask { override suspend fun execute(params: SaveFilterTask.Params) { @@ -56,7 +59,7 @@ internal class DefaultSaveFilterTask @Inject constructor(@UserId private val use } val updated = filterRepository.storeFilter(filterBody, roomFilter) if (updated) { - val filterResponse = executeRequest { + val filterResponse = executeRequest(eventBus) { // TODO auto retry apiCall = filterAPI.uploadFilter(userId, filterBody) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGetGroupDataTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGetGroupDataTask.kt index 40dc0a44c0..069c9b8d21 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGetGroupDataTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGetGroupDataTask.kt @@ -25,6 +25,7 @@ import im.vector.matrix.android.internal.session.group.model.GroupRooms import im.vector.matrix.android.internal.session.group.model.GroupSummaryResponse import im.vector.matrix.android.internal.session.group.model.GroupUsers import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetGroupDataTask : Task { @@ -34,18 +35,19 @@ internal interface GetGroupDataTask : Task { internal class DefaultGetGroupDataTask @Inject constructor( private val groupAPI: GroupAPI, - private val monarchy: Monarchy + private val monarchy: Monarchy, + private val eventBus: EventBus ) : GetGroupDataTask { override suspend fun execute(params: GetGroupDataTask.Params) { val groupId = params.groupId - val groupSummary = executeRequest { + val groupSummary = executeRequest(eventBus) { apiCall = groupAPI.getSummary(groupId) } - val groupRooms = executeRequest { + val groupRooms = executeRequest(eventBus) { apiCall = groupAPI.getRooms(groupId) } - val groupUsers = executeRequest { + val groupUsers = executeRequest(eventBus) { apiCall = groupAPI.getUsers(groupId) } insertInDb(groupSummary, groupRooms, groupUsers, groupId) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt index 76a7d5a48d..93705774e6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt @@ -29,7 +29,7 @@ internal class GetGroupDataWorker(context: Context, params: WorkerParameters) : @JsonClass(generateAdapter = true) internal data class Params( - override val userId: String, + override val sessionId: String, val groupIds: List, override val lastFailureMessage: String? = null ) : SessionWorkerParams @@ -40,7 +40,7 @@ internal class GetGroupDataWorker(context: Context, params: WorkerParameters) : val params = WorkerParamsFactory.fromData(inputData) ?: return Result.failure() - val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() + val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success() sessionComponent.inject(this) val results = params.groupIds.map { groupId -> runCatching { fetchGroupData(groupId) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt index 553a0387c5..a60bc78b6c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt @@ -26,7 +26,7 @@ import im.vector.matrix.android.internal.database.awaitTransaction import im.vector.matrix.android.internal.database.model.GroupEntity import im.vector.matrix.android.internal.database.model.GroupSummaryEntity import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.di.UserId +import im.vector.matrix.android.internal.di.SessionId import im.vector.matrix.android.internal.worker.WorkManagerUtil import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder import im.vector.matrix.android.internal.worker.WorkerParamsFactory @@ -37,9 +37,10 @@ import javax.inject.Inject private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER" -internal class GroupSummaryUpdater @Inject constructor(private val context: Context, - @UserId private val userId: String, - private val monarchy: Monarchy) +internal class GroupSummaryUpdater @Inject constructor( + private val context: Context, + @SessionId private val sessionId: String, + private val monarchy: Monarchy) : RealmLiveEntityObserver(monarchy.realmConfiguration) { override val query = Monarchy.Query { GroupEntity.where(it) } @@ -51,9 +52,9 @@ internal class GroupSummaryUpdater @Inject constructor(private val context: Cont .mapNotNull { results[it] } fetchGroupsData(modifiedGroupEntity - .filter { it.membership == Membership.JOIN || it.membership == Membership.INVITE } - .map { it.groupId } - .toList()) + .filter { it.membership == Membership.JOIN || it.membership == Membership.INVITE } + .map { it.groupId } + .toList()) modifiedGroupEntity .filter { it.membership == Membership.LEAVE } @@ -67,7 +68,7 @@ internal class GroupSummaryUpdater @Inject constructor(private val context: Cont } private fun fetchGroupsData(groupIds: List) { - val getGroupDataWorkerParams = GetGroupDataWorker.Params(userId, groupIds) + val getGroupDataWorkerParams = GetGroupDataWorker.Params(sessionId, groupIds) val workData = WorkerParamsFactory.toData(getGroupDataWorkerParams) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt index 45571286b9..3837d893f9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt @@ -23,14 +23,16 @@ import im.vector.matrix.android.internal.database.query.getOrCreate import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.awaitTransaction -import java.util.Date +import org.greenrobot.eventbus.EventBus +import java.util.* import javax.inject.Inject internal interface GetHomeServerCapabilitiesTask : Task internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( private val capabilitiesAPI: CapabilitiesAPI, - private val monarchy: Monarchy + private val monarchy: Monarchy, + private val eventBus: EventBus ) : GetHomeServerCapabilitiesTask { override suspend fun execute(params: Unit) { @@ -45,7 +47,7 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( return } - val uploadCapabilities = executeRequest { + val uploadCapabilities = executeRequest(eventBus) { apiCall = capabilitiesAPI.getUploadCapabilities() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddHttpPusherWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddHttpPusherWorker.kt index 9eed515d14..adb4bf32c2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddHttpPusherWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddHttpPusherWorker.kt @@ -27,8 +27,10 @@ import im.vector.matrix.android.internal.database.model.PusherEntity import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.util.awaitTransaction +import im.vector.matrix.android.internal.worker.SessionWorkerParams import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.getSessionComponent +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal class AddHttpPusherWorker(context: Context, params: WorkerParameters) @@ -36,18 +38,20 @@ internal class AddHttpPusherWorker(context: Context, params: WorkerParameters) @JsonClass(generateAdapter = true) internal data class Params( + override val sessionId: String, val pusher: JsonPusher, - val userId: String - ) + override val lastFailureMessage: String? = null + ) : SessionWorkerParams @Inject lateinit var pushersAPI: PushersAPI @Inject lateinit var monarchy: Monarchy + @Inject lateinit var eventBus: EventBus override suspend fun doWork(): Result { val params = WorkerParamsFactory.fromData(inputData) ?: return Result.failure() - val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() + val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success() sessionComponent.inject(this) val pusher = params.pusher @@ -76,7 +80,7 @@ internal class AddHttpPusherWorker(context: Context, params: WorkerParameters) } private suspend fun setPusher(pusher: JsonPusher) { - executeRequest { + executeRequest(eventBus) { apiCall = pushersAPI.setPusher(pusher) } monarchy.awaitTransaction { realm -> diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddPushRuleTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddPushRuleTask.kt index 99992ef4dc..b310ba7cd1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddPushRuleTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/AddPushRuleTask.kt @@ -19,6 +19,7 @@ import im.vector.matrix.android.api.pushrules.RuleKind import im.vector.matrix.android.api.pushrules.rest.PushRule import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface AddPushRuleTask : Task { @@ -28,11 +29,13 @@ internal interface AddPushRuleTask : Task { ) } -internal class DefaultAddPushRuleTask @Inject constructor(private val pushRulesApi: PushRulesApi) - : AddPushRuleTask { +internal class DefaultAddPushRuleTask @Inject constructor( + private val pushRulesApi: PushRulesApi, + private val eventBus: EventBus +) : AddPushRuleTask { override suspend fun execute(params: AddPushRuleTask.Params) { - return executeRequest { + return executeRequest(eventBus) { apiCall = pushRulesApi.addRule(params.kind.value, params.pushRule.ruleId, params.pushRule) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt index fcce69c2fc..cdbf6aeee4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt @@ -26,22 +26,23 @@ import im.vector.matrix.android.api.session.pushers.PushersService import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.PusherEntity import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.di.UserId +import im.vector.matrix.android.internal.di.SessionId import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.worker.WorkManagerUtil import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder import im.vector.matrix.android.internal.worker.WorkerParamsFactory -import java.util.UUID +import java.util.* import java.util.concurrent.TimeUnit import javax.inject.Inject -internal class DefaultPusherService @Inject constructor(private val context: Context, - private val monarchy: Monarchy, - @UserId private val userId: String, - private val getPusherTask: GetPushersTask, - private val removePusherTask: RemovePusherTask, - private val taskExecutor: TaskExecutor +internal class DefaultPusherService @Inject constructor( + private val context: Context, + private val monarchy: Monarchy, + @SessionId private val sessionId: String, + private val getPusherTask: GetPushersTask, + private val removePusherTask: RemovePusherTask, + private val taskExecutor: TaskExecutor ) : PushersService { override fun refreshPushers() { @@ -65,7 +66,7 @@ internal class DefaultPusherService @Inject constructor(private val context: Con data = JsonPusherData(url, if (withEventIdOnly) PushersService.EVENT_ID_ONLY else null), append = append) - val params = AddHttpPusherWorker.Params(pusher, userId) + val params = AddHttpPusherWorker.Params(sessionId, pusher) val request = matrixOneTimeWorkRequestBuilder() .setConstraints(WorkManagerUtil.workConstraints) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushRulesTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushRulesTask.kt index d135c36543..4f3b39d1a7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushRulesTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushRulesTask.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.session.pushers import im.vector.matrix.android.api.pushrules.rest.GetPushRulesResponse import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetPushRulesTask : Task { @@ -27,11 +28,14 @@ internal interface GetPushRulesTask : Task { /** * We keep this task, but it should not be used anymore, the push rules comes from the sync response */ -internal class DefaultGetPushRulesTask @Inject constructor(private val pushRulesApi: PushRulesApi, - private val savePushRulesTask: SavePushRulesTask) : GetPushRulesTask { +internal class DefaultGetPushRulesTask @Inject constructor( + private val pushRulesApi: PushRulesApi, + private val savePushRulesTask: SavePushRulesTask, + private val eventBus: EventBus +) : GetPushRulesTask { override suspend fun execute(params: GetPushRulesTask.Params) { - val response = executeRequest { + val response = executeRequest(eventBus) { apiCall = pushRulesApi.getAllRules() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersTask.kt index 045db56786..64e982914a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/GetPushersTask.kt @@ -22,15 +22,19 @@ import im.vector.matrix.android.internal.database.model.PusherEntity import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.awaitTransaction +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetPushersTask : Task -internal class DefaultGetPushersTask @Inject constructor(private val pushersAPI: PushersAPI, - private val monarchy: Monarchy) : GetPushersTask { +internal class DefaultGetPushersTask @Inject constructor( + private val pushersAPI: PushersAPI, + private val monarchy: Monarchy, + private val eventBus: EventBus +) : GetPushersTask { override suspend fun execute(params: Unit) { - val response = executeRequest { + val response = executeRequest(eventBus) { apiCall = pushersAPI.getPushers() } monarchy.awaitTransaction { realm -> diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePushRuleTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePushRuleTask.kt index c4938fa0cc..d0edda9cfb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePushRuleTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePushRuleTask.kt @@ -19,6 +19,7 @@ import im.vector.matrix.android.api.pushrules.RuleKind import im.vector.matrix.android.api.pushrules.rest.PushRule import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface RemovePushRuleTask : Task { @@ -28,11 +29,13 @@ internal interface RemovePushRuleTask : Task { ) } -internal class DefaultRemovePushRuleTask @Inject constructor(private val pushRulesApi: PushRulesApi) - : RemovePushRuleTask { +internal class DefaultRemovePushRuleTask @Inject constructor( + private val pushRulesApi: PushRulesApi, + private val eventBus: EventBus +) : RemovePushRuleTask { override suspend fun execute(params: RemovePushRuleTask.Params) { - return executeRequest { + return executeRequest(eventBus) { apiCall = pushRulesApi.deleteRule(params.kind.value, params.pushRule.ruleId) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePusherTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePusherTask.kt index 297375454a..48e3f40a21 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePusherTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/RemovePusherTask.kt @@ -25,6 +25,7 @@ import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.awaitTransaction import io.realm.Realm +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface RemovePusherTask : Task { @@ -34,7 +35,8 @@ internal interface RemovePusherTask : Task { internal class DefaultRemovePusherTask @Inject constructor( private val pushersAPI: PushersAPI, - private val monarchy: Monarchy + private val monarchy: Monarchy, + private val eventBus: EventBus ) : RemovePusherTask { override suspend fun execute(params: RemovePusherTask.Params) { @@ -59,7 +61,7 @@ internal class DefaultRemovePusherTask @Inject constructor( data = JsonPusherData(existing.data.url, existing.data.format), append = false ) - executeRequest { + executeRequest(eventBus) { apiCall = pushersAPI.setPusher(deleteBody) } monarchy.awaitTransaction { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt index 91ed65d833..c5d593c263 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt @@ -19,6 +19,7 @@ import im.vector.matrix.android.api.pushrules.RuleKind import im.vector.matrix.android.api.pushrules.rest.PushRule import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface UpdatePushRuleEnableStatusTask : Task { @@ -27,11 +28,13 @@ internal interface UpdatePushRuleEnableStatusTask : Task) { + if (isEncrypted()) { + callback.onFailure(IllegalStateException("Encryption is already enabled for this room")) + } else { + stateService.enableEncryption(algorithm, callback) + } + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt index 4a14005fe9..c6bcdf396e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt @@ -36,9 +36,10 @@ import javax.inject.Inject * The summaries can then be extracted and added (as a decoration) to a TimelineEvent for final display. */ -internal class EventRelationsAggregationUpdater @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration, - @UserId private val userId: String, - private val task: EventRelationsAggregationTask) : +internal class EventRelationsAggregationUpdater @Inject constructor( + @SessionDatabase realmConfiguration: RealmConfiguration, + @UserId private val userId: String, + private val task: EventRelationsAggregationTask) : RealmLiveEntityObserver(realmConfiguration) { override val query = Monarchy.Query { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt index ea5c2e858c..9fa922b940 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt @@ -29,10 +29,6 @@ import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.query.* -import im.vector.matrix.android.internal.database.query.isEventRead -import im.vector.matrix.android.internal.database.query.latestEvent -import im.vector.matrix.android.internal.database.query.prev -import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.session.room.membership.RoomDisplayNameResolver import im.vector.matrix.android.internal.session.room.membership.RoomMembers @@ -41,10 +37,11 @@ import im.vector.matrix.android.internal.session.sync.model.RoomSyncUnreadNotifi import io.realm.Realm import javax.inject.Inject -internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId: String, - private val roomDisplayNameResolver: RoomDisplayNameResolver, - private val roomAvatarResolver: RoomAvatarResolver, - private val monarchy: Monarchy) { +internal class RoomSummaryUpdater @Inject constructor( + @UserId private val userId: String, + private val roomDisplayNameResolver: RoomDisplayNameResolver, + private val roomAvatarResolver: RoomAvatarResolver, + private val monarchy: Monarchy) { // TODO: maybe allow user of SDK to give that list private val PREVIEWABLE_TYPES = listOf( @@ -57,7 +54,7 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId EventType.CALL_HANGUP, EventType.CALL_ANSWER, EventType.ENCRYPTED, - EventType.ENCRYPTION, + EventType.STATE_ROOM_ENCRYPTION, EventType.STATE_ROOM_THIRD_PARTY_INVITE, EventType.STICKER, EventType.STATE_ROOM_CREATE @@ -93,7 +90,7 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev() val lastCanonicalAliasEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_CANONICAL_ALIAS).prev() val lastAliasesEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_ALIASES).prev() - val encryptionEvent = EventEntity.where(realm, roomId, EventType.ENCRYPTION).prev() + val encryptionEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_ENCRYPTION).prev() roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0 // avoid this call if we are sure there are unread events diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/alias/GetRoomIdByAliasTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/alias/GetRoomIdByAliasTask.kt index 1a726c3fe5..6aee8c170b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/alias/GetRoomIdByAliasTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/alias/GetRoomIdByAliasTask.kt @@ -24,6 +24,7 @@ import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.task.Task import io.realm.Realm +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetRoomIdByAliasTask : Task> { @@ -33,8 +34,11 @@ internal interface GetRoomIdByAliasTask : Task { var roomId = Realm.getInstance(monarchy.realmConfiguration).use { @@ -45,7 +49,7 @@ internal class DefaultGetRoomIdByAliasTask @Inject constructor(private val monar } else if (!params.searchOnServer) { Optional.from(null) } else { - roomId = executeRequest { + roomId = executeRequest(eventBus) { apiCall = roomAPI.getRoomIdByAlias(params.roomAlias) }.roomId Optional.from(roomId) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt index 970a1fed7e..6567b7ad97 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt @@ -35,21 +35,25 @@ import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.awaitTransaction import io.realm.RealmConfiguration import kotlinx.coroutines.TimeoutCancellationException +import org.greenrobot.eventbus.EventBus import java.util.concurrent.TimeUnit import javax.inject.Inject internal interface CreateRoomTask : Task -internal class DefaultCreateRoomTask @Inject constructor(private val roomAPI: RoomAPI, - private val monarchy: Monarchy, - private val directChatsHelper: DirectChatsHelper, - private val updateUserAccountDataTask: UpdateUserAccountDataTask, - private val readMarkersTask: SetReadMarkersTask, - @SessionDatabase - private val realmConfiguration: RealmConfiguration) : CreateRoomTask { +internal class DefaultCreateRoomTask @Inject constructor( + private val roomAPI: RoomAPI, + private val monarchy: Monarchy, + private val directChatsHelper: DirectChatsHelper, + private val updateUserAccountDataTask: UpdateUserAccountDataTask, + private val readMarkersTask: SetReadMarkersTask, + @SessionDatabase + private val realmConfiguration: RealmConfiguration, + private val eventBus: EventBus +) : CreateRoomTask { override suspend fun execute(params: CreateRoomParams): String { - val createRoomResponse = executeRequest { + val createRoomResponse = executeRequest(eventBus) { apiCall = roomAPI.createRoom(params) } val roomId = createRoomResponse.roomId!! diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/directory/GetPublicRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/directory/GetPublicRoomTask.kt index a24765e0bb..2b7d2e7dd9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/directory/GetPublicRoomTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/directory/GetPublicRoomTask.kt @@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRooms import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetPublicRoomTask : Task { @@ -30,10 +31,13 @@ internal interface GetPublicRoomTask : Task> -internal class DefaultGetThirdPartyProtocolsTask @Inject constructor(private val roomAPI: RoomAPI) : GetThirdPartyProtocolsTask { +internal class DefaultGetThirdPartyProtocolsTask @Inject constructor( + private val roomAPI: RoomAPI, + private val eventBus: EventBus +) : GetThirdPartyProtocolsTask { override suspend fun execute(params: Unit): Map { - return executeRequest { + return executeRequest(eventBus) { apiCall = roomAPI.thirdPartyProtocols() } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt index dd91875f98..3610511dbf 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt @@ -30,6 +30,7 @@ import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.awaitTransaction import io.realm.Realm import io.realm.kotlin.createObject +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface LoadRoomMembersTask : Task { @@ -40,12 +41,14 @@ internal interface LoadRoomMembersTask : Task ) } -internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAPI: RoomAPI, - private val monarchy: Monarchy, - private val syncTokenStore: SyncTokenStore, - private val roomSummaryUpdater: RoomSummaryUpdater, - private val roomMemberEventHandler: RoomMemberEventHandler, - private val timelineEventSenderVisitor: TimelineEventSenderVisitor +internal class DefaultLoadRoomMembersTask @Inject constructor( + private val roomAPI: RoomAPI, + private val monarchy: Monarchy, + private val syncTokenStore: SyncTokenStore, + private val roomSummaryUpdater: RoomSummaryUpdater, + private val roomMemberEventHandler: RoomMemberEventHandler, + private val timelineEventSenderVisitor: TimelineEventSenderVisitor, + private val eventBus: EventBus ) : LoadRoomMembersTask { override suspend fun execute(params: LoadRoomMembersTask.Params) { @@ -53,7 +56,7 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAP return } val lastToken = syncTokenStore.getLastToken() - val response = executeRequest { + val response = executeRequest(eventBus) { apiCall = roomAPI.getMembers(params.roomId, lastToken, null, params.excludeMembership?.value) } insertInDb(response, params.roomId) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteTask.kt index 6bc453a0f3..93b3889455 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteTask.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.room.membership.joining import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface InviteTask : Task { @@ -29,10 +30,13 @@ internal interface InviteTask : Task { ) } -internal class DefaultInviteTask @Inject constructor(private val roomAPI: RoomAPI) : InviteTask { +internal class DefaultInviteTask @Inject constructor( + private val roomAPI: RoomAPI, + private val eventBus: EventBus +) : InviteTask { override suspend fun execute(params: InviteTask.Params) { - return executeRequest { + return executeRequest(eventBus) { val body = InviteBody(params.userId, params.reason) apiCall = roomAPI.invite(params.roomId, body) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt index fbede72520..d4341951eb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt @@ -27,6 +27,7 @@ import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask import im.vector.matrix.android.internal.task.Task import io.realm.RealmConfiguration import kotlinx.coroutines.TimeoutCancellationException +import org.greenrobot.eventbus.EventBus import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -38,13 +39,16 @@ internal interface JoinRoomTask : Task { ) } -internal class DefaultJoinRoomTask @Inject constructor(private val roomAPI: RoomAPI, - private val readMarkersTask: SetReadMarkersTask, - @SessionDatabase - private val realmConfiguration: RealmConfiguration) : JoinRoomTask { +internal class DefaultJoinRoomTask @Inject constructor( + private val roomAPI: RoomAPI, + private val readMarkersTask: SetReadMarkersTask, + @SessionDatabase + private val realmConfiguration: RealmConfiguration, + private val eventBus: EventBus +) : JoinRoomTask { override suspend fun execute(params: JoinRoomTask.Params) { - executeRequest { + executeRequest(eventBus) { apiCall = roomAPI.join(params.roomId, params.viaServers, mapOf("reason" to params.reason)) } // Wait for room to come back from the sync (but it can maybe be in the DB is the sync response is received before) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt index 01198c47de..08eb71fc89 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.room.membership.leaving import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface LeaveRoomTask : Task { @@ -28,10 +29,13 @@ internal interface LeaveRoomTask : Task { ) } -internal class DefaultLeaveRoomTask @Inject constructor(private val roomAPI: RoomAPI) : LeaveRoomTask { +internal class DefaultLeaveRoomTask @Inject constructor( + private val roomAPI: RoomAPI, + private val eventBus: EventBus +) : LeaveRoomTask { override suspend fun execute(params: LeaveRoomTask.Params) { - return executeRequest { + return executeRequest(eventBus) { apiCall = roomAPI.leave(params.roomId, mapOf("reason" to params.reason)) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt index b8b9fe82ef..a9a0f60083 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt @@ -36,12 +36,13 @@ import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith -internal class DefaultReadService @AssistedInject constructor(@Assisted private val roomId: String, - private val monarchy: Monarchy, - private val taskExecutor: TaskExecutor, - private val setReadMarkersTask: SetReadMarkersTask, - private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper, - @UserId private val userId: String +internal class DefaultReadService @AssistedInject constructor( + @Assisted private val roomId: String, + private val monarchy: Monarchy, + private val taskExecutor: TaskExecutor, + private val setReadMarkersTask: SetReadMarkersTask, + private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper, + @UserId private val userId: String ) : ReadService { @AssistedInject.Factory diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt index b9dca748cb..6a0d04193d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt @@ -20,7 +20,8 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.events.model.LocalEcho import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity -import im.vector.matrix.android.internal.database.query.* +import im.vector.matrix.android.internal.database.query.isEventRead +import im.vector.matrix.android.internal.database.query.isReadMarkerMoreRecent import im.vector.matrix.android.internal.database.query.latestEvent import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.UserId @@ -31,8 +32,11 @@ import im.vector.matrix.android.internal.session.sync.RoomFullyReadHandler import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.awaitTransaction import io.realm.Realm +import org.greenrobot.eventbus.EventBus import timber.log.Timber import javax.inject.Inject +import kotlin.collections.HashMap +import kotlin.collections.set internal interface SetReadMarkersTask : Task { @@ -47,12 +51,14 @@ internal interface SetReadMarkersTask : Task { private const val READ_MARKER = "m.fully_read" private const val READ_RECEIPT = "m.read" -internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI: RoomAPI, - private val monarchy: Monarchy, - private val roomFullyReadHandler: RoomFullyReadHandler, - private val readReceiptHandler: ReadReceiptHandler, - @UserId private val userId: String) - : SetReadMarkersTask { +internal class DefaultSetReadMarkersTask @Inject constructor( + private val roomAPI: RoomAPI, + private val monarchy: Monarchy, + private val roomFullyReadHandler: RoomFullyReadHandler, + private val readReceiptHandler: ReadReceiptHandler, + @UserId private val userId: String, + private val eventBus: EventBus +) : SetReadMarkersTask { override suspend fun execute(params: SetReadMarkersTask.Params) { val markers = HashMap() @@ -76,7 +82,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI } if (readReceiptEventId != null - && !isEventRead(monarchy, userId, params.roomId, readReceiptEventId)) { + && !isEventRead(monarchy, userId, params.roomId, readReceiptEventId)) { if (LocalEcho.isLocalEchoId(readReceiptEventId)) { Timber.w("Can't set read receipt for local event $readReceiptEventId") } else { @@ -87,7 +93,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI return } updateDatabase(params.roomId, markers) - executeRequest { + executeRequest(eventBus) { apiCall = roomAPI.sendReadMarker(params.roomId, markers) } } @@ -105,7 +111,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI val isLatestReceived = TimelineEventEntity.latestEvent(realm, roomId = roomId, includesSending = false)?.eventId == readReceiptId if (isLatestReceived) { val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() - ?: return@awaitTransaction + ?: return@awaitTransaction roomSummary.notificationCount = 0 roomSummary.highlightCount = 0 roomSummary.hasUnreadMessages = false diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt index 180776ba8d..1b2b27a3eb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt @@ -38,7 +38,7 @@ import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.di.UserId +import im.vector.matrix.android.internal.di.SessionId import im.vector.matrix.android.internal.session.room.send.EncryptEventWorker import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory import im.vector.matrix.android.internal.session.room.send.RedactEventWorker @@ -51,16 +51,17 @@ import im.vector.matrix.android.internal.util.fetchCopyMap import im.vector.matrix.android.internal.worker.WorkerParamsFactory import timber.log.Timber -internal class DefaultRelationService @AssistedInject constructor(@Assisted private val roomId: String, - private val context: Context, - @UserId private val userId: String, - private val eventFactory: LocalEchoEventFactory, - private val cryptoService: CryptoService, - private val findReactionEventForUndoTask: FindReactionEventForUndoTask, - private val fetchEditHistoryTask: FetchEditHistoryTask, - private val timelineEventMapper: TimelineEventMapper, - private val monarchy: Monarchy, - private val taskExecutor: TaskExecutor) +internal class DefaultRelationService @AssistedInject constructor( + @Assisted private val roomId: String, + private val context: Context, + @SessionId private val sessionId: String, + private val eventFactory: LocalEchoEventFactory, + private val cryptoService: CryptoService, + private val findReactionEventForUndoTask: FindReactionEventForUndoTask, + private val fetchEditHistoryTask: FetchEditHistoryTask, + private val timelineEventMapper: TimelineEventMapper, + private val monarchy: Monarchy, + private val taskExecutor: TaskExecutor) : RelationService { @AssistedInject.Factory @@ -125,7 +126,7 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv // TODO duplicate with send service? private fun createRedactEventWork(localEvent: Event, eventId: String, reason: String?): OneTimeWorkRequest { val sendContentWorkerParams = RedactEventWorker.Params( - userId, + sessionId, localEvent.eventId!!, roomId, eventId, @@ -204,13 +205,13 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv private fun createEncryptEventWork(event: Event, keepKeys: List?): OneTimeWorkRequest { // Same parameter - val params = EncryptEventWorker.Params(userId, roomId, event, keepKeys) + val params = EncryptEventWorker.Params(sessionId, roomId, event, keepKeys) val sendWorkData = WorkerParamsFactory.toData(params) return TimelineSendEventWorkCommon.createWork(sendWorkData, true) } private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest { - val sendContentWorkerParams = SendEventWorker.Params(userId, roomId, event) + val sendContentWorkerParams = SendEventWorker.Params(sessionId, roomId, event) val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) return TimelineSendEventWorkCommon.createWork(sendWorkData, startChain) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FetchEditHistoryTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FetchEditHistoryTask.kt index 5e5db58bdb..a4ab06f767 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FetchEditHistoryTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FetchEditHistoryTask.kt @@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.events.model.RelationType import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface FetchEditHistoryTask : Task> { @@ -33,11 +34,12 @@ internal interface FetchEditHistoryTask : Task { - val response = executeRequest { + val response = executeRequest(eventBus) { apiCall = roomAPI.getRelations(params.roomId, params.eventId, RelationType.REPLACE, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FindReactionEventForUndoTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FindReactionEventForUndoTask.kt index baa01e4042..bdf4fab35e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FindReactionEventForUndoTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FindReactionEventForUndoTask.kt @@ -38,8 +38,9 @@ internal interface FindReactionEventForUndoTask : Task @@ -55,7 +56,7 @@ internal class DefaultFindReactionEventForUndoTask @Inject constructor(private v .equalTo(ReactionAggregatedSummaryEntityFields.KEY, reaction) .findFirst() ?: return null - // want to find the event orignated by me! + // want to find the event originated by me! return rase.sourceEvents .asSequence() .mapNotNull { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/SendRelationWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/SendRelationWorker.kt index eafe1e7419..5857eaa89b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/SendRelationWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/SendRelationWorker.kt @@ -30,20 +30,23 @@ import im.vector.matrix.android.internal.session.room.send.SendResponse import im.vector.matrix.android.internal.worker.SessionWorkerParams import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.getSessionComponent +import org.greenrobot.eventbus.EventBus import javax.inject.Inject +// TODO This is not used. Delete? internal class SendRelationWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) { @JsonClass(generateAdapter = true) internal data class Params( - override val userId: String, + override val sessionId: String, val roomId: String, val event: Event, val relationType: String? = null, - override val lastFailureMessage: String? + override val lastFailureMessage: String? = null ) : SessionWorkerParams @Inject lateinit var roomAPI: RoomAPI + @Inject lateinit var eventBus: EventBus override suspend fun doWork(): Result { val params = WorkerParamsFactory.fromData(inputData) @@ -54,7 +57,7 @@ internal class SendRelationWorker(context: Context, params: WorkerParameters) : return Result.success(inputData) } - val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() + val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success() sessionComponent.inject(this) val localEvent = params.event @@ -82,7 +85,7 @@ internal class SendRelationWorker(context: Context, params: WorkerParameters) : } private suspend fun sendRelation(roomId: String, relationType: String, relatedEventId: String, localEvent: Event) { - executeRequest { + executeRequest(eventBus) { apiCall = roomAPI.sendRelation( roomId = roomId, parent_id = relatedEventId, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/reporting/ReportContentTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/reporting/ReportContentTask.kt index 60c031158a..74012348df 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/reporting/ReportContentTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/reporting/ReportContentTask.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.room.reporting import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface ReportContentTask : Task { @@ -30,9 +31,13 @@ internal interface ReportContentTask : Task { ) } -internal class DefaultReportContentTask @Inject constructor(private val roomAPI: RoomAPI) : ReportContentTask { +internal class DefaultReportContentTask @Inject constructor( + private val roomAPI: RoomAPI, + private val eventBus: EventBus +) : ReportContentTask { + override suspend fun execute(params: ReportContentTask.Params) { - return executeRequest { + return executeRequest(eventBus) { apiCall = roomAPI.reportContent(params.roomId, params.eventId, ReportContentBody(params.score, params.reason)) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt index 8fad03b588..507c8dd247 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt @@ -37,7 +37,7 @@ import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.query.findAllInRoomWithSendStates import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.di.UserId +import im.vector.matrix.android.internal.di.SessionId import im.vector.matrix.android.internal.session.content.UploadContentWorker import im.vector.matrix.android.internal.session.room.timeline.TimelineSendEventWorkCommon import im.vector.matrix.android.internal.util.CancelableWork @@ -53,12 +53,13 @@ import java.util.concurrent.TimeUnit private const val UPLOAD_WORK = "UPLOAD_WORK" private const val BACKOFF_DELAY = 10_000L -internal class DefaultSendService @AssistedInject constructor(@Assisted private val roomId: String, - private val context: Context, - @UserId private val userId: String, - private val localEchoEventFactory: LocalEchoEventFactory, - private val cryptoService: CryptoService, - private val monarchy: Monarchy +internal class DefaultSendService @AssistedInject constructor( + @Assisted private val roomId: String, + private val context: Context, + @SessionId private val sessionId: String, + private val localEchoEventFactory: LocalEchoEventFactory, + private val cryptoService: CryptoService, + private val monarchy: Monarchy ) : SendService { @AssistedInject.Factory @@ -285,7 +286,7 @@ internal class DefaultSendService @AssistedInject constructor(@Assisted private private fun createEncryptEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest { // Same parameter - val params = EncryptEventWorker.Params(userId, roomId, event) + val params = EncryptEventWorker.Params(sessionId, roomId, event) val sendWorkData = WorkerParamsFactory.toData(params) return matrixOneTimeWorkRequestBuilder() @@ -297,7 +298,7 @@ internal class DefaultSendService @AssistedInject constructor(@Assisted private } private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest { - val sendContentWorkerParams = SendEventWorker.Params(userId, roomId, event) + val sendContentWorkerParams = SendEventWorker.Params(sessionId, roomId, event) val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) return TimelineSendEventWorkCommon.createWork(sendWorkData, startChain) @@ -307,7 +308,7 @@ internal class DefaultSendService @AssistedInject constructor(@Assisted private val redactEvent = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason).also { saveLocalEcho(it) } - val sendContentWorkerParams = RedactEventWorker.Params(userId, redactEvent.eventId!!, roomId, event.eventId, reason) + val sendContentWorkerParams = RedactEventWorker.Params(sessionId, redactEvent.eventId!!, roomId, event.eventId, reason) val redactWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) return TimelineSendEventWorkCommon.createWork(redactWorkData, true) } @@ -316,7 +317,7 @@ internal class DefaultSendService @AssistedInject constructor(@Assisted private attachment: ContentAttachmentData, isRoomEncrypted: Boolean, startChain: Boolean): OneTimeWorkRequest { - val uploadMediaWorkerParams = UploadContentWorker.Params(userId, roomId, event, attachment, isRoomEncrypted) + val uploadMediaWorkerParams = UploadContentWorker.Params(sessionId, roomId, event, attachment, isRoomEncrypted) val uploadWorkData = WorkerParamsFactory.toData(uploadMediaWorkerParams) return matrixOneTimeWorkRequestBuilder() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt index a269529dd2..6f1593bc08 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt @@ -37,7 +37,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters) @JsonClass(generateAdapter = true) internal data class Params( - override val userId: String, + override val sessionId: String, val roomId: String, val event: Event, /**Do not encrypt these keys, keep them as is in encrypted content (e.g. m.relates_to)*/ @@ -61,7 +61,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters) return Result.success(inputData) } - val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() + val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success() sessionComponent.inject(this) val localEvent = params.event @@ -97,7 +97,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters) type = safeResult.eventType, content = safeResult.eventContent ) - val nextWorkerParams = SendEventWorker.Params(params.userId, params.roomId, encryptedEvent) + val nextWorkerParams = SendEventWorker.Params(params.sessionId, params.roomId, encryptedEvent) return Result.success(WorkerParamsFactory.toData(nextWorkerParams)) } else { val sendState = when (error) { @@ -106,7 +106,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters) } localEchoUpdater.updateSendState(localEvent.eventId, sendState) // always return success, or the chain will be stuck for ever! - val nextWorkerParams = SendEventWorker.Params(params.userId, params.roomId, localEvent, error?.localizedMessage + val nextWorkerParams = SendEventWorker.Params(params.sessionId, params.roomId, localEvent, error?.localizedMessage ?: "Error") return Result.success(WorkerParamsFactory.toData(nextWorkerParams)) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/RedactEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/RedactEventWorker.kt index ec311458cd..3ff318aa8a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/RedactEventWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/RedactEventWorker.kt @@ -25,13 +25,14 @@ import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.worker.SessionWorkerParams import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.getSessionComponent +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal class RedactEventWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) { @JsonClass(generateAdapter = true) internal data class Params( - override val userId: String, + override val sessionId: String, val txID: String, val roomId: String, val eventId: String, @@ -40,6 +41,7 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters) : C ) : SessionWorkerParams @Inject lateinit var roomAPI: RoomAPI + @Inject lateinit var eventBus: EventBus override suspend fun doWork(): Result { val params = WorkerParamsFactory.fromData(inputData) @@ -50,12 +52,12 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters) : C return Result.success(inputData) } - val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() + val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success() sessionComponent.inject(this) val eventId = params.eventId return runCatching { - executeRequest { + executeRequest(eventBus) { apiCall = roomAPI.redactEvent( params.txID, params.roomId, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt index 6527113054..3215662ba2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt @@ -30,6 +30,7 @@ import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.worker.SessionWorkerParams import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.getSessionComponent +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal class SendEventWorker constructor(context: Context, params: WorkerParameters) @@ -37,7 +38,7 @@ internal class SendEventWorker constructor(context: Context, params: WorkerParam @JsonClass(generateAdapter = true) internal data class Params( - override val userId: String, + override val sessionId: String, val roomId: String, val event: Event, override val lastFailureMessage: String? = null @@ -45,12 +46,13 @@ internal class SendEventWorker constructor(context: Context, params: WorkerParam @Inject lateinit var localEchoUpdater: LocalEchoUpdater @Inject lateinit var roomAPI: RoomAPI + @Inject lateinit var eventBus: EventBus override suspend fun doWork(): Result { val params = WorkerParamsFactory.fromData(inputData) ?: return Result.success() - val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() + val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success() sessionComponent.inject(this) val event = params.event @@ -84,7 +86,7 @@ internal class SendEventWorker constructor(context: Context, params: WorkerParam private suspend fun sendEvent(eventId: String, eventType: String, content: Content?, roomId: String) { localEchoUpdater.updateSendState(eventId, SendState.SENDING) - executeRequest { + executeRequest(eventBus) { apiCall = roomAPI.send( eventId, roomId, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/DefaultStateService.kt index 785fd9ae71..3cd73a97e0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/DefaultStateService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/DefaultStateService.kt @@ -22,6 +22,7 @@ import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.room.state.StateService +import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.query.prev @@ -31,6 +32,7 @@ import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import io.realm.Realm import io.realm.RealmConfiguration +import java.security.InvalidParameterException internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String, @SessionDatabase @@ -52,10 +54,10 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private override fun updateTopic(topic: String, callback: MatrixCallback) { val params = SendStateTask.Params(roomId, - EventType.STATE_ROOM_TOPIC, - mapOf( - "topic" to topic - )) + EventType.STATE_ROOM_TOPIC, + mapOf( + "topic" to topic + )) sendStateTask .configureWith(params) { @@ -63,4 +65,22 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private } .executeBy(taskExecutor) } + + override fun enableEncryption(algorithm: String, callback: MatrixCallback) { + if (algorithm != MXCRYPTO_ALGORITHM_MEGOLM) { + callback.onFailure(InvalidParameterException("Only MXCRYPTO_ALGORITHM_MEGOLM algorithm is supported")) + } else { + val params = SendStateTask.Params(roomId, + EventType.STATE_ROOM_ENCRYPTION, + mapOf( + "algorithm" to algorithm + )) + + sendStateTask + .configureWith(params) { + this.callback = callback + } + .executeBy(taskExecutor) + } + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/SendStateTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/SendStateTask.kt index 39d606f5df..b0d583c6d1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/SendStateTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/state/SendStateTask.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.room.state import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface SendStateTask : Task { @@ -29,9 +30,13 @@ internal interface SendStateTask : Task { ) } -internal class DefaultSendStateTask @Inject constructor(private val roomAPI: RoomAPI) : SendStateTask { +internal class DefaultSendStateTask @Inject constructor( + private val roomAPI: RoomAPI, + private val eventBus: EventBus +) : SendStateTask { + override suspend fun execute(params: SendStateTask.Params) { - return executeRequest { + return executeRequest(eventBus) { apiCall = roomAPI.sendStateEvent(params.roomId, params.eventType, params.body) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultGetContextOfEventTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultGetContextOfEventTask.kt index 08d34d3056..966bdcc1fd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultGetContextOfEventTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultGetContextOfEventTask.kt @@ -20,6 +20,7 @@ import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.filter.FilterRepository import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface GetContextOfEventTask : Task { @@ -31,14 +32,16 @@ internal interface GetContextOfEventTask : Task { + val response = executeRequest(eventBus) { apiCall = roomAPI.getContextOfEvent(params.roomId, params.eventId, params.limit, filter) } return tokenChunkEventPersistor.insertInDb(response, params.roomId, PaginationDirection.BACKWARDS) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultPaginationTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultPaginationTask.kt index d817b22996..aa7b4321dc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultPaginationTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultPaginationTask.kt @@ -20,6 +20,7 @@ import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.filter.FilterRepository import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface PaginationTask : Task { @@ -32,14 +33,16 @@ internal interface PaginationTask : Task { + val chunk = executeRequest(eventBus) { apiCall = roomAPI.getRoomMessagesFrom(params.roomId, params.from, params.direction.value, params.limit, filter) } return tokenChunkEventPersistor.insertInDb(chunk, params.roomId, params.direction) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/GetEventTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/GetEventTask.kt index 8e097c50d9..10c0f5003b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/GetEventTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/GetEventTask.kt @@ -20,9 +20,14 @@ import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject -internal class GetEventTask @Inject constructor(private val roomAPI: RoomAPI +// TODO Add parent task + +internal class GetEventTask @Inject constructor( + private val roomAPI: RoomAPI, + private val eventBus: EventBus ) : Task { internal data class Params( @@ -31,7 +36,7 @@ internal class GetEventTask @Inject constructor(private val roomAPI: RoomAPI ) override suspend fun execute(params: Params): Event { - return executeRequest { + return executeRequest(eventBus) { apiCall = roomAPI.getEvent(params.roomId, params.eventId) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/DefaultSignOutService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/DefaultSignOutService.kt index 17c91011e7..68df456831 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/DefaultSignOutService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/DefaultSignOutService.kt @@ -50,10 +50,10 @@ internal class DefaultSignOutService @Inject constructor(private val signOutTask } } - override fun signOut(sigOutFromHomeserver: Boolean, + override fun signOut(signOutFromHomeserver: Boolean, callback: MatrixCallback): Cancelable { return signOutTask - .configureWith(SignOutTask.Params(sigOutFromHomeserver)) { + .configureWith(SignOutTask.Params(signOutFromHomeserver)) { this.callback = callback } .executeBy(taskExecutor) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignInAgainTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignInAgainTask.kt index 666852c988..0b8902e71b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignInAgainTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignInAgainTask.kt @@ -22,6 +22,7 @@ import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.auth.data.PasswordLoginParams import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface SignInAgainTask : Task { @@ -33,10 +34,12 @@ internal interface SignInAgainTask : Task { internal class DefaultSignInAgainTask @Inject constructor( private val signOutAPI: SignOutAPI, private val sessionParams: SessionParams, - private val sessionParamsStore: SessionParamsStore) : SignInAgainTask { + private val sessionParamsStore: SessionParamsStore, + private val eventBus: EventBus +) : SignInAgainTask { override suspend fun execute(params: SignInAgainTask.Params) { - val newCredentials = executeRequest { + val newCredentials = executeRequest(eventBus) { apiCall = signOutAPI.loginAgain( PasswordLoginParams.userIdentifier( // Reuse the same userId diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt index b43bfa603c..9c31ce567b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt @@ -32,6 +32,7 @@ import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.worker.WorkManagerUtil import io.realm.Realm import io.realm.RealmConfiguration +import org.greenrobot.eventbus.EventBus import timber.log.Timber import java.io.File import java.net.HttpURLConnection @@ -43,25 +44,28 @@ internal interface SignOutTask : Task { ) } -internal class DefaultSignOutTask @Inject constructor(private val context: Context, - @UserId private val userId: String, - private val signOutAPI: SignOutAPI, - private val sessionManager: SessionManager, - private val sessionParamsStore: SessionParamsStore, - @SessionDatabase private val clearSessionDataTask: ClearCacheTask, - @CryptoDatabase private val clearCryptoDataTask: ClearCacheTask, - @UserCacheDirectory private val userFile: File, - private val realmKeysUtils: RealmKeysUtils, - @SessionDatabase private val realmSessionConfiguration: RealmConfiguration, - @CryptoDatabase private val realmCryptoConfiguration: RealmConfiguration, - @UserMd5 private val userMd5: String) : SignOutTask { +internal class DefaultSignOutTask @Inject constructor( + private val context: Context, + @SessionId private val sessionId: String, + private val signOutAPI: SignOutAPI, + private val sessionManager: SessionManager, + private val sessionParamsStore: SessionParamsStore, + @SessionDatabase private val clearSessionDataTask: ClearCacheTask, + @CryptoDatabase private val clearCryptoDataTask: ClearCacheTask, + @UserCacheDirectory private val userFile: File, + private val realmKeysUtils: RealmKeysUtils, + @SessionDatabase private val realmSessionConfiguration: RealmConfiguration, + @CryptoDatabase private val realmCryptoConfiguration: RealmConfiguration, + @UserMd5 private val userMd5: String, + private val eventBus: EventBus +) : SignOutTask { override suspend fun execute(params: SignOutTask.Params) { // It should be done even after a soft logout, to be sure the deviceId is deleted on the if (params.sigOutFromHomeserver) { Timber.d("SignOut: send request...") try { - executeRequest { + executeRequest(eventBus) { apiCall = signOutAPI.signOut() } } catch (throwable: Throwable) { @@ -79,13 +83,13 @@ internal class DefaultSignOutTask @Inject constructor(private val context: Conte } Timber.d("SignOut: release session...") - sessionManager.releaseSession(userId) + sessionManager.releaseSession(sessionId) Timber.d("SignOut: cancel pending works...") WorkManagerUtil.cancelAllWorks(context) Timber.d("SignOut: delete session params...") - sessionParamsStore.delete(userId) + sessionParamsStore.delete(sessionId) Timber.d("SignOut: clear session data...") clearSessionDataTask.execute(Unit) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt index d99b9df4df..b56594bd16 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt @@ -25,6 +25,7 @@ import im.vector.matrix.android.internal.session.homeserver.GetHomeServerCapabil import im.vector.matrix.android.internal.session.sync.model.SyncResponse import im.vector.matrix.android.internal.session.user.UserStore import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import timber.log.Timber import javax.inject.Inject @@ -33,15 +34,17 @@ internal interface SyncTask : Task { data class Params(var timeout: Long = 30_000L) } -internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI, - @UserId private val userId: String, - private val filterRepository: FilterRepository, - private val syncResponseHandler: SyncResponseHandler, - private val initialSyncProgressService: DefaultInitialSyncProgressService, - private val syncTokenStore: SyncTokenStore, - private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask, - private val userStore: UserStore, - private val syncTaskSequencer: SyncTaskSequencer +internal class DefaultSyncTask @Inject constructor( + private val syncAPI: SyncAPI, + @UserId private val userId: String, + private val filterRepository: FilterRepository, + private val syncResponseHandler: SyncResponseHandler, + private val initialSyncProgressService: DefaultInitialSyncProgressService, + private val syncTokenStore: SyncTokenStore, + private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask, + private val userStore: UserStore, + private val syncTaskSequencer: SyncTaskSequencer, + private val eventBus: EventBus ) : SyncTask { override suspend fun execute(params: SyncTask.Params) = syncTaskSequencer.post { @@ -70,7 +73,7 @@ internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI, initialSyncProgressService.endAll() initialSyncProgressService.startTask(R.string.initial_sync_start_importing_account, 100) } - val syncResponse = executeRequest { + val syncResponse = executeRequest(eventBus) { apiCall = syncAPI.sync(requestParams) } syncResponseHandler.handleResponse(syncResponse, token) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt index 9bc8c86be5..139a3297cd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt @@ -38,10 +38,11 @@ import io.realm.RealmList import timber.log.Timber import javax.inject.Inject -internal class UserAccountDataSyncHandler @Inject constructor(private val monarchy: Monarchy, - @UserId private val userId: String, - private val directChatsHelper: DirectChatsHelper, - private val updateUserAccountDataTask: UpdateUserAccountDataTask) { +internal class UserAccountDataSyncHandler @Inject constructor( + private val monarchy: Monarchy, + @UserId private val userId: String, + private val directChatsHelper: DirectChatsHelper, + private val updateUserAccountDataTask: UpdateUserAccountDataTask) { fun handle(realm: Realm, accountData: UserAccountDataSync?) { accountData?.list?.forEach { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt index 9fe3e38d36..724b0e1360 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt @@ -39,7 +39,7 @@ import java.util.concurrent.atomic.AtomicBoolean */ abstract class SyncService : Service() { - private var userId: String? = null + private var sessionId: String? = null private var mIsSelfDestroyed: Boolean = false private var isInitialSync: Boolean = false @@ -58,18 +58,17 @@ abstract class SyncService : Service() { Timber.i("onStartCommand $intent") intent?.let { val matrix = Matrix.getInstance(applicationContext) - val safeUserId = it.getStringExtra(EXTRA_USER_ID) ?: return@let - val sessionComponent = matrix.sessionManager.getSessionComponent(safeUserId) + val safeSessionId = it.getStringExtra(EXTRA_SESSION_ID) ?: return@let + val sessionComponent = matrix.sessionManager.getSessionComponent(safeSessionId) ?: return@let session = sessionComponent.session() - userId = safeUserId + sessionId = safeSessionId syncTask = sessionComponent.syncTask() isInitialSync = !session.hasAlreadySynced() networkConnectivityChecker = sessionComponent.networkConnectivityChecker() taskExecutor = sessionComponent.taskExecutor() coroutineDispatchers = sessionComponent.coroutineDispatchers() backgroundDetectionObserver = matrix.backgroundDetectionObserver - onStart(isInitialSync) if (isRunning.get()) { Timber.i("Received a start while was already syncing... ignore") } else { @@ -79,6 +78,7 @@ abstract class SyncService : Service() { } } } + onStart(isInitialSync) // No intent just start the service, an alarm will should call with intent return START_STICKY } @@ -101,7 +101,7 @@ abstract class SyncService : Service() { private suspend fun doSync() { if (!networkConnectivityChecker.hasInternetAccess()) { Timber.v("No network reschedule to avoid wasting resources") - userId?.also { + sessionId?.also { onRescheduleAsked(it, isInitialSync, delay = 10_000L) } stopMe() @@ -131,14 +131,14 @@ abstract class SyncService : Service() { abstract fun onStart(isInitialSync: Boolean) - abstract fun onRescheduleAsked(userId: String, isInitialSync: Boolean, delay: Long) + abstract fun onRescheduleAsked(sessionId: String, isInitialSync: Boolean, delay: Long) override fun onBind(intent: Intent?): IBinder? { return null } companion object { - const val EXTRA_USER_ID = "EXTRA_USER_ID" + const val EXTRA_SESSION_ID = "EXTRA_SESSION_ID" private const val TIME_OUT = 0L private const val DELAY_FAILURE = 5_000L } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt index eb4f2ff7c2..3637cc624f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt @@ -22,6 +22,7 @@ import im.vector.matrix.android.api.failure.isTokenError import im.vector.matrix.android.internal.network.NetworkConnectivityChecker import im.vector.matrix.android.internal.session.sync.SyncTask import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.worker.SessionWorkerParams import im.vector.matrix.android.internal.worker.WorkManagerUtil import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder import im.vector.matrix.android.internal.worker.WorkerParamsFactory @@ -38,10 +39,11 @@ internal class SyncWorker(context: Context, @JsonClass(generateAdapter = true) internal data class Params( - val userId: String, + override val sessionId: String, val timeout: Long = DEFAULT_LONG_POOL_TIMEOUT, - val automaticallyRetry: Boolean = false - ) + val automaticallyRetry: Boolean = false, + override val lastFailureMessage: String? = null + ) : SessionWorkerParams @Inject lateinit var syncTask: SyncTask @Inject lateinit var taskExecutor: TaskExecutor @@ -50,7 +52,7 @@ internal class SyncWorker(context: Context, override suspend fun doWork(): Result { Timber.i("Sync work starting") val params = WorkerParamsFactory.fromData(inputData) ?: return Result.success() - val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() + val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success() sessionComponent.inject(this) return runCatching { doSync(params.timeout) @@ -75,8 +77,8 @@ internal class SyncWorker(context: Context, const val BG_SYNC_WORK_NAME = "BG_SYNCP" - fun requireBackgroundSync(context: Context, userId: String, serverTimeout: Long = 0) { - val data = WorkerParamsFactory.toData(Params(userId, serverTimeout, false)) + fun requireBackgroundSync(context: Context, sessionId: String, serverTimeout: Long = 0) { + val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, false)) val workRequest = matrixOneTimeWorkRequestBuilder() .setConstraints(WorkManagerUtil.workConstraints) .setBackoffCriteria(BackoffPolicy.LINEAR, 1_000, TimeUnit.MILLISECONDS) @@ -85,8 +87,8 @@ internal class SyncWorker(context: Context, WorkManager.getInstance(context).enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.REPLACE, workRequest) } - fun automaticallyBackgroundSync(context: Context, userId: String, serverTimeout: Long = 0, delay: Long = 30_000) { - val data = WorkerParamsFactory.toData(Params(userId, serverTimeout, true)) + fun automaticallyBackgroundSync(context: Context, sessionId: String, serverTimeout: Long = 0, delay: Long = 30_000) { + val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, true)) val workRequest = matrixOneTimeWorkRequestBuilder() .setConstraints(WorkManagerUtil.workConstraints) .setInputData(data) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt index 075eeb23d1..55b26f203f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt @@ -23,6 +23,7 @@ import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.sync.model.accountdata.IgnoredUsersContent import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface UpdateIgnoredUserIdsTask : Task { @@ -33,10 +34,13 @@ internal interface UpdateIgnoredUserIdsTask : Task { + executeRequest(eventBus) { apiCall = accountDataApi.setAccountData(userId, UserAccountData.TYPE_IGNORED_USER_LIST, body) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateUserAccountDataTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateUserAccountDataTask.kt index 4c4f40add5..068ce4777a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateUserAccountDataTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateUserAccountDataTask.kt @@ -21,6 +21,7 @@ import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.sync.model.accountdata.BreadcrumbsContent import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus import javax.inject.Inject internal interface UpdateUserAccountDataTask : Task { @@ -50,11 +51,14 @@ internal interface UpdateUserAccountDataTask : Task> { @@ -31,10 +32,13 @@ internal interface SearchUserTask : Task> { ) } -internal class DefaultSearchUserTask @Inject constructor(private val searchUserAPI: SearchUserAPI) : SearchUserTask { +internal class DefaultSearchUserTask @Inject constructor( + private val searchUserAPI: SearchUserAPI, + private val eventBus: EventBus +) : SearchUserTask { override suspend fun execute(params: SearchUserTask.Params): List { - val response = executeRequest { + val response = executeRequest(eventBus) { apiCall = searchUserAPI.searchUsers(SearchUsersParams(params.search, params.limit)) } return response.users.map { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/worker/SessionWorkerParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/worker/SessionWorkerParams.kt index 0c53a3ef0b..c05367cf10 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/worker/SessionWorkerParams.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/worker/SessionWorkerParams.kt @@ -17,7 +17,7 @@ package im.vector.matrix.android.internal.worker interface SessionWorkerParams { - val userId: String + val sessionId: String // Null is no error occurs. When chaining Workers, first step is to check that there is no lastFailureMessage from the previous workers val lastFailureMessage: String? diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/worker/Worker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/worker/Worker.kt index 58abd10e81..88680786ad 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/worker/Worker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/worker/Worker.kt @@ -20,6 +20,6 @@ import androidx.work.ListenableWorker import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.internal.session.SessionComponent -internal fun ListenableWorker.getSessionComponent(userId: String): SessionComponent? { - return Matrix.getInstance(applicationContext).sessionManager.getSessionComponent(userId) +internal fun ListenableWorker.getSessionComponent(sessionId: String): SessionComponent? { + return Matrix.getInstance(applicationContext).sessionManager.getSessionComponent(sessionId) } diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt index 1d1bbe1406..d67f14842b 100644 --- a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt +++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt @@ -25,7 +25,8 @@ import im.vector.matrix.android.api.session.room.model.RoomMemberContent import im.vector.matrix.android.api.session.room.model.message.MessageTextContent import io.mockk.every import io.mockk.mockk -import org.junit.Assert +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Test class PushrulesConditionTest { @@ -147,9 +148,9 @@ class PushrulesConditionTest { content = MessageTextContent("m.text", "A").toContent(), originServerTs = 0, roomId = room2JoinedId).also { - Assert.assertFalse("This room does not have 3 members", conditionEqual3.isSatisfied(it, sessionStub)) - Assert.assertFalse("This room does not have 3 members", conditionEqual3Bis.isSatisfied(it, sessionStub)) - Assert.assertTrue("This room has less than 3 members", conditionLessThan3.isSatisfied(it, sessionStub)) + assertFalse("This room does not have 3 members", conditionEqual3.isSatisfied(it, sessionStub)) + assertFalse("This room does not have 3 members", conditionEqual3Bis.isSatisfied(it, sessionStub)) + assertTrue("This room has less than 3 members", conditionLessThan3.isSatisfied(it, sessionStub)) } Event( @@ -158,9 +159,9 @@ class PushrulesConditionTest { content = MessageTextContent("m.text", "A").toContent(), originServerTs = 0, roomId = room3JoinedId).also { - Assert.assertTrue("This room has 3 members", conditionEqual3.isSatisfied(it, sessionStub)) - Assert.assertTrue("This room has 3 members", conditionEqual3Bis.isSatisfied(it, sessionStub)) - Assert.assertFalse("This room has more than 3 members", conditionLessThan3.isSatisfied(it, sessionStub)) + assertTrue("This room has 3 members", conditionEqual3.isSatisfied(it, sessionStub)) + assertTrue("This room has 3 members", conditionEqual3Bis.isSatisfied(it, sessionStub)) + assertFalse("This room has more than 3 members", conditionLessThan3.isSatisfied(it, sessionStub)) } } @@ -174,7 +175,7 @@ class PushrulesConditionTest { content = MessageTextContent("m.notice", "A").toContent(), originServerTs = 0, roomId = "2joined").also { - Assert.assertTrue("Notice", conditionEqual.isSatisfied(it)) + assertTrue("Notice", conditionEqual.isSatisfied(it)) } } } diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/crypto/keysbackup/util/Base58Test.kt b/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/crypto/keysbackup/util/Base58Test.kt new file mode 100644 index 0000000000..42295dada0 --- /dev/null +++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/crypto/keysbackup/util/Base58Test.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2018 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.crypto.keysbackup.util + +import org.junit.Assert.assertArrayEquals +import org.junit.Assert.assertEquals +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runners.MethodSorters + +@FixMethodOrder(MethodSorters.JVM) +class Base58Test { + + @Test + fun encode() { + // Example comes from https://github.com/keis/base58 + assertEquals("StV1DL6CwTryKyV", base58encode("hello world".toByteArray())) + } + + @Test + fun decode() { + // Example comes from https://github.com/keis/base58 + assertArrayEquals("hello world".toByteArray(), base58decode("StV1DL6CwTryKyV")) + } + + @Test + fun encode_curve25519() { + // Encode a 32 bytes key + assertEquals("4F85ZySpwyY6FuH7mQYyyr5b8nV9zFRBLj92AJa37sMr", + base58encode(("0123456789" + "0123456789" + "0123456789" + "01").toByteArray())) + } + + @Test + fun decode_curve25519() { + assertArrayEquals(("0123456789" + "0123456789" + "0123456789" + "01").toByteArray(), + base58decode("4F85ZySpwyY6FuH7mQYyyr5b8nV9zFRBLj92AJa37sMr")) + } +} diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/crypto/keysbackup/util/RecoveryKeyTest.kt b/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/crypto/keysbackup/util/RecoveryKeyTest.kt new file mode 100644 index 0000000000..47a2aa08df --- /dev/null +++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/crypto/keysbackup/util/RecoveryKeyTest.kt @@ -0,0 +1,79 @@ +/* + * Copyright 2018 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.crypto.keysbackup.util + +import org.junit.Assert.* +import org.junit.Test + +class RecoveryKeyTest { + private val curve25519Key = byteArrayOf( + 0x77.toByte(), 0x07.toByte(), 0x6D.toByte(), 0x0A.toByte(), 0x73.toByte(), 0x18.toByte(), 0xA5.toByte(), 0x7D.toByte(), + 0x3C.toByte(), 0x16.toByte(), 0xC1.toByte(), 0x72.toByte(), 0x51.toByte(), 0xB2.toByte(), 0x66.toByte(), 0x45.toByte(), + 0xDF.toByte(), 0x4C.toByte(), 0x2F.toByte(), 0x87.toByte(), 0xEB.toByte(), 0xC0.toByte(), 0x99.toByte(), 0x2A.toByte(), + 0xB1.toByte(), 0x77.toByte(), 0xFB.toByte(), 0xA5.toByte(), 0x1D.toByte(), 0xB9.toByte(), 0x2C.toByte(), 0x2A.toByte()) + + @Test + fun isValidRecoveryKey_valid_true() { + assertTrue(isValidRecoveryKey("EsTcLW2KPGiFwKEA3As5g5c4BXwkqeeJZJV8Q9fugUMNUE4d")) + + // Space should be ignored + assertTrue(isValidRecoveryKey("EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d")) + + // All whitespace should be ignored + assertTrue(isValidRecoveryKey("EsTc LW2K PGiF wKEA 3As5 g5c4\r\nBXwk qeeJ ZJV8 Q9fu gUMN UE4d")) + } + + @Test + fun isValidRecoveryKey_null_false() { + assertFalse(isValidRecoveryKey(null)) + } + + @Test + fun isValidRecoveryKey_empty_false() { + assertFalse(isValidRecoveryKey("")) + } + + @Test + fun isValidRecoveryKey_wrong_size_false() { + assertFalse(isValidRecoveryKey("abc")) + } + + @Test + fun isValidRecoveryKey_bad_first_byte_false() { + assertFalse(isValidRecoveryKey("FsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d")) + } + + @Test + fun isValidRecoveryKey_bad_second_byte_false() { + assertFalse(isValidRecoveryKey("EqTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d")) + } + + @Test + fun isValidRecoveryKey_bad_parity_false() { + assertFalse(isValidRecoveryKey("EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4e")) + } + + @Test + fun computeRecoveryKey_ok() { + assertEquals("EsTcLW2KPGiFwKEA3As5g5c4BXwkqeeJZJV8Q9fugUMNUE4d", computeRecoveryKey(curve25519Key)) + } + + @Test + fun extractCurveKeyFromRecoveryKey_ok() { + assertArrayEquals(curve25519Key, extractCurveKeyFromRecoveryKey("EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d")) + } +} diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/crypto/store/db/HelperTest.kt b/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/crypto/store/db/HelperTest.kt new file mode 100644 index 0000000000..d4740bdc4f --- /dev/null +++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/crypto/store/db/HelperTest.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2018 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.crypto.store.db + +import im.vector.matrix.android.internal.util.md5 +import org.junit.Assert.assertEquals +import org.junit.Test + +class HelperTest { + + @Test + fun testHash_ok() { + assertEquals("e9ee13b1ba2afc0825f4e556114785dd", "alice_15428931567802abf5ba7-d685-4333-af47-d38417ab3724:localhost:8480".md5()) + } + + @Test + fun testHash_size_ok() { + // Any String will have a 32 char hash + for (i in 1..100) { + assertEquals(32, "a".repeat(i).md5().length) + } + } +} diff --git a/vector/src/fdroid/java/im/vector/riotx/fdroid/receiver/AlarmSyncBroadcastReceiver.kt b/vector/src/fdroid/java/im/vector/riotx/fdroid/receiver/AlarmSyncBroadcastReceiver.kt index 951fcaa14d..84895f9f43 100644 --- a/vector/src/fdroid/java/im/vector/riotx/fdroid/receiver/AlarmSyncBroadcastReceiver.kt +++ b/vector/src/fdroid/java/im/vector/riotx/fdroid/receiver/AlarmSyncBroadcastReceiver.kt @@ -31,17 +31,17 @@ import timber.log.Timber class AlarmSyncBroadcastReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { - // Aquire a lock to give enough time for the sync :/ + // Acquire a lock to give enough time for the sync :/ (context.getSystemService(Context.POWER_SERVICE) as PowerManager).run { newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "riotx:fdroidSynclock").apply { acquire((10_000).toLong()) } } - val userId = intent.getStringExtra(SyncService.EXTRA_USER_ID) + val sessionId = intent.getStringExtra(SyncService.EXTRA_SESSION_ID) // This method is called when the BroadcastReceiver is receiving an Intent broadcast. Timber.d("RestartBroadcastReceiver received intent") - VectorSyncService.newIntent(context, userId).also { + VectorSyncService.newIntent(context, sessionId).let { try { ContextCompat.startForegroundService(context, it) } catch (ex: Throwable) { @@ -50,7 +50,7 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() { } } - scheduleAlarm(context, userId, 30_000L) + scheduleAlarm(context, sessionId, 30_000L) Timber.i("Alarm scheduled to restart service") } @@ -58,10 +58,10 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() { companion object { private const val REQUEST_CODE = 0 - fun scheduleAlarm(context: Context, userId: String, delay: Long) { + fun scheduleAlarm(context: Context, sessionId: String, delay: Long) { // Reschedule val intent = Intent(context, AlarmSyncBroadcastReceiver::class.java).apply { - putExtra(SyncService.EXTRA_USER_ID, userId) + putExtra(SyncService.EXTRA_SESSION_ID, sessionId) } val pIntent = PendingIntent.getBroadcast(context, REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT) val firstMillis = System.currentTimeMillis() + delay diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt b/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt index 620e32f51f..0a8345c650 100644 --- a/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt +++ b/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt @@ -47,7 +47,7 @@ fun Session.configureAndStart(context: Context, fun Session.startSyncing(context: Context) { val applicationContext = context.applicationContext if (!hasAlreadySynced()) { - VectorSyncService.newIntent(applicationContext, myUserId).also { + VectorSyncService.newIntent(applicationContext, sessionId).also { try { ContextCompat.startForegroundService(applicationContext, it) } catch (ex: Throwable) { diff --git a/vector/src/main/java/im/vector/riotx/core/services/VectorSyncService.kt b/vector/src/main/java/im/vector/riotx/core/services/VectorSyncService.kt index b6b8fbf06a..314e12db05 100644 --- a/vector/src/main/java/im/vector/riotx/core/services/VectorSyncService.kt +++ b/vector/src/main/java/im/vector/riotx/core/services/VectorSyncService.kt @@ -30,9 +30,9 @@ class VectorSyncService : SyncService() { companion object { - fun newIntent(context: Context, userId: String): Intent { + fun newIntent(context: Context, sessionId: String): Intent { return Intent(context, VectorSyncService::class.java).also { - it.putExtra(EXTRA_USER_ID, userId) + it.putExtra(EXTRA_SESSION_ID, sessionId) } } } @@ -54,25 +54,25 @@ class VectorSyncService : SyncService() { startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification) } - override fun onRescheduleAsked(userId: String, isInitialSync: Boolean, delay: Long) { - reschedule(userId, delay) + override fun onRescheduleAsked(sessionId: String, isInitialSync: Boolean, delay: Long) { + reschedule(sessionId, delay) } override fun onDestroy() { - removeForegroundNotif() + removeForegroundNotification() super.onDestroy() } - private fun removeForegroundNotif() { + private fun removeForegroundNotification() { val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.cancel(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE) } - private fun reschedule(userId: String, delay: Long) { + private fun reschedule(sessionId: String, delay: Long) { val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - PendingIntent.getForegroundService(this, 0, newIntent(this, userId), 0) + PendingIntent.getForegroundService(this, 0, newIntent(this, sessionId), 0) } else { - PendingIntent.getService(this, 0, newIntent(this, userId), 0) + PendingIntent.getService(this, 0, newIntent(this, sessionId), 0) } val firstMillis = System.currentTimeMillis() + delay val alarmMgr = getSystemService(Context.ALARM_SERVICE) as AlarmManager diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt index 3f234fcd3e..fd29fe6bf5 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt @@ -65,7 +65,7 @@ class EncryptionItemFactory @Inject constructor(private val stringProvider: Stri private fun buildNoticeText(event: Event, senderName: String?): CharSequence? { return when { - EventType.ENCRYPTION == event.getClearType() -> { + EventType.STATE_ROOM_ENCRYPTION == event.getClearType() -> { val content = event.content.toModel() ?: return null stringProvider.getString(R.string.notice_end_to_end, senderName, content.algorithm) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 4a7a1e2a86..861be5cc86 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -54,7 +54,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me EventType.CALL_ANSWER, EventType.REACTION, EventType.REDACTION, - EventType.ENCRYPTION -> noticeItemFactory.create(event, highlight, callback) + EventType.STATE_ROOM_ENCRYPTION -> noticeItemFactory.create(event, highlight, callback) // State room create EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(event, callback) // Crypto diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt index b0f3e617a6..d669068cd7 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt @@ -34,7 +34,7 @@ object TimelineDisplayableEvents { EventType.CALL_HANGUP, EventType.CALL_ANSWER, EventType.ENCRYPTED, - EventType.ENCRYPTION, + EventType.STATE_ROOM_ENCRYPTION, EventType.STATE_ROOM_THIRD_PARTY_INVITE, EventType.STICKER, EventType.STATE_ROOM_CREATE, diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/picker/RoomDirectoryListCreator.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/picker/RoomDirectoryListCreator.kt index 4073929a4f..0b02101ecb 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/picker/RoomDirectoryListCreator.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/picker/RoomDirectoryListCreator.kt @@ -26,13 +26,11 @@ import javax.inject.Inject class RoomDirectoryListCreator @Inject constructor(private val stringArrayProvider: StringArrayProvider, private val session: Session) { - private val credentials = session.sessionParams.credentials - fun computeDirectories(thirdPartyProtocolData: Map): List { val result = ArrayList() // Add user homeserver name - val userHsName = credentials.userId.substring(credentials.userId.indexOf(":") + 1) + val userHsName = session.myUserId.substringAfter(":") result.add(RoomDirectoryData( displayName = userHsName,