Compare commits

..

1 Commits

Author SHA1 Message Date
1d3faa7d9b Group: should refresh when user data come. [WIP] 2019-07-02 20:50:25 +02:00
327 changed files with 4864 additions and 6742 deletions

View File

@ -14,7 +14,7 @@ steps:
- "./gradlew clean lintGplayRelease assembleGplayDebug --stacktrace" - "./gradlew clean lintGplayRelease assembleGplayDebug --stacktrace"
artifact_paths: artifact_paths:
- "vector/build/outputs/apk/gplay/debug/*.apk" - "vector/build/outputs/apk/gplay/debug/*.apk"
branches: "!master" branches: "develop feature/*"
plugins: plugins:
- docker#v3.1.0: - docker#v3.1.0:
image: "runmymind/docker-android-sdk" image: "runmymind/docker-android-sdk"
@ -28,7 +28,7 @@ steps:
- "./gradlew clean lintFdroidRelease assembleFdroidDebug --stacktrace" - "./gradlew clean lintFdroidRelease assembleFdroidDebug --stacktrace"
artifact_paths: artifact_paths:
- "vector/build/outputs/apk/fdroid/debug/*.apk" - "vector/build/outputs/apk/fdroid/debug/*.apk"
branches: "!master" branches: "develop feature/*"
plugins: plugins:
- docker#v3.1.0: - docker#v3.1.0:
image: "runmymind/docker-android-sdk" image: "runmymind/docker-android-sdk"

View File

@ -1,10 +1,23 @@
Changes in RiotX 0.1.0 (2019-07-11) Changes in RiotX 0.XX (2019-XX-XX)
=================================================== ===================================================
First release! Features:
- Contextual action menu for messages in room
Improvements:
-
Other changes:
-
Bugfix:
-
Translations:
-
Build:
-

View File

@ -2,6 +2,8 @@
buildscript { buildscript {
ext.kotlin_version = '1.3.21' ext.kotlin_version = '1.3.21'
ext.koin_version = '1.0.2'
// TODO ext.koin_version = '2.0.0-GA'
repositories { repositories {
google() google()
jcenter() jcenter()
@ -24,27 +26,15 @@ buildscript {
allprojects { allprojects {
repositories { repositories {
// For olm library. This has to be declared first, to ensure that Olm library is not downloaded from another repo maven { url "http://dl.bintray.com/piasy/maven" }
maven { maven { url 'https://jitpack.io' }
url 'https://jitpack.io'
content {
// Use this repo only for olm library
includeGroupByRegex "org\\.matrix\\.gitlab\\.matrix-org"
// And also for FilePicker
includeGroupByRegex "com\\.github\\.jaiselrahman"
// And monarchy
includeGroupByRegex "com\\.github\\.Zhuinden"
}
}
maven {
url "http://dl.bintray.com/piasy/maven"
content {
includeGroupByRegex "com\\.github\\.piasy"
}
}
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
google() google()
jcenter() jcenter()
// For Olm SDK
maven {
url 'https://jitpack.io'
}
} }
} }
@ -54,10 +44,6 @@ task clean(type: Delete) {
apply plugin: 'org.sonarqube' apply plugin: 'org.sonarqube'
// To run a sonar analysis:
// Run './gradlew sonarqube -Dsonar.login=<REPLACE_WITH_SONAR_KEY>'
// The SONAR_KEY is stored in passbolt
sonarqube { sonarqube {
properties { properties {
property "sonar.projectName", "RiotX-Android" property "sonar.projectName", "RiotX-Android"
@ -83,23 +69,3 @@ project(":vector") {
} }
} }
} }
//project(":matrix-sdk-android") {
// sonarqube {
// properties {
// property "sonar.sources", project(":matrix-sdk-android").android.sourceSets.main.java.srcDirs
// // exclude source code from analyses separated by a colon (:)
// // property "sonar.exclusions", "**/*.*"
// }
// }
//}
//
//project(":matrix-sdk-android-rx") {
// sonarqube {
// properties {
// property "sonar.sources", project(":matrix-sdk-android-rx").android.sourceSets.main.java.srcDirs
// // exclude source code from analyses separated by a colon (:)
// // property "sonar.exclusions", "**/*.*"
// }
// }
//}

View File

@ -35,11 +35,11 @@ android {
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(":matrix-sdk-android") implementation project(":matrix-sdk-android")
implementation 'androidx.appcompat:appcompat:1.1.0-beta01' implementation 'androidx.appcompat:appcompat:1.1.0-alpha01'
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0' implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
} }

View File

@ -56,6 +56,6 @@ private class LiveDataObservable<T>(
} }
} }
fun <T> LiveData<T>.asObservable(): Observable<T> { fun <T> LiveData<T>.asObservable(defaultValue: T? = null): Observable<T> {
return LiveDataObservable(this) return LiveDataObservable(this, defaultValue)
} }

View File

@ -21,24 +21,23 @@ import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
class RxRoom(private val room: Room) { class RxRoom(private val room: Room) {
fun liveRoomSummary(): Observable<RoomSummary> { fun liveRoomSummary(): Observable<RoomSummary> {
return room.liveRoomSummary().asObservable().observeOn(Schedulers.computation()) return room.liveRoomSummary.asObservable()
} }
fun liveRoomMemberIds(): Observable<List<String>> { fun liveRoomMemberIds(): Observable<List<String>> {
return room.getRoomMemberIdsLive().asObservable().observeOn(Schedulers.computation()) return room.getRoomMemberIdsLive().asObservable()
} }
fun liveAnnotationSummary(eventId: String): Observable<EventAnnotationsSummary> { fun liveAnnotationSummary(eventId: String): Observable<EventAnnotationsSummary> {
return room.getEventSummaryLive(eventId).asObservable().observeOn(Schedulers.computation()) return room.getEventSummaryLive(eventId).asObservable()
} }
fun liveTimelineEvent(eventId: String): Observable<TimelineEvent> { fun liveTimelineEvent(eventId: String): Observable<TimelineEvent> {
return room.liveTimeLineEvent(eventId).asObservable().observeOn(Schedulers.computation()) return room.liveTimeLineEvent(eventId).asObservable()
} }
} }

View File

@ -21,25 +21,29 @@ import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.session.pushers.Pusher import im.vector.matrix.android.api.session.pushers.Pusher
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.sync.SyncState import im.vector.matrix.android.api.session.sync.SyncState
import im.vector.matrix.android.api.session.user.model.User
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
class RxSession(private val session: Session) { class RxSession(private val session: Session) {
fun liveRoomSummaries(): Observable<List<RoomSummary>> { fun liveRoomSummaries(): Observable<List<RoomSummary>> {
return session.liveRoomSummaries().asObservable().observeOn(Schedulers.computation()) return session.liveRoomSummaries().asObservable()
} }
fun liveGroupSummaries(): Observable<List<GroupSummary>> { fun liveGroupSummaries(): Observable<List<GroupSummary>> {
return session.liveGroupSummaries().asObservable().observeOn(Schedulers.computation()) return session.liveGroupSummaries().asObservable()
} }
fun liveSyncState(): Observable<SyncState> { fun liveSyncState(): Observable<SyncState> {
return session.syncState().asObservable().observeOn(Schedulers.computation()) return session.syncState().asObservable()
} }
fun livePushers(): Observable<List<Pusher>> { fun livePushers(): Observable<List<Pusher>> {
return session.livePushers().asObservable().observeOn(Schedulers.computation()) return session.livePushers().asObservable()
}
fun liveUser(userId: String): Observable<User?> {
return session.observeUser(userId).asObservable(User(userId))
} }
} }

View File

@ -6,14 +6,20 @@ apply plugin: 'realm-android'
apply plugin: 'okreplay' apply plugin: 'okreplay'
buildscript { buildscript {
repositories { repositories {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath "io.realm:realm-gradle-plugin:5.12.0" classpath "io.realm:realm-gradle-plugin:5.9.0"
} }
} }
repositories {
google()
jcenter()
}
androidExtensions { androidExtensions {
experimental = true experimental = true
} }
@ -27,8 +33,6 @@ android {
targetSdkVersion 28 targetSdkVersion 28
versionCode 1 versionCode 1
versionName "0.0.1" versionName "0.0.1"
// Multidex is useful for tests
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
@ -62,11 +66,6 @@ android {
lintOptions { lintOptions {
lintConfig file("lint.xml") lintConfig file("lint.xml")
} }
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
} }
static def gitRevision() { static def gitRevision() {
@ -87,7 +86,7 @@ static def gitRevisionDate() {
dependencies { dependencies {
def arrow_version = "0.8.0" def arrow_version = "0.8.0"
def support_version = '1.1.0-beta01' def support_version = '1.1.0-alpha03'
def moshi_version = '1.8.0' def moshi_version = '1.8.0'
def lifecycle_version = '2.0.0' def lifecycle_version = '2.0.0'
def coroutines_version = "1.0.1" def coroutines_version = "1.0.1"
@ -99,16 +98,16 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
implementation "androidx.appcompat:appcompat:1.1.0-rc01" implementation "androidx.appcompat:appcompat:$support_version"
implementation "androidx.recyclerview:recyclerview:1.1.0-beta01" implementation "androidx.recyclerview:recyclerview:$support_version"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
// Network // Network
implementation 'com.squareup.retrofit2:retrofit:2.6.0' implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-moshi:2.4.0' implementation 'com.squareup.retrofit2:converter-moshi:2.4.0'
implementation 'com.squareup.okhttp3:okhttp:3.14.1' implementation 'com.squareup.okhttp3:okhttp:3.11.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0' implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
implementation 'com.novoda:merlin:1.1.6' implementation 'com.novoda:merlin:1.1.6'
implementation "com.squareup.moshi:moshi-adapters:$moshi_version" implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
@ -121,7 +120,7 @@ dependencies {
kapt 'dk.ilios:realmfieldnameshelper:1.1.1' kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
// Work // Work
implementation "androidx.work:work-runtime-ktx:2.1.0-rc01" implementation "androidx.work:work-runtime-ktx:2.1.0-beta01"
// FP // FP
implementation "io.arrow-kt:arrow-core:$arrow_version" implementation "io.arrow-kt:arrow-core:$arrow_version"
@ -149,16 +148,18 @@ dependencies {
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
testImplementation 'org.robolectric:robolectric:4.0.2' testImplementation 'org.robolectric:robolectric:4.0.2'
testImplementation "org.koin:koin-test:$koin_version"
//testImplementation 'org.robolectric:shadows-support-v4:3.0' //testImplementation 'org.robolectric:shadows-support-v4:3.0'
testImplementation "io.mockk:mockk:1.8.13.kotlin13" testImplementation "io.mockk:mockk:1.8.13.kotlin13"
testImplementation 'org.amshove.kluent:kluent-android:1.44' testImplementation 'org.amshove.kluent:kluent-android:1.44'
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
androidTestImplementation 'androidx.test:core:1.2.0' androidTestImplementation "org.koin:koin-test:$koin_version"
androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:core:1.1.0'
androidTestImplementation 'androidx.test:rules:1.2.0' androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test:rules:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
androidTestImplementation 'org.amshove.kluent:kluent-android:1.44' androidTestImplementation 'org.amshove.kluent:kluent-android:1.44'
androidTestImplementation "io.mockk:mockk-android:1.8.13.kotlin13" androidTestImplementation "io.mockk:mockk-android:1.8.13.kotlin13"
androidTestImplementation "androidx.arch.core:core-testing:$lifecycle_version" androidTestImplementation "androidx.arch.core:core-testing:$lifecycle_version"

View File

@ -19,4 +19,4 @@ package im.vector.matrix.android
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main, Main) internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main)

View File

@ -19,18 +19,25 @@ package im.vector.matrix.android.auth
import androidx.test.annotation.UiThreadTest import androidx.test.annotation.UiThreadTest
import androidx.test.rule.GrantPermissionRule import androidx.test.rule.GrantPermissionRule
import androidx.test.runner.AndroidJUnit4 import androidx.test.runner.AndroidJUnit4
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.InstrumentedTest import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.OkReplayRuleChainNoActivity import im.vector.matrix.android.OkReplayRuleChainNoActivity
import im.vector.matrix.android.api.auth.Authenticator import im.vector.matrix.android.api.auth.Authenticator
import im.vector.matrix.android.internal.auth.AuthModule
import im.vector.matrix.android.internal.di.MatrixModule
import im.vector.matrix.android.internal.di.NetworkModule
import okreplay.* import okreplay.*
import org.junit.ClassRule import org.junit.ClassRule
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.koin.standalone.StandAloneContext.loadKoinModules
import org.koin.standalone.inject
import org.koin.test.KoinTest
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
internal class AuthenticatorTest : InstrumentedTest { internal class AuthenticatorTest : InstrumentedTest, KoinTest {
lateinit var authenticator: Authenticator lateinit var authenticator: Authenticator
lateinit var okReplayInterceptor: OkReplayInterceptor lateinit var okReplayInterceptor: OkReplayInterceptor

View File

@ -52,7 +52,7 @@ internal class JsonCanonicalizerTest : InstrumentedTest {
@Test @Test
fun realSampleTest() { fun realSampleTest() {
assertEquals("""{"algorithms":["m.megolm.v1.aes-sha2","m.olm.v1.curve25519-aes-sha2"],"device_id":"VSCUNFSOUI","keys":{"curve25519:VSCUNFSOUI":"utyOjnhiQ73qNhi9HlN0OgWIowe5gthTS8r0r9TcJ3o","ed25519:VSCUNFSOUI":"qNhEt+Yggaajet0hX\/FjTRLfySgs65ldYyomm7PIx6U"},"user_id":"@benoitx:matrix.org"}""", assertEquals("""{"algorithms":["m.megolm.v1.aes-sha2","m.olm.v1.curve25519-aes-sha2"],"device_id":"VSCUNFSOUI","keys":{"curve25519:VSCUNFSOUI":"utyOjnhiQ73qNhi9HlN0OgWIowe5gthTS8r0r9TcJ3o","ed25519:VSCUNFSOUI":"qNhEt+Yggaajet0hX/FjTRLfySgs65ldYyomm7PIx6U"},"user_id":"@benoitx:matrix.org"}""",
JsonCanonicalizer.canonicalize("""{"algorithms":["m.megolm.v1.aes-sha2","m.olm.v1.curve25519-aes-sha2"],"device_id":"VSCUNFSOUI","user_id":"@benoitx:matrix.org","keys":{"curve25519:VSCUNFSOUI":"utyOjnhiQ73qNhi9HlN0OgWIowe5gthTS8r0r9TcJ3o","ed25519:VSCUNFSOUI":"qNhEt+Yggaajet0hX/FjTRLfySgs65ldYyomm7PIx6U"}}""")) JsonCanonicalizer.canonicalize("""{"algorithms":["m.megolm.v1.aes-sha2","m.olm.v1.curve25519-aes-sha2"],"device_id":"VSCUNFSOUI","user_id":"@benoitx:matrix.org","keys":{"curve25519:VSCUNFSOUI":"utyOjnhiQ73qNhi9HlN0OgWIowe5gthTS8r0r9TcJ3o","ed25519:VSCUNFSOUI":"qNhEt+Yggaajet0hX/FjTRLfySgs65ldYyomm7PIx6U"}}"""))
} }

View File

@ -59,7 +59,7 @@ internal class ChunkEntityTest : InstrumentedTest {
val chunk: ChunkEntity = realm.createObject() val chunk: ChunkEntity = realm.createObject()
val fakeEvent = createFakeMessageEvent() val fakeEvent = createFakeMessageEvent()
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS) chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
chunk.timelineEvents.size shouldEqual 1 chunk.events.size shouldEqual 1
} }
} }
@ -70,7 +70,7 @@ internal class ChunkEntityTest : InstrumentedTest {
val fakeEvent = createFakeMessageEvent() val fakeEvent = createFakeMessageEvent()
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS) chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS) chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
chunk.timelineEvents.size shouldEqual 1 chunk.events.size shouldEqual 1
} }
} }
@ -126,7 +126,7 @@ internal class ChunkEntityTest : InstrumentedTest {
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS) chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS) chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS) chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
chunk1.timelineEvents.size shouldEqual 60 chunk1.events.size shouldEqual 60
} }
} }
@ -142,7 +142,7 @@ internal class ChunkEntityTest : InstrumentedTest {
chunk1.addAll("roomId", eventsForChunk1, PaginationDirection.FORWARDS) chunk1.addAll("roomId", eventsForChunk1, PaginationDirection.FORWARDS)
chunk2.addAll("roomId", eventsForChunk2, PaginationDirection.BACKWARDS) chunk2.addAll("roomId", eventsForChunk2, PaginationDirection.BACKWARDS)
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS) chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
chunk1.timelineEvents.size shouldEqual 40 chunk1.events.size shouldEqual 40
chunk1.isLastForward.shouldBeTrue() chunk1.isLastForward.shouldBeTrue()
} }
} }

View File

@ -18,6 +18,25 @@ package im.vector.matrix.android.session.room.timeline
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.InstrumentedTest import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.api.session.crypto.CryptoService
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.internal.crypto.CryptoManager
import im.vector.matrix.android.internal.database.model.SessionRealmModule
import im.vector.matrix.android.internal.session.room.EventRelationExtractor
import im.vector.matrix.android.internal.session.room.membership.SenderRoomMemberExtractor
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimeline
import im.vector.matrix.android.internal.session.room.timeline.TimelineEventFactory
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.testCoroutineDispatchers
import io.realm.Realm
import io.realm.RealmConfiguration
import org.amshove.kluent.shouldEqual
import org.junit.Before
import org.junit.Test
import timber.log.Timber
import java.util.concurrent.CountDownLatch
internal class TimelineTest : InstrumentedTest { internal class TimelineTest : InstrumentedTest {

View File

@ -87,8 +87,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
val matrixConfiguration = (appContext as MatrixConfiguration.Provider).providesMatrixConfiguration() val matrixConfiguration = (appContext as MatrixConfiguration.Provider).providesMatrixConfiguration()
instance = Matrix(appContext, matrixConfiguration) instance = Matrix(appContext, matrixConfiguration)
} else { } else {
throw IllegalStateException("Matrix is not initialized properly." + throw IllegalStateException("Matrix is not initialized properly. You should call Matrix.initialize or let your application implements MatrixConfiguration.Provider.")
" You should call Matrix.initialize or let your application implements MatrixConfiguration.Provider.")
} }
} }
return instance return instance

View File

@ -16,6 +16,7 @@
package im.vector.matrix.android.api package im.vector.matrix.android.api
import java.util.regex.Pattern
/** /**
* This class contains pattern to match the different Matrix ids * This class contains pattern to match the different Matrix ids
@ -28,31 +29,31 @@ object MatrixPatterns {
// regex pattern to find matrix user ids in a string. // regex pattern to find matrix user ids in a string.
// See https://matrix.org/speculator/spec/HEAD/appendices.html#historical-user-ids // See https://matrix.org/speculator/spec/HEAD/appendices.html#historical-user-ids
private const val MATRIX_USER_IDENTIFIER_REGEX = "@[A-Z0-9\\x21-\\x39\\x3B-\\x7F]+$DOMAIN_REGEX" private const val MATRIX_USER_IDENTIFIER_REGEX = "@[A-Z0-9\\x21-\\x39\\x3B-\\x7F]+$DOMAIN_REGEX"
private val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE) private val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = Pattern.compile(MATRIX_USER_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE)
// regex pattern to find room ids in a string. // regex pattern to find room ids in a string.
private const val MATRIX_ROOM_IDENTIFIER_REGEX = "![A-Z0-9]+$DOMAIN_REGEX" private const val MATRIX_ROOM_IDENTIFIER_REGEX = "![A-Z0-9]+$DOMAIN_REGEX"
private val PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER = MATRIX_ROOM_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE) private val PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER = Pattern.compile(MATRIX_ROOM_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE)
// regex pattern to find room aliases in a string. // regex pattern to find room aliases in a string.
private const val MATRIX_ROOM_ALIAS_REGEX = "#[A-Z0-9._%#@=+-]+$DOMAIN_REGEX" private const val MATRIX_ROOM_ALIAS_REGEX = "#[A-Z0-9._%#@=+-]+$DOMAIN_REGEX"
private val PATTERN_CONTAIN_MATRIX_ALIAS = MATRIX_ROOM_ALIAS_REGEX.toRegex(RegexOption.IGNORE_CASE) private val PATTERN_CONTAIN_MATRIX_ALIAS = Pattern.compile(MATRIX_ROOM_ALIAS_REGEX, Pattern.CASE_INSENSITIVE)
// regex pattern to find message ids in a string. // regex pattern to find message ids in a string.
private const val MATRIX_EVENT_IDENTIFIER_REGEX = "\\$[A-Z0-9]+$DOMAIN_REGEX" private const val MATRIX_EVENT_IDENTIFIER_REGEX = "\\$[A-Z0-9]+$DOMAIN_REGEX"
private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER = MATRIX_EVENT_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE) private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER = Pattern.compile(MATRIX_EVENT_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE)
// regex pattern to find message ids in a string. // regex pattern to find message ids in a string.
private const val MATRIX_EVENT_IDENTIFIER_V3_REGEX = "\\$[A-Z0-9/+]+" private const val MATRIX_EVENT_IDENTIFIER_V3_REGEX = "\\$[A-Z0-9/+]+"
private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 = MATRIX_EVENT_IDENTIFIER_V3_REGEX.toRegex(RegexOption.IGNORE_CASE) private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 = Pattern.compile(MATRIX_EVENT_IDENTIFIER_V3_REGEX, Pattern.CASE_INSENSITIVE)
// Ref: https://matrix.org/docs/spec/rooms/v4#event-ids // Ref: https://matrix.org/docs/spec/rooms/v4#event-ids
private const val MATRIX_EVENT_IDENTIFIER_V4_REGEX = "\\$[A-Z0-9\\-_]+" private const val MATRIX_EVENT_IDENTIFIER_V4_REGEX = "\\$[A-Z0-9\\-_]+"
private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4 = MATRIX_EVENT_IDENTIFIER_V4_REGEX.toRegex(RegexOption.IGNORE_CASE) private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4 = Pattern.compile(MATRIX_EVENT_IDENTIFIER_V4_REGEX, Pattern.CASE_INSENSITIVE)
// regex pattern to find group ids in a string. // regex pattern to find group ids in a string.
private const val MATRIX_GROUP_IDENTIFIER_REGEX = "\\+[A-Z0-9=_\\-./]+$DOMAIN_REGEX" private const val MATRIX_GROUP_IDENTIFIER_REGEX = "\\+[A-Z0-9=_\\-./]+$DOMAIN_REGEX"
private val PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER = MATRIX_GROUP_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE) private val PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER = Pattern.compile(MATRIX_GROUP_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE)
// regex pattern to find permalink with message id. // regex pattern to find permalink with message id.
// Android does not support in URL so extract it. // Android does not support in URL so extract it.
@ -61,16 +62,16 @@ object MatrixPatterns {
const val SEP_REGEX = "/" const val SEP_REGEX = "/"
private const val LINK_TO_ROOM_ID_REGEXP = PERMALINK_BASE_REGEX + MATRIX_ROOM_IDENTIFIER_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX private const val LINK_TO_ROOM_ID_REGEXP = PERMALINK_BASE_REGEX + MATRIX_ROOM_IDENTIFIER_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
private val PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ID = LINK_TO_ROOM_ID_REGEXP.toRegex(RegexOption.IGNORE_CASE) private val PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ID = Pattern.compile(LINK_TO_ROOM_ID_REGEXP, Pattern.CASE_INSENSITIVE)
private const val LINK_TO_ROOM_ALIAS_REGEXP = PERMALINK_BASE_REGEX + MATRIX_ROOM_ALIAS_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX private const val LINK_TO_ROOM_ALIAS_REGEXP = PERMALINK_BASE_REGEX + MATRIX_ROOM_ALIAS_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
private val PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ALIAS = LINK_TO_ROOM_ALIAS_REGEXP.toRegex(RegexOption.IGNORE_CASE) private val PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ALIAS = Pattern.compile(LINK_TO_ROOM_ALIAS_REGEXP, Pattern.CASE_INSENSITIVE)
private const val LINK_TO_APP_ROOM_ID_REGEXP = APP_BASE_REGEX + MATRIX_ROOM_IDENTIFIER_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX private const val LINK_TO_APP_ROOM_ID_REGEXP = APP_BASE_REGEX + MATRIX_ROOM_IDENTIFIER_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
private val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ID = LINK_TO_APP_ROOM_ID_REGEXP.toRegex(RegexOption.IGNORE_CASE) private val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ID = Pattern.compile(LINK_TO_APP_ROOM_ID_REGEXP, Pattern.CASE_INSENSITIVE)
private const val LINK_TO_APP_ROOM_ALIAS_REGEXP = APP_BASE_REGEX + MATRIX_ROOM_ALIAS_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX private const val LINK_TO_APP_ROOM_ALIAS_REGEXP = APP_BASE_REGEX + MATRIX_ROOM_ALIAS_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
private val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ALIAS = LINK_TO_APP_ROOM_ALIAS_REGEXP.toRegex(RegexOption.IGNORE_CASE) private val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ALIAS = Pattern.compile(LINK_TO_APP_ROOM_ALIAS_REGEXP, Pattern.CASE_INSENSITIVE)
// list of patterns to find some matrix item. // list of patterns to find some matrix item.
val MATRIX_PATTERNS = listOf( val MATRIX_PATTERNS = listOf(
@ -92,7 +93,7 @@ object MatrixPatterns {
* @return true if the string is a valid user id * @return true if the string is a valid user id
*/ */
fun isUserId(str: String?): Boolean { fun isUserId(str: String?): Boolean {
return str != null && str matches PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER return str != null && PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER.matcher(str).matches()
} }
/** /**
@ -102,7 +103,7 @@ object MatrixPatterns {
* @return true if the string is a valid room Id * @return true if the string is a valid room Id
*/ */
fun isRoomId(str: String?): Boolean { fun isRoomId(str: String?): Boolean {
return str != null && str matches PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER return str != null && PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER.matcher(str).matches()
} }
/** /**
@ -112,7 +113,7 @@ object MatrixPatterns {
* @return true if the string is a valid room alias. * @return true if the string is a valid room alias.
*/ */
fun isRoomAlias(str: String?): Boolean { fun isRoomAlias(str: String?): Boolean {
return str != null && str matches PATTERN_CONTAIN_MATRIX_ALIAS return str != null && PATTERN_CONTAIN_MATRIX_ALIAS.matcher(str).matches()
} }
/** /**
@ -123,9 +124,9 @@ object MatrixPatterns {
*/ */
fun isEventId(str: String?): Boolean { fun isEventId(str: String?): Boolean {
return str != null return str != null
&& (str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER && (PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER.matcher(str).matches()
|| str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 || PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3.matcher(str).matches()
|| str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4) || PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4.matcher(str).matches())
} }
/** /**
@ -135,6 +136,6 @@ object MatrixPatterns {
* @return true if the string is a valid group id. * @return true if the string is a valid group id.
*/ */
fun isGroupId(str: String?): Boolean { fun isGroupId(str: String?): Boolean {
return str != null && str matches PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER return str != null && PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER.matcher(str).matches()
} }
} }

View File

@ -36,15 +36,17 @@ interface Authenticator {
*/ */
fun authenticate(homeServerConnectionConfig: HomeServerConnectionConfig, login: String, password: String, callback: MatrixCallback<Session>): Cancelable fun authenticate(homeServerConnectionConfig: HomeServerConnectionConfig, login: String, password: String, callback: MatrixCallback<Session>): Cancelable
//TODO remove this method. Shouldn't be managed like that.
/** /**
* Check if there is an authenticated [Session]. * Check if there is an active [Session].
* @return true if there is at least one active session. * @return true if there is at least one active session.
*/ */
fun hasAuthenticatedSessions(): Boolean fun hasAuthenticatedSessions(): Boolean
//TODO remove this method. Shouldn't be managed like that.
/** /**
* Get the last authenticated [Session], if there is an active session. * Get the last active [Session], if there is an active session.
* @return the last active session if any, or null * @return the lastActive session if any, or null
*/ */
fun getLastAuthenticatedSession(): Session? fun getLastAuthenticatedSession(): Session?
@ -56,4 +58,7 @@ interface Authenticator {
* @return the associated session if any, or null * @return the associated session if any, or null
*/ */
fun getSession(sessionParams: SessionParams): Session? fun getSession(sessionParams: SessionParams): Session?
} }

