forked from GitHub-Mirror/riotX-android
Compare commits
1 Commits
feature/ro
...
feature/fi
Author | SHA1 | Date | |
---|---|---|---|
1d3faa7d9b |
@ -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"
|
||||||
|
17
CHANGES.md
17
CHANGES.md
@ -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:
|
||||||
|
-
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
50
build.gradle
50
build.gradle
@ -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", "**/*.*"
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
@ -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'
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
@ -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)
|
@ -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
|
||||||
|
@ -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"}}"""))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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?
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -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()
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
) {
|
) {
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -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?
|
||||||
|
|
||||||
|
@ -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."
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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?
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -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>)
|
|
||||||
}
|
|
@ -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?
|
||||||
|
|
||||||
}
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
@ -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
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
@ -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
|
@ -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>
|
||||||
}
|
}
|
@ -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,
|
||||||
|
@ -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.
|
||||||
|
@ -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()
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
10
matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXEventDecryptionResult.kt
Executable file → Normal file
10
matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXEventDecryptionResult.kt
Executable file → Normal 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()
|
||||||
)
|
)
|
||||||
|
@ -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)) {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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!!)!!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
)
|
)
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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"]
|
||||||
}
|
}
|
||||||
|
@ -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("")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)!!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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!!
|
||||||
|
@ -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,
|
||||||
/**
|
/**
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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)!!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
|
||||||
|
@ -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(
|
||||||
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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> {
|
||||||
|
@ -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
|
||||||
|
@ -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 Bob’s m.key.verification.key and the content of Alice’s m.key.verification.start message.
|
// in Bob’s m.key.verification.key and the content of Alice’s 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)) {
|
||||||
|
@ -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> {
|
||||||
|
@ -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
|
||||||
|
@ -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>)
|
||||||
|
|
||||||
}
|
}
|
@ -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 {
|
||||||
|
@ -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)
|
|
||||||
}
|
}
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
@ -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 {
|
||||||
|
@ -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) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -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,
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -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)
|
||||||
|
@ -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() {
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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
Reference in New Issue
Block a user