View File

@ -38,7 +38,7 @@ sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
data class RegistrationFlowError(val registrationFlowResponse: RegistrationFlowResponse) : Failure(RuntimeException(registrationFlowResponse.toString())) data class RegistrationFlowError(val registrationFlowResponse: RegistrationFlowResponse) : Failure(RuntimeException(registrationFlowResponse.toString()))
data class CryptoError(val error: MXCryptoError) : Failure(error) data class CryptoError(val error: MXCryptoError) : Failure(RuntimeException(error.toString()))
abstract class FeatureFailure : Failure() abstract class FeatureFailure : Failure()

View File

@ -37,13 +37,15 @@ object MatrixLinkify {
} }
val text = spannable.toString() val text = spannable.toString()
var hasMatch = false var hasMatch = false
for (pattern in MatrixPatterns.MATRIX_PATTERNS) { for (index in MatrixPatterns.MATRIX_PATTERNS.indices) {
for (match in pattern.findAll(spannable)) { val pattern = MatrixPatterns.MATRIX_PATTERNS[index]
val matcher = pattern.matcher(spannable)
while (matcher.find()) {
hasMatch = true hasMatch = true
val startPos = match.range.first val startPos = matcher.start(0)
if (startPos == 0 || text[startPos - 1] != '/') { if (startPos == 0 || text[startPos - 1] != '/') {
val endPos = match.range.last val endPos = matcher.end(0)
val url = text.substring(match.range) val url = text.substring(matcher.start(0), matcher.end(0))
val span = MatrixPermalinkSpan(url, callback) val span = MatrixPermalinkSpan(url, callback)
spannable.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) spannable.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
} }

View File

@ -20,6 +20,7 @@ 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.EventType
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import timber.log.Timber import timber.log.Timber
import java.util.regex.Pattern import java.util.regex.Pattern
@ -34,15 +35,15 @@ class ContainsDisplayNameCondition : Condition(Kind.contains_display_name) {
} }
fun isSatisfied(event: Event, displayName: String): Boolean { fun isSatisfied(event: Event, displayName: String): Boolean {
//TODO the spec says:
// Matches any message whose content is unencrypted and contains the user's current display name
var message = when (event.type) { var message = when (event.type) {
EventType.MESSAGE -> { EventType.MESSAGE -> {
event.content.toModel<MessageContent>() event.content.toModel<MessageContent>()
} }
//TODO the spec says: // EventType.ENCRYPTED -> {
// Matches any message whose content is unencrypted and contains the user's current display name // event.root.getClearContent()?.toModel<MessageContent>()
// EventType.ENCRYPTED -> { // }
// event.root.getClearContent()?.toModel<MessageContent>()
// }
else -> null else -> null
} ?: return false } ?: return false

View File

@ -40,8 +40,7 @@ data class PushCondition(
/** /**
* Required for room_member_count conditions. * Required for room_member_count conditions.
* A decimal integer optionally prefixed by one of, ==, <, >, >= or <=. * A decimal integer optionally prefixed by one of, ==, <, >, >= or <=.
* A prefix of < matches rooms where the member count is strictly less than the given number and so forth. * A prefix of < matches rooms where the member count is strictly less than the given number and so forth. If no prefix is present, this parameter defaults to ==.
* If no prefix is present, this parameter defaults to ==.
*/ */
@Json(name = "is") val iz: String? = null @Json(name = "is") val iz: String? = null
) { ) {

View File

@ -24,7 +24,6 @@ import im.vector.matrix.android.api.session.cache.CacheService
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.matrix.android.api.session.content.ContentUrlResolver
import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.file.FileService
import im.vector.matrix.android.api.session.group.GroupService import im.vector.matrix.android.api.session.group.GroupService
import im.vector.matrix.android.api.session.pushers.PushersService import im.vector.matrix.android.api.session.pushers.PushersService
import im.vector.matrix.android.api.session.room.RoomDirectoryService import im.vector.matrix.android.api.session.room.RoomDirectoryService
@ -47,17 +46,15 @@ interface Session :
CacheService, CacheService,
SignOutService, SignOutService,
FilterService, FilterService,
FileService,
PushRuleService, PushRuleService,
PushersService, PushersService {
InitialSyncProgressService {
/** /**
* The params associated to the session * The params associated to the session
*/ */
val sessionParams: SessionParams val sessionParams: SessionParams
val myUserId: String val myUserId : String
get() = sessionParams.credentials.userId get() = sessionParams.credentials.userId

View File

@ -28,11 +28,10 @@ interface ContentUploadStateTracker {
sealed class State { sealed class State {
object Idle : State() object Idle : State()
object EncryptingThumbnail : State() data class ProgressData(val current: Long, val total: Long) : State()
data class UploadingThumbnail(val current: Long, val total: Long) : State()
object Encrypting : State()
data class Uploading(val current: Long, val total: Long) : State()
object Success : State() object Success : State()
data class Failure(val throwable: Throwable) : State() object Failure : State()
} }
} }

View File

@ -26,14 +26,12 @@ import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.crypto.NewSessionListener import im.vector.matrix.android.internal.crypto.NewSessionListener
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult 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.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import java.io.File
interface CryptoService { interface CryptoService {
@ -87,8 +85,6 @@ interface CryptoService {
fun addRoomKeysRequestListener(listener: RoomKeysRequestListener) fun addRoomKeysRequestListener(listener: RoomKeysRequestListener)
fun removeRoomKeysRequestListener(listener: RoomKeysRequestListener)
fun getDevicesList(callback: MatrixCallback<DevicesListResponse>) fun getDevicesList(callback: MatrixCallback<DevicesListResponse>)
fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int
@ -100,10 +96,9 @@ interface CryptoService {
roomId: String, roomId: String,
callback: MatrixCallback<MXEncryptEventContentResult>) callback: MatrixCallback<MXEncryptEventContentResult>)
@Throws(MXCryptoError::class) fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult?
fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>) fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult?>)
fun getEncryptionAlgorithm(roomId: String): String? fun getEncryptionAlgorithm(roomId: String): String?

View File

@ -18,65 +18,106 @@
package im.vector.matrix.android.api.session.crypto package im.vector.matrix.android.api.session.crypto
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import android.text.TextUtils
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import org.matrix.olm.OlmException
/** /**
* Represents a crypto error response. * Represents a crypto error response.
*/ */
sealed class MXCryptoError : Throwable() { class MXCryptoError(var code: String,
var message: String) {
data class Base(val errorType: ErrorType,
val technicalMessage: String,
/** /**
* Describe the error with more details * Describe the error with more details
*/ */
val detailedErrorDescription: String? = null) : MXCryptoError() private var mDetailedErrorDescription: String? = null
data class OlmError(val olmException: OlmException) : MXCryptoError() /**
* Data exception.
* Some exceptions provide some data to describe the exception
*/
var mExceptionData: Any? = null
data class UnknownDevice(val deviceList: MXUsersDevicesMap<MXDeviceInfo>) : MXCryptoError() /**
* @return true if the current error is an olm one.
*/
val isOlmError: Boolean
get() = OLM_ERROR_CODE == code
enum class ErrorType {
ENCRYPTING_NOT_ENABLED, /**
UNABLE_TO_ENCRYPT, * @return the detailed error description
UNABLE_TO_DECRYPT, */
UNKNOWN_INBOUND_SESSION_ID, val detailedErrorDescription: String?
INBOUND_SESSION_MISMATCH_ROOM_ID, get() = if (TextUtils.isEmpty(mDetailedErrorDescription)) {
MISSING_FIELDS, message
BAD_EVENT_FORMAT, } else mDetailedErrorDescription
MISSING_SENDER_KEY,
MISSING_CIPHER_TEXT, /**
BAD_DECRYPTED_FORMAT, * Create a crypto error
NOT_INCLUDE_IN_RECIPIENTS, *
BAD_RECIPIENT, * @param code the error code (see XX_ERROR_CODE)
BAD_RECIPIENT_KEY, * @param shortErrorDescription the short error description
FORWARDED_MESSAGE, * @param detailedErrorDescription the detailed error description
BAD_ROOM, */
BAD_ENCRYPTED_MESSAGE, constructor(code: String, shortErrorDescription: String, detailedErrorDescription: String?) : this(code, shortErrorDescription) {
DUPLICATED_MESSAGE_INDEX, mDetailedErrorDescription = detailedErrorDescription
MISSING_PROPERTY, }
OLM,
UNKNOWN_DEVICES, /**
UNKNOWN_MESSAGE_INDEX * Create a crypto error
*
* @param code the error code (see XX_ERROR_CODE)
* @param shortErrorDescription the short error description
* @param detailedErrorDescription the detailed error description
* @param exceptionData the exception data
*/
constructor(code: String, shortErrorDescription: String, detailedErrorDescription: String?, exceptionData: Any) : this(code, shortErrorDescription) {
mDetailedErrorDescription = detailedErrorDescription
mExceptionData = exceptionData
} }
companion object { companion object {
/** /**
* Resource for technicalMessage * Error codes
*/ */
const val ENCRYPTING_NOT_ENABLED_ERROR_CODE = "ENCRYPTING_NOT_ENABLED"
const val UNABLE_TO_ENCRYPT_ERROR_CODE = "UNABLE_TO_ENCRYPT"
const val UNABLE_TO_DECRYPT_ERROR_CODE = "UNABLE_TO_DECRYPT"
const val UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE = "UNKNOWN_INBOUND_SESSION_ID"
const val INBOUND_SESSION_MISMATCH_ROOM_ID_ERROR_CODE = "INBOUND_SESSION_MISMATCH_ROOM_ID"
const val MISSING_FIELDS_ERROR_CODE = "MISSING_FIELDS"
const val MISSING_CIPHER_TEXT_ERROR_CODE = "MISSING_CIPHER_TEXT"
const val NOT_INCLUDE_IN_RECIPIENTS_ERROR_CODE = "NOT_INCLUDE_IN_RECIPIENTS"
const val BAD_RECIPIENT_ERROR_CODE = "BAD_RECIPIENT"
const val BAD_RECIPIENT_KEY_ERROR_CODE = "BAD_RECIPIENT_KEY"
const val FORWARDED_MESSAGE_ERROR_CODE = "FORWARDED_MESSAGE"
const val BAD_ROOM_ERROR_CODE = "BAD_ROOM"
const val BAD_ENCRYPTED_MESSAGE_ERROR_CODE = "BAD_ENCRYPTED_MESSAGE"
const val DUPLICATED_MESSAGE_INDEX_ERROR_CODE = "DUPLICATED_MESSAGE_INDEX"
const val MISSING_PROPERTY_ERROR_CODE = "MISSING_PROPERTY"
const val OLM_ERROR_CODE = "OLM_ERROR_CODE"
const val UNKNOWN_DEVICES_CODE = "UNKNOWN_DEVICES_CODE"
const val UNKNOWN_MESSAGE_INDEX = "UNKNOWN_MESSAGE_INDEX"
/**
* short error reasons
*/
const val UNABLE_TO_DECRYPT = "Unable to decrypt"
const val UNABLE_TO_ENCRYPT = "Unable to encrypt"
/**
* Detailed error reasons
*/
const val ENCRYPTING_NOT_ENABLED_REASON = "Encryption not enabled"
const val UNABLE_TO_ENCRYPT_REASON = "Unable to encrypt %s" const val UNABLE_TO_ENCRYPT_REASON = "Unable to encrypt %s"
const val UNABLE_TO_DECRYPT_REASON = "Unable to decrypt %1\$s. Algorithm: %2\$s" const val UNABLE_TO_DECRYPT_REASON = "Unable to decrypt %1\$s. Algorithm: %2\$s"
const val OLM_REASON = "OLM error: %1\$s" const val OLM_REASON = "OLM error: %1\$s"
const val DETAILED_OLM_REASON = "Unable to decrypt %1\$s. OLM error: %2\$s" const val DETAILLED_OLM_REASON = "Unable to decrypt %1\$s. OLM error: %2\$s"
const val UNKNOWN_INBOUND_SESSION_ID_REASON = "Unknown inbound session id" const val UNKNOWN_INBOUND_SESSION_ID_REASON = "Unknown inbound session id"
const val INBOUND_SESSION_MISMATCH_ROOM_ID_REASON = "Mismatched room_id for inbound group session (expected %1\$s, was %2\$s)" const val INBOUND_SESSION_MISMATCH_ROOM_ID_REASON = "Mismatched room_id for inbound group session (expected %1\$s, was %2\$s)"
const val MISSING_FIELDS_REASON = "Missing fields in input" const val MISSING_FIELDS_REASON = "Missing fields in input"
const val BAD_EVENT_FORMAT_TEXT_REASON = "Bad event format"
const val MISSING_SENDER_KEY_TEXT_REASON = "Missing senderKey"
const val MISSING_CIPHER_TEXT_REASON = "Missing ciphertext" const val MISSING_CIPHER_TEXT_REASON = "Missing ciphertext"
const val BAD_DECRYPTED_FORMAT_TEXT_REASON = "Bad decrypted event format"
const val NOT_INCLUDED_IN_RECIPIENT_REASON = "Not included in recipients" const val NOT_INCLUDED_IN_RECIPIENT_REASON = "Not included in recipients"
const val BAD_RECIPIENT_REASON = "Message was intended for %1\$s" const val BAD_RECIPIENT_REASON = "Message was intended for %1\$s"
const val BAD_RECIPIENT_KEY_REASON = "Message not intended for this device" const val BAD_RECIPIENT_KEY_REASON = "Message not intended for this device"
@ -85,9 +126,7 @@ sealed class MXCryptoError : Throwable() {
const val BAD_ENCRYPTED_MESSAGE_REASON = "Bad Encrypted Message" const val BAD_ENCRYPTED_MESSAGE_REASON = "Bad Encrypted Message"
const val DUPLICATE_MESSAGE_INDEX_REASON = "Duplicate message index, possible replay attack %1\$s" const val DUPLICATE_MESSAGE_INDEX_REASON = "Duplicate message index, possible replay attack %1\$s"
const val ERROR_MISSING_PROPERTY_REASON = "No '%1\$s' property. Cannot prevent unknown-key attack" const val ERROR_MISSING_PROPERTY_REASON = "No '%1\$s' property. Cannot prevent unknown-key attack"
const val UNKNOWN_DEVICES_REASON = "This room contains unknown devices which have not been verified.\n" + const val UNKNOWN_DEVICES_REASON = "This room contains unknown devices which have not been verified.\n" + "We strongly recommend you verify them before continuing."
"We strongly recommend you verify them before continuing." const val NO_MORE_ALGORITHM_REASON = "Room was previously configured to use encryption, but is no longer." + " Perhaps the homeserver is hiding the configuration event."
const val NO_MORE_ALGORITHM_REASON = "Room was previously configured to use encryption, but is no longer." +
" Perhaps the homeserver is hiding the configuration event."
} }
} }

View File

@ -40,8 +40,7 @@ interface KeysBackupService {
* @param keysBackupCreationInfo the info object from [prepareKeysBackupVersion]. * @param keysBackupCreationInfo the info object from [prepareKeysBackupVersion].
* @param callback Asynchronous callback * @param callback Asynchronous callback
*/ */
fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo, fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo, callback: MatrixCallback<KeysVersion>)
callback: MatrixCallback<KeysVersion>)
/** /**
* Facility method to get the total number of locally stored keys * Facility method to get the total number of locally stored keys
@ -59,8 +58,7 @@ interface KeysBackupService {
* @param progressListener the callback to follow the progress * @param progressListener the callback to follow the progress
* @param callback the main callback * @param callback the main callback
*/ */
fun backupAllGroupSessions(progressListener: ProgressListener?, fun backupAllGroupSessions(progressListener: ProgressListener?, callback: MatrixCallback<Unit>?)
callback: MatrixCallback<Unit>?)
/** /**
* Check trust on a key backup version. * Check trust on a key backup version.
@ -68,8 +66,7 @@ interface KeysBackupService {
* @param keysBackupVersion the backup version to check. * @param keysBackupVersion the backup version to check.
* @param callback block called when the operations completes. * @param callback block called when the operations completes.
*/ */
fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult, fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult, callback: MatrixCallback<KeysBackupVersionTrust>)
callback: MatrixCallback<KeysBackupVersionTrust>)
/** /**
* Return the current progress of the backup * Return the current progress of the backup
@ -83,8 +80,7 @@ interface KeysBackupService {
* @param version the backup version * @param version the backup version
* @param callback * @param callback
*/ */
fun getVersion(version: String, fun getVersion(version: String, callback: MatrixCallback<KeysVersionResult?>)
callback: MatrixCallback<KeysVersionResult?>)
/** /**
* This method fetches the last backup version on the server, then compare to the currently backup version use. * This method fetches the last backup version on the server, then compare to the currently backup version use.
@ -118,9 +114,7 @@ interface KeysBackupService {
* @param progressListener a progress listener, as generating private key from password may take a while * @param progressListener a progress listener, as generating private key from password may take a while
* @param callback Asynchronous callback * @param callback Asynchronous callback
*/ */
fun prepareKeysBackupVersion(password: String?, fun prepareKeysBackupVersion(password: String?, progressListener: ProgressListener?, callback: MatrixCallback<MegolmBackupCreationInfo>)
progressListener: ProgressListener?,
callback: MatrixCallback<MegolmBackupCreationInfo>)
/** /**
* Delete a keys backup version. It will delete all backed up keys on the server, and the backup itself. * Delete a keys backup version. It will delete all backed up keys on the server, and the backup itself.
@ -129,8 +123,7 @@ interface KeysBackupService {
* @param version the backup version to delete. * @param version the backup version to delete.
* @param callback Asynchronous callback * @param callback Asynchronous callback
*/ */
fun deleteBackup(version: String, fun deleteBackup(version: String, callback: MatrixCallback<Unit>?)
callback: MatrixCallback<Unit>?)
/** /**
* Ask if the backup on the server contains keys that we may do not have locally. * Ask if the backup on the server contains keys that we may do not have locally.
@ -146,9 +139,7 @@ interface KeysBackupService {
* @param trust the trust to set to the keys backup. * @param trust the trust to set to the keys backup.
* @param callback block called when the operations completes. * @param callback block called when the operations completes.
*/ */
fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult, fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult, trust: Boolean, callback: MatrixCallback<Unit>)
trust: Boolean,
callback: MatrixCallback<Unit>)
/** /**
* Set trust on a keys backup version. * Set trust on a keys backup version.
@ -157,9 +148,7 @@ interface KeysBackupService {
* @param recoveryKey the recovery key to challenge with the key backup public key. * @param recoveryKey the recovery key to challenge with the key backup public key.
* @param callback block called when the operations completes. * @param callback block called when the operations completes.
*/ */
fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, recoveryKey: String, callback: MatrixCallback<Unit>)
recoveryKey: String,
callback: MatrixCallback<Unit>)
/** /**
* Set trust on a keys backup version. * Set trust on a keys backup version.
@ -168,9 +157,7 @@ interface KeysBackupService {
* @param password the pass phrase to challenge with the keyBackupVersion public key. * @param password the pass phrase to challenge with the keyBackupVersion public key.
* @param callback block called when the operations completes. * @param callback block called when the operations completes.
*/ */
fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult, fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult, password: String, callback: MatrixCallback<Unit>)
password: String,
callback: MatrixCallback<Unit>)
/** /**
* Restore a backup with a recovery key from a given backup version stored on the homeserver. * Restore a backup with a recovery key from a given backup version stored on the homeserver.
@ -182,11 +169,7 @@ interface KeysBackupService {
* @param stepProgressListener the step progress listener * @param stepProgressListener the step progress listener
* @param callback Callback. It provides the number of found keys and the number of successfully imported keys. * @param callback Callback. It provides the number of found keys and the number of successfully imported keys.
*/ */
fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult, fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult, recoveryKey: String, roomId: String?, sessionId: String?, stepProgressListener: StepProgressListener?, callback: MatrixCallback<ImportRoomKeysResult>)
recoveryKey: String, roomId: String?,
sessionId: String?,
stepProgressListener: StepProgressListener?,
callback: MatrixCallback<ImportRoomKeysResult>)
/** /**
* Restore a backup with a password from a given backup version stored on the homeserver. * Restore a backup with a password from a given backup version stored on the homeserver.
@ -198,12 +181,7 @@ interface KeysBackupService {
* @param stepProgressListener the step progress listener * @param stepProgressListener the step progress listener
* @param callback Callback. It provides the number of found keys and the number of successfully imported keys. * @param callback Callback. It provides the number of found keys and the number of successfully imported keys.
*/ */
fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult, fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult, password: String, roomId: String?, sessionId: String?, stepProgressListener: StepProgressListener?, callback: MatrixCallback<ImportRoomKeysResult>)
password: String,
roomId: String?,
sessionId: String?,
stepProgressListener: StepProgressListener?,
callback: MatrixCallback<ImportRoomKeysResult>)
val keysBackupVersion: KeysVersionResult? val keysBackupVersion: KeysVersionResult?
val currentBackupVersion: String? val currentBackupVersion: String?

View File

@ -21,10 +21,11 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider
import org.json.JSONObject
import timber.log.Timber import timber.log.Timber
import java.util.*
import kotlin.collections.HashMap
typealias Content = JsonDict typealias Content = JsonDict
@ -78,11 +79,6 @@ data class Event(
@Json(name = "redacts") val redacts: String? = null @Json(name = "redacts") val redacts: String? = null
) { ) {
var mxDecryptionResult: OlmDecryptionResult? = null
var mCryptoError: MXCryptoError.ErrorType? = null
/** /**
* Check if event is a state event. * Check if event is a state event.
* @return true if event is state event. * @return true if event is state event.
@ -95,41 +91,41 @@ data class Event(
// Crypto // Crypto
//============================================================================================================== //==============================================================================================================
// /** /**
// * For encrypted events, the plaintext payload for the event. * For encrypted events, the plaintext payload for the event.
// * This is a small MXEvent instance with typically value for `type` and 'content' fields. * This is a small MXEvent instance with typically value for `type` and 'content' fields.
// */ */
// @Transient @Transient
// var mClearEvent: Event? = null var mClearEvent: Event? = null
// private set private set
//
// /** /**
// * Curve25519 key which we believe belongs to the sender of the event. * Curve25519 key which we believe belongs to the sender of the event.
// * See `senderKey` property. * See `senderKey` property.
// */ */
// @Transient @Transient
// private var mSenderCurve25519Key: String? = null private var mSenderCurve25519Key: String? = null
//
// /** /**
// * Ed25519 key which the sender of this event (for olm) or the creator of the megolm session (for megolm) claims to own. * Ed25519 key which the sender of this event (for olm) or the creator of the megolm session (for megolm) claims to own.
// * See `claimedEd25519Key` property. * See `claimedEd25519Key` property.
// */ */
// @Transient @Transient
// private var mClaimedEd25519Key: String? = null private var mClaimedEd25519Key: String? = null
//
// /** /**
// * Curve25519 keys of devices involved in telling us about the senderCurve25519Key and claimedEd25519Key. * Curve25519 keys of devices involved in telling us about the senderCurve25519Key and claimedEd25519Key.
// * See `forwardingCurve25519KeyChain` property. * See `forwardingCurve25519KeyChain` property.
// */ */
// @Transient @Transient
// private var mForwardingCurve25519KeyChain: List<String> = ArrayList() private var mForwardingCurve25519KeyChain: List<String> = ArrayList()
//
// /** /**
// * Decryption error * Decryption error
// */ */
// @Transient @Transient
// var mCryptoError: MXCryptoError? = null var mCryptoError: MXCryptoError? = null
// private set private set
/** /**
* @return true if this event is encrypted. * @return true if this event is encrypted.
@ -144,151 +140,96 @@ data class Event(
* *
* @param decryptionResult the decryption result, including the plaintext and some key info. * @param decryptionResult the decryption result, including the plaintext and some key info.
*/ */
// internal fun setClearData(decryptionResult: MXEventDecryptionResult?) { internal fun setClearData(decryptionResult: MXEventDecryptionResult?) {
// mClearEvent = null mClearEvent = null
// if (decryptionResult != null) { if (decryptionResult != null) {
// if (decryptionResult.clearEvent != null) { if (decryptionResult.clearEvent != null) {
// val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java) val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java)
// mClearEvent = adapter.fromJsonValue(decryptionResult.clearEvent) mClearEvent = adapter.fromJsonValue(decryptionResult.clearEvent)
//
// if (mClearEvent != null) { if (mClearEvent != null) {
// mSenderCurve25519Key = decryptionResult.senderCurve25519Key mSenderCurve25519Key = decryptionResult.senderCurve25519Key
// mClaimedEd25519Key = decryptionResult.claimedEd25519Key mClaimedEd25519Key = decryptionResult.claimedEd25519Key
// mForwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain mForwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain
//
// // For encrypted events with relation, the m.relates_to is kept in clear, so we need to put it back // For encrypted events with relation, the m.relates_to is kept in clear, so we need to put it back
// // in the clear event // in the clear event
// try { try {
// content?.get("m.relates_to")?.let { clearRelates -> content?.get("m.relates_to")?.let { clearRelates ->
// mClearEvent = mClearEvent?.copy( mClearEvent = mClearEvent?.copy(
// content = HashMap(mClearEvent!!.content).apply { content = HashMap(mClearEvent!!.content).apply {
// this["m.relates_to"] = clearRelates this["m.relates_to"] = clearRelates
// } }
// ) )
// } }
// } catch (e: Exception) { } catch (e: Exception) {
// Timber.e(e, "Unable to restore 'm.relates_to' the clear event") Timber.e(e, "Unable to restore 'm.relates_to' the clear event")
// } }
// } }
//
//
// } }
// } }
// mCryptoError = null mCryptoError = null
// } }
/** /**
* @return The curve25519 key that sent this event. * @return The curve25519 key that sent this event.
*/ */
fun getSenderKey(): String? { fun getSenderKey(): String? {
return mxDecryptionResult?.senderKey return mClearEvent?.mSenderCurve25519Key ?: mSenderCurve25519Key
// return mClearEvent?.mSenderCurve25519Key ?: mSenderCurve25519Key
} }
/** /**
* @return The additional keys the sender of this encrypted event claims to possess. * @return The additional keys the sender of this encrypted event claims to possess.
*/ */
fun getKeysClaimed(): Map<String, String> { fun getKeysClaimed(): Map<String, String> {
return mxDecryptionResult?.keysClaimed ?: HashMap() val res = HashMap<String, String>()
// val res = HashMap<String, String>()
// val claimedEd25519Key = if (null != mClearEvent) mClearEvent!!.mClaimedEd25519Key else mClaimedEd25519Key
// val claimedEd25519Key = if (null != mClearEvent) mClearEvent!!.mClaimedEd25519Key else mClaimedEd25519Key
// if (null != claimedEd25519Key) {
// if (null != claimedEd25519Key) { res["ed25519"] = claimedEd25519Key
// res["ed25519"] = claimedEd25519Key
// }
//
// return res
} }
//
return res
}
/** /**
* @return the event type * @return the event type
*/ */
fun getClearType(): String { fun getClearType(): String {
return mxDecryptionResult?.payload?.get("type")?.toString() return mClearEvent?.type ?: type
?: type//get("type")?.toString() ?: type
} }
/** /**
* @return the event content * @return the event content
*/ */
fun getClearContent(): Content? { fun getClearContent(): Content? {
return mxDecryptionResult?.payload?.get("content") as? Content ?: content return mClearEvent?.content ?: content
} }
// /** /**
// * @return the linked crypto error * @return the linked crypto error
// */ */
// fun getCryptoError(): MXCryptoError? { fun getCryptoError(): MXCryptoError? {
// return mCryptoError return mCryptoError
// }
//
// /**
// * Update the linked crypto error
// *
// * @param error the new crypto error.
// */
// fun setCryptoError(error: MXCryptoError?) {
// mCryptoError = error
// if (null != error) {
// mClearEvent = null
// }
// }
fun toContentStringWithIndent(): String {
val contentMap = this.toContent()?.toMutableMap() ?: HashMap()
contentMap.remove("mxDecryptionResult")
contentMap.remove("mCryptoError")
return JSONObject(contentMap).toString(4)
} }
fun toClearContentStringWithIndent(): String? { /**
val contentMap = this.mxDecryptionResult?.payload?.toMutableMap() * Update the linked crypto error
val adapter = MoshiProvider.providesMoshi().adapter(Map::class.java) *
return contentMap?.let { JSONObject(adapter.toJson(it)).toString(4) } * @param error the new crypto error.
*/
fun setCryptoError(error: MXCryptoError?) {
mCryptoError = error
if (null != error) {
mClearEvent = null
}
} }
/** /**
* Tells if the event is redacted * Tells if the event is redacted
*/ */
fun isRedacted() = unsignedData?.redactedEvent != null fun isRedacted() = unsignedData?.redactedEvent != null
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Event
if (type != other.type) return false
if (eventId != other.eventId) return false
if (content != other.content) return false
if (prevContent != other.prevContent) return false
if (originServerTs != other.originServerTs) return false
if (senderId != other.senderId) return false
if (stateKey != other.stateKey) return false
if (roomId != other.roomId) return false
if (unsignedData != other.unsignedData) return false
if (redacts != other.redacts) return false
if (mxDecryptionResult != other.mxDecryptionResult) return false
if (mCryptoError != other.mCryptoError) return false
return true
}
override fun hashCode(): Int {
var result = type.hashCode()
result = 31 * result + (eventId?.hashCode() ?: 0)
result = 31 * result + (content?.hashCode() ?: 0)
result = 31 * result + (prevContent?.hashCode() ?: 0)
result = 31 * result + (originServerTs?.hashCode() ?: 0)
result = 31 * result + (senderId?.hashCode() ?: 0)
result = 31 * result + (stateKey?.hashCode() ?: 0)
result = 31 * result + (roomId?.hashCode() ?: 0)
result = 31 * result + (unsignedData?.hashCode() ?: 0)
result = 31 * result + (redacts?.hashCode() ?: 0)
result = 31 * result + (mxDecryptionResult?.hashCode() ?: 0)
result = 31 * result + (mCryptoError?.hashCode() ?: 0)
return result
}
} }

View File

@ -1,52 +0,0 @@
/*
* 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.api.session.file
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
import java.io.File
/**
* This interface defines methods to get files.
*/
interface FileService {
enum class DownloadMode {
/**
* Download file in external storage
*/
TO_EXPORT,
/**
* Download file in cache
*/
FOR_INTERNAL_USE
}
/**
* Download a file.
* Result will be a decrypted file, stored in the cache folder. id parameter will be used to create a sub folder to avoid name collision.
* You can pass the eventId
*/
fun downloadFile(
downloadMode: DownloadMode,
id: String,
fileName: String,
url: String?,
elementToDecrypt: ElementToDecrypt?,
callback: MatrixCallback<File>)
}

View File

@ -47,8 +47,8 @@ interface Room :
* A live [RoomSummary] associated with the room * A live [RoomSummary] associated with the room
* You can observe this summary to get dynamic data from this room. * You can observe this summary to get dynamic data from this room.
*/ */
fun liveRoomSummary(): LiveData<RoomSummary> val liveRoomSummary: LiveData<RoomSummary>
fun roomSummary(): RoomSummary? val roomSummary: RoomSummary?
} }

View File

@ -210,7 +210,13 @@ class CreateRoomParams {
* @return the first invited user id * @return the first invited user id
*/ */
fun getFirstInvitedUserId(): String? { fun getFirstInvitedUserId(): String? {
return invitedUserIds?.firstOrNull() ?: invite3pids?.firstOrNull()?.address if (0 != getInviteCount()) {
return invitedUserIds!![0]
}
return if (0 != getInvite3PidCount()) {
invite3pids!![0].address
} else null
} }
/** /**

View File

@ -42,7 +42,7 @@ data class MessageAudioContent(
/** /**
* Required. Required if the file is not encrypted. The URL (typically MXC URI) to the audio clip. * Required. Required if the file is not encrypted. The URL (typically MXC URI) to the audio clip.
*/ */
@Json(name = "url") override val url: String? = null, @Json(name = "url") val url: String? = null,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null, @Json(name = "m.new_content") override val newContent: Content? = null,
@ -51,4 +51,4 @@ data class MessageAudioContent(
* Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption. * Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
*/ */
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageEncryptedContent ) : MessageEncyptedContent

View File

@ -20,18 +20,8 @@ import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
/** /**
* Interface for message which can contains an encrypted file * Interface for message which can contains encrypted data
*/ */
interface MessageEncryptedContent : MessageContent { interface MessageEncyptedContent : MessageContent {
/**
* Required. Required if the file is unencrypted. The URL (typically MXC URI) to the image.
*/
val url: String?
val encryptedFileInfo: EncryptedFileInfo? val encryptedFileInfo: EncryptedFileInfo?
} }
/**
* Get the url of the encrypted file or of the file
*/
fun MessageEncryptedContent.getFileUrl() = encryptedFileInfo?.url ?: url

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.api.session.room.model.message package im.vector.matrix.android.api.session.room.model.message
import android.content.ClipDescription
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Content
@ -48,22 +47,10 @@ data class MessageFileContent(
/** /**
* Required. Required if the file is unencrypted. The URL (typically MXC URI) to the file. * Required. Required if the file is unencrypted. The URL (typically MXC URI) to the file.
*/ */
@Json(name = "url") override val url: String? = null, @Json(name = "url") val url: String? = null,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null, @Json(name = "m.new_content") override val newContent: Content? = null,
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageEncryptedContent { ) : MessageEncyptedContent
fun getMimeType(): String {
// Mimetype default to plain text, should not be used
return encryptedFileInfo?.mimetype
?: info?.mimeType
?: ClipDescription.MIMETYPE_TEXT_PLAIN
}
fun getFileName(): String {
return filename ?: body
}
}

View File

@ -43,7 +43,7 @@ data class MessageImageContent(
/** /**
* Required. Required if the file is unencrypted. The URL (typically MXC URI) to the image. * Required. Required if the file is unencrypted. The URL (typically MXC URI) to the image.
*/ */
@Json(name = "url") override val url: String? = null, @Json(name = "url") val url: String? = null,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null, @Json(name = "m.new_content") override val newContent: Content? = null,
@ -52,4 +52,4 @@ data class MessageImageContent(
* Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption. * Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
*/ */
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageEncryptedContent ) : MessageEncyptedContent

View File

@ -42,7 +42,7 @@ data class MessageVideoContent(
/** /**
* Required. Required if the file is unencrypted. The URL (typically MXC URI) to the video clip. * Required. Required if the file is unencrypted. The URL (typically MXC URI) to the video clip.
*/ */
@Json(name = "url") override val url: String? = null, @Json(name = "url") val url: String? = null,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null, @Json(name = "m.new_content") override val newContent: Content? = null,
@ -51,4 +51,4 @@ data class MessageVideoContent(
* Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption. * Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
*/ */
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageEncryptedContent ) : MessageEncyptedContent

View File

@ -16,8 +16,8 @@
package im.vector.matrix.android.api.session.room.model.relation package im.vector.matrix.android.api.session.room.model.relation
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
/** /**
@ -51,8 +51,7 @@ interface RelationService {
* @param reaction the reaction (preferably emoji) * @param reaction the reaction (preferably emoji)
* @param targetEventId the id of the event being reacted * @param targetEventId the id of the event being reacted
*/ */
fun sendReaction(reaction: String, fun sendReaction(reaction: String, targetEventId: String): Cancelable
targetEventId: String): Cancelable
/** /**
@ -61,9 +60,7 @@ interface RelationService {
* @param targetEventId the id of the event being reacted * @param targetEventId the id of the event being reacted
* @param myUserId used to know if a reaction event was made by the user * @param myUserId used to know if a reaction event was made by the user
*/ */
fun undoReaction(reaction: String, fun undoReaction(reaction: String, targetEventId: String, myUserId: String)//: Cancelable
targetEventId: String,
myUserId: String)//: Cancelable
/** /**
@ -72,11 +69,7 @@ interface RelationService {
* @param newBodyText The edited body * @param newBodyText The edited body
* @param compatibilityBodyText The text that will appear on clients that don't support yet edition * @param compatibilityBodyText The text that will appear on clients that don't support yet edition
*/ */
fun editTextMessage(targetEventId: String, fun editTextMessage(targetEventId: String, newBodyText: String, newBodyAutoMarkdown: Boolean, compatibilityBodyText: String = "* $newBodyText"): Cancelable
msgType: String,
newBodyText: String,
newBodyAutoMarkdown: Boolean,
compatibilityBodyText: String = "* $newBodyText"): Cancelable
/** /**
@ -84,11 +77,8 @@ interface RelationService {
* https://matrix.org/docs/spec/client_server/r0.4.0.html#id350 * https://matrix.org/docs/spec/client_server/r0.4.0.html#id350
* @param eventReplied the event referenced by the reply * @param eventReplied the event referenced by the reply
* @param replyText the reply text * @param replyText the reply text
* @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present
*/ */
fun replyToMessage(eventReplied: TimelineEvent, fun replyToMessage(eventReplied: Event, replyText: String): Cancelable?
replyText: String,
autoMarkdown: Boolean = false): Cancelable?
fun getEventSummaryLive(eventId: String): LiveData<EventAnnotationsSummary> fun getEventSummaryLive(eventId: String): LiveData<EventAnnotationsSummary>
} }

View File

@ -67,8 +67,7 @@ data class PublicRoom(
var worldReadable: Boolean = false, var worldReadable: Boolean = false,
/** /**
* Required. Whether guest users may join the room and participate in it. If they can, * Required. Whether guest users may join the room and participate in it. If they can, they will be subject to ordinary power level rules like any other user.
* they will be subject to ordinary power level rules like any other user.
*/ */
@Json(name = "guest_can_join") @Json(name = "guest_can_join")
var guestCanJoin: Boolean = false, var guestCanJoin: Boolean = false,

View File

@ -42,7 +42,7 @@ interface SendService {
* @param formattedText The formatted body using MessageType#FORMAT_MATRIX_HTML * @param formattedText The formatted body using MessageType#FORMAT_MATRIX_HTML
* @return a [Cancelable] * @return a [Cancelable]
*/ */
fun sendFormattedTextMessage(text: String, formattedText: String): Cancelable fun sendFormattedTextMessage(text: String,formattedText: String): Cancelable
/** /**
* Method to send a media asynchronously. * Method to send a media asynchronously.

View File

@ -18,9 +18,7 @@ package im.vector.matrix.android.api.session.room.timeline
import im.vector.matrix.android.api.session.events.model.Event 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.EventType
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.send.SendState
/** /**
@ -30,12 +28,13 @@ import im.vector.matrix.android.api.session.room.send.SendState
*/ */
data class TimelineEvent( data class TimelineEvent(
val root: Event, val root: Event,
val localId: Long, val localId: String,
val displayIndex: Int, val displayIndex: Int,
val senderName: String?, val senderName: String?,
val isUniqueDisplayName: Boolean, val isUniqueDisplayName: Boolean,
val senderAvatar: String?, val senderAvatar: String?,
val sendState: SendState, val sendState: SendState,
val hasClearEventFlag: Boolean = false,
val annotations: EventAnnotationsSummary? = null val annotations: EventAnnotationsSummary? = null
) { ) {
@ -82,9 +81,3 @@ data class TimelineEvent(
return EventType.ENCRYPTED == root.type return EventType.ENCRYPTED == root.type
} }
} }
/**
* Get last MessageContent, after a possible edition
*/
fun TimelineEvent.getLastMessageContent(): MessageContent? = annotations?.editSummary?.aggregatedContent?.toModel()
?: root.getClearContent().toModel()

View File

@ -16,8 +16,20 @@
package im.vector.matrix.android.api.util package im.vector.matrix.android.api.util
class CancelableBag : Cancelable, MutableList<Cancelable> by ArrayList() { class CancelableBag : Cancelable {
override fun cancel() {
forEach { it.cancel() } private val cancelableList = ArrayList<Cancelable>()
fun add(cancelable: Cancelable) {
cancelableList.add(cancelable)
} }
override fun cancel() {
cancelableList.forEach { it.cancel() }
}
}
fun Cancelable.addTo(cancelables: CancelableBag) {
cancelables.add(this)
} }

View File

@ -21,4 +21,13 @@ import im.vector.matrix.android.api.MatrixCallback
/** /**
* Simple MatrixCallback implementation which delegate its calls to another callback * Simple MatrixCallback implementation which delegate its calls to another callback
*/ */
open class MatrixCallbackDelegate<T>(private val callback: MatrixCallback<T>) : MatrixCallback<T> by callback open class MatrixCallbackDelegate<T>(private val callback: MatrixCallback<T>) : MatrixCallback<T> {
override fun onSuccess(data: T) {
callback.onSuccess(data)
}
override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
}
}

View File

@ -21,7 +21,6 @@ import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
import im.vector.matrix.android.internal.network.NetworkConstants import im.vector.matrix.android.internal.network.NetworkConstants
import retrofit2.Call import retrofit2.Call
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.Headers
import retrofit2.http.POST import retrofit2.http.POST
/** /**
@ -31,11 +30,9 @@ internal interface AuthAPI {
/** /**
* Pass params to the server for the current login phase. * Pass params to the server for the current login phase.
* Set all the timeouts to 1 minute
* *
* @param loginParams the login parameters * @param loginParams the login parameters
*/ */
@Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000")
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login") @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
fun login(@Body loginParams: PasswordLoginParams): Call<Credentials> fun login(@Body loginParams: PasswordLoginParams): Call<Credentials>

View File

@ -25,7 +25,6 @@ import android.text.TextUtils
import arrow.core.Try import arrow.core.Try
import com.squareup.moshi.Types import com.squareup.moshi.Types
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import dagger.Lazy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
@ -73,8 +72,6 @@ import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import im.vector.matrix.android.internal.session.sync.model.SyncResponse import im.vector.matrix.android.internal.session.sync.model.SyncResponse
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.task.toConfigurableTask
import im.vector.matrix.android.internal.util.JsonCanonicalizer
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.fetchCopied import im.vector.matrix.android.internal.util.fetchCopied
import kotlinx.coroutines.* import kotlinx.coroutines.*
@ -101,7 +98,7 @@ internal class CryptoManager @Inject constructor(
private val olmManager: OlmManager, private val olmManager: OlmManager,
// The credentials, // The credentials,
private val credentials: Credentials, private val credentials: Credentials,
private val myDeviceInfoHolder: Lazy<MyDeviceInfoHolder>, private val myDeviceInfoHolder: MyDeviceInfoHolder,
// the crypto store // the crypto store
private val cryptoStore: IMXCryptoStore, private val cryptoStore: IMXCryptoStore,
// Olm device // Olm device
@ -193,12 +190,12 @@ internal class CryptoManager @Inject constructor(
} }
override fun getMyDevice(): MXDeviceInfo { override fun getMyDevice(): MXDeviceInfo {
return myDeviceInfoHolder.get().myDevice return myDeviceInfoHolder.myDevice
} }
override fun getDevicesList(callback: MatrixCallback<DevicesListResponse>) { override fun getDevicesList(callback: MatrixCallback<DevicesListResponse>) {
getDevicesTask getDevicesTask
.toConfigurableTask() .configureWith(Unit)
.dispatchTo(callback) .dispatchTo(callback)
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }
@ -244,16 +241,16 @@ internal class CryptoManager @Inject constructor(
* @param isInitialSync true if it starts from an initial sync * @param isInitialSync true if it starts from an initial sync
*/ */
fun start(isInitialSync: Boolean) { fun start(isInitialSync: Boolean) {
if (isStarted.get() || isStarting.get()) {
return
}
isStarting.set(true)
CoroutineScope(coroutineDispatchers.crypto).launch { CoroutineScope(coroutineDispatchers.crypto).launch {
internalStart(isInitialSync) internalStart(isInitialSync)
} }
} }
private suspend fun internalStart(isInitialSync: Boolean) { private suspend fun internalStart(isInitialSync: Boolean) {
if (isStarted.get() || isStarting.get()) {
return
}
isStarting.set(true)
// Open the store // Open the store
cryptoStore.open() cryptoStore.open()
uploadDeviceKeys() uploadDeviceKeys()
@ -390,12 +387,12 @@ internal class CryptoManager @Inject constructor(
var isUpdated = false var isUpdated = false
val deviceIds = devicesIdListByUserId[userId] val deviceIds = devicesIdListByUserId[userId]
deviceIds?.forEach { deviceId -> for (deviceId in deviceIds!!) {
val device = storedDeviceIDs[deviceId] val device = storedDeviceIDs[deviceId]
// assume if the device is either verified or blocked // assume if the device is either verified or blocked
// it means that the device is known // it means that the device is known
if (device?.isUnknown == true) { if (null != device && device.isUnknown) {
device.verified = MXDeviceInfo.DEVICE_VERIFICATION_UNVERIFIED device.verified = MXDeviceInfo.DEVICE_VERIFICATION_UNVERIFIED
isUpdated = true isUpdated = true
} }
@ -447,7 +444,7 @@ internal class CryptoManager @Inject constructor(
val encryptingClass = MXCryptoAlgorithms.hasEncryptorClassForAlgorithm(algorithm) val encryptingClass = MXCryptoAlgorithms.hasEncryptorClassForAlgorithm(algorithm)
if (!encryptingClass) { if (!encryptingClass) {
Timber.e("## setEncryptionInRoom() : Unable to encrypt room ${roomId} with $algorithm") Timber.e("## setEncryptionInRoom() : Unable to encrypt with " + algorithm!!)
return false return false
} }
@ -562,7 +559,7 @@ internal class CryptoManager @Inject constructor(
.fold( .fold(
{ callback.onFailure(it) }, { callback.onFailure(it) },
{ {
Timber.v("## encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms") Timber.v("## encryptEventContent() : succeeds after " + (System.currentTimeMillis() - t0) + " ms")
callback.onSuccess(MXEncryptEventContentResult(it, EventType.ENCRYPTED)) callback.onSuccess(MXEncryptEventContentResult(it, EventType.ENCRYPTED))
} }
) )
@ -571,7 +568,8 @@ internal class CryptoManager @Inject constructor(
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON, val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON,
algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON) algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON)
Timber.e("## encryptEventContent() : $reason") Timber.e("## encryptEventContent() : $reason")
callback.onFailure(Failure.CryptoError(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_ENCRYPT, reason))) callback.onFailure(Failure.CryptoError(MXCryptoError(MXCryptoError.UNABLE_TO_ENCRYPT_ERROR_CODE,
MXCryptoError.UNABLE_TO_ENCRYPT, reason)))
} }
} }
} }
@ -581,10 +579,10 @@ internal class CryptoManager @Inject constructor(
* *
* @param event the raw event. * @param event the raw event.
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
* @return the MXEventDecryptionResult data, or throw in case of error * @return the MXEventDecryptionResult data, or null in case of error
*/ */
@Throws(MXCryptoError::class) @Throws(MXDecryptionException::class)
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult { override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult? {
return runBlocking { return runBlocking {
internalDecryptEvent(event, timeline).fold( internalDecryptEvent(event, timeline).fold(
{ throw it }, { throw it },
@ -600,7 +598,7 @@ internal class CryptoManager @Inject constructor(
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
* @param callback the callback to return data or null * @param callback the callback to return data or null
*/ */
override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>) { override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult?>) {
GlobalScope.launch(EmptyCoroutineContext) { GlobalScope.launch(EmptyCoroutineContext) {
val result = withContext(coroutineDispatchers.crypto) { val result = withContext(coroutineDispatchers.crypto) {
internalDecryptEvent(event, timeline) internalDecryptEvent(event, timeline)
@ -616,18 +614,18 @@ internal class CryptoManager @Inject constructor(
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
* @return the MXEventDecryptionResult data, or null in case of error wrapped into [Try] * @return the MXEventDecryptionResult data, or null in case of error wrapped into [Try]
*/ */
private suspend fun internalDecryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> { private suspend fun internalDecryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult?> {
return Try {
val eventContent = event.content val eventContent = event.content
return if (eventContent == null) { if (eventContent == null) {
Timber.e("## decryptEvent : empty event content") Timber.e("## decryptEvent : empty event content")
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)) return@Try null
} else { }
val algorithm = eventContent["algorithm"]?.toString() val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, eventContent["algorithm"] as String)
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm)
if (alg == null) { if (alg == null) {
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm) val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, eventContent["algorithm"] as String)
Timber.e("## decryptEvent() : $reason") Timber.e("## decryptEvent() : $reason")
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason)) throw MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE, MXCryptoError.UNABLE_TO_DECRYPT, reason))
} else { } else {
alg.decryptEvent(event, timeline) alg.decryptEvent(event, timeline)
} }
@ -677,7 +675,7 @@ internal class CryptoManager @Inject constructor(
} }
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(roomKeyContent.roomId, roomKeyContent.algorithm) val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(roomKeyContent.roomId, roomKeyContent.algorithm)
if (alg == null) { if (alg == null) {
Timber.e("## onRoomKeyEvent() : Unable to handle keys for ${roomKeyContent.algorithm}") Timber.e("## onRoomKeyEvent() : Unable to handle keys for " + roomKeyContent.algorithm)
return return
} }
alg.onRoomKeyEvent(event, keysBackup) alg.onRoomKeyEvent(event, keysBackup)
@ -693,9 +691,9 @@ internal class CryptoManager @Inject constructor(
val params = LoadRoomMembersTask.Params(roomId) val params = LoadRoomMembersTask.Params(roomId)
loadRoomMembersTask loadRoomMembersTask
.execute(params) .execute(params)
.map { _ -> .map { allLoaded ->
val userIds = getRoomUserIds(roomId) val userIds = getRoomUserIds(roomId)
setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds) setEncryptionInRoom(roomId, event.content!!["algorithm"] as String, true, userIds)
} }
} }
} }
@ -766,7 +764,7 @@ internal class CryptoManager @Inject constructor(
private suspend fun uploadDeviceKeys(): Try<KeysUploadResponse> { private suspend fun uploadDeviceKeys(): Try<KeysUploadResponse> {
// Prepare the device keys data to send // Prepare the device keys data to send
// Sign it // Sign it
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, getMyDevice().signalableJSONDictionary()) val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, getMyDevice().signalableJSONDictionary())
getMyDevice().signatures = objectSigner.signObject(canonicalJson) getMyDevice().signatures = objectSigner.signObject(canonicalJson)
// For now, we set the device id explicitly, as we may not be using the // For now, we set the device id explicitly, as we may not be using the
@ -840,7 +838,7 @@ internal class CryptoManager @Inject constructor(
val roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password) val roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password)
val t1 = System.currentTimeMillis() val t1 = System.currentTimeMillis()
Timber.v("## importRoomKeys : decryptMegolmKeyFile done in ${t1 - t0} ms") Timber.v("## importRoomKeys : decryptMegolmKeyFile done in " + (t1 - t0) + " ms")
val importedSessions = MoshiProvider.providesMoshi() val importedSessions = MoshiProvider.providesMoshi()
.adapter<List<MegolmSessionData>>(Types.newParameterizedType(List::class.java, MegolmSessionData::class.java)) .adapter<List<MegolmSessionData>>(Types.newParameterizedType(List::class.java, MegolmSessionData::class.java))
@ -848,7 +846,7 @@ internal class CryptoManager @Inject constructor(
val t2 = System.currentTimeMillis() val t2 = System.currentTimeMillis()
Timber.v("## importRoomKeys : JSON parsing ${t2 - t1} ms") Timber.v("## importRoomKeys : JSON parsing " + (t2 - t1) + " ms")
if (importedSessions == null) { if (importedSessions == null) {
throw Exception("Error") throw Exception("Error")
@ -872,7 +870,7 @@ internal class CryptoManager @Inject constructor(
/** /**
* Check if the user ids list have some unknown devices. * Check if the user ids list have some unknown devices.
* A success means there is no unknown devices. * A success means there is no unknown devices.
* If there are some unknown devices, a MXCryptoError.UnknownDevice exception is triggered. * If there are some unknown devices, a MXCryptoError.UNKNOWN_DEVICES_CODE exception is triggered.
* *
* @param userIds the user ids list * @param userIds the user ids list
* @param callback the asynchronous callback. * @param callback the asynchronous callback.
@ -890,7 +888,9 @@ internal class CryptoManager @Inject constructor(
callback.onSuccess(Unit) callback.onSuccess(Unit)
} else { } else {
// trigger an an unknown devices exception // trigger an an unknown devices exception
callback.onFailure(Failure.CryptoError(MXCryptoError.UnknownDevice(unknownDevices))) callback.onFailure(
Failure.CryptoError(MXCryptoError(MXCryptoError.UNKNOWN_DEVICES_CODE,
MXCryptoError.UNABLE_TO_ENCRYPT, MXCryptoError.UNKNOWN_DEVICES_REASON, unknownDevices)))
} }
} }
) )
@ -930,8 +930,11 @@ internal class CryptoManager @Inject constructor(
*/ */
// TODO add this info in CryptoRoomEntity? // TODO add this info in CryptoRoomEntity?
override fun isRoomBlacklistUnverifiedDevices(roomId: String?): Boolean { override fun isRoomBlacklistUnverifiedDevices(roomId: String?): Boolean {
return roomId?.let { cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(it) } return if (null != roomId) {
?: false cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId)
} else {
false
}
} }
/** /**
@ -989,18 +992,18 @@ internal class CryptoManager @Inject constructor(
* @param event the event to decrypt again. * @param event the event to decrypt again.
*/ */
override fun reRequestRoomKeyForEvent(event: Event) { override fun reRequestRoomKeyForEvent(event: Event) {
val wireContent = event.content val wireContent = event.content!!
if (wireContent == null) {
Timber.e("## reRequestRoomKeyForEvent Failed to re-request key, null content") val algorithm = wireContent["algorithm"].toString()
return val senderKey = wireContent["sender_key"].toString()
} val sessionId = wireContent["session_id"].toString()
val requestBody = RoomKeyRequestBody() val requestBody = RoomKeyRequestBody()
requestBody.roomId = event.roomId requestBody.roomId = event.roomId
requestBody.algorithm = wireContent["algorithm"]?.toString() requestBody.algorithm = algorithm
requestBody.senderKey = wireContent["sender_key"]?.toString() requestBody.senderKey = senderKey
requestBody.sessionId = wireContent["session_id"]?.toString() requestBody.sessionId = sessionId
outgoingRoomKeyRequestManager.resendRoomKeyRequest(requestBody) outgoingRoomKeyRequestManager.resendRoomKeyRequest(requestBody)
} }
@ -1019,7 +1022,7 @@ internal class CryptoManager @Inject constructor(
* *
* @param listener listener * @param listener listener
*/ */
override fun removeRoomKeysRequestListener(listener: RoomKeysRequestListener) { fun removeRoomKeysRequestListener(listener: RoomKeysRequestListener) {
incomingRoomKeyRequestManager.removeRoomKeysRequestListener(listener) incomingRoomKeyRequestManager.removeRoomKeysRequestListener(listener)
} }
@ -1034,11 +1037,11 @@ internal class CryptoManager @Inject constructor(
val userIds = devicesInRoom.userIds val userIds = devicesInRoom.userIds
for (userId in userIds) { for (userId in userIds) {
val deviceIds = devicesInRoom.getUserDeviceIds(userId) val deviceIds = devicesInRoom.getUserDeviceIds(userId)
deviceIds?.forEach { deviceId -> for (deviceId in deviceIds!!) {
devicesInRoom.getObject(userId, deviceId) val deviceInfo = devicesInRoom.getObject(deviceId, userId)
?.takeIf { it.isUnknown }
?.let { if (deviceInfo!!.isUnknown) {
unknownDevices.setObject(userId, deviceId, it) unknownDevices.setObject(deviceInfo, userId, deviceId)
} }
} }
} }
@ -1055,8 +1058,7 @@ internal class CryptoManager @Inject constructor(
} }
override fun clearCryptoCache(callback: MatrixCallback<Unit>) { override fun clearCryptoCache(callback: MatrixCallback<Unit>) {
clearCryptoDataTask clearCryptoDataTask.configureWith(Unit)
.toConfigurableTask()
.dispatchTo(callback) .dispatchTo(callback)
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }

View File

@ -76,7 +76,7 @@ internal abstract class CryptoModule {
@Provides @Provides
fun providesCryptoStore(@CryptoDatabase fun providesCryptoStore(@CryptoDatabase
realmConfiguration: RealmConfiguration, credentials: Credentials): IMXCryptoStore { realmConfiguration: RealmConfiguration, credentials: Credentials): IMXCryptoStore {
return RealmCryptoStore( return RealmCryptoStore(false /* TODO*/,
realmConfiguration, realmConfiguration,
credentials) credentials)
} }
@ -168,10 +168,8 @@ internal abstract class CryptoModule {
abstract fun bindSendToDeviceTask(sendToDeviceTask: DefaultSendToDeviceTask): SendToDeviceTask abstract fun bindSendToDeviceTask(sendToDeviceTask: DefaultSendToDeviceTask): SendToDeviceTask
@Binds @Binds
abstract fun bindClaimOneTimeKeysForUsersDeviceTask(claimOneTimeKeysForUsersDevice: DefaultClaimOneTimeKeysForUsersDevice) abstract fun bindClaimOneTimeKeysForUsersDeviceTask(claimOneTimeKeysForUsersDevice: DefaultClaimOneTimeKeysForUsersDevice): ClaimOneTimeKeysForUsersDeviceTask
: ClaimOneTimeKeysForUsersDeviceTask
@Binds @Binds
abstract fun bindDeleteDeviceWithUserPasswordTask(deleteDeviceWithUserPasswordTask: DefaultDeleteDeviceWithUserPasswordTask) abstract fun bindDeleteDeviceWithUserPasswordTask(deleteDeviceWithUserPasswordTask: DefaultDeleteDeviceWithUserPasswordTask): DeleteDeviceWithUserPasswordTask
: DeleteDeviceWithUserPasswordTask
} }

View File

@ -21,11 +21,11 @@ import android.text.TextUtils
import arrow.core.Try import arrow.core.Try
import im.vector.matrix.android.api.MatrixPatterns import im.vector.matrix.android.api.MatrixPatterns
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.extensions.onError
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo 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.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.DownloadKeysForUsersTask import im.vector.matrix.android.internal.crypto.tasks.DownloadKeysForUsersTask
import im.vector.matrix.android.internal.extensions.onError
import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.sync.SyncTokenStore import im.vector.matrix.android.internal.session.sync.SyncTokenStore
import timber.log.Timber import timber.log.Timber
@ -221,7 +221,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
Timber.v("Device list for $userId now up to date") Timber.v("Device list for $userId now up to date")
} }
// And the response result // And the response result
usersDevicesInfoMap.setObjects(userId, devices) usersDevicesInfoMap.setObjects(devices, userId)
} }
} }
cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses) cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
@ -239,7 +239,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
*/ */
suspend fun downloadKeys(userIds: List<String>?, forceDownload: Boolean): Try<MXUsersDevicesMap<MXDeviceInfo>> { suspend fun downloadKeys(userIds: List<String>?, forceDownload: Boolean): Try<MXUsersDevicesMap<MXDeviceInfo>> {
Timber.v("## downloadKeys() : forceDownload $forceDownload : $userIds") Timber.v("## downloadKeys() : forceDownload $forceDownload : $userIds")
// Map from userId -> deviceId -> MXDeviceInfo // Map from userid -> deviceid -> DeviceInfo
val stored = MXUsersDevicesMap<MXDeviceInfo>() val stored = MXUsersDevicesMap<MXDeviceInfo>()
// List of user ids we need to download keys for // List of user ids we need to download keys for
@ -258,7 +258,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
val devices = cryptoStore.getUserDevices(userId) val devices = cryptoStore.getUserDevices(userId)
// should always be true // should always be true
if (devices != null) { if (devices != null) {
stored.setObjects(userId, devices) stored.setObjects(devices, userId)
} else { } else {
downloadUsers.add(userId) downloadUsers.add(userId)
} }
@ -380,14 +380,14 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
} }
val signKeyId = "ed25519:" + deviceKeys.deviceId val signKeyId = "ed25519:" + deviceKeys.deviceId
val signKey = deviceKeys.keys?.get(signKeyId) val signKey = deviceKeys.keys!![signKeyId]
if (null == signKey) { if (null == signKey) {
Timber.e("## validateDeviceKeys() : Device " + userId + ":" + deviceKeys.deviceId + " has no ed25519 key") Timber.e("## validateDeviceKeys() : Device " + userId + ":" + deviceKeys.deviceId + " has no ed25519 key")
return false return false
} }
val signatureMap = deviceKeys.signatures?.get(userId) val signatureMap = deviceKeys.signatures!![userId]
if (null == signatureMap) { if (null == signatureMap) {
Timber.e("## validateDeviceKeys() : Device " + userId + ":" + deviceKeys.deviceId + " has no map for " + userId) Timber.e("## validateDeviceKeys() : Device " + userId + ":" + deviceKeys.deviceId + " has no map for " + userId)

View File

@ -84,7 +84,7 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
Timber.e("## processReceivedRoomKeyRequests() : Ignoring room key request from other user for now") Timber.e("## processReceivedRoomKeyRequests() : Ignoring room key request from other user for now")
return return
} }
// TODO: should we queue up requests we don't yet have keys for, in case they turn up later? // todo: should we queue up requests we don't yet have keys for, in case they turn up later?
// if we don't have a decryptor for this room/alg, we don't have // if we don't have a decryptor for this room/alg, we don't have
// the keys for the requested events, and can drop the requests. // the keys for the requested events, and can drop the requests.
val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, alg) val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, alg)

View File

@ -0,0 +1,39 @@
/*
* Copyright 2016 OpenMarket Ltd
* Copyright 2017 Vector Creations 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 im.vector.matrix.android.api.session.crypto.MXCryptoError
/**
* This class represents a decryption exception
*/
class MXDecryptionException
(
/**
* the linked crypto error
*/
val cryptoError: MXCryptoError?
) : Exception() {
override val message: String?
get() = cryptoError?.message ?: super.message
override fun getLocalizedMessage(): String {
return cryptoError?.message ?: super.getLocalizedMessage()
}
}

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 OpenMarket Ltd * Copyright 2016 OpenMarket Ltd
* Copyright 2017 Vector Creations Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,6 +18,7 @@
package im.vector.matrix.android.internal.crypto package im.vector.matrix.android.internal.crypto
import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.api.util.JsonDict
import java.util.*
/** /**
* The result of a (successful) call to decryptEvent. * The result of a (successful) call to decryptEvent.
@ -26,23 +28,23 @@ data class MXEventDecryptionResult(
/** /**
* The plaintext payload for the event (typically containing "type" and "content" fields). * The plaintext payload for the event (typically containing "type" and "content" fields).
*/ */
val clearEvent: JsonDict, var clearEvent: JsonDict? = null,
/** /**
* Key owned by the sender of this event. * Key owned by the sender of this event.
* See MXEvent.senderKey. * See MXEvent.senderKey.
*/ */
val senderCurve25519Key: String? = null, var senderCurve25519Key: String? = null,
/** /**
* Ed25519 key claimed by the sender of this event. * Ed25519 key claimed by the sender of this event.
* See MXEvent.claimedEd25519Key. * See MXEvent.claimedEd25519Key.
*/ */
val claimedEd25519Key: String? = null, var claimedEd25519Key: String? = null,
/** /**
* List of curve25519 keys involved in telling us about the senderCurve25519Key and * List of curve25519 keys involved in telling us about the senderCurve25519Key and
* claimedEd25519Key. See MXEvent.forwardingCurve25519KeyChain. * claimedEd25519Key. See MXEvent.forwardingCurve25519KeyChain.
*/ */
val forwardingCurve25519KeyChain: List<String> = emptyList() var forwardingCurve25519KeyChain: List<String> = ArrayList()
) )

View File

@ -229,7 +229,7 @@ object MXMegolmExportEncryption {
throw Exception("Header line not found") throw Exception("Header line not found")
} }
val line = fileStr.substring(lineStart, lineEnd).trim() val line = fileStr.substring(lineStart, lineEnd).trim { it <= ' ' }
// start the next line after the newline // start the next line after the newline
lineStart = lineEnd + 1 lineStart = lineEnd + 1
@ -247,9 +247,9 @@ object MXMegolmExportEncryption {
val line: String val line: String
if (lineEnd < 0) { if (lineEnd < 0) {
line = fileStr.substring(lineStart).trim() line = fileStr.substring(lineStart).trim { it <= ' ' }
} else { } else {
line = fileStr.substring(lineStart, lineEnd).trim() line = fileStr.substring(lineStart, lineEnd).trim { it <= ' ' }
} }
if (TextUtils.equals(line, TRAILER_LINE)) { if (TextUtils.equals(line, TRAILER_LINE)) {

View File

@ -18,20 +18,23 @@
package im.vector.matrix.android.internal.crypto package im.vector.matrix.android.internal.crypto
import android.text.TextUtils import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE
import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.matrix.android.internal.crypto.algorithms.MXDecryptionResult
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.util.JsonCanonicalizer
import im.vector.matrix.android.internal.util.convertFromUTF8 import im.vector.matrix.android.internal.util.convertFromUTF8
import im.vector.matrix.android.internal.util.convertToUTF8 import im.vector.matrix.android.internal.util.convertToUTF8
import org.matrix.olm.* import org.matrix.olm.OlmAccount
import org.matrix.olm.OlmInboundGroupSession
import org.matrix.olm.OlmMessage
import org.matrix.olm.OlmOutboundGroupSession
import org.matrix.olm.OlmSession
import org.matrix.olm.OlmUtility
import timber.log.Timber import timber.log.Timber
import java.net.URLEncoder import java.net.URLEncoder
import java.util.* import java.util.*
@ -83,6 +86,11 @@ internal class MXOlmDevice @Inject constructor(
// Values are true. // Values are true.
private val inboundGroupSessionMessageIndexes: MutableMap<String, MutableMap<String, Boolean>> = HashMap() private val inboundGroupSessionMessageIndexes: MutableMap<String, MutableMap<String, Boolean>> = HashMap()
/**
* inboundGroupSessionWithId error
*/
private var inboundGroupSessionWithIdError: MXCryptoError? = null
init { init {
// Retrieve the account from the store // Retrieve the account from the store
olmAccount = store.getAccount() olmAccount = store.getAccount()
@ -111,13 +119,13 @@ internal class MXOlmDevice @Inject constructor(
try { try {
deviceCurve25519Key = olmAccount!!.identityKeys()[OlmAccount.JSON_KEY_IDENTITY_KEY] deviceCurve25519Key = olmAccount!!.identityKeys()[OlmAccount.JSON_KEY_IDENTITY_KEY]
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "## MXOlmDevice : cannot find ${OlmAccount.JSON_KEY_IDENTITY_KEY} with error") Timber.e(e, "## MXOlmDevice : cannot find " + OlmAccount.JSON_KEY_IDENTITY_KEY + " with error")
} }
try { try {
deviceEd25519Key = olmAccount!!.identityKeys()[OlmAccount.JSON_KEY_FINGER_PRINT_KEY] deviceEd25519Key = olmAccount!!.identityKeys()[OlmAccount.JSON_KEY_FINGER_PRINT_KEY]
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "## MXOlmDevice : cannot find ${OlmAccount.JSON_KEY_FINGER_PRINT_KEY} with error") Timber.e(e, "## MXOlmDevice : cannot find " + OlmAccount.JSON_KEY_FINGER_PRINT_KEY + " with error")
} }
} }
@ -289,13 +297,13 @@ internal class MXOlmDevice @Inject constructor(
val res = HashMap<String, String>() val res = HashMap<String, String>()
if (!payloadString.isNullOrEmpty()) { if (!TextUtils.isEmpty(payloadString)) {
res["payload"] = payloadString res["payload"] = payloadString!!
} }
val sessionIdentifier = olmSession.sessionIdentifier() val sessionIdentifier = olmSession.sessionIdentifier()
if (!sessionIdentifier.isNullOrEmpty()) { if (!TextUtils.isEmpty(sessionIdentifier)) {
res["session_id"] = sessionIdentifier res["session_id"] = sessionIdentifier
} }
@ -505,26 +513,24 @@ internal class MXOlmDevice @Inject constructor(
forwardingCurve25519KeyChain: List<String>, forwardingCurve25519KeyChain: List<String>,
keysClaimed: Map<String, String>, keysClaimed: Map<String, String>,
exportFormat: Boolean): Boolean { exportFormat: Boolean): Boolean {
val existingInboundSession = getInboundGroupSession(sessionId, senderKey, roomId)
val session = OlmInboundGroupSessionWrapper(sessionKey, exportFormat) val session = OlmInboundGroupSessionWrapper(sessionKey, exportFormat)
getInboundGroupSession(sessionId, senderKey, roomId).fold( if (null != existingInboundSession) {
{
// Nothing to do in case of error
},
{
// If we already have this session, consider updating it // If we already have this session, consider updating it
Timber.e("## addInboundGroupSession() : Update for megolm session $senderKey/$sessionId") Timber.e("## addInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
val existingFirstKnown = it.firstKnownIndex!! val existingFirstKnown = existingInboundSession.firstKnownIndex!!
val newKnownFirstIndex = session.firstKnownIndex val newKnownFirstIndex = session.firstKnownIndex!!
//If our existing session is better we keep it //If our existing session is better we keep it
if (newKnownFirstIndex != null && existingFirstKnown <= newKnownFirstIndex) { if (newKnownFirstIndex != null && existingFirstKnown <= newKnownFirstIndex) {
session.olmInboundGroupSession?.releaseSession() if (session.olmInboundGroupSession != null) {
session.olmInboundGroupSession!!.releaseSession()
}
return false return false
} }
} }
)
// sanity check // sanity check
if (null == session.olmInboundGroupSession) { if (null == session.olmInboundGroupSession) {
@ -539,7 +545,7 @@ internal class MXOlmDevice @Inject constructor(
return false return false
} }
} catch (e: Exception) { } catch (e: Exception) {
session.olmInboundGroupSession?.releaseSession() session.olmInboundGroupSession!!.releaseSession()
Timber.e(e, "## addInboundGroupSession : sessionIdentifier() failed") Timber.e(e, "## addInboundGroupSession : sessionIdentifier() failed")
return false return false
} }
@ -578,13 +584,13 @@ internal class MXOlmDevice @Inject constructor(
} }
// sanity check // sanity check
if (session?.olmInboundGroupSession == null) { if (null == session || null == session.olmInboundGroupSession) {
Timber.e("## importInboundGroupSession : invalid session") Timber.e("## importInboundGroupSession : invalid session")
continue continue
} }
try { try {
if (!TextUtils.equals(session.olmInboundGroupSession?.sessionIdentifier(), sessionId)) { if (!TextUtils.equals(session.olmInboundGroupSession!!.sessionIdentifier(), sessionId)) {
Timber.e("## importInboundGroupSession : ERROR: Mismatched group session ID from senderKey: " + senderKey!!) Timber.e("## importInboundGroupSession : ERROR: Mismatched group session ID from senderKey: " + senderKey!!)
if (session.olmInboundGroupSession != null) session.olmInboundGroupSession!!.releaseSession() if (session.olmInboundGroupSession != null) session.olmInboundGroupSession!!.releaseSession()
continue continue
@ -595,27 +601,21 @@ internal class MXOlmDevice @Inject constructor(
continue continue
} }
getInboundGroupSession(sessionId, senderKey, roomId) val existingOlmSession = getInboundGroupSession(sessionId, senderKey, roomId)
.fold( if (null != existingOlmSession) {
{
// Session does not already exist, add it
sessions.add(session)
},
{
// If we already have this session, consider updating it // If we already have this session, consider updating it
Timber.e("## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId") Timber.e("## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
// For now we just ignore updates. TODO: implement something here // For now we just ignore updates. TODO: implement something here
if (it.firstKnownIndex!! <= session.firstKnownIndex!!) { if (existingOlmSession.firstKnownIndex!! <= session.firstKnownIndex!!) {
//Ignore this, keep existing //Ignore this, keep existing
session.olmInboundGroupSession!!.releaseSession() session.olmInboundGroupSession!!.releaseSession()
} else { continue
}
}
sessions.add(session) sessions.add(session)
} }
Unit
}
)
}
store.storeInboundGroupSessions(sessions) store.storeInboundGroupSessions(sessions)
@ -644,24 +644,29 @@ internal class MXOlmDevice @Inject constructor(
* @param senderKey the base64-encoded curve25519 key of the sender. * @param senderKey the base64-encoded curve25519 key of the sender.
* @return the decrypting result. Nil if the sessionId is unknown. * @return the decrypting result. Nil if the sessionId is unknown.
*/ */
@Throws(MXDecryptionException::class)
fun decryptGroupMessage(body: String, fun decryptGroupMessage(body: String,
roomId: String, roomId: String,
timeline: String?, timeline: String?,
sessionId: String, sessionId: String,
senderKey: String): Try<OlmDecryptionResult> { senderKey: String): MXDecryptionResult? {
return getInboundGroupSession(sessionId, senderKey, roomId) val result = MXDecryptionResult()
.flatMap { session -> val session = getInboundGroupSession(sessionId, senderKey, roomId)
if (null != session) {
// Check that the room id matches the original one for the session. This stops // Check that the room id matches the original one for the session. This stops
// the HS pretending a message was targeting a different room. // the HS pretending a message was targeting a different room.
if (roomId == session.roomId) { if (TextUtils.equals(roomId, session.roomId)) {
var errorMessage = ""
var decryptResult: OlmInboundGroupSession.DecryptMessageResult? = null var decryptResult: OlmInboundGroupSession.DecryptMessageResult? = null
try { try {
decryptResult = session.olmInboundGroupSession!!.decryptMessage(body) decryptResult = session.olmInboundGroupSession!!.decryptMessage(body)
} catch (e: OlmException) { } catch (e: Exception) {
Timber.e(e, "## decryptGroupMessage () : decryptMessage failed") Timber.e(e, "## decryptGroupMessage () : decryptMessage failed")
return@flatMap Try.Failure(MXCryptoError.OlmError(e)) errorMessage = e.message ?: ""
} }
if (null != decryptResult) {
if (null != timeline) { if (null != timeline) {
if (!inboundGroupSessionMessageIndexes.containsKey(timeline)) { if (!inboundGroupSessionMessageIndexes.containsKey(timeline)) {
inboundGroupSessionMessageIndexes[timeline] = HashMap() inboundGroupSessionMessageIndexes[timeline] = HashMap()
@ -669,40 +674,51 @@ internal class MXOlmDevice @Inject constructor(
val messageIndexKey = senderKey + "|" + sessionId + "|" + decryptResult.mIndex val messageIndexKey = senderKey + "|" + sessionId + "|" + decryptResult.mIndex
if (inboundGroupSessionMessageIndexes[timeline]?.get(messageIndexKey) != null) { if (null != inboundGroupSessionMessageIndexes[timeline]!![messageIndexKey]) {
val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex) val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex)
Timber.e("## decryptGroupMessage() : $reason") Timber.e("## decryptGroupMessage() : $reason")
return@flatMap Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason)) throw MXDecryptionException(MXCryptoError(MXCryptoError.DUPLICATED_MESSAGE_INDEX_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, reason))
} }
inboundGroupSessionMessageIndexes[timeline]!!.put(messageIndexKey, true) inboundGroupSessionMessageIndexes[timeline]!!.put(messageIndexKey, true)
} }
store.storeInboundGroupSessions(listOf(session)) store.storeInboundGroupSessions(listOf(session))
val payload = try { try {
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE) val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage) val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage)
adapter.fromJson(payloadString) val payload = adapter.fromJson(payloadString)
result.payload = payload
} catch (e: Exception) { } catch (e: Exception) {
Timber.e("## decryptGroupMessage() : fails to parse the payload") Timber.e(e, "## decryptGroupMessage() : RLEncoder.encode failed " + e.message)
return@flatMap Try.Failure( return null
MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON))
} }
return@flatMap Try.just( if (null == result.payload) {
OlmDecryptionResult( Timber.e("## decryptGroupMessage() : fails to parse the payload")
payload, return null
session.keysClaimed, }
senderKey,
session.forwardingCurve25519KeyChain result.keysClaimed = session.keysClaimed
) result.senderKey = senderKey
) result.forwardingCurve25519KeyChain = session.forwardingCurve25519KeyChain
} else {
Timber.e("## decryptGroupMessage() : failed to decode the message")
throw MXDecryptionException(MXCryptoError(MXCryptoError.OLM_ERROR_CODE, errorMessage, null))
}
} else { } else {
val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId) val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
Timber.e("## decryptGroupMessage() : $reason") Timber.e("## decryptGroupMessage() : $reason")
return@flatMap Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason)) throw MXDecryptionException(MXCryptoError(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, reason))
} }
} else {
Timber.e("## decryptGroupMessage() : Cannot retrieve inbound group session $sessionId")
throw MXDecryptionException(inboundGroupSessionWithIdError)
} }
return result
} }
/** /**
@ -716,7 +732,7 @@ internal class MXOlmDevice @Inject constructor(
} }
} }
// Utilities // Utilities
/** /**
* Verify an ed25519 signature on a JSON object. * Verify an ed25519 signature on a JSON object.
@ -729,7 +745,7 @@ internal class MXOlmDevice @Inject constructor(
@Throws(Exception::class) @Throws(Exception::class)
fun verifySignature(key: String, jsonDictionary: Map<String, Any>, signature: String) { fun verifySignature(key: String, jsonDictionary: Map<String, Any>, signature: String) {
// Check signature on the canonical version of the JSON // Check signature on the canonical version of the JSON
olmUtility!!.verifyEd25519Signature(signature, key, JsonCanonicalizer.getCanonicalJson(Map::class.java, jsonDictionary)) olmUtility!!.verifyEd25519Signature(signature, key, MoshiProvider.getCanonicalJson<Map<*, *>>(Map::class.java, jsonDictionary))
} }
/** /**
@ -751,9 +767,9 @@ internal class MXOlmDevice @Inject constructor(
*/ */
private fun getSessionForDevice(theirDeviceIdentityKey: String, sessionId: String): OlmSessionWrapper? { private fun getSessionForDevice(theirDeviceIdentityKey: String, sessionId: String): OlmSessionWrapper? {
// sanity check // sanity check
return if (theirDeviceIdentityKey.isEmpty() || sessionId.isEmpty()) null else { return if (!TextUtils.isEmpty(theirDeviceIdentityKey) && !TextUtils.isEmpty(sessionId)) {
store.getDeviceSession(sessionId, theirDeviceIdentityKey) store.getDeviceSession(sessionId, theirDeviceIdentityKey)
} } else null
} }
@ -766,27 +782,26 @@ internal class MXOlmDevice @Inject constructor(
* @param senderKey the base64-encoded curve25519 key of the sender. * @param senderKey the base64-encoded curve25519 key of the sender.
* @return the inbound group session. * @return the inbound group session.
*/ */
fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): Try<OlmInboundGroupSessionWrapper> { fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): OlmInboundGroupSessionWrapper? {
if (sessionId.isNullOrBlank() || senderKey.isNullOrBlank()) { inboundGroupSessionWithIdError = null
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY, MXCryptoError.ERROR_MISSING_PROPERTY_REASON))
}
val session = store.getInboundGroupSession(sessionId, senderKey) val session = store.getInboundGroupSession(sessionId!!, senderKey!!)
return if (null != session) { if (null != session) {
// Check that the room id matches the original one for the session. This stops // Check that the room id matches the original one for the session. This stops
// the HS pretending a message was targeting a different room. // the HS pretending a message was targeting a different room.
if (!TextUtils.equals(roomId, session.roomId)) { if (!TextUtils.equals(roomId, session.roomId)) {
val errorDescription = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId) val errorDescription = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
Timber.e("## getInboundGroupSession() : $errorDescription") Timber.e("## getInboundGroupSession() : $errorDescription")
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, errorDescription)) inboundGroupSessionWithIdError = MXCryptoError(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_ERROR_CODE,
} else { MXCryptoError.UNABLE_TO_DECRYPT, errorDescription)
Try.just(session)
} }
} else { } else {
Timber.e("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId") Timber.e("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId")
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON)) inboundGroupSessionWithIdError = MXCryptoError(MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE,
MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON, null)
} }
return session
} }
/** /**
@ -798,6 +813,6 @@ internal class MXOlmDevice @Inject constructor(
* @return true if the unbound session keys are known. * @return true if the unbound session keys are known.
*/ */
fun hasInboundSessionKeys(roomId: String, senderKey: String, sessionId: String): Boolean { fun hasInboundSessionKeys(roomId: String, senderKey: String, sessionId: String): Boolean {
return getInboundGroupSession(sessionId, senderKey, roomId).isSuccess() return null != getInboundGroupSession(sessionId, senderKey, roomId)
} }
} }

View File

@ -44,9 +44,7 @@ internal class ObjectSigner @Inject constructor(private val credentials: Credent
val content = HashMap<String, String>() val content = HashMap<String, String>()
content["ed25519:" + credentials.deviceId] = olmDevice.signMessage(strToSign)!!
content["ed25519:" + credentials.deviceId] = olmDevice.signMessage(strToSign)
?: "" //null reported by rageshake if happens during logout
result[credentials.userId] = content result[credentials.userId] = content

View File

@ -22,8 +22,8 @@ import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.crypto.model.MXKey import im.vector.matrix.android.internal.crypto.model.MXKey
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.util.JsonCanonicalizer
import org.matrix.olm.OlmAccount import org.matrix.olm.OlmAccount
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
@ -150,7 +150,7 @@ internal class OneTimeKeysUploader @Inject constructor(
val oneTimeKeys = olmDevice.getOneTimeKeys() val oneTimeKeys = olmDevice.getOneTimeKeys()
val oneTimeJson = HashMap<String, Any>() val oneTimeJson = HashMap<String, Any>()
val curve25519Map = oneTimeKeys?.get(OlmAccount.JSON_KEY_ONE_TIME_KEY) val curve25519Map = oneTimeKeys!![OlmAccount.JSON_KEY_ONE_TIME_KEY]
if (null != curve25519Map) { if (null != curve25519Map) {
for (key_id in curve25519Map.keys) { for (key_id in curve25519Map.keys) {
@ -158,7 +158,7 @@ internal class OneTimeKeysUploader @Inject constructor(
k["key"] = curve25519Map.getValue(key_id) k["key"] = curve25519Map.getValue(key_id)
// the key is also signed // the key is also signed
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, k) val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, k)
k["signatures"] = objectSigner.signObject(canonicalJson) k["signatures"] = objectSigner.signObject(canonicalJson)

View File

@ -17,6 +17,7 @@
package im.vector.matrix.android.internal.crypto package im.vector.matrix.android.internal.crypto
import android.os.Handler
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
@ -27,11 +28,9 @@ import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.TaskThread
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.createBackgroundHandler
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean import java.util.*
import javax.inject.Inject import javax.inject.Inject
@SessionScope @SessionScope
@ -48,7 +47,7 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
// sanity check to ensure that we don't end up with two concurrent runs // sanity check to ensure that we don't end up with two concurrent runs
// of sendOutgoingRoomKeyRequestsTimer // of sendOutgoingRoomKeyRequestsTimer
private val sendOutgoingRoomKeyRequestsRunning = AtomicBoolean(false) private var sendOutgoingRoomKeyRequestsRunning: Boolean = false
/** /**
* Called when the client is started. Sets background processes running. * Called when the client is started. Sets background processes running.
@ -102,10 +101,8 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
* @param requestBody requestBody * @param requestBody requestBody
*/ */
fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody) { fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody) {
BACKGROUND_HANDLER.post {
cancelRoomKeyRequest(requestBody, false) cancelRoomKeyRequest(requestBody, false)
} }
}
/** /**
* Cancel room key requests, if any match the given details, and resend * Cancel room key requests, if any match the given details, and resend
@ -113,10 +110,8 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
* @param requestBody requestBody * @param requestBody requestBody
*/ */
fun resendRoomKeyRequest(requestBody: RoomKeyRequestBody) { fun resendRoomKeyRequest(requestBody: RoomKeyRequestBody) {
BACKGROUND_HANDLER.post {
cancelRoomKeyRequest(requestBody, true) cancelRoomKeyRequest(requestBody, true)
} }
}
/** /**
* Cancel room key requests, if any match the given details, and resend * Cancel room key requests, if any match the given details, and resend
@ -131,17 +126,12 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
Timber.v("cancelRoomKeyRequest: requestId: " + req.requestId + " state: " + req.state + " andResend: " + andResend) Timber.v("cancelRoomKeyRequest: requestId: " + req.requestId + " state: " + req.state + " andResend: " + andResend)
when (req.state) { if (req.state === OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING || req.state === OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND) {
OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING,
OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND -> {
// nothing to do here // nothing to do here
} } else if (req.state === OutgoingRoomKeyRequest.RequestState.UNSENT || req.state === OutgoingRoomKeyRequest.RequestState.FAILED) {
OutgoingRoomKeyRequest.RequestState.UNSENT,
OutgoingRoomKeyRequest.RequestState.FAILED -> {
Timber.v("## cancelRoomKeyRequest() : deleting unnecessary room key request for $requestBody") Timber.v("## cancelRoomKeyRequest() : deleting unnecessary room key request for $requestBody")
cryptoStore.deleteOutgoingRoomKeyRequest(req.requestId) cryptoStore.deleteOutgoingRoomKeyRequest(req.requestId)
} } else if (req.state === OutgoingRoomKeyRequest.RequestState.SENT) {
OutgoingRoomKeyRequest.RequestState.SENT -> {
if (andResend) { if (andResend) {
req.state = OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND req.state = OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND
} else { } else {
@ -152,23 +142,22 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
sendOutgoingRoomKeyRequestCancellation(req) sendOutgoingRoomKeyRequestCancellation(req)
} }
} }
}
/** /**
* Start the background timer to send queued requests, if the timer isn't already running. * Start the background timer to send queued requests, if the timer isn't already running.
*/ */
private fun startTimer() { private fun startTimer() {
if (sendOutgoingRoomKeyRequestsRunning.get()) { if (sendOutgoingRoomKeyRequestsRunning) {
return return
} }
BACKGROUND_HANDLER.postDelayed(Runnable { Handler().postDelayed(Runnable {
if (sendOutgoingRoomKeyRequestsRunning.get()) { if (sendOutgoingRoomKeyRequestsRunning) {
Timber.v("## startTimer() : RoomKeyRequestSend already in progress!") Timber.v("## startTimer() : RoomKeyRequestSend already in progress!")
return@Runnable return@Runnable
} }
sendOutgoingRoomKeyRequestsRunning.set(true) sendOutgoingRoomKeyRequestsRunning = true
sendOutgoingRoomKeyRequests() sendOutgoingRoomKeyRequests()
}, SEND_KEY_REQUESTS_DELAY_MS.toLong()) }, SEND_KEY_REQUESTS_DELAY_MS.toLong())
} }
@ -178,19 +167,19 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
// timer will be restarted before the promise resolves). // timer will be restarted before the promise resolves).
private fun sendOutgoingRoomKeyRequests() { private fun sendOutgoingRoomKeyRequests() {
if (!isClientRunning) { if (!isClientRunning) {
sendOutgoingRoomKeyRequestsRunning.set(false) sendOutgoingRoomKeyRequestsRunning = false
return return
} }
Timber.v("## sendOutgoingRoomKeyRequests() : Looking for queued outgoing room key requests") Timber.v("## sendOutgoingRoomKeyRequests() : Looking for queued outgoing room key requests")
val outgoingRoomKeyRequest = cryptoStore.getOutgoingRoomKeyRequestByState( val outgoingRoomKeyRequest = cryptoStore.getOutgoingRoomKeyRequestByState(
setOf(OutgoingRoomKeyRequest.RequestState.UNSENT, HashSet<OutgoingRoomKeyRequest.RequestState>(Arrays.asList<OutgoingRoomKeyRequest.RequestState>(OutgoingRoomKeyRequest.RequestState.UNSENT,
OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING, OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING,
OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND)) OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND)))
if (null == outgoingRoomKeyRequest) { if (null == outgoingRoomKeyRequest) {
Timber.e("## sendOutgoingRoomKeyRequests() : No more outgoing room key requests") Timber.e("## sendOutgoingRoomKeyRequests() : No more outgoing room key requests")
sendOutgoingRoomKeyRequestsRunning.set(false) sendOutgoingRoomKeyRequestsRunning = false
return return
} }
@ -224,7 +213,7 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
cryptoStore.updateOutgoingRoomKeyRequest(request) cryptoStore.updateOutgoingRoomKeyRequest(request)
} }
sendOutgoingRoomKeyRequestsRunning.set(false) sendOutgoingRoomKeyRequestsRunning = false
startTimer() startTimer()
} }
@ -257,7 +246,7 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
sendMessageToDevices(roomKeyShareCancellation, request.recipients, request.cancellationTxnId, object : MatrixCallback<Unit> { sendMessageToDevices(roomKeyShareCancellation, request.recipients, request.cancellationTxnId, object : MatrixCallback<Unit> {
private fun onDone() { private fun onDone() {
cryptoStore.deleteOutgoingRoomKeyRequest(request.requestId) cryptoStore.deleteOutgoingRoomKeyRequest(request.requestId)
sendOutgoingRoomKeyRequestsRunning.set(false) sendOutgoingRoomKeyRequestsRunning = false
startTimer() startTimer()
} }
@ -296,20 +285,15 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
val contentMap = MXUsersDevicesMap<Any>() val contentMap = MXUsersDevicesMap<Any>()
for (recipient in recipients) { for (recipient in recipients) {
// TODO Change this two hard coded key to something better contentMap.setObject(message, recipient["userId"], recipient["deviceId"]) // TODO Change this two hard coded key to something better
contentMap.setObject(recipient["userId"], recipient["deviceId"], message)
} }
sendToDeviceTask.configureWith(SendToDeviceTask.Params(EventType.ROOM_KEY_REQUEST, contentMap, transactionId)) sendToDeviceTask.configureWith(SendToDeviceTask.Params(EventType.ROOM_KEY_REQUEST, contentMap, transactionId))
.dispatchTo(callback) .dispatchTo(callback)
.executeOn(TaskThread.CALLER)
.callbackOn(TaskThread.CALLER)
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }
companion object { companion object {
private const val SEND_KEY_REQUESTS_DELAY_MS = 500 private const val SEND_KEY_REQUESTS_DELAY_MS = 500
private val BACKGROUND_HANDLER = createBackgroundHandler("OutgoingRoomKeyRequest")
} }
} }

View File

@ -24,6 +24,7 @@ import im.vector.matrix.android.internal.crypto.model.MXKey
import im.vector.matrix.android.internal.crypto.model.MXOlmSessionResult import im.vector.matrix.android.internal.crypto.model.MXOlmSessionResult
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask import im.vector.matrix.android.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
import im.vector.matrix.android.internal.session.SessionScope
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
@ -53,7 +54,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
} }
val olmSessionResult = MXOlmSessionResult(deviceInfo, sessionId) val olmSessionResult = MXOlmSessionResult(deviceInfo, sessionId)
results.setObject(userId, deviceId, olmSessionResult) results.setObject(olmSessionResult, userId, deviceId)
} }
} }
@ -67,7 +68,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
val oneTimeKeyAlgorithm = MXKey.KEY_SIGNED_CURVE_25519_TYPE val oneTimeKeyAlgorithm = MXKey.KEY_SIGNED_CURVE_25519_TYPE
for (device in devicesWithoutSession) { for (device in devicesWithoutSession) {
usersDevicesToClaim.setObject(device.userId, device.deviceId, oneTimeKeyAlgorithm) usersDevicesToClaim.setObject(oneTimeKeyAlgorithm, device.userId, device.deviceId)
} }
// TODO: this has a race condition - if we try to send another message // TODO: this has a race condition - if we try to send another message
@ -90,12 +91,12 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
val deviceIds = it.getUserDeviceIds(userId) val deviceIds = it.getUserDeviceIds(userId)
if (null != deviceIds) { if (null != deviceIds) {
for (deviceId in deviceIds) { for (deviceId in deviceIds) {
val olmSessionResult = results.getObject(userId, deviceId) val olmSessionResult = results.getObject(deviceId, userId)
if (olmSessionResult!!.sessionId != null) { if (olmSessionResult!!.sessionId != null) {
// We already have a result for this device // We already have a result for this device
continue continue
} }
val key = it.getObject(userId, deviceId) val key = it.getObject(deviceId, userId)
if (key?.type == oneTimeKeyAlgorithm) { if (key?.type == oneTimeKeyAlgorithm) {
oneTimeKey = key oneTimeKey = key
} }
@ -125,14 +126,12 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
var isVerified = false var isVerified = false
var errorMessage: String? = null var errorMessage: String? = null
if (signature != null) {
try { try {
olmDevice.verifySignature(deviceInfo.fingerprint()!!, oneTimeKey.signalableJSONDictionary(), signature) olmDevice.verifySignature(deviceInfo.fingerprint()!!, oneTimeKey.signalableJSONDictionary(), signature)
isVerified = true isVerified = true
} catch (e: Exception) { } catch (e: Exception) {
errorMessage = e.message errorMessage = e.message
} }
}
// Check one-time key signature // Check one-time key signature
if (isVerified) { if (isVerified) {

View File

@ -22,7 +22,8 @@ import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_OLM
import im.vector.matrix.android.internal.crypto.MXOlmDevice import im.vector.matrix.android.internal.crypto.MXOlmDevice
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.EncryptedMessage import im.vector.matrix.android.internal.crypto.model.rest.EncryptedMessage
import im.vector.matrix.android.internal.util.JsonCanonicalizer import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.util.convertToUTF8 import im.vector.matrix.android.internal.util.convertToUTF8
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
@ -80,7 +81,10 @@ internal class MessageEncrypter @Inject constructor(private val credentials: Cre
recipientsKeysMap["ed25519"] = deviceInfo.fingerprint()!! recipientsKeysMap["ed25519"] = deviceInfo.fingerprint()!!
payloadJson["recipient_keys"] = recipientsKeysMap payloadJson["recipient_keys"] = recipientsKeysMap
val payloadString = convertToUTF8(JsonCanonicalizer.getCanonicalJson(Map::class.java, payloadJson)) // FIXME We have to canonicalize the JSON
//JsonUtility.canonicalize(JsonUtility.getGson(false).toJsonTree(payloadJson)).toString()
val payloadString = convertToUTF8(MoshiProvider.getCanonicalJson(Map::class.java, payloadJson))
ciphertext[deviceKey] = olmDevice.encryptMessage(deviceKey, sessionId!!, payloadString!!)!! ciphertext[deviceKey] = olmDevice.encryptMessage(deviceKey, sessionId!!, payloadString!!)!!
} }
} }

View File

@ -17,9 +17,9 @@
package im.vector.matrix.android.internal.crypto.algorithms package im.vector.matrix.android.internal.crypto.algorithms
import arrow.core.Try
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.MXDecryptionException
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup
@ -33,9 +33,11 @@ internal interface IMXDecrypting {
* *
* @param event the raw event. * @param event the raw event.
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
* @return the decryption information, or an error * @return the decryption information, or null in case of error
* @throws MXDecryptionException the decryption failure reason
*/ */
suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> @Throws(MXDecryptionException::class)
suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult?
/** /**
* Handle a key event. * Handle a key event.

View File

@ -18,6 +18,7 @@
package im.vector.matrix.android.internal.crypto.algorithms package im.vector.matrix.android.internal.crypto.algorithms
import arrow.core.Try import arrow.core.Try
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Content
/** /**

View File

@ -14,35 +14,32 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.matrix.android.internal.crypto.algorithms.olm package im.vector.matrix.android.internal.crypto.algorithms
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.api.util.JsonDict
/** /**
* This class represents the decryption result. * This class represents the decryption result.
*/ */
@JsonClass(generateAdapter = true) data class MXDecryptionResult(
data class OlmDecryptionResult(
/** /**
* The decrypted payload (with properties 'type', 'content') * The decrypted payload (with properties 'type', 'content')
*/ */
@Json(name = "payload") val payload: JsonDict? = null, var payload: JsonDict? = null,
/** /**
* keys that the sender of the event claims ownership of: * keys that the sender of the event claims ownership of:
* map from key type to base64-encoded key. * map from key type to base64-encoded key.
*/ */
@Json(name = "keysClaimed") val keysClaimed: Map<String, String>? = null, var keysClaimed: Map<String, String>? = null,
/** /**
* The curve25519 key that the sender of the event is known to have ownership of. * The curve25519 key that the sender of the event is known to have ownership of.
*/ */
@Json(name = "senderKey") val senderKey: String? = null, var senderKey: String? = null,
/** /**
* Devices which forwarded this session to us (normally empty). * Devices which forwarded this session to us (normally empty).
*/ */
@Json(name = "forwardingCurve25519KeyChain") val forwardingCurve25519KeyChain: List<String>? = null var forwardingCurve25519KeyChain: List<String>? = null
) )

View File

@ -28,6 +28,7 @@ import im.vector.matrix.android.internal.crypto.*
import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
import im.vector.matrix.android.internal.crypto.algorithms.IMXDecrypting import im.vector.matrix.android.internal.crypto.algorithms.IMXDecrypting
import im.vector.matrix.android.internal.crypto.algorithms.MXDecryptionResult
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo 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.MXUsersDevicesMap
@ -42,7 +43,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
import kotlin.collections.HashMap
internal class MXMegolmDecryption(private val credentials: Credentials, internal class MXMegolmDecryption(private val credentials: Credentials,
private val olmDevice: MXOlmDevice, private val olmDevice: MXOlmDevice,
@ -63,77 +63,67 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
*/ */
private var pendingEvents: MutableMap<String /* senderKey|sessionId */, MutableMap<String /* timelineId */, MutableList<Event>>> = HashMap() private var pendingEvents: MutableMap<String /* senderKey|sessionId */, MutableMap<String /* timelineId */, MutableList<Event>>> = HashMap()
override suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> { @Throws(MXDecryptionException::class)
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult? {
return decryptEvent(event, timeline, true) return decryptEvent(event, timeline, true)
} }
private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): Try<MXEventDecryptionResult> { @Throws(MXDecryptionException::class)
if (event.roomId.isNullOrBlank()) { private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): MXEventDecryptionResult? {
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)) val encryptedEventContent = event.content.toModel<EncryptedEventContent>()!!
if (TextUtils.isEmpty(encryptedEventContent.senderKey) || TextUtils.isEmpty(encryptedEventContent.sessionId) || TextUtils.isEmpty(encryptedEventContent.ciphertext)) {
throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_FIELDS_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_FIELDS_REASON))
} }
val encryptedEventContent = event.content.toModel<EncryptedEventContent>() var eventDecryptionResult: MXEventDecryptionResult? = null
?: return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)) var cryptoError: MXCryptoError? = null
var decryptGroupMessageResult: MXDecryptionResult? = null
if (encryptedEventContent.senderKey.isNullOrBlank() try {
|| encryptedEventContent.sessionId.isNullOrBlank() decryptGroupMessageResult = olmDevice.decryptGroupMessage(encryptedEventContent.ciphertext!!, event.roomId!!, timeline, encryptedEventContent.sessionId!!, encryptedEventContent.senderKey!!)
|| encryptedEventContent.ciphertext.isNullOrBlank()) { } catch (e: MXDecryptionException) {
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)) cryptoError = e.cryptoError
}
// the decryption succeeds
if (decryptGroupMessageResult?.payload != null && cryptoError == null) {
eventDecryptionResult = MXEventDecryptionResult()
eventDecryptionResult.clearEvent = decryptGroupMessageResult.payload
eventDecryptionResult.senderCurve25519Key = decryptGroupMessageResult.senderKey
if (null != decryptGroupMessageResult.keysClaimed) {
eventDecryptionResult.claimedEd25519Key = decryptGroupMessageResult.keysClaimed!!["ed25519"]
} }
return olmDevice.decryptGroupMessage(encryptedEventContent.ciphertext, eventDecryptionResult.forwardingCurve25519KeyChain = decryptGroupMessageResult.forwardingCurve25519KeyChain!!
event.roomId, } else if (cryptoError != null) {
timeline, if (cryptoError.isOlmError) {
encryptedEventContent.sessionId, if (MXCryptoError.UNKNOWN_MESSAGE_INDEX == cryptoError.message) {
encryptedEventContent.senderKey)
.fold(
{ throwable ->
if (throwable is MXCryptoError.OlmError) {
// TODO Check the value of .message
if (throwable.olmException.message == "UNKNOWN_MESSAGE_INDEX") {
addEventToPendingList(event, timeline) addEventToPendingList(event, timeline)
if (requestKeysOnFail) { if (requestKeysOnFail) {
requestKeysForEvent(event) requestKeysForEvent(event)
} }
} }
val reason = String.format(MXCryptoError.OLM_REASON, throwable.olmException.message) val reason = String.format(MXCryptoError.OLM_REASON, cryptoError.message)
val detailedReason = String.format(MXCryptoError.DETAILED_OLM_REASON, encryptedEventContent.ciphertext, reason) val detailedReason = String.format(MXCryptoError.DETAILLED_OLM_REASON, encryptedEventContent.ciphertext, cryptoError.message)
Try.Failure(MXCryptoError.Base( throw MXDecryptionException(MXCryptoError(
MXCryptoError.ErrorType.OLM, MXCryptoError.OLM_ERROR_CODE,
reason, reason,
detailedReason)) detailedReason))
} } else if (TextUtils.equals(cryptoError.code, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE)) {
if (throwable is MXCryptoError.Base) {
if (throwable.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
addEventToPendingList(event, timeline) addEventToPendingList(event, timeline)
if (requestKeysOnFail) { if (requestKeysOnFail) {
requestKeysForEvent(event) requestKeysForEvent(event)
} }
} }
throw MXDecryptionException(cryptoError)
} }
Try.Failure(throwable) return eventDecryptionResult
},
{ olmDecryptionResult ->
// the decryption succeeds
if (olmDecryptionResult.payload != null) {
Try.just(
MXEventDecryptionResult(
clearEvent = olmDecryptionResult.payload,
senderCurve25519Key = olmDecryptionResult.senderKey,
claimedEd25519Key = olmDecryptionResult.keysClaimed?.get("ed25519"),
forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain ?: emptyList()
)
)
} else {
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON))
} }
}
)
}
/** /**
* Helper for the real decryptEvent and for _retryDecryption. If * Helper for the real decryptEvent and for _retryDecryption. If
@ -149,8 +139,7 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
val recipients = ArrayList<Map<String, String>>() val recipients = ArrayList<Map<String, String>>()
val selfMap = HashMap<String, String>() val selfMap = HashMap<String, String>()
// TODO Replace this hard coded keys (see OutgoingRoomKeyRequestManager) selfMap["userId"] = credentials.userId // TODO Replace this hard coded keys (see OutgoingRoomKeyRequestManager)
selfMap["userId"] = credentials.userId
selfMap["deviceId"] = "*" selfMap["deviceId"] = "*"
recipients.add(selfMap) recipients.add(selfMap)
@ -182,18 +171,17 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
val encryptedEventContent = event.content.toModel<EncryptedEventContent>() ?: return val encryptedEventContent = event.content.toModel<EncryptedEventContent>() ?: return
val pendingEventsKey = "${encryptedEventContent.senderKey}|${encryptedEventContent.sessionId}" val pendingEventsKey = "${encryptedEventContent.senderKey}|${encryptedEventContent.sessionId}"
if (!pendingEvents.containsKey(pendingEventsKey)) { if (!pendingEvents.containsKey(pendingEventsKey)) {
pendingEvents[pendingEventsKey] = HashMap() pendingEvents[pendingEventsKey] = HashMap()
} }
if (pendingEvents[pendingEventsKey]?.containsKey(timelineId) == false) { if (!pendingEvents[pendingEventsKey]!!.containsKey(timelineId)) {
pendingEvents[pendingEventsKey]?.put(timelineId, ArrayList()) pendingEvents[pendingEventsKey]!![timelineId] = ArrayList()
} }
if (pendingEvents[pendingEventsKey]?.get(timelineId)?.contains(event) == false) { if (pendingEvents[pendingEventsKey]!![timelineId]!!.indexOf(event) < 0) {
Timber.v("## addEventToPendingList() : add Event " + event.eventId + " in room id " + event.roomId) Timber.v("## addEventToPendingList() : add Event " + event.eventId + " in room id " + event.roomId)
pendingEvents[pendingEventsKey]?.get(timelineId)?.add(event) pendingEvents[pendingEventsKey]!![timelineId]!!.add(event)
} }
} }
@ -208,20 +196,21 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
var senderKey: String? = event.getSenderKey() var senderKey: String? = event.getSenderKey()
var keysClaimed: MutableMap<String, String> = HashMap() var keysClaimed: MutableMap<String, String> = HashMap()
val forwardingCurve25519KeyChain: MutableList<String> = ArrayList() var forwardingCurve25519KeyChain: MutableList<String> = ArrayList()
if (TextUtils.isEmpty(roomKeyContent.roomId) || TextUtils.isEmpty(roomKeyContent.sessionId) || TextUtils.isEmpty(roomKeyContent.sessionKey)) { if (TextUtils.isEmpty(roomKeyContent.roomId) || TextUtils.isEmpty(roomKeyContent.sessionId) || TextUtils.isEmpty(roomKeyContent.sessionKey)) {
Timber.e("## onRoomKeyEvent() : Key event is missing fields") Timber.e("## onRoomKeyEvent() : Key event is missing fields")
return return
} }
if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) { if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) {
Timber.v("## onRoomKeyEvent(), forward adding key : roomId ${roomKeyContent.roomId}" + Timber.v("## onRoomKeyEvent(), forward adding key : roomId " + roomKeyContent.roomId + " sessionId " + roomKeyContent.sessionId
" sessionId ${roomKeyContent.sessionId} sessionKey ${roomKeyContent.sessionKey}") + " sessionKey " + roomKeyContent.sessionKey) // from " + event);
val forwardedRoomKeyContent = event.getClearContent().toModel<ForwardedRoomKeyContent>() val forwardedRoomKeyContent = event.getClearContent().toModel<ForwardedRoomKeyContent>()
?: return ?: return
forwardingCurve25519KeyChain = if (forwardedRoomKeyContent.forwardingCurve25519KeyChain == null) {
forwardedRoomKeyContent.forwardingCurve25519KeyChain?.let { ArrayList()
forwardingCurve25519KeyChain.addAll(it) } else {
ArrayList(forwardedRoomKeyContent.forwardingCurve25519KeyChain)
} }
if (senderKey == null) { if (senderKey == null) {
@ -264,13 +253,7 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
return return
} }
val added = olmDevice.addInboundGroupSession(roomKeyContent.sessionId, val added = olmDevice.addInboundGroupSession(roomKeyContent.sessionId, roomKeyContent.sessionKey, roomKeyContent.roomId, senderKey, forwardingCurve25519KeyChain, keysClaimed, exportFormat)
roomKeyContent.sessionKey,
roomKeyContent.roomId,
senderKey,
forwardingCurve25519KeyChain,
keysClaimed,
exportFormat)
if (added) { if (added) {
keysBackup.maybeBackupKeys() keysBackup.maybeBackupKeys()
@ -300,10 +283,8 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
} }
override fun hasKeysForKeyRequest(request: IncomingRoomKeyRequest): Boolean { override fun hasKeysForKeyRequest(request: IncomingRoomKeyRequest): Boolean {
val roomId = request.requestBody?.roomId ?: return false return (null != request.requestBody
val senderKey = request.requestBody?.senderKey ?: return false && olmDevice.hasInboundSessionKeys(request.requestBody!!.roomId!!, request.requestBody!!.senderKey!!, request.requestBody!!.sessionId!!))
val sessionId = request.requestBody?.sessionId ?: return false
return olmDevice.hasInboundSessionKeys(roomId, senderKey, sessionId)
} }
override fun shareKeysWithDevice(request: IncomingRoomKeyRequest) { override fun shareKeysWithDevice(request: IncomingRoomKeyRequest) {
@ -311,13 +292,13 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
if (request.requestBody == null) { if (request.requestBody == null) {
return return
} }
val userId = request.userId ?: return val userId = request.userId!!
CoroutineScope(coroutineDispatchers.crypto).launch { CoroutineScope(coroutineDispatchers.crypto).launch {
deviceListManager deviceListManager
.downloadKeys(listOf(userId), false) .downloadKeys(listOf(userId), false)
.flatMap { .flatMap {
val deviceId = request.deviceId val deviceId = request.deviceId
val deviceInfo = cryptoStore.getUserDevice(deviceId ?: "", userId) val deviceInfo = cryptoStore.getUserDevice(deviceId!!, userId)
if (deviceInfo == null) { if (deviceInfo == null) {
throw RuntimeException() throw RuntimeException()
} else { } else {
@ -327,36 +308,29 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
.handle(devicesByUser) .handle(devicesByUser)
.flatMap { .flatMap {
val body = request.requestBody val body = request.requestBody
val olmSessionResult = it.getObject(userId, deviceId) val olmSessionResult = it.getObject(deviceId, userId)
if (olmSessionResult?.sessionId == null) { if (olmSessionResult?.sessionId == null) {
// no session with this device, probably because there // no session with this device, probably because there
// were no one-time keys. // were no one-time keys.
Try.just(Unit) Try.just(Unit)
} }
Timber.v("## shareKeysWithDevice() : sharing keys for session" + Timber.v("## shareKeysWithDevice() : sharing keys for session " + body!!.senderKey + "|" + body.sessionId
" ${body?.senderKey}|${body?.sessionId} with device $userId:$deviceId") + " with device " + userId + ":" + deviceId)
val inboundGroupSession = olmDevice.getInboundGroupSession(body.sessionId, body.senderKey, body.roomId)
val payloadJson = HashMap<String, Any>() val payloadJson = HashMap<String, Any>()
payloadJson["type"] = EventType.FORWARDED_ROOM_KEY payloadJson["type"] = EventType.FORWARDED_ROOM_KEY
payloadJson["content"] = inboundGroupSession!!.exportKeys()!!
olmDevice.getInboundGroupSession(body?.sessionId, body?.senderKey, body?.roomId)
.fold(
{
// TODO
},
{
// TODO
payloadJson["content"] = it.exportKeys() ?: ""
}
)
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, Arrays.asList(deviceInfo)) val encodedPayload = messageEncrypter.encryptMessage(payloadJson, Arrays.asList(deviceInfo))
val sendToDeviceMap = MXUsersDevicesMap<Any>() val sendToDeviceMap = MXUsersDevicesMap<Any>()
sendToDeviceMap.setObject(userId, deviceId, encodedPayload) sendToDeviceMap.setObject(encodedPayload, userId, deviceId)
Timber.v("## shareKeysWithDevice() : sending to $userId:$deviceId") Timber.v("## shareKeysWithDevice() : sending to $userId:$deviceId")
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
sendToDeviceTask.execute(sendToDeviceParams) sendToDeviceTask.execute(sendToDeviceParams)
} }
} }
} }
} }

View File

@ -21,6 +21,7 @@ package im.vector.matrix.android.internal.crypto.algorithms.megolm
import android.text.TextUtils import android.text.TextUtils
import arrow.core.Try import arrow.core.Try
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
@ -36,7 +37,7 @@ import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
import im.vector.matrix.android.internal.util.JsonCanonicalizer import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.util.convertToUTF8 import im.vector.matrix.android.internal.util.convertToUTF8
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
@ -117,8 +118,8 @@ internal class MXMegolmEncryption(
for (userId in userIds) { for (userId in userIds) {
val deviceIds = devicesInRoom.getUserDeviceIds(userId) val deviceIds = devicesInRoom.getUserDeviceIds(userId)
for (deviceId in deviceIds!!) { for (deviceId in deviceIds!!) {
val deviceInfo = devicesInRoom.getObject(userId, deviceId) val deviceInfo = devicesInRoom.getObject(deviceId, userId)
if (deviceInfo != null && null == safeSession.sharedWithDevices.getObject(userId, deviceId)) { if (null == safeSession.sharedWithDevices.getObject(deviceId, userId)) {
if (!shareMap.containsKey(userId)) { if (!shareMap.containsKey(userId)) {
shareMap[userId] = ArrayList() shareMap[userId] = ArrayList()
} }
@ -200,7 +201,7 @@ internal class MXMegolmEncryption(
for (userId in userIds) { for (userId in userIds) {
val devicesToShareWith = devicesByUser[userId] val devicesToShareWith = devicesByUser[userId]
for ((deviceID) in devicesToShareWith!!) { for ((deviceID) in devicesToShareWith!!) {
val sessionResult = it.getObject(userId, deviceID) val sessionResult = it.getObject(deviceID, userId)
if (sessionResult?.sessionId == null) { if (sessionResult?.sessionId == null) {
// no session with this device, probably because there // no session with this device, probably because there
// were no one-time keys. // were no one-time keys.
@ -217,7 +218,7 @@ internal class MXMegolmEncryption(
} }
Timber.v("## shareUserDevicesKey() : Sharing keys with device $userId:$deviceID") Timber.v("## shareUserDevicesKey() : Sharing keys with device $userId:$deviceID")
//noinspection ArraysAsListWithZeroOrOneArgument,ArraysAsListWithZeroOrOneArgument //noinspection ArraysAsListWithZeroOrOneArgument,ArraysAsListWithZeroOrOneArgument
contentMap.setObject(userId, deviceID, messageEncrypter.encryptMessage(payload, Arrays.asList(sessionResult.deviceInfo))) contentMap.setObject(messageEncrypter.encryptMessage(payload, Arrays.asList(sessionResult.deviceInfo)), userId, deviceID)
haveTargets = true haveTargets = true
} }
} }
@ -238,7 +239,7 @@ internal class MXMegolmEncryption(
for (userId in devicesByUser.keys) { for (userId in devicesByUser.keys) {
val devicesToShareWith = devicesByUser[userId] val devicesToShareWith = devicesByUser[userId]
for ((deviceId) in devicesToShareWith!!) { for ((deviceId) in devicesToShareWith!!) {
session.sharedWithDevices.setObject(userId, deviceId, chainIndex) session.sharedWithDevices.setObject(chainIndex, userId, deviceId)
} }
} }
Unit Unit
@ -253,8 +254,7 @@ internal class MXMegolmEncryption(
/** /**
* process the pending encryptions * process the pending encryptions
*/ */
private fun encryptContent(session: MXOutboundSessionInfo, eventType: String, eventContent: Content): Try<Content> { private suspend fun encryptContent(session: MXOutboundSessionInfo, eventType: String, eventContent: Content) = Try<Content> {
return Try<Content> {
// Everything is in place, encrypt all pending events // Everything is in place, encrypt all pending events
val payloadJson = HashMap<String, Any>() val payloadJson = HashMap<String, Any>()
payloadJson["room_id"] = roomId payloadJson["room_id"] = roomId
@ -263,7 +263,7 @@ internal class MXMegolmEncryption(
// Get canonical Json from // Get canonical Json from
val payloadString = convertToUTF8(JsonCanonicalizer.getCanonicalJson(Map::class.java, payloadJson)) val payloadString = convertToUTF8(MoshiProvider.getCanonicalJson(Map::class.java, payloadJson))
val ciphertext = olmDevice.encryptGroupMessage(session.sessionId, payloadString!!) val ciphertext = olmDevice.encryptGroupMessage(session.sessionId, payloadString!!)
val map = HashMap<String, Any>() val map = HashMap<String, Any>()
@ -278,7 +278,6 @@ internal class MXMegolmEncryption(
session.useCount++ session.useCount++
map map
} }
}
/** /**
* Get the list of devices which can encrypt data to. * Get the list of devices which can encrypt data to.
@ -304,10 +303,10 @@ internal class MXMegolmEncryption(
for (userId in it.userIds) { for (userId in it.userIds) {
val deviceIds = it.getUserDeviceIds(userId) ?: continue val deviceIds = it.getUserDeviceIds(userId) ?: continue
for (deviceId in deviceIds) { for (deviceId in deviceIds) {
val deviceInfo = it.getObject(userId, deviceId) ?: continue val deviceInfo = it.getObject(deviceId, userId) ?: continue
if (warnOnUnknownDevicesRepository.warnOnUnknownDevices() && deviceInfo.isUnknown) { if (warnOnUnknownDevicesRepository.warnOnUnknownDevices() && deviceInfo.isUnknown) {
// The device is not yet known by the user // The device is not yet known by the user
unknownDevices.setObject(userId, deviceId, deviceInfo) unknownDevices.setObject(deviceInfo, userId, deviceId)
continue continue
} }
if (deviceInfo.isBlocked) { if (deviceInfo.isBlocked) {
@ -323,13 +322,15 @@ internal class MXMegolmEncryption(
// Don't bother sending to ourself // Don't bother sending to ourself
continue continue
} }
devicesInRoom.setObject(userId, deviceId, deviceInfo) devicesInRoom.setObject(deviceInfo, userId, deviceId)
} }
} }
if (unknownDevices.isEmpty) { if (unknownDevices.isEmpty) {
Try.just(devicesInRoom) Try.just(devicesInRoom)
} else { } else {
Try.Failure(MXCryptoError.UnknownDevice(unknownDevices)) val cryptoError = MXCryptoError(MXCryptoError.UNKNOWN_DEVICES_CODE,
MXCryptoError.UNABLE_TO_ENCRYPT, MXCryptoError.UNKNOWN_DEVICES_REASON, unknownDevices)
Try.Failure(Failure.CryptoError(cryptoError))
} }
} }
} }

View File

@ -25,6 +25,8 @@ import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup
import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.TaskExecutor
import javax.inject.Inject import javax.inject.Inject
internal class MXMegolmEncryptionFactory @Inject constructor( internal class MXMegolmEncryptionFactory @Inject constructor(
@ -35,6 +37,8 @@ internal class MXMegolmEncryptionFactory @Inject constructor(
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction, private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
private val credentials: Credentials, private val credentials: Credentials,
private val sendToDeviceTask: SendToDeviceTask, private val sendToDeviceTask: SendToDeviceTask,
// FIXME Why taskExecutor is not used?
private val taskExecutor: TaskExecutor,
private val messageEncrypter: MessageEncrypter, private val messageEncrypter: MessageEncrypter,
private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository) { private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository) {

View File

@ -64,7 +64,7 @@ internal class MXOutboundSessionInfo(
val deviceIds = sharedWithDevices.getUserDeviceIds(userId) val deviceIds = sharedWithDevices.getUserDeviceIds(userId)
for (deviceId in deviceIds!!) { for (deviceId in deviceIds!!) {
if (null == devicesInRoom.getObject(userId, deviceId)) { if (null == devicesInRoom.getObject(deviceId, userId)) {
Timber.v("## sharedWithTooManyDevices() : Starting new session because we shared with $userId:$deviceId") Timber.v("## sharedWithTooManyDevices() : Starting new session because we shared with $userId:$deviceId")
return true return true
} }

View File

@ -17,13 +17,14 @@
package im.vector.matrix.android.internal.crypto.algorithms.olm package im.vector.matrix.android.internal.crypto.algorithms.olm
import arrow.core.Try import android.text.TextUtils
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.session.events.model.Event 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.api.session.events.model.toModel
import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE
import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.internal.crypto.MXDecryptionException
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.crypto.MXOlmDevice import im.vector.matrix.android.internal.crypto.MXOlmDevice
import im.vector.matrix.android.internal.crypto.algorithms.IMXDecrypting import im.vector.matrix.android.internal.crypto.algorithms.IMXDecrypting
@ -41,120 +42,111 @@ internal class MXOlmDecryption(
private val credentials: Credentials) private val credentials: Credentials)
: IMXDecrypting { : IMXDecrypting {
override suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> { @Throws(MXDecryptionException::class)
val olmEventContent = event.content.toModel<OlmEventContent>() ?: run { override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult? {
Timber.e("## decryptEvent() : bad event format") val olmEventContent = event.content.toModel<OlmEventContent>()!!
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_EVENT_FORMAT,
MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON))
}
val cipherText = olmEventContent.ciphertext ?: run { if (null == olmEventContent.ciphertext) {
Timber.e("## decryptEvent() : missing cipher text") Timber.e("## decryptEvent() : missing cipher text")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_CIPHER_TEXT, throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_CIPHER_TEXT_ERROR_CODE,
MXCryptoError.MISSING_CIPHER_TEXT_REASON)) MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_CIPHER_TEXT_REASON))
} }
val senderKey = olmEventContent.senderKey ?: run { if (!olmEventContent.ciphertext!!.containsKey(olmDevice.deviceCurve25519Key)) {
Timber.e("## decryptEvent() : missing sender key") Timber.e("## decryptEvent() : our device " + olmDevice.deviceCurve25519Key
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY, + " is not included in recipients. Event")
MXCryptoError.MISSING_SENDER_KEY_TEXT_REASON)) throw MXDecryptionException(MXCryptoError(MXCryptoError.NOT_INCLUDE_IN_RECIPIENTS_ERROR_CODE,
} MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.NOT_INCLUDED_IN_RECIPIENT_REASON))
val messageAny = cipherText[olmDevice.deviceCurve25519Key] ?: run {
Timber.e("## decryptEvent() : our device ${olmDevice.deviceCurve25519Key} is not included in recipients")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.NOT_INCLUDE_IN_RECIPIENTS,
MXCryptoError.NOT_INCLUDED_IN_RECIPIENT_REASON))
} }
// The message for myUser // The message for myUser
val message = messageAny as JsonDict val message = olmEventContent.ciphertext!![olmDevice.deviceCurve25519Key] as JsonDict
val decryptedPayload = decryptMessage(message, olmEventContent.senderKey!!)
val decryptedPayload = decryptMessage(message, senderKey)
if (decryptedPayload == null) { if (decryptedPayload == null) {
Timber.e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey") Timber.e("## decryptEvent() Failed to decrypt Olm event (id= " + event.eventId + " ) from " + olmEventContent.senderKey)
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_ENCRYPTED_MESSAGE_ERROR_CODE,
MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)) MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON))
} }
val payloadString = convertFromUTF8(decryptedPayload) val payloadString = convertFromUTF8(decryptedPayload)
if (payloadString == null) { if (payloadString == null) {
Timber.e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey") Timber.e("## decryptEvent() Failed to decrypt Olm event (id= " + event.eventId + " ) from " + olmEventContent.senderKey)
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_ENCRYPTED_MESSAGE_ERROR_CODE,
MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)) MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON))
} }
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE) val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
val payload = adapter.fromJson(payloadString) val payload = adapter.fromJson(payloadString)
if (payload == null) { if (payload == null) {
Timber.e("## decryptEvent failed : null payload") Timber.e("## decryptEvent failed : null payload")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, throw MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE,
MXCryptoError.MISSING_CIPHER_TEXT_REASON)) MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_CIPHER_TEXT_REASON))
} }
val olmPayloadContent = OlmPayloadContent.fromJsonString(payloadString) ?: run { val olmPayloadContent = OlmPayloadContent.fromJsonString(payloadString)
Timber.e("## decryptEvent() : bad olmPayloadContent format")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT,
MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON))
}
if (olmPayloadContent.recipient.isNullOrBlank()) { if (TextUtils.isEmpty(olmPayloadContent.recipient)) {
val reason = String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient") val reason = String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient")
Timber.e("## decryptEvent() : $reason") Timber.e("## decryptEvent() : $reason")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY, throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_PROPERTY_ERROR_CODE,
reason)) MXCryptoError.UNABLE_TO_DECRYPT, reason))
} }
if (olmPayloadContent.recipient != credentials.userId) { if (!TextUtils.equals(olmPayloadContent.recipient, credentials.userId)) {
Timber.e("## decryptEvent() : Event ${event.eventId}:" + Timber.e("## decryptEvent() : Event " + event.eventId + ": Intended recipient " + olmPayloadContent.recipient
" Intended recipient ${olmPayloadContent.recipient} does not match our id ${credentials.userId}") + " does not match our id " + credentials.userId)
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT, throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_RECIPIENT_ERROR_CODE,
String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient))) MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient)))
} }
val recipientKeys = olmPayloadContent.recipient_keys ?: run { if (null == olmPayloadContent.recipient_keys) {
Timber.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'recipient_keys' property; cannot prevent unknown-key attack") Timber.e("## decryptEvent() : Olm event (id=" + event.eventId
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY, + ") contains no " + "'recipient_keys' property; cannot prevent unknown-key attack")
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys"))) throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_PROPERTY_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys")))
} }
val ed25519 = recipientKeys["ed25519"] val ed25519 = olmPayloadContent.recipient_keys!!.get("ed25519")
if (ed25519 != olmDevice.deviceEd25519Key) { if (!TextUtils.equals(ed25519, olmDevice.deviceEd25519Key)) {
Timber.e("## decryptEvent() : Event ${event.eventId}: Intended recipient ed25519 key $ed25519 did not match ours") Timber.e("## decryptEvent() : Event " + event.eventId + ": Intended recipient ed25519 key " + ed25519 + " did not match ours")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT_KEY, throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_RECIPIENT_KEY_ERROR_CODE,
MXCryptoError.BAD_RECIPIENT_KEY_REASON)) MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.BAD_RECIPIENT_KEY_REASON))
} }
if (olmPayloadContent.sender.isNullOrBlank()) { if (TextUtils.isEmpty(olmPayloadContent.sender)) {
Timber.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'sender' property; cannot prevent unknown-key attack") Timber.e("## decryptEvent() : Olm event (id=" + event.eventId
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY, + ") contains no 'sender' property; cannot prevent unknown-key attack")
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender"))) throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_PROPERTY_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender")))
} }
if (olmPayloadContent.sender != event.senderId) { if (!TextUtils.equals(olmPayloadContent.sender, event.senderId)) {
Timber.e("Event ${event.eventId}: original sender ${olmPayloadContent.sender} does not match reported sender ${event.senderId}") Timber.e("Event " + event.eventId + ": original sender " + olmPayloadContent.sender
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.FORWARDED_MESSAGE, + " does not match reported sender " + event.senderId)
String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender))) throw MXDecryptionException(MXCryptoError(MXCryptoError.FORWARDED_MESSAGE_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender)))
} }
if (olmPayloadContent.room_id != event.roomId) { if (!TextUtils.equals(olmPayloadContent.room_id, event.roomId)) {
Timber.e("## decryptEvent() : Event ${event.eventId}: original room ${olmPayloadContent.room_id} does not match reported room ${event.roomId}") Timber.e("## decryptEvent() : Event " + event.eventId + ": original room " + olmPayloadContent.room_id
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ROOM, + " does not match reported room " + event.roomId)
String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.room_id))) throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_ROOM_ERROR_CODE,
MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.room_id)))
} }
val keys = olmPayloadContent.keys ?: run { if (null == olmPayloadContent.keys) {
Timber.e("## decryptEvent failed : null keys") Timber.e("## decryptEvent failed : null keys")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, throw MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE,
MXCryptoError.MISSING_CIPHER_TEXT_REASON)) MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_CIPHER_TEXT_REASON))
} }
return Try.just(MXEventDecryptionResult( val result = MXEventDecryptionResult()
clearEvent = payload, result.clearEvent = payload
senderCurve25519Key = senderKey, result.senderCurve25519Key = olmEventContent.senderKey
claimedEd25519Key = keys["ed25519"] result.claimedEd25519Key = olmPayloadContent.keys!!.get("ed25519")
))
return result
} }
/** /**
@ -175,7 +167,7 @@ internal class MXOlmDecryption(
sessionIds = ArrayList(sessionIdsSet) sessionIds = ArrayList(sessionIdsSet)
} }
val messageBody = message["body"] as? String val messageBody = message["body"] as String?
var messageType: Int? = null var messageType: Int? = null
val typeAsVoid = message["type"] val typeAsVoid = message["type"]
@ -218,7 +210,7 @@ internal class MXOlmDecryption(
// not a prekey message, so it should have matched an existing session, but it // not a prekey message, so it should have matched an existing session, but it
// didn't work. // didn't work.
if (sessionIds.isEmpty()) { if (sessionIds.size == 0) {
Timber.e("## decryptMessage() : No existing sessions") Timber.e("## decryptMessage() : No existing sessions")
} else { } else {
Timber.e("## decryptMessage() : Error decrypting non-prekey message with existing sessions") Timber.e("## decryptMessage() : Error decrypting non-prekey message with existing sessions")
@ -236,7 +228,7 @@ internal class MXOlmDecryption(
return null return null
} }
Timber.v("## decryptMessage() : Created new inbound Olm session get id ${res["session_id"]} with $theirDeviceIdentityKey") Timber.v("## decryptMessage() : Created new inbound Olm session get id " + res["session_id"] + " with " + theirDeviceIdentityKey)
return res["payload"] return res["payload"]
} }

View File

@ -24,11 +24,10 @@ import kotlinx.android.parcel.Parcelize
fun EncryptedFileInfo.toElementToDecrypt(): ElementToDecrypt? { fun EncryptedFileInfo.toElementToDecrypt(): ElementToDecrypt? {
// Check the validity of some fields // Check the validity of some fields
if (isValid()) { if (isValid()) {
// It's valid so the data are here
return ElementToDecrypt( return ElementToDecrypt(
iv = this.iv ?: "", iv = this.iv!!,
k = this.key?.k ?: "", k = this.key!!.k!!,
sha256 = this.hashes?.get("sha256") ?: "" sha256 = this.hashes!!["sha256"] ?: error("")
) )
} }

View File

@ -16,8 +16,8 @@
package im.vector.matrix.android.internal.crypto.attachments package im.vector.matrix.android.internal.crypto.attachments
import android.text.TextUtils
import android.util.Base64 import android.util.Base64
import arrow.core.Try
import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileKey import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileKey
import timber.log.Timber import timber.log.Timber
@ -51,7 +51,7 @@ object MXEncryptedAttachments {
* @param mimetype the mime type * @param mimetype the mime type
* @return the encryption file info * @return the encryption file info
*/ */
fun encryptAttachment(attachmentStream: InputStream, mimetype: String): Try<EncryptionResult> { fun encryptAttachment(attachmentStream: InputStream, mimetype: String): EncryptionResult? {
val t0 = System.currentTimeMillis() val t0 = System.currentTimeMillis()
val secureRandom = SecureRandom() val secureRandom = SecureRandom()
@ -115,21 +115,23 @@ object MXEncryptedAttachments {
encryptedByteArray = outStream.toByteArray() encryptedByteArray = outStream.toByteArray()
) )
outStream.close()
Timber.v("Encrypt in " + (System.currentTimeMillis() - t0) + " ms") Timber.v("Encrypt in " + (System.currentTimeMillis() - t0) + " ms")
return Try.just(result) return result
} catch (oom: OutOfMemoryError) { } catch (oom: OutOfMemoryError) {
Timber.e(oom, "## encryptAttachment failed") Timber.e(oom, "## encryptAttachment failed " + oom.message)
return Try.Failure(oom)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "## encryptAttachment failed") Timber.e(e, "## encryptAttachment failed " + e.message)
return Try.Failure(e) }
} finally {
try { try {
outStream.close() outStream.close()
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "## encryptAttachment() : fail to close outStream") Timber.e(e, "## encryptAttachment() : fail to close outStream")
} }
}
return null
} }
/** /**
@ -197,7 +199,7 @@ object MXEncryptedAttachments {
val currentDigestValue = base64ToUnpaddedBase64(Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT)) val currentDigestValue = base64ToUnpaddedBase64(Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT))
if (elementToDecrypt.sha256 != currentDigestValue) { if (!TextUtils.equals(elementToDecrypt.sha256, currentDigestValue)) {
Timber.e("## decryptAttachment() : Digest value mismatch") Timber.e("## decryptAttachment() : Digest value mismatch")
outStream.close() outStream.close()
return null return null

View File

@ -45,18 +45,17 @@ import im.vector.matrix.android.internal.crypto.keysbackup.tasks.*
import im.vector.matrix.android.internal.crypto.keysbackup.util.computeRecoveryKey import im.vector.matrix.android.internal.crypto.keysbackup.util.computeRecoveryKey
import im.vector.matrix.android.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey import im.vector.matrix.android.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult 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 im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.extensions.foldToCallback import im.vector.matrix.android.internal.extensions.foldToCallback
import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.*
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.TaskThread import im.vector.matrix.android.internal.task.TaskThread
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.JsonCanonicalizer
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -178,7 +177,7 @@ internal class KeysBackup @Inject constructor(
megolmBackupAuthData.publicKey = publicKey megolmBackupAuthData.publicKey = publicKey
} }
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, megolmBackupAuthData.signalableJSONDictionary()) val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, megolmBackupAuthData.signalableJSONDictionary())
megolmBackupAuthData.signatures = objectSigner.signObject(canonicalJson) megolmBackupAuthData.signatures = objectSigner.signObject(canonicalJson)
@ -389,8 +388,8 @@ internal class KeysBackup @Inject constructor(
return keysBackupVersionTrust return keysBackupVersionTrust
} }
val mySigs = authData.signatures?.get(myUserId) val mySigs: Map<String, *> = authData.signatures!![myUserId] as Map<String, *>
if (mySigs.isNullOrEmpty()) { if (mySigs.isEmpty()) {
Timber.v("getKeysBackupTrust: Ignoring key backup because it lacks any signatures from this user") Timber.v("getKeysBackupTrust: Ignoring key backup because it lacks any signatures from this user")
return keysBackupVersionTrust return keysBackupVersionTrust
} }
@ -403,22 +402,21 @@ internal class KeysBackup @Inject constructor(
deviceId = components[1] deviceId = components[1]
} }
var device: MXDeviceInfo? = null
if (deviceId != null) { if (deviceId != null) {
val device = cryptoStore.getUserDevice(deviceId, myUserId) device = cryptoStore.getUserDevice(deviceId, myUserId)
var isSignatureValid = false var isSignatureValid = false
if (device == null) { if (device == null) {
Timber.v("getKeysBackupTrust: Signature from unknown device $deviceId") Timber.v("getKeysBackupTrust: Signature from unknown device $deviceId")
} else { } else {
val fingerprint = device.fingerprint()
if (fingerprint != null) {
try { try {
olmDevice.verifySignature(fingerprint, authData.signalableJSONDictionary(), mySigs[keyId] as String) olmDevice.verifySignature(device.fingerprint()!!, authData.signalableJSONDictionary(), mySigs[keyId] as String)
isSignatureValid = true isSignatureValid = true
} catch (e: OlmException) { } catch (e: OlmException) {
Timber.v("getKeysBackupTrust: Bad signature from device " + device.deviceId + " " + e.localizedMessage) Timber.v("getKeysBackupTrust: Bad signature from device " + device.deviceId + " " + e.localizedMessage)
} }
}
if (isSignatureValid && device.isVerified) { if (isSignatureValid && device.isVerified) {
keysBackupVersionTrust.usable = true keysBackupVersionTrust.usable = true
@ -454,11 +452,12 @@ internal class KeysBackup @Inject constructor(
val myUserId = credentials.userId val myUserId = credentials.userId
// Get current signatures, or create an empty set // Get current signatures, or create an empty set
val myUserSignatures = authData.signatures?.get(myUserId)?.toMutableMap() ?: HashMap() val myUserSignatures = (authData.signatures!![myUserId]?.toMutableMap()
?: HashMap())
if (trust) { if (trust) {
// Add current device signature // Add current device signature
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, authData.signalableJSONDictionary()) val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, authData.signalableJSONDictionary())
val deviceSignatures = objectSigner.signObject(canonicalJson) val deviceSignatures = objectSigner.signObject(canonicalJson)
@ -667,8 +666,7 @@ internal class KeysBackup @Inject constructor(
// Do not trigger a backup for them if they come from the backup version we are using // Do not trigger a backup for them if they come from the backup version we are using
val backUp = keysVersionResult.version != keysBackupVersion?.version val backUp = keysVersionResult.version != keysBackupVersion?.version
if (backUp) { if (backUp) {
Timber.v("restoreKeysWithRecoveryKey: Those keys will be backed up to backup version: " Timber.v("restoreKeysWithRecoveryKey: Those keys will be backed up to backup version: " + keysBackupVersion?.version)
+ keysBackupVersion?.version)
} }
// Import them into the crypto store // Import them into the crypto store
@ -877,7 +875,7 @@ internal class KeysBackup @Inject constructor(
override fun getCurrentVersion(callback: MatrixCallback<KeysVersionResult?>) { override fun getCurrentVersion(callback: MatrixCallback<KeysVersionResult?>) {
getKeysBackupLastVersionTask getKeysBackupLastVersionTask
.toConfigurableTask() .configureWith(Unit)
.dispatchTo(object : MatrixCallback<KeysVersionResult> { .dispatchTo(object : MatrixCallback<KeysVersionResult> {
override fun onSuccess(data: KeysVersionResult) { override fun onSuccess(data: KeysVersionResult) {
callback.onSuccess(data) callback.onSuccess(data)
@ -1029,7 +1027,8 @@ internal class KeysBackup @Inject constructor(
val authData = keysBackupData.getAuthDataAsMegolmBackupAuthData() val authData = keysBackupData.getAuthDataAsMegolmBackupAuthData()
if (authData?.signatures == null || authData.publicKey.isBlank()) { if (authData.signatures == null
|| authData.publicKey.isBlank()) {
return null return null
} }
@ -1227,8 +1226,7 @@ internal class KeysBackup @Inject constructor(
} }
try { try {
keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId]!! keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId]!!.sessionIdToKeyBackupData[olmInboundGroupSessionWrapper.olmInboundGroupSession!!.sessionIdentifier()] = keyBackupData
.sessionIdToKeyBackupData[olmInboundGroupSessionWrapper.olmInboundGroupSession!!.sessionIdentifier()] = keyBackupData
} catch (e: OlmException) { } catch (e: OlmException) {
Timber.e(e, "OlmException") Timber.e(e, "OlmException")
} }
@ -1280,8 +1278,7 @@ internal class KeysBackup @Inject constructor(
// Do not stay in KeysBackupState.WrongBackUpVersion but check what is available on the homeserver // Do not stay in KeysBackupState.WrongBackUpVersion but check what is available on the homeserver
checkAndStartKeysBackup() checkAndStartKeysBackup()
} }
else -> else -> // Come back to the ready state so that we will retry on the next received key
// Come back to the ready state so that we will retry on the next received key
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
} }
} }

View File

@ -54,9 +54,9 @@ open class KeysAlgorithmAndData {
/** /**
* Facility method to convert authData to a MegolmBackupAuthData object * Facility method to convert authData to a MegolmBackupAuthData object
*/ */
fun getAuthDataAsMegolmBackupAuthData(): MegolmBackupAuthData? { fun getAuthDataAsMegolmBackupAuthData(): MegolmBackupAuthData {
return MoshiProvider.providesMoshi() return MoshiProvider.providesMoshi()
.adapter(MegolmBackupAuthData::class.java) .adapter(MegolmBackupAuthData::class.java)
.fromJsonValue(authData) .fromJsonValue(authData)!!
} }
} }

View File

@ -18,6 +18,7 @@
package im.vector.matrix.android.internal.crypto.model package im.vector.matrix.android.internal.crypto.model
import android.text.TextUtils
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.api.util.JsonDict
@ -106,25 +107,30 @@ data class MXDeviceInfo(
* @return the fingerprint * @return the fingerprint
*/ */
fun fingerprint(): String? { fun fingerprint(): String? {
return keys return if (null != keys && !TextUtils.isEmpty(deviceId)) {
?.takeIf { !deviceId.isBlank() } keys!!["ed25519:$deviceId"]
?.get("ed25519:$deviceId") } else null
} }
/** /**
* @return the identity key * @return the identity key
*/ */
fun identityKey(): String? { fun identityKey(): String? {
return keys return if (null != keys && !TextUtils.isEmpty(deviceId)) {
?.takeIf { !deviceId.isBlank() } keys!!["curve25519:$deviceId"]
?.get("curve25519:$deviceId") } else null
} }
/** /**
* @return the display name * @return the display name
*/ */
fun displayName(): String? { fun displayName(): String? {
return unsigned?.get("device_display_name") as? String return if (null != unsigned) {
unsigned!!["device_display_name"] as String?
} else null
} }
/** /**
@ -135,7 +141,9 @@ data class MXDeviceInfo(
map["device_id"] = deviceId map["device_id"] = deviceId
map["user_id"] = userId if (null != userId) {
map["user_id"] = userId!!
}
if (null != algorithms) { if (null != algorithms) {
map["algorithms"] = algorithms!! map["algorithms"] = algorithms!!

View File

@ -20,7 +20,7 @@ import im.vector.matrix.android.api.session.events.model.Content
data class MXEncryptEventContentResult( data class MXEncryptEventContentResult(
/** /**
* The encrypted event content * The event content
*/ */
val eventContent: Content, val eventContent: Content,
/** /**

View File

@ -0,0 +1,139 @@
/*
* 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.internal.crypto.model;
import android.text.TextUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import timber.log.Timber;
public class MXKey implements Serializable {
/**
* Key types.
*/
public static final String KEY_CURVE_25519_TYPE = "curve25519";
public static final String KEY_SIGNED_CURVE_25519_TYPE = "signed_curve25519";
//public static final String KEY_ED_25519_TYPE = "ed25519";
/**
* The type of the key.
*/
public String type;
/**
* The id of the key.
*/
public String keyId;
/**
* The key.
*/
public String value;
/**
* signature user Id to [deviceid][signature]
*/
public Map<String, Map<String, String>> signatures;
/**
* Default constructor
*/
public MXKey() {
}
/**
* Convert a map to a MXKey
*
* @param map the map to convert
*/
public MXKey(Map<String, Map<String, Object>> map) {
if ((null != map) && (map.size() > 0)) {
List<String> mapKeys = new ArrayList<>(map.keySet());
String firstEntry = mapKeys.get(0);
setKeyFullId(firstEntry);
Map<String, Object> params = map.get(firstEntry);
value = (String) params.get("key");
signatures = (Map<String, Map<String, String>>) params.get("signatures");
}
}
/**
* @return the key full id
*/
public String getKeyFullId() {
return type + ":" + keyId;
}
/**
* Update the key fields with a key full id
*
* @param keyFullId the key full id
*/
private void setKeyFullId(String keyFullId) {
if (!TextUtils.isEmpty(keyFullId)) {
try {
String[] components = keyFullId.split(":");
if (components.length == 2) {
type = components[0];
keyId = components[1];
}
} catch (Exception e) {
Timber.e(e, "## setKeyFullId() failed");
}
}
}
/**
* @return the signed data map
*/
public Map<String, Object> signalableJSONDictionary() {
Map<String, Object> map = new HashMap<>();
if (null != value) {
map.put("key", value);
}
return map;
}
/**
* Returns a signature for an user Id and a signkey
*
* @param userId the user id
* @param signkey the sign key
* @return the signature
*/
public String signatureForUserId(String userId, String signkey) {
// sanity checks
if (!TextUtils.isEmpty(userId) && !TextUtils.isEmpty(signkey)) {
if ((null != signatures) && signatures.containsKey(userId)) {
return signatures.get(userId).get(signkey);
}
}
return null;
}
}

View File

@ -1,129 +0,0 @@
/*
* 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.model
import im.vector.matrix.android.api.util.JsonDict
import timber.log.Timber
import java.util.*
data class MXKey(
/**
* The type of the key (in the example: "signed_curve25519").
*/
val type: String,
/**
* The id of the key (in the example: "AAAAFw").
*/
private val keyId: String,
/**
* The key (in the example: "IjwIcskng7YjYcn0tS8TUOT2OHHtBSfMpcfIczCgXj4").
*/
val value: String,
/**
* signature user Id to [deviceid][signature]
*/
private val signatures: Map<String, Map<String, String>>
) {
/**
* @return the signed data map
*/
fun signalableJSONDictionary(): Map<String, Any> {
val map = HashMap<String, Any>()
map["key"] = value
return map
}
/**
* Returns a signature for an user Id and a signkey
*
* @param userId the user id
* @param signkey the sign key
* @return the signature
*/
fun signatureForUserId(userId: String, signkey: String): String? {
// sanity checks
if (userId.isNotBlank() && signkey.isNotBlank()) {
if (signatures.containsKey(userId)) {
return signatures[userId]?.get(signkey)
}
}
return null
}
companion object {
/**
* Key types.
*/
const val KEY_CURVE_25519_TYPE = "curve25519"
const val KEY_SIGNED_CURVE_25519_TYPE = "signed_curve25519"
// const val KEY_ED_25519_TYPE = "ed25519"
/**
* Convert a map to a MXKey
*
* @param map the map to convert
*
* Json Example:
*
* <pre>
* "signed_curve25519:AAAAFw": {
* "key": "IjwIcskng7YjYcn0tS8TUOT2OHHtBSfMpcfIczCgXj4",
* "signatures": {
* "@userId:matrix.org": {
* "ed25519:GMJRREOASV": "EUjp6pXzK9u3SDFR\/qLbzpOi3bEREeI6qMnKzXu992HsfuDDZftfJfiUXv9b\/Hqq1og4qM\/vCQJGTHAWMmgkCg"
* }
* }
* }
* </pre>
*
* into several val members
*/
fun from(map: Map<String, JsonDict>?): MXKey? {
if (map?.isNotEmpty() == true) {
val firstKey = map.keys.first()
val components = firstKey.split(":").dropLastWhile { it.isEmpty() }
if (components.size == 2) {
val params = map[firstKey]
if (params != null) {
if (params["key"] is String) {
return MXKey(
type = components[0],
keyId = components[1],
value = params["key"] as String,
signatures = params["signatures"] as Map<String, Map<String, String>>
)
}
}
}
}
// Error case
Timber.e("## Unable to parse map")
return null
}
}
}

View File

@ -0,0 +1,190 @@
/*
* 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.internal.crypto.model;
import android.text.TextUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class MXUsersDevicesMap<E> implements Serializable {
// The device keys as returned by the homeserver: a map of a map (userId -> deviceId -> Object).
private final Map<String, Map<String, E>> mMap = new HashMap<>();
/**
* @return the inner map
*/
public Map<String, Map<String, E>> getMap() {
return mMap;
}
/**
* Default constructor constructor
*/
public MXUsersDevicesMap() {
}
/**
* The constructor
*
* @param map the map
*/
public MXUsersDevicesMap(Map<String, Map<String, E>> map) {
if (null != map) {
Set<String> keys = map.keySet();
for (String key : keys) {
mMap.put(key, new HashMap<>(map.get(key)));
}
}
}
/**
* @return a deep copy
*/
public MXUsersDevicesMap<E> deepCopy() {
MXUsersDevicesMap<E> copy = new MXUsersDevicesMap<>();
Set<String> keys = mMap.keySet();
for (String key : keys) {
copy.mMap.put(key, new HashMap<>(mMap.get(key)));
}
return copy;
}
/**
* @return the user Ids
*/
public List<String> getUserIds() {
return new ArrayList<>(mMap.keySet());
}
/**
* Provides the device ids list for an user id
*
* @param userId the user id
* @return the device ids list
*/
public List<String> getUserDeviceIds(String userId) {
if (!TextUtils.isEmpty(userId) && mMap.containsKey(userId)) {
return new ArrayList<>(mMap.get(userId).keySet());
}
return null;
}
/**
* Provides the object for a device id and an user Id
*
* @param deviceId the device id
* @param userId the object id
* @return the object
*/
public E getObject(String deviceId, String userId) {
if (!TextUtils.isEmpty(userId) && mMap.containsKey(userId) && !TextUtils.isEmpty(deviceId)) {
return mMap.get(userId).get(deviceId);
}
return null;
}
/**
* Set an object for a dedicated user Id and device Id
*
* @param object the object to set
* @param userId the user Id
* @param deviceId the device id
*/
public void setObject(E object, String userId, String deviceId) {
if ((null != object) && !TextUtils.isEmpty(userId) && !TextUtils.isEmpty(deviceId)) {
Map<String, E> subMap = mMap.get(userId);
if (null == subMap) {
subMap = new HashMap<>();
mMap.put(userId, subMap);
}
subMap.put(deviceId, object);
}
}
/**
* Defines the objects map for an user Id
*
* @param objectsPerDevices the objects maps
* @param userId the user id
*/
public void setObjects(Map<String, E> objectsPerDevices, String userId) {
if (!TextUtils.isEmpty(userId)) {
if (null == objectsPerDevices) {
mMap.remove(userId);
} else {
mMap.put(userId, new HashMap<>(objectsPerDevices));
}
}
}
/**
* Removes objects for a dedicated user
*
* @param userId the user id.
*/
public void removeUserObjects(String userId) {
if (!TextUtils.isEmpty(userId)) {
mMap.remove(userId);
}
}
/**
* Clear the internal dictionary
*/
public void removeAllObjects() {
mMap.clear();
}
/**
* Add entries from another MXUsersDevicesMap
*
* @param other the other one
*/
public void addEntriesFromMap(MXUsersDevicesMap<E> other) {
if (null != other) {
mMap.putAll(other.getMap());
}
}
public boolean isEmpty(){
return mMap.isEmpty();
}
@Override
public String toString() {
if (null != mMap) {
return "MXUsersDevicesMap " + mMap.toString();
} else {
return "MXDeviceInfo : null map";
}
}
}

View File

@ -1,126 +0,0 @@
/*
* 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.model
import java.util.*
class MXUsersDevicesMap<E> {
// A map of maps (userId -> (deviceId -> Object)).
val map = HashMap<String /* userId */, HashMap<String /* deviceId */, E>>()
/**
* @return the user Ids
*/
val userIds: List<String>
get() = ArrayList(map.keys)
val isEmpty: Boolean
get() = map.isEmpty()
/**
* Provides the device ids list for a user id
* FIXME Should maybe return emptyList and not null, to avoid many !! in the code
*
* @param userId the user id
* @return the device ids list
*/
fun getUserDeviceIds(userId: String?): List<String>? {
return if (userId?.isNotBlank() == true && map.containsKey(userId)) {
map[userId]!!.keys.toList()
} else null
}
/**
* Provides the object for a device id and a user Id
*
* @param deviceId the device id
* @param userId the object id
* @return the object
*/
fun getObject(userId: String?, deviceId: String?): E? {
return if (userId?.isNotBlank() == true && deviceId?.isNotBlank() == true && map.containsKey(userId)) {
map[userId]?.get(deviceId)
} else null
}
/**
* Set an object for a dedicated user Id and device Id
*
* @param userId the user Id
* @param deviceId the device id
* @param o the object to set
*/
fun setObject(userId: String?, deviceId: String?, o: E?) {
if (null != o && userId?.isNotBlank() == true && deviceId?.isNotBlank() == true) {
if (map[userId] == null) {
map[userId] = HashMap()
}
map[userId]?.put(deviceId, o)
}
}
/**
* Defines the objects map for a user Id
*
* @param objectsPerDevices the objects maps
* @param userId the user id
*/
fun setObjects(userId: String?, objectsPerDevices: Map<String, E>?) {
if (userId?.isNotBlank() == true) {
if (null == objectsPerDevices) {
map.remove(userId)
} else {
map[userId] = HashMap(objectsPerDevices)
}
}
}
/**
* Removes objects for a dedicated user
*
* @param userId the user id.
*/
fun removeUserObjects(userId: String?) {
if (userId?.isNotBlank() == true) {
map.remove(userId)
}
}
/**
* Clear the internal dictionary
*/
fun removeAllObjects() {
map.clear()
}
/**
* Add entries from another MXUsersDevicesMap
*
* @param other the other one
*/
fun addEntriesFromMap(other: MXUsersDevicesMap<E>?) {
if (null != other) {
map.putAll(other.map)
}
}
override fun toString(): String {
return "MXUsersDevicesMap $map"
}
}

View File

@ -111,29 +111,27 @@ class OlmInboundGroupSessionWrapper : Serializable {
* @return the inbound group session as MegolmSessionData if the operation succeeds * @return the inbound group session as MegolmSessionData if the operation succeeds
*/ */
fun exportKeys(): MegolmSessionData? { fun exportKeys(): MegolmSessionData? {
return try { var megolmSessionData: MegolmSessionData? = MegolmSessionData()
try {
if (null == forwardingCurve25519KeyChain) { if (null == forwardingCurve25519KeyChain) {
forwardingCurve25519KeyChain = ArrayList() forwardingCurve25519KeyChain = ArrayList()
} }
if (keysClaimed == null) { megolmSessionData!!.senderClaimedEd25519Key = keysClaimed!!["ed25519"]
return null megolmSessionData.forwardingCurve25519KeyChain = ArrayList(forwardingCurve25519KeyChain!!)
megolmSessionData.senderKey = senderKey
megolmSessionData.senderClaimedKeys = keysClaimed
megolmSessionData.roomId = roomId
megolmSessionData.sessionId = olmInboundGroupSession!!.sessionIdentifier()
megolmSessionData.sessionKey = olmInboundGroupSession!!.export(olmInboundGroupSession!!.firstKnownIndex)
megolmSessionData.algorithm = MXCRYPTO_ALGORITHM_MEGOLM
} catch (e: Exception) {
megolmSessionData = null
Timber.e(e, "## export() : senderKey " + senderKey + " failed")
} }
MegolmSessionData().also { return megolmSessionData
it.senderClaimedEd25519Key = keysClaimed?.get("ed25519")
it.forwardingCurve25519KeyChain = ArrayList(forwardingCurve25519KeyChain!!)
it.senderKey = senderKey
it.senderClaimedKeys = keysClaimed
it.roomId = roomId
it.sessionId = olmInboundGroupSession!!.sessionIdentifier()
it.sessionKey = olmInboundGroupSession!!.export(olmInboundGroupSession!!.firstKnownIndex)
it.algorithm = MXCRYPTO_ALGORITHM_MEGOLM
}
} catch (e: Exception) {
Timber.e(e, "## export() : senderKey $senderKey failed")
null
}
} }
/** /**

View File

@ -53,8 +53,8 @@ data class OlmPayloadContent(
} }
companion object { companion object {
fun fromJsonString(str: String): OlmPayloadContent? { fun fromJsonString(str: String): OlmPayloadContent {
return MoshiProvider.providesMoshi().adapter(OlmPayloadContent::class.java).fromJson(str) return MoshiProvider.providesMoshi().adapter(OlmPayloadContent::class.java).fromJson(str)!!
} }
} }
} }

View File

@ -20,14 +20,11 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
/** /**
* This class represents the response to /keys/claim request made by claimOneTimeKeysForUsersDevices. * This class represents the response to /keys/query request made by claimOneTimeKeysForUsersDevices.
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class KeysClaimBody( data class KeysClaimBody(
/**
* The time (in milliseconds) to wait when downloading keys from remote servers. 10 seconds is the recommended default.
*/
@Json(name = "timeout") @Json(name = "timeout")
var timeout: Int? = null, var timeout: Int? = null,

View File

@ -20,7 +20,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
/** /**
* This class represents the response to /keys/claim request made by claimOneTimeKeysForUsersDevices. * This class represents the response to /keys/query request made by claimOneTimeKeysForUsersDevices.
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class KeysClaimResponse( data class KeysClaimResponse(

View File

@ -18,9 +18,8 @@ package im.vector.matrix.android.internal.crypto.model.rest
class SendToDeviceBody { class SendToDeviceBody {
// `Any` should implement SendToDeviceObject, but we cannot use interface here because of Gson serialization
/** /**
* `Any` should implement [SendToDeviceObject], but we cannot use interface here because of Json serialization
*
* The messages to send. A map from user ID, to a map from device ID to message body. * The messages to send. A map from user ID, to a map from device ID to message body.
* The device ID may also be *, meaning all known devices for the user. * The device ID may also be *, meaning all known devices for the user.
*/ */

View File

@ -31,7 +31,6 @@ import im.vector.matrix.android.internal.crypto.store.db.query.delete
import im.vector.matrix.android.internal.crypto.store.db.query.getById import im.vector.matrix.android.internal.crypto.store.db.query.getById
import im.vector.matrix.android.internal.crypto.store.db.query.getOrCreate import im.vector.matrix.android.internal.crypto.store.db.query.getOrCreate
import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.SessionScope
import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import io.realm.Sort import io.realm.Sort
import io.realm.kotlin.where import io.realm.kotlin.where
@ -40,17 +39,16 @@ import org.matrix.olm.OlmException
import timber.log.Timber import timber.log.Timber
import kotlin.collections.set import kotlin.collections.set
// enableFileEncryption is used to migrate the previous store
@SessionScope @SessionScope
internal class RealmCryptoStore(private val realmConfiguration: RealmConfiguration, internal class RealmCryptoStore(private val enableFileEncryption: Boolean = false,
private val realmConfiguration: RealmConfiguration,
private val credentials: Credentials) : IMXCryptoStore { private val credentials: Credentials) : IMXCryptoStore {
/* ========================================================================================== /* ==========================================================================================
* Memory cache, to correctly release JNI objects * Memory cache, to correctly release JNI objects
* ========================================================================================== */ * ========================================================================================== */
// A realm instance, for faster future getInstance. Do not use it
private var realmLocker: Realm? = null
// The olm account // The olm account
private var olmAccount: OlmAccount? = null private var olmAccount: OlmAccount? = null
@ -90,8 +88,6 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
} }
override fun open() { override fun open() {
realmLocker = Realm.getInstance(realmConfiguration)
// Ensure CryptoMetadataEntity is inserted in DB // Ensure CryptoMetadataEntity is inserted in DB
doWithRealm(realmConfiguration) { realm -> doWithRealm(realmConfiguration) { realm ->
var currentMetadata = realm.where<CryptoMetadataEntity>().findFirst() var currentMetadata = realm.where<CryptoMetadataEntity>().findFirst()
@ -137,9 +133,6 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
inboundGroupSessionToRelease.clear() inboundGroupSessionToRelease.clear()
olmAccount?.releaseAccount() olmAccount?.releaseAccount()
realmLocker?.close()
realmLocker = null
} }
override fun storeDeviceId(deviceId: String) { override fun storeDeviceId(deviceId: String) {

View File

@ -16,9 +16,9 @@
package im.vector.matrix.android.internal.crypto.store.db.model package im.vector.matrix.android.internal.crypto.store.db.model
import io.realm.RealmObject
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import io.realm.RealmObject
internal open class IncomingRoomKeyRequestEntity( internal open class IncomingRoomKeyRequestEntity(
var requestId: String? = null, var requestId: String? = null,
@ -32,11 +32,11 @@ internal open class IncomingRoomKeyRequestEntity(
) : RealmObject() { ) : RealmObject() {
fun toIncomingRoomKeyRequest(): IncomingRoomKeyRequest { fun toIncomingRoomKeyRequest(): IncomingRoomKeyRequest {
return IncomingRoomKeyRequest().also { return IncomingRoomKeyRequest().apply {
it.requestId = requestId requestId = requestId
it.userId = userId userId = userId
it.deviceId = deviceId deviceId = deviceId
it.requestBody = RoomKeyRequestBody().apply { requestBody = RoomKeyRequestBody().apply {
algorithm = requestBodyAlgorithm algorithm = requestBodyAlgorithm
roomId = requestBodyRoomId roomId = requestBodyRoomId
senderKey = requestBodySenderKey senderKey = requestBodySenderKey

View File

@ -23,8 +23,10 @@ import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.rest.KeysClaimBody import im.vector.matrix.android.internal.crypto.model.rest.KeysClaimBody
import im.vector.matrix.android.internal.crypto.model.rest.KeysClaimResponse import im.vector.matrix.android.internal.crypto.model.rest.KeysClaimResponse
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import timber.log.Timber import timber.log.Timber
import java.util.*
import javax.inject.Inject import javax.inject.Inject
internal interface ClaimOneTimeKeysForUsersDeviceTask : Task<ClaimOneTimeKeysForUsersDeviceTask.Params, MXUsersDevicesMap<MXKey>> { internal interface ClaimOneTimeKeysForUsersDeviceTask : Task<ClaimOneTimeKeysForUsersDeviceTask.Params, MXUsersDevicesMap<MXKey>> {
@ -44,27 +46,30 @@ internal class DefaultClaimOneTimeKeysForUsersDevice @Inject constructor(private
apiCall = cryptoApi.claimOneTimeKeysForUsersDevices(body) apiCall = cryptoApi.claimOneTimeKeysForUsersDevices(body)
}.flatMap { keysClaimResponse -> }.flatMap { keysClaimResponse ->
Try { Try {
val map = MXUsersDevicesMap<MXKey>() val map = HashMap<String, Map<String, MXKey>>()
keysClaimResponse.oneTimeKeys?.let { oneTimeKeys -> if (null != keysClaimResponse.oneTimeKeys) {
for (userId in oneTimeKeys.keys) { for (userId in keysClaimResponse.oneTimeKeys!!.keys) {
val mapByUserId = oneTimeKeys[userId] val mapByUserId = keysClaimResponse.oneTimeKeys!![userId]
if (mapByUserId != null) { val keysMap = HashMap<String, MXKey>()
for (deviceId in mapByUserId.keys) {
val mxKey = MXKey.from(mapByUserId[deviceId])
if (mxKey != null) { for (deviceId in mapByUserId!!.keys) {
map.setObject(userId, deviceId, mxKey) try {
} else { keysMap[deviceId] = MXKey(mapByUserId[deviceId])
Timber.e("## claimOneTimeKeysForUsersDevices : fail to create a MXKey") } catch (e: Exception) {
Timber.e(e, "## claimOneTimeKeysForUsersDevices : fail to create a MXKey ")
} }
} }
if (keysMap.size != 0) {
map[userId] = keysMap
} }
} }
} }
map MXUsersDevicesMap(map)
} }
} }
} }

View File

@ -18,7 +18,6 @@ package im.vector.matrix.android.internal.crypto.verification
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import dagger.Lazy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.sas.CancelCode import im.vector.matrix.android.api.session.crypto.sas.CancelCode
@ -56,7 +55,7 @@ import kotlin.collections.HashMap
@SessionScope @SessionScope
internal class DefaultSasVerificationService @Inject constructor(private val credentials: Credentials, internal class DefaultSasVerificationService @Inject constructor(private val credentials: Credentials,
private val cryptoStore: IMXCryptoStore, private val cryptoStore: IMXCryptoStore,
private val myDeviceInfoHolder: Lazy<MyDeviceInfoHolder>, private val myDeviceInfoHolder: MyDeviceInfoHolder,
private val deviceListManager: DeviceListManager, private val deviceListManager: DeviceListManager,
private val setDeviceVerificationAction: SetDeviceVerificationAction, private val setDeviceVerificationAction: SetDeviceVerificationAction,
private val sendToDeviceTask: SendToDeviceTask, private val sendToDeviceTask: SendToDeviceTask,
@ -198,7 +197,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
cryptoStore, cryptoStore,
sendToDeviceTask, sendToDeviceTask,
taskExecutor, taskExecutor,
myDeviceInfoHolder.get().myDevice.fingerprint()!!, myDeviceInfoHolder.myDevice.fingerprint()!!,
startReq.transactionID!!, startReq.transactionID!!,
otherUserId) otherUserId)
addTransaction(tx) addTransaction(tx)
@ -223,7 +222,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
.fold( .fold(
{ error() }, { error() },
{ {
if (it.getUserDeviceIds(otherUserId)?.contains(startReq.fromDevice) == true) { if (it.getUserDeviceIds(otherUserId).contains(startReq.fromDevice)) {
success(it) success(it)
} else { } else {
error() error()
@ -367,7 +366,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
cryptoStore, cryptoStore,
sendToDeviceTask, sendToDeviceTask,
taskExecutor, taskExecutor,
myDeviceInfoHolder.get().myDevice.fingerprint()!!, myDeviceInfoHolder.myDevice.fingerprint()!!,
txID, txID,
userId, userId,
deviceID) deviceID)
@ -410,7 +409,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
fun cancelTransaction(transactionId: String, userId: String, userDevice: String, code: CancelCode) { fun cancelTransaction(transactionId: String, userId: String, userDevice: String, code: CancelCode) {
val cancelMessage = KeyVerificationCancel.create(transactionId, code) val cancelMessage = KeyVerificationCancel.create(transactionId, code)
val contentMap = MXUsersDevicesMap<Any>() val contentMap = MXUsersDevicesMap<Any>()
contentMap.setObject(userId, userDevice, cancelMessage) contentMap.setObject(cancelMessage, userId, userDevice)
sendToDeviceTask.configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap, transactionId)) sendToDeviceTask.configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap, transactionId))
.dispatchTo(object : MatrixCallback<Unit> { .dispatchTo(object : MatrixCallback<Unit> {

View File

@ -29,8 +29,8 @@ import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationMac
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.util.JsonCanonicalizer
import timber.log.Timber import timber.log.Timber
internal class IncomingSASVerificationTransaction( internal class IncomingSASVerificationTransaction(
@ -147,7 +147,7 @@ internal class IncomingSASVerificationTransaction(
//The hash commitment is the hash (using the selected hash algorithm) of the unpadded base64 representation of QB, //The hash commitment is the hash (using the selected hash algorithm) of the unpadded base64 representation of QB,
// concatenated with the canonical JSON representation of the content of the m.key.verification.start message // concatenated with the canonical JSON representation of the content of the m.key.verification.start message
val concat = getSAS().publicKey + JsonCanonicalizer.getCanonicalJson(KeyVerificationStart::class.java, startReq!!) val concat = getSAS().publicKey + MoshiProvider.getCanonicalJson(KeyVerificationStart::class.java, startReq!!)
accept.commitment = hashUsingAgreedHashMethod(concat) ?: "" accept.commitment = hashUsingAgreedHashMethod(concat) ?: ""
//we need to send this to other device now //we need to send this to other device now
state = SasVerificationTxState.SendingAccept state = SasVerificationTxState.SendingAccept

View File

@ -21,14 +21,15 @@ import im.vector.matrix.android.api.session.crypto.sas.OutgoingSasVerificationRe
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
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.KeyVerificationAccept
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationKey import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationKey
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationMac import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationMac
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.util.JsonCanonicalizer
import timber.log.Timber import timber.log.Timber
internal class OutgoingSASVerificationRequest( internal class OutgoingSASVerificationRequest(
@ -101,6 +102,8 @@ internal class OutgoingSASVerificationRequest(
startMessage.shortAuthenticationStrings = KNOWN_SHORT_CODES startMessage.shortAuthenticationStrings = KNOWN_SHORT_CODES
startReq = startMessage startReq = startMessage
val contentMap = MXUsersDevicesMap<Any>()
contentMap.setObject(startMessage, otherUserId, otherDeviceId)
state = SasVerificationTxState.SendingStart state = SasVerificationTxState.SendingStart
sendToOther( sendToOther(
@ -164,7 +167,7 @@ internal class OutgoingSASVerificationRequest(
// in Bobs m.key.verification.key and the content of Alices m.key.verification.start message. // in Bobs m.key.verification.key and the content of Alices m.key.verification.start message.
//check commitment //check commitment
val concat = vKey.key + JsonCanonicalizer.getCanonicalJson(KeyVerificationStart::class.java, startReq!!) val concat = vKey.key + MoshiProvider.getCanonicalJson(KeyVerificationStart::class.java, startReq!!)
val otherCommitment = hashUsingAgreedHashMethod(concat) ?: "" val otherCommitment = hashUsingAgreedHashMethod(concat) ?: ""
if (accepted!!.commitment.equals(otherCommitment)) { if (accepted!!.commitment.equals(otherCommitment)) {

View File

@ -285,7 +285,7 @@ internal abstract class SASVerificationTransaction(
onErrorReason: CancelCode, onErrorReason: CancelCode,
onDone: (() -> Unit)?) { onDone: (() -> Unit)?) {
val contentMap = MXUsersDevicesMap<Any>() val contentMap = MXUsersDevicesMap<Any>()
contentMap.setObject(otherUserId, otherDeviceId, keyToDevice) contentMap.setObject(keyToDevice, otherUserId, otherDeviceId)
sendToDeviceTask.configureWith(SendToDeviceTask.Params(type, contentMap, transactionId)) sendToDeviceTask.configureWith(SendToDeviceTask.Params(type, contentMap, transactionId))
.dispatchTo(object : MatrixCallback<Unit> { .dispatchTo(object : MatrixCallback<Unit> {

View File

@ -31,7 +31,8 @@ class RealmLiveData<T : RealmModel>(private val realmConfiguration: RealmConfigu
override fun onActive() { override fun onActive() {
val realm = Realm.getInstance(realmConfiguration) val realm = Realm.getInstance(realmConfiguration)
val results = query.invoke(realm).findAllAsync() val results = query.invoke(realm).findAll()
value = results
results.addChangeListener(listener) results.addChangeListener(listener)
this.realm = realm this.realm = realm
this.results = results this.results = results

View File

@ -17,11 +17,7 @@
package im.vector.matrix.android.internal.database package im.vector.matrix.android.internal.database
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.internal.util.createBackgroundHandler
import io.realm.OrderedCollectionChangeSet import io.realm.OrderedCollectionChangeSet
import io.realm.OrderedRealmCollectionChangeListener
import io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.RealmResults import io.realm.RealmResults
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -33,25 +29,20 @@ internal interface LiveEntityObserver {
fun isStarted(): Boolean fun isStarted(): Boolean
} }
internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val realmConfiguration: RealmConfiguration) internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val monarchy: Monarchy)
: LiveEntityObserver, OrderedRealmCollectionChangeListener<RealmResults<T>> { : LiveEntityObserver {
private companion object {
val BACKGROUND_HANDLER = createBackgroundHandler("LIVE_ENTITY_BACKGROUND")
}
protected abstract val query: Monarchy.Query<T> protected abstract val query: Monarchy.Query<T>
private val isStarted = AtomicBoolean(false) private val isStarted = AtomicBoolean(false)
private val backgroundRealm = AtomicReference<Realm>()
private lateinit var results: AtomicReference<RealmResults<T>> private lateinit var results: AtomicReference<RealmResults<T>>
override fun start() { override fun start() {
if (isStarted.compareAndSet(false, true)) { if (isStarted.compareAndSet(false, true)) {
BACKGROUND_HANDLER.post { monarchy.postToMonarchyThread {
val realm = Realm.getInstance(realmConfiguration) val queryResults = query.createQuery(it).findAll()
backgroundRealm.set(realm) queryResults.addChangeListener { t, changeSet ->
val queryResults = query.createQuery(realm).findAll() onChanged(t, changeSet)
queryResults.addChangeListener(this) }
results = AtomicReference(queryResults) results = AtomicReference(queryResults)
} }
} }
@ -59,11 +50,8 @@ internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val r
override fun dispose() { override fun dispose() {
if (isStarted.compareAndSet(true, false)) { if (isStarted.compareAndSet(true, false)) {
BACKGROUND_HANDLER.post { monarchy.postToMonarchyThread {
results.getAndSet(null).removeAllChangeListeners() results.getAndSet(null).removeAllChangeListeners()
backgroundRealm.getAndSet(null).also {
it.close()
}
} }
} }
} }
@ -72,4 +60,19 @@ internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val r
return isStarted.get() return isStarted.get()
} }
private fun onChanged(realmResults: RealmResults<T>, changeSet: OrderedCollectionChangeSet) {
val insertionIndexes = changeSet.insertions
val updateIndexes = changeSet.changes
val deletionIndexes = changeSet.deletions
val inserted = realmResults.filterIndexed { index, _ -> insertionIndexes.contains(index) }
val updated = realmResults.filterIndexed { index, _ -> updateIndexes.contains(index) }
val deleted = realmResults.filterIndexed { index, _ -> deletionIndexes.contains(index) }
processChanges(inserted, updated, deleted)
}
/**
* Do quick treatment or delegate on a task
*/
protected abstract fun processChanges(inserted: List<T>, updated: List<T>, deleted: List<T>)
} }

View File

@ -22,11 +22,9 @@ import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.mapper.toEntity import im.vector.matrix.android.internal.database.mapper.toEntity
import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields import im.vector.matrix.android.internal.database.query.fastContains
import im.vector.matrix.android.internal.database.query.find
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.extensions.assertIsManaged import im.vector.matrix.android.internal.extensions.assertIsManaged
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
import io.realm.Sort import io.realm.Sort
@ -34,15 +32,12 @@ import io.realm.Sort
// By default if a chunk is empty we consider it unlinked // By default if a chunk is empty we consider it unlinked
internal fun ChunkEntity.isUnlinked(): Boolean { internal fun ChunkEntity.isUnlinked(): Boolean {
assertIsManaged() assertIsManaged()
return timelineEvents.where() return events.where().equalTo(EventEntityFields.IS_UNLINKED, false).findAll().isEmpty()
.equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, false)
.findAll()
.isEmpty()
} }
internal fun ChunkEntity.deleteOnCascade() { internal fun ChunkEntity.deleteOnCascade() {
assertIsManaged() assertIsManaged()
this.timelineEvents.deleteAllFromRealm() this.events.deleteAllFromRealm()
this.deleteFromRealm() this.deleteFromRealm()
} }
@ -55,27 +50,21 @@ internal fun ChunkEntity.merge(roomId: String,
val isUnlinked = isCurrentChunkUnlinked && isChunkToMergeUnlinked val isUnlinked = isCurrentChunkUnlinked && isChunkToMergeUnlinked
if (isCurrentChunkUnlinked && !isChunkToMergeUnlinked) { if (isCurrentChunkUnlinked && !isChunkToMergeUnlinked) {
this.timelineEvents.forEach { it.root?.isUnlinked = false } this.events.forEach { it.isUnlinked = false }
} }
val eventsToMerge: List<TimelineEventEntity> val eventsToMerge: List<EventEntity>
if (direction == PaginationDirection.FORWARDS) { if (direction == PaginationDirection.FORWARDS) {
this.nextToken = chunkToMerge.nextToken this.nextToken = chunkToMerge.nextToken
this.isLastForward = chunkToMerge.isLastForward this.isLastForward = chunkToMerge.isLastForward
eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.ASCENDING) eventsToMerge = chunkToMerge.events.sort(EventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
} else { } else {
this.prevToken = chunkToMerge.prevToken this.prevToken = chunkToMerge.prevToken
this.isLastBackward = chunkToMerge.isLastBackward this.isLastBackward = chunkToMerge.isLastBackward
eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING) eventsToMerge = chunkToMerge.events.sort(EventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
} }
val events = eventsToMerge.mapNotNull { it.root?.asDomain() } eventsToMerge.forEach {
val eventIds = ArrayList<String>() add(roomId, it.asDomain(), direction, isUnlinked = isUnlinked)
events.forEach { event ->
add(roomId, event, direction, isUnlinked = isUnlinked)
if (event.eventId != null) {
eventIds.add(event.eventId)
} }
}
updateSenderDataFor(eventIds)
} }
internal fun ChunkEntity.addAll(roomId: String, internal fun ChunkEntity.addAll(roomId: String,
@ -85,20 +74,8 @@ internal fun ChunkEntity.addAll(roomId: String,
// Set to true for Event retrieved from a Permalink (i.e. not linked to live Chunk) // Set to true for Event retrieved from a Permalink (i.e. not linked to live Chunk)
isUnlinked: Boolean = false) { isUnlinked: Boolean = false) {
assertIsManaged() assertIsManaged()
val eventIds = ArrayList<String>()
events.forEach { event -> events.forEach { event ->
add(roomId, event, direction, stateIndexOffset, isUnlinked) add(roomId, event, direction, stateIndexOffset, isUnlinked)
if (event.eventId != null) {
eventIds.add(event.eventId)
}
}
updateSenderDataFor(eventIds)
}
internal fun ChunkEntity.updateSenderDataFor(eventIds: List<String>) {
for (eventId in eventIds) {
val timelineEventEntity = timelineEvents.find(eventId) ?: continue
timelineEventEntity.updateSenderData()
} }
} }
@ -109,7 +86,7 @@ internal fun ChunkEntity.add(roomId: String,
isUnlinked: Boolean = false) { isUnlinked: Boolean = false) {
assertIsManaged() assertIsManaged()
if (event.eventId != null && timelineEvents.find(event.eventId) != null) { if (event.eventId != null && events.fastContains(event.eventId)) {
return return
} }
var currentDisplayIndex = lastDisplayIndex(direction, 0) var currentDisplayIndex = lastDisplayIndex(direction, 0)
@ -124,28 +101,21 @@ internal fun ChunkEntity.add(roomId: String,
if (direction == PaginationDirection.FORWARDS && EventType.isStateEvent(event.getClearType())) { if (direction == PaginationDirection.FORWARDS && EventType.isStateEvent(event.getClearType())) {
currentStateIndex += 1 currentStateIndex += 1
forwardsStateIndex = currentStateIndex forwardsStateIndex = currentStateIndex
} else if (direction == PaginationDirection.BACKWARDS && timelineEvents.isNotEmpty()) { } else if (direction == PaginationDirection.BACKWARDS && events.isNotEmpty()) {
val lastEventType = timelineEvents.last()?.root?.type ?: "" val lastEventType = events.last()?.type ?: ""
if (EventType.isStateEvent(lastEventType)) { if (EventType.isStateEvent(lastEventType)) {
currentStateIndex -= 1 currentStateIndex -= 1
backwardsStateIndex = currentStateIndex backwardsStateIndex = currentStateIndex
} }
} }
val eventEntity = event.toEntity(roomId).apply {
val localId = TimelineEventEntity.nextId(realm)
val eventEntity = TimelineEventEntity(localId).also {
it.root = event.toEntity(roomId).apply {
this.stateIndex = currentStateIndex this.stateIndex = currentStateIndex
this.isUnlinked = isUnlinked this.isUnlinked = isUnlinked
this.displayIndex = currentDisplayIndex this.displayIndex = currentDisplayIndex
this.sendState = SendState.SYNCED this.sendState = SendState.SYNCED
} }
it.eventId = event.eventId ?: "" val position = if (direction == PaginationDirection.FORWARDS) 0 else this.events.size
it.roomId = roomId events.add(position, eventEntity)
it.annotations = EventAnnotationsSummaryEntity.where(realm, it.eventId).findFirst()
}
val position = if (direction == PaginationDirection.FORWARDS) 0 else this.timelineEvents.size
timelineEvents.add(position, eventEntity)
} }
internal fun ChunkEntity.lastDisplayIndex(direction: PaginationDirection, defaultValue: Int = 0): Int { internal fun ChunkEntity.lastDisplayIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {

View File

@ -21,10 +21,9 @@ import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.internal.database.mapper.toEntity import im.vector.matrix.android.internal.database.mapper.toEntity
import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.RoomEntity 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.fastContains import im.vector.matrix.android.internal.database.query.fastContains
import im.vector.matrix.android.internal.extensions.assertIsManaged import im.vector.matrix.android.internal.extensions.assertIsManaged
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
internal fun RoomEntity.deleteOnCascade(chunkEntity: ChunkEntity) { internal fun RoomEntity.deleteOnCascade(chunkEntity: ChunkEntity) {
chunks.remove(chunkEntity) chunks.remove(chunkEntity)
@ -37,39 +36,29 @@ internal fun RoomEntity.addOrUpdate(chunkEntity: ChunkEntity) {
} }
} }
internal fun RoomEntity.addStateEvent(stateEvent: Event, internal fun RoomEntity.addStateEvents(stateEvents: List<Event>,
stateIndex: Int = Int.MIN_VALUE, stateIndex: Int = Int.MIN_VALUE,
filterDuplicates: Boolean = false, filterDuplicates: Boolean = false,
isUnlinked: Boolean = false) { isUnlinked: Boolean = false) {
assertIsManaged() assertIsManaged()
if (stateEvent.eventId == null || (filterDuplicates && fastContains(stateEvent.eventId))) {
return stateEvents.forEach { event ->
} else { if (event.eventId == null || (filterDuplicates && fastContains(event.eventId))) {
val entity = stateEvent.toEntity(roomId).apply { return@forEach
}
val eventEntity = event.toEntity(roomId).apply {
this.stateIndex = stateIndex this.stateIndex = stateIndex
this.isUnlinked = isUnlinked this.isUnlinked = isUnlinked
this.sendState = SendState.SYNCED this.sendState = SendState.SYNCED
} }
untimelinedStateEvents.add(entity) untimelinedStateEvents.add(0, eventEntity)
} }
} }
internal fun RoomEntity.addSendingEvent(event: Event) { internal fun RoomEntity.addSendingEvent(event: Event) {
assertIsManaged() assertIsManaged()
val senderId = event.senderId ?: return
val eventEntity = event.toEntity(roomId).apply { val eventEntity = event.toEntity(roomId).apply {
this.sendState = SendState.UNSENT this.sendState = SendState.UNSENT
} }
val roomMembers = RoomMembers(realm, roomId) sendingTimelineEvents.add(0, eventEntity)
val myUser = roomMembers.get(senderId)
val localId = TimelineEventEntity.nextId(realm)
val timelineEventEntity = TimelineEventEntity(localId).also {
it.root = eventEntity
it.eventId = event.eventId ?: ""
it.roomId = roomId
it.senderName = myUser?.displayName
it.senderAvatar = myUser?.avatarUrl
it.isUniqueDisplayName = roomMembers.isUniqueDisplayName(myUser?.displayName)
it.senderMembershipEvent = roomMembers.queryRoomMemberEvent(senderId).findFirst()
}
sendingTimelineEvents.add(0, timelineEventEntity)
} }

View File

@ -1,89 +0,0 @@
/*
* 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.database.helper
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.internal.database.mapper.ContentMapper
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields
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.model.TimelineEventEntityFields
import im.vector.matrix.android.internal.database.query.next
import im.vector.matrix.android.internal.database.query.prev
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.extensions.assertIsManaged
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import io.realm.Realm
import io.realm.RealmList
import io.realm.RealmQuery
internal fun TimelineEventEntity.updateSenderData() {
assertIsManaged()
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return
val stateIndex = root?.stateIndex ?: return
val senderId = root?.sender ?: return
val chunkEntity = chunk?.firstOrNull() ?: return
val isUnlinked = chunkEntity.isUnlinked()
var senderMembershipEvent: EventEntity?
var senderRoomMemberContent: String?
when {
stateIndex <= 0 -> {
senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).next(from = stateIndex)?.root
senderRoomMemberContent = senderMembershipEvent?.prevContent
}
else -> {
senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).prev(since = stateIndex)?.root
senderRoomMemberContent = senderMembershipEvent?.content
}
}
// We fallback to untimelinedStateEvents if we can't find membership events in timeline
if (senderMembershipEvent == null) {
senderMembershipEvent = roomEntity.untimelinedStateEvents
.where()
.equalTo(EventEntityFields.STATE_KEY, senderId)
.equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_MEMBER)
.prev(since = stateIndex)
senderRoomMemberContent = senderMembershipEvent?.content
}
val senderRoomMember: RoomMember? = ContentMapper.map(senderRoomMemberContent).toModel()
this.senderAvatar = senderRoomMember?.avatarUrl
this.senderName = senderRoomMember?.displayName
this.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(senderRoomMember?.displayName)
this.senderMembershipEvent = senderMembershipEvent
}
internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long{
val currentIdNum = realm.where(TimelineEventEntity::class.java).max(TimelineEventEntityFields.LOCAL_ID)
return if (currentIdNum == null) {
1
} else {
currentIdNum.toLong() + 1
}
}
private fun RealmList<TimelineEventEntity>.buildQuery(sender: String, isUnlinked: Boolean): RealmQuery<TimelineEventEntity> {
return where()
.equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, sender)
.equalTo(TimelineEventEntityFields.ROOT.TYPE, EventType.STATE_ROOM_MEMBER)
.equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, isUnlinked)
}

View File

@ -19,10 +19,7 @@ package im.vector.matrix.android.internal.database.mapper
import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
import im.vector.matrix.android.api.session.room.model.ReactionAggregatedSummary import im.vector.matrix.android.api.session.room.model.ReactionAggregatedSummary
import im.vector.matrix.android.internal.database.model.EditAggregatedSummaryEntity
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
import im.vector.matrix.android.internal.database.model.ReactionAggregatedSummaryEntity
import io.realm.RealmList
internal object EventAnnotationsSummaryMapper { internal object EventAnnotationsSummaryMapper {
fun map(annotationsSummary: EventAnnotationsSummaryEntity): EventAnnotationsSummary { fun map(annotationsSummary: EventAnnotationsSummaryEntity): EventAnnotationsSummary {
@ -48,35 +45,6 @@ internal object EventAnnotationsSummaryMapper {
} }
) )
} }
fun map(annotationsSummary: EventAnnotationsSummary, roomId: String): EventAnnotationsSummaryEntity {
val eventAnnotationsSummaryEntity = EventAnnotationsSummaryEntity()
eventAnnotationsSummaryEntity.eventId = annotationsSummary.eventId
eventAnnotationsSummaryEntity.roomId = roomId
eventAnnotationsSummaryEntity.editSummary = annotationsSummary.editSummary?.let {
EditAggregatedSummaryEntity(
ContentMapper.map(it.aggregatedContent),
RealmList<String>().apply { addAll(it.sourceEvents) },
RealmList<String>().apply { addAll(it.localEchos) },
it.lastEditTs
)
}
eventAnnotationsSummaryEntity.reactionsSummary = annotationsSummary.reactionsSummary?.let {
RealmList<ReactionAggregatedSummaryEntity>().apply {
addAll(it.map {
ReactionAggregatedSummaryEntity(
it.key,
it.count,
it.addedByMe,
it.firstTimestamp,
RealmList<String>().apply { addAll(it.sourceEvents) },
RealmList<String>().apply { addAll(it.localEchoEvents) }
)
})
}
}
return eventAnnotationsSummaryEntity
}
} }
internal fun EventAnnotationsSummaryEntity.asDomain(): EventAnnotationsSummary { internal fun EventAnnotationsSummaryEntity.asDomain(): EventAnnotationsSummary {

View File

@ -17,15 +17,10 @@
package im.vector.matrix.android.internal.database.mapper package im.vector.matrix.android.internal.database.mapper
import com.squareup.moshi.JsonDataException import com.squareup.moshi.JsonDataException
import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.UnsignedData import im.vector.matrix.android.api.session.events.model.UnsignedData
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.crypto.algorithms.olm.MXOlmDecryption
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider
import timber.log.Timber
import java.util.* import java.util.*
internal object EventMapper { internal object EventMapper {
@ -35,7 +30,7 @@ internal object EventMapper {
val uds = if (event.unsignedData == null) null val uds = if (event.unsignedData == null) null
else MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).toJson(event.unsignedData) else MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).toJson(event.unsignedData)
val eventEntity = EventEntity() val eventEntity = EventEntity()
eventEntity.eventId = event.eventId ?: "" eventEntity.eventId = event.eventId ?: UUID.randomUUID().toString()
eventEntity.roomId = event.roomId ?: roomId eventEntity.roomId = event.roomId ?: roomId
eventEntity.content = ContentMapper.map(event.content) eventEntity.content = ContentMapper.map(event.content)
val resolvedPrevContent = event.prevContent ?: event.unsignedData?.prevContent val resolvedPrevContent = event.prevContent ?: event.unsignedData?.prevContent
@ -51,6 +46,7 @@ internal object EventMapper {
} }
fun map(eventEntity: EventEntity): Event { fun map(eventEntity: EventEntity): Event {
//TODO proxy the event to only parse unsigned data when accessed?
val ud = if (eventEntity.unsignedData.isNullOrBlank()) { val ud = if (eventEntity.unsignedData.isNullOrBlank()) {
null null
} else { } else {
@ -72,17 +68,7 @@ internal object EventMapper {
roomId = eventEntity.roomId, roomId = eventEntity.roomId,
unsignedData = ud, unsignedData = ud,
redacts = eventEntity.redacts redacts = eventEntity.redacts
).also { )
eventEntity.decryptionResultJson?.let { json ->
try {
it.mxDecryptionResult = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java).fromJson(json)
} catch (t: JsonDataException) {
Timber.e(t, "Failed to parse decryption result")
}
}
//TODO get the full crypto error object
it.mCryptoError = eventEntity.decryptionErrorCode?.let { MXCryptoError.ErrorType.valueOf(it) }
}
} }
} }

View File

@ -16,39 +16,29 @@
package im.vector.matrix.android.internal.database.mapper package im.vector.matrix.android.internal.database.mapper
import im.vector.matrix.android.api.session.crypto.CryptoService import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.tag.RoomTag import im.vector.matrix.android.api.session.room.model.tag.RoomTag
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import java.util.* import im.vector.matrix.android.internal.session.room.timeline.TimelineEventFactory
import javax.inject.Inject import javax.inject.Inject
internal class RoomSummaryMapper @Inject constructor( internal class RoomSummaryMapper @Inject constructor(
val cryptoService: CryptoService private val timelineEventFactory: TimelineEventFactory,
) { private val monarchy: Monarchy) {
fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary { fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary {
val tags = roomSummaryEntity.tags.map { val tags = roomSummaryEntity.tags.map {
RoomTag(it.tagName, it.tagOrder) RoomTag(it.tagName, it.tagOrder)
} }
val latestEvent = roomSummaryEntity.latestEvent?.let {
val latestEvent = roomSummaryEntity.latestEvent?.asDomain() var ev: TimelineEvent? = null
if (latestEvent?.root?.isEncrypted() == true && latestEvent.root.mxDecryptionResult == null) { monarchy.doWithRealm { realm ->
//TODO use a global event decryptor? attache to session and that listen to new sessionId? ev = timelineEventFactory.create(it, realm)
//for now decrypt sync
try {
val result = cryptoService.decryptEvent(latestEvent.root, latestEvent.root.roomId + UUID.randomUUID().toString())
latestEvent.root.mxDecryptionResult = OlmDecryptionResult(
payload = result.clearEvent,
senderKey = result.senderCurve25519Key,
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
)
} catch (e: MXCryptoError) {
} }
ev
} }
return RoomSummary( return RoomSummary(
roomId = roomSummaryEntity.roomId, roomId = roomSummaryEntity.roomId,

View File

@ -1,47 +0,0 @@
/*
* 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.database.mapper
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
internal object TimelineEventMapper {
fun map(timelineEventEntity: TimelineEventEntity): TimelineEvent {
return TimelineEvent(
root = timelineEventEntity.root?.asDomain()
?: Event("", timelineEventEntity.eventId),
annotations = timelineEventEntity.annotations?.asDomain(),
localId = timelineEventEntity.localId,
displayIndex = timelineEventEntity.root?.displayIndex ?: 0,
senderName = timelineEventEntity.senderName,
isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName,
senderAvatar = timelineEventEntity.senderAvatar,
sendState = timelineEventEntity.root?.sendState ?: SendState.UNKNOWN
)
}
}
internal fun TimelineEventEntity.asDomain(): TimelineEvent {
return TimelineEventMapper.map(this)
}

View File

@ -24,7 +24,7 @@ import io.realm.annotations.LinkingObjects
internal open class ChunkEntity(@Index var prevToken: String? = null, internal open class ChunkEntity(@Index var prevToken: String? = null,
@Index var nextToken: String? = null, @Index var nextToken: String? = null,
var timelineEvents: RealmList<TimelineEventEntity> = RealmList(), var events: RealmList<EventEntity> = RealmList(),
@Index var isLastForward: Boolean = false, @Index var isLastForward: Boolean = false,
@Index var isLastBackward: Boolean = false, @Index var isLastBackward: Boolean = false,
var backwardsDisplayIndex: Int? = null, var backwardsDisplayIndex: Int? = null,

View File

@ -17,9 +17,6 @@
package im.vector.matrix.android.internal.database.model package im.vector.matrix.android.internal.database.model
import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
import im.vector.matrix.android.internal.di.MoshiProvider
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.RealmResults import io.realm.RealmResults
import io.realm.annotations.Index import io.realm.annotations.Index
@ -27,8 +24,9 @@ import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import java.util.* import java.util.*
internal open class EventEntity(@Index var eventId: String = "", internal open class EventEntity(@PrimaryKey var localId: String = UUID.randomUUID().toString(),
@Index var roomId: String = "", @Index var eventId: String = "",
var roomId: String = "",
@Index var type: String = "", @Index var type: String = "",
var content: String? = null, var content: String? = null,
var prevContent: String? = null, var prevContent: String? = null,
@ -40,9 +38,7 @@ internal open class EventEntity(@Index var eventId: String = "",
var redacts: String? = null, var redacts: String? = null,
@Index var stateIndex: Int = 0, @Index var stateIndex: Int = 0,
@Index var displayIndex: Int = 0, @Index var displayIndex: Int = 0,
@Index var isUnlinked: Boolean = false, @Index var isUnlinked: Boolean = false
var decryptionResultJson: String? = null,
var decryptionErrorCode: String? = null
) : RealmObject() { ) : RealmObject() {
enum class LinkFilterMode { enum class LinkFilterMode {
@ -64,23 +60,10 @@ internal open class EventEntity(@Index var eventId: String = "",
companion object companion object
@LinkingObjects("events")
val chunk: RealmResults<ChunkEntity>? = null
@LinkingObjects("untimelinedStateEvents") @LinkingObjects("untimelinedStateEvents")
val room: RealmResults<RoomEntity>? = null val room: RealmResults<RoomEntity>? = null
@LinkingObjects("root")
val timelineEventEntity: RealmResults<TimelineEventEntity>? = null
fun setDecryptionResult(result: MXEventDecryptionResult) {
val decryptionResult = OlmDecryptionResult(
payload = result.clearEvent,
senderKey = result.senderCurve25519Key,
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
)
val adapter = MoshiProvider.providesMoshi().adapter<OlmDecryptionResult>(OlmDecryptionResult::class.java)
decryptionResultJson = adapter.toJson(decryptionResult)
decryptionErrorCode = null
timelineEventEntity?.firstOrNull()?.root = this
}
} }

View File

@ -23,8 +23,7 @@ import io.realm.annotations.Index
// at java.lang.Thread.run(Thread.java:764) // at java.lang.Thread.run(Thread.java:764)
// Caused by: java.lang.IllegalArgumentException: 'value' is not a valid managed object. // Caused by: java.lang.IllegalArgumentException: 'value' is not a valid managed object.
// at io.realm.ProxyState.checkValidObject(ProxyState.java:213) // at io.realm.ProxyState.checkValidObject(ProxyState.java:213)
// at io.realm.im_vector_matrix_android_internal_database_model_PusherEntityRealmProxy // at io.realm.im_vector_matrix_android_internal_database_model_PusherEntityRealmProxy.realmSet$data(im_vector_matrix_android_internal_database_model_PusherEntityRealmProxy.java:413)
// .realmSet$data(im_vector_matrix_android_internal_database_model_PusherEntityRealmProxy.java:413)
// at im.vector.matrix.android.internal.database.model.PusherEntity.setData(PusherEntity.kt:16) // at im.vector.matrix.android.internal.database.model.PusherEntity.setData(PusherEntity.kt:16)
// at im.vector.matrix.android.internal.session.pushers.AddHttpPusherWorker$doWork$$inlined$fold$lambda$2.execute(AddHttpPusherWorker.kt:70) // at im.vector.matrix.android.internal.session.pushers.AddHttpPusherWorker$doWork$$inlined$fold$lambda$2.execute(AddHttpPusherWorker.kt:70)
// at io.realm.Realm.executeTransaction(Realm.java:1493) // at io.realm.Realm.executeTransaction(Realm.java:1493)

View File

@ -26,7 +26,7 @@ import kotlin.properties.Delegates
internal open class RoomEntity(@PrimaryKey var roomId: String = "", internal open class RoomEntity(@PrimaryKey var roomId: String = "",
var chunks: RealmList<ChunkEntity> = RealmList(), var chunks: RealmList<ChunkEntity> = RealmList(),
var untimelinedStateEvents: RealmList<EventEntity> = RealmList(), var untimelinedStateEvents: RealmList<EventEntity> = RealmList(),
var sendingTimelineEvents: RealmList<TimelineEventEntity> = RealmList(), var sendingTimelineEvents: RealmList<EventEntity> = RealmList(),
var areAllMembersLoaded: Boolean = false var areAllMembersLoaded: Boolean = false
) : RealmObject() { ) : RealmObject() {

View File

@ -27,7 +27,7 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
var displayName: String? = "", var displayName: String? = "",
var avatarUrl: String? = "", var avatarUrl: String? = "",
var topic: String? = "", var topic: String? = "",
var latestEvent: TimelineEventEntity? = null, var latestEvent: EventEntity? = null,
var heroes: RealmList<String> = RealmList(), var heroes: RealmList<String> = RealmList(),
var joinedMembersCount: Int? = 0, var joinedMembersCount: Int? = 0,
var invitedMembersCount: Int? = 0, var invitedMembersCount: Int? = 0,

View File

@ -25,7 +25,6 @@ import io.realm.annotations.RealmModule
classes = [ classes = [
ChunkEntity::class, ChunkEntity::class,
EventEntity::class, EventEntity::class,
TimelineEventEntity::class,
FilterEntity::class, FilterEntity::class,
GroupEntity::class, GroupEntity::class,
GroupSummaryEntity::class, GroupSummaryEntity::class,

View File

@ -1,41 +0,0 @@
/*
* 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.database.model
import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.Index
import io.realm.annotations.LinkingObjects
internal open class TimelineEventEntity(var localId: Long = 0,
@Index var eventId: String = "",
@Index var roomId: String = "",
var root: EventEntity? = null,
var annotations: EventAnnotationsSummaryEntity? = null,
var senderName: String? = null,
var isUniqueDisplayName: Boolean = false,
var senderAvatar: String? = null,
var senderMembershipEvent: EventEntity? = null
) : RealmObject() {
@LinkingObjects("timelineEvents")
val chunk: RealmResults<ChunkEntity>? = null
companion object
}

Some files were not shown because too many files have changed in this diff Show More