forked from GitHub-Mirror/riotX-android
Compare commits
294 Commits
feature/fi
...
v0.2.0
Author | SHA1 | Date | |
---|---|---|---|
df6080b1da | |||
d609c49b31 | |||
d87ee32422 | |||
f0671b9e73 | |||
e218691bf2 | |||
9c67036c08 | |||
62657538af | |||
5438207fba | |||
fe88aaffbd | |||
21ba72e5e7 | |||
d48ae967bd | |||
0afde3b021 | |||
49ae954183 | |||
64bee91f7a | |||
51fdccb393 | |||
7e3b300130 | |||
a98b324c89 | |||
977721881f | |||
7d41352918 | |||
077396a832 | |||
32b79bd50e | |||
844f6d16a4 | |||
fc9ef579ca | |||
77fa5af1b8 | |||
2948018453 | |||
90d25ff45e | |||
173452d38c | |||
a9f9083745 | |||
22dc2a6790 | |||
927cd7285d | |||
4d5bdecec6 | |||
0be987ac0d | |||
4bfaa00be4 | |||
8e78d8a58d | |||
e3e86c0a41 | |||
8a5fddd952 | |||
208460850e | |||
0ddef67cc9 | |||
896e582a9c | |||
477920f411 | |||
c647648e79 | |||
b654025a3b | |||
786a7d7560 | |||
b935b9311e | |||
8e12f71535 | |||
7eea2ccfb4 | |||
c32ef02a12 | |||
3651ec4870 | |||
87de7bd3e6 | |||
9494174c33 | |||
b7e0b400fb | |||
a8f06f609b | |||
d469299f42 | |||
9bdea5b325 | |||
2f01ad99b3 | |||
bb3b5788ba | |||
45f7d3e9c4 | |||
0f7a56d005 | |||
63d2861bc8 | |||
6bbc784c29 | |||
c6fd625761 | |||
d8092abc4e | |||
6effb90361 | |||
42584fc55a | |||
30d9ddb3e8 | |||
020c32bb1a | |||
efd973208f | |||
30a6c98c08 | |||
1440080d04 | |||
61bb4c0427 | |||
3c25088243 | |||
fc1c0caea3 | |||
8901a5e09a | |||
25f1d21bc7 | |||
4d2ab9fa31 | |||
0289d2ee87 | |||
222201cc64 | |||
b15dea6de3 | |||
2ba83e456d | |||
1822fc4fbb | |||
e6dd1fbfec | |||
e2ea76f871 | |||
9182f2ce4e | |||
34d14eb304 | |||
3625c462f0 | |||
fe69206340 | |||
f9885fd04c | |||
316c8ec27e | |||
41465450d8 | |||
bd009caaf1 | |||
33252c3b65 | |||
10e4d0190f | |||
b77310fe92 | |||
919dec4a56 | |||
43b3680774 | |||
bfb5fce809 | |||
1f3731aae7 | |||
52dced43ff | |||
ff80c3c8d5 | |||
34e4d27573 | |||
6522148e63 | |||
252b2ea30a | |||
f493ce44f2 | |||
c4c5069ee5 | |||
423125b5d9 | |||
9e3d29b7d7 | |||
f65becf7c0 | |||
80a61cf6b5 | |||
77056aff94 | |||
65e123d87f | |||
d0b145d031 | |||
98306e223b | |||
c9fe1adb77 | |||
1b95336ad3 | |||
f007fb04b8 | |||
141434e8f8 | |||
b8669d5ed2 | |||
7a08a11b19 | |||
54b1d18812 | |||
3aa30e5f15 | |||
ddf4a81905 | |||
794fd650a4 | |||
9a57a02996 | |||
7e8cd07e1e | |||
d613abf4b4 | |||
06699eaefc | |||
e5082f662c | |||
c8ab53e39c | |||
d424a135a9 | |||
e6409d4c60 | |||
19c7de687e | |||
1918302297 | |||
92e3a02389 | |||
0a54801fcc | |||
228ee52563 | |||
e6c74dc1fe | |||
fe82ad2002 | |||
f66739491a | |||
c5dc9d4a9a | |||
8f858f8119 | |||
6e036c24b8 | |||
5e832e07cd | |||
e9700e04d8 | |||
c19b1f917f | |||
4281b5967a | |||
aa743d8469 | |||
a09850b16c | |||
6cb94dd4d6 | |||
c9931e3ba3 | |||
fc302c1b5a | |||
34ac987494 | |||
24b2387703 | |||
8a0c9ae9b0 | |||
a79227424f | |||
ffe0b9712c | |||
d92c090c30 | |||
1a4157a663 | |||
fa81d1a9c7 | |||
dba4df6836 | |||
4aae1f78d8 | |||
8159a52bd7 | |||
95d83db90c | |||
ac5b0af63e | |||
e80473903e | |||
d08778c674 | |||
0919b9460d | |||
66a018c79e | |||
dcd64de4b8 | |||
a0bd206308 | |||
ba589e7961 | |||
5dc83d64c1 | |||
9a4eb8e9a4 | |||
058e7153a1 | |||
d7b2371854 | |||
b0c939866f | |||
a07f8b615e | |||
12bd85e0a9 | |||
1b82ed5abb | |||
c13ab62187 | |||
ea77686746 | |||
8a5612be3d | |||
d24ce27903 | |||
2099965508 | |||
829e8da8dc | |||
e149ee53de | |||
b73d3b15f8 | |||
61d7f23870 | |||
b5650b2b8f | |||
8777d13d8b | |||
d52613d723 | |||
7ce476f858 | |||
dd07f5c2a6 | |||
7e6e09bc19 | |||
1d11a163af | |||
57bd103de8 | |||
25bc5001f9 | |||
e4c52484b1 | |||
a30da07fd1 | |||
ee27d3e047 | |||
7096094224 | |||
443fb41d18 | |||
94b4351e19 | |||
e90aeff417 | |||
e50dd265d4 | |||
4521ea14ee | |||
535b41d818 | |||
21357a1ec7 | |||
8c872caf78 | |||
62a81a556e | |||
568e8c8bc0 | |||
98a7652403 | |||
78951b9155 | |||
8c86a653b2 | |||
ea0526821e | |||
c503445092 | |||
205af8b122 | |||
3abb7c8de6 | |||
a40510da3b | |||
a6ab4a349d | |||
79a704d240 | |||
e5adf174a8 | |||
f01e796271 | |||
302d23ba96 | |||
03050c3f25 | |||
cbfd2af74b | |||
f3fab0dc08 | |||
4a512d2425 | |||
07f80f43bd | |||
87dec337d8 | |||
b37877746a | |||
b0e5612bdc | |||
25b0cd0e4b | |||
2800d86a57 | |||
01bc0de2c2 | |||
857a4c5a26 | |||
063c35380a | |||
5322251bc0 | |||
c21b9df9a5 | |||
f2a52f0253 | |||
baaf493cb4 | |||
6cbd6d3a33 | |||
72e5aa981a | |||
c0f085cdf8 | |||
10bc2297d4 | |||
8fa5e63b07 | |||
9d0c50907c | |||
e5958983d8 | |||
ab23ec3f35 | |||
a79a6443e7 | |||
9ff24cbf2a | |||
2eee25bbc1 | |||
2a2431e490 | |||
4041e2e8ca | |||
031c4e5746 | |||
b4ea85fc76 | |||
480f14902d | |||
20c8e8d922 | |||
9cdecced57 | |||
60d46538de | |||
223295c2f1 | |||
f789fb275d | |||
a7c12aeb93 | |||
0ca9a5f68b | |||
842345df9b | |||
7d5c31c510 | |||
1ee1c31b9c | |||
e9eada77f9 | |||
93ce0cc5e9 | |||
eefd09d022 | |||
ef597cc67a | |||
5d171e0240 | |||
39070820be | |||
1fdad38b9d | |||
f41c0311fa | |||
a476ac71da | |||
4b971a9e67 | |||
bc2d321a84 | |||
9adeab6bae | |||
0f3a63e366 | |||
d90698fe92 | |||
af0af6e260 | |||
6e71fb565a | |||
6c66ab1568 | |||
0d329f0338 | |||
2f66321c2a | |||
5b102485bc | |||
698fc35704 | |||
37199da52f | |||
1c69d8e425 | |||
11bf00030d | |||
9378d30601 | |||
41ed4b23d8 | |||
de9a5a3d12 | |||
19202cfca6 |
@ -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: "develop feature/*"
|
branches: "!master"
|
||||||
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: "develop feature/*"
|
branches: "!master"
|
||||||
plugins:
|
plugins:
|
||||||
- docker#v3.1.0:
|
- docker#v3.1.0:
|
||||||
image: "runmymind/docker-android-sdk"
|
image: "runmymind/docker-android-sdk"
|
||||||
|
28
CHANGES.md
28
CHANGES.md
@ -1,24 +1,34 @@
|
|||||||
Changes in RiotX 0.XX (2019-XX-XX)
|
Changes in RiotX 0.2.0 (2019-07-18)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
- Contextual action menu for messages in room
|
- Message Editing: View edit history (#121)
|
||||||
|
- Rooms filtering (#304)
|
||||||
|
- Edit in encrypted room
|
||||||
|
|
||||||
Improvements:
|
Improvements:
|
||||||
-
|
- Handle click on redacted events: view source and create permalink
|
||||||
|
- Improve long tap menu: reply on top, more compact (#368)
|
||||||
|
- Quick reply in timeline with swipe gesture (#167)
|
||||||
|
- Improve edit of replies
|
||||||
|
- Improve performance on Room Members and Users management (#381)
|
||||||
|
|
||||||
Other changes:
|
Other changes:
|
||||||
-
|
- migrate from rxbinding 2 to rxbinding 3
|
||||||
|
|
||||||
Bugfix:
|
Bugfix:
|
||||||
-
|
- Fix regression on permalink click
|
||||||
|
- Fix crash reported by the PlayStore (#341)
|
||||||
|
- Fix Chat composer separator color in dark/black theme
|
||||||
|
- Fix bad layout for room directory filter (#349)
|
||||||
|
- Fix Copying link from a message shouldn't open context menu (#364)
|
||||||
|
|
||||||
Translations:
|
Changes in RiotX 0.1.0 (2019-07-11)
|
||||||
-
|
===================================================
|
||||||
|
|
||||||
Build:
|
First release!
|
||||||
-
|
|
||||||
|
|
||||||
|
Mode details here: https://medium.com/@RiotChat/introducing-the-riotx-beta-for-android-b17952e8f771
|
||||||
|
|
||||||
|
|
||||||
=======================================================
|
=======================================================
|
||||||
|
29
README.md
29
README.md
@ -1,4 +1,4 @@
|
|||||||
[](https://buildkite.com/matrix-dot-org/riotx-android)
|
[](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)
|
||||||
[](https://translate.riot.im/engage/riot-android/?utm_source=widget)
|
[](https://translate.riot.im/engage/riot-android/?utm_source=widget)
|
||||||
[](https://matrix.to/#/#riotx:matrix.org)
|
[](https://matrix.to/#/#riotx:matrix.org)
|
||||||
[](https://sonarcloud.io/dashboard?id=vector.android.riotx)
|
[](https://sonarcloud.io/dashboard?id=vector.android.riotx)
|
||||||
@ -7,14 +7,31 @@
|
|||||||
|
|
||||||
# RiotX Android
|
# RiotX Android
|
||||||
|
|
||||||
RiotX is an Android Matrix Client currently in development. The application is not yet available on the PlayStore.
|
RiotX is an Android Matrix Client currently in beta but in active development.
|
||||||
|
|
||||||
It's based on a new Matrix SDK, written in Kotlin.
|
It is a total rewrite of [Riot-Android](https://github.com/vector-im/riot-android) with a new user experience. RiotX will become the official replacement as soon as all features are implemented.
|
||||||
|
|
||||||
Download nightly build here: [](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)
|
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" alt="Get it on Google Play" height="60">](https://play.google.com/store/apps/details?id=im.vector.riotx)
|
||||||
|
|
||||||
|
Nightly build: [](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)
|
||||||
|
|
||||||
|
# New Android SDK
|
||||||
|
|
||||||
|
RiotX is based on a new Android SDK fully written in Kotlin (like RiotX). In order to make the early development as fast as possible, RiotX and the new SDK currently share the same git repository. We will make separate repos once the API is stable enough.
|
||||||
|
|
||||||
|
|
||||||
|
# Roadmap
|
||||||
|
|
||||||
|
The current target is to release an application out of beta with the same level of features (and even more) as Riot.
|
||||||
|
The roadmap has 3 phases:
|
||||||
|
|
||||||
|
- [phase 0](https://github.com/vector-im/riotX-android/labels/phase0): Prototyping / Project setup
|
||||||
|
- [phase 1](https://github.com/vector-im/riotX-android/labels/phase1): Beta release to the Play Store
|
||||||
|
- [phase 2](https://github.com/vector-im/riotX-android/labels/phase2): Out of beta
|
||||||
|
|
||||||
Matrix Room: [](https://matrix.to/#/#riotx:matrix.org)
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Please refer to [CONTRIBUTING.md](https://github.com/vector-im/riotX-android/blob/develop/CONTRIBUTING.md) if you want to contribute the Matrix on Android projects!
|
Please refer to [CONTRIBUTING.md](https://github.com/vector-im/riotX-android/blob/develop/CONTRIBUTING.md) if you want to contribute on Matrix Android projects!
|
||||||
|
|
||||||
|
Come chat with the community in the dedicated Matrix [room](https://matrix.to/#/#riotx:matrix.org).
|
||||||
|
67
build.gradle
67
build.gradle
@ -1,9 +1,9 @@
|
|||||||
|
import javax.tools.JavaCompiler
|
||||||
|
|
||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
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()
|
||||||
@ -26,24 +26,59 @@ buildscript {
|
|||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
maven { url "http://dl.bintray.com/piasy/maven" }
|
// For olm library. This has to be declared first, to ensure that Olm library is not downloaded from another repo
|
||||||
maven { url 'https://jitpack.io' }
|
maven {
|
||||||
|
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 {
|
maven {
|
||||||
url 'https://jitpack.io'
|
url 'https://repo.adobe.com/nexus/content/repositories/public/'
|
||||||
|
content {
|
||||||
|
includeGroupByRegex "diff_match_patch"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.withType(JavaCompile).all {
|
||||||
|
options.compilerArgs += [
|
||||||
|
'-Adagger.gradle.incremental=enabled'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEvaluate {
|
||||||
|
extensions.findByName("kapt")?.arguments {
|
||||||
|
arg("dagger.gradle.incremental", "enabled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
task clean(type: Delete) {
|
task clean(type: Delete) {
|
||||||
delete rootProject.buildDir
|
delete rootProject.buildDir
|
||||||
}
|
}
|
||||||
|
|
||||||
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"
|
||||||
@ -69,3 +104,23 @@ 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-alpha01'
|
implementation 'androidx.appcompat:appcompat:1.1.0-beta01'
|
||||||
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
|
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
|
||||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
|
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'androidx.test:runner:1.1.1'
|
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,6 @@ private class LiveDataObservable<T>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T> LiveData<T>.asObservable(defaultValue: T? = null): Observable<T> {
|
fun <T> LiveData<T>.asObservable(): Observable<T> {
|
||||||
return LiveDataObservable(this, defaultValue)
|
return LiveDataObservable(this)
|
||||||
}
|
}
|
@ -21,23 +21,24 @@ 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()
|
return room.liveRoomSummary().asObservable().observeOn(Schedulers.computation())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveRoomMemberIds(): Observable<List<String>> {
|
fun liveRoomMemberIds(): Observable<List<String>> {
|
||||||
return room.getRoomMemberIdsLive().asObservable()
|
return room.getRoomMemberIdsLive().asObservable().observeOn(Schedulers.computation())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveAnnotationSummary(eventId: String): Observable<EventAnnotationsSummary> {
|
fun liveAnnotationSummary(eventId: String): Observable<EventAnnotationsSummary> {
|
||||||
return room.getEventSummaryLive(eventId).asObservable()
|
return room.getEventSummaryLive(eventId).asObservable().observeOn(Schedulers.computation())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveTimelineEvent(eventId: String): Observable<TimelineEvent> {
|
fun liveTimelineEvent(eventId: String): Observable<TimelineEvent> {
|
||||||
return room.liveTimeLineEvent(eventId).asObservable()
|
return room.liveTimeLineEvent(eventId).asObservable().observeOn(Schedulers.computation())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,29 +21,25 @@ 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()
|
return session.liveRoomSummaries().asObservable().observeOn(Schedulers.computation())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveGroupSummaries(): Observable<List<GroupSummary>> {
|
fun liveGroupSummaries(): Observable<List<GroupSummary>> {
|
||||||
return session.liveGroupSummaries().asObservable()
|
return session.liveGroupSummaries().asObservable().observeOn(Schedulers.computation())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveSyncState(): Observable<SyncState> {
|
fun liveSyncState(): Observable<SyncState> {
|
||||||
return session.syncState().asObservable()
|
return session.syncState().asObservable().observeOn(Schedulers.computation())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun livePushers(): Observable<List<Pusher>> {
|
fun livePushers(): Observable<List<Pusher>> {
|
||||||
return session.livePushers().asObservable()
|
return session.livePushers().asObservable().observeOn(Schedulers.computation())
|
||||||
}
|
|
||||||
|
|
||||||
fun liveUser(userId: String): Observable<User?> {
|
|
||||||
return session.observeUser(userId).asObservable(User(userId))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,20 +6,14 @@ 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.9.0"
|
classpath "io.realm:realm-gradle-plugin:5.12.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
jcenter()
|
|
||||||
}
|
|
||||||
|
|
||||||
androidExtensions {
|
androidExtensions {
|
||||||
experimental = true
|
experimental = true
|
||||||
}
|
}
|
||||||
@ -33,6 +27,8 @@ 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()}\""
|
||||||
@ -66,6 +62,11 @@ 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() {
|
||||||
@ -86,7 +87,7 @@ static def gitRevisionDate() {
|
|||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
def arrow_version = "0.8.0"
|
def arrow_version = "0.8.0"
|
||||||
def support_version = '1.1.0-alpha03'
|
def support_version = '1.1.0-beta01'
|
||||||
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"
|
||||||
@ -98,16 +99,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:$support_version"
|
implementation "androidx.appcompat:appcompat:1.1.0-rc01"
|
||||||
implementation "androidx.recyclerview:recyclerview:$support_version"
|
implementation "androidx.recyclerview:recyclerview:1.1.0-beta01"
|
||||||
|
|
||||||
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.4.0'
|
implementation 'com.squareup.retrofit2:retrofit:2.6.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.11.0'
|
implementation 'com.squareup.okhttp3:okhttp:3.14.1'
|
||||||
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"
|
||||||
@ -120,7 +121,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-beta01"
|
implementation "androidx.work:work-runtime-ktx:2.1.0-rc01"
|
||||||
|
|
||||||
// FP
|
// FP
|
||||||
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
||||||
@ -148,18 +149,16 @@ 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 "org.koin:koin-test:$koin_version"
|
androidTestImplementation 'androidx.test:core:1.2.0'
|
||||||
androidTestImplementation 'androidx.test:core:1.1.0'
|
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||||
androidTestImplementation 'androidx.test:runner:1.1.1'
|
androidTestImplementation 'androidx.test:rules:1.2.0'
|
||||||
androidTestImplementation 'androidx.test:rules:1.1.1'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.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)
|
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main, Main)
|
@ -19,25 +19,18 @@ 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, KoinTest {
|
internal class AuthenticatorTest : InstrumentedTest {
|
||||||
|
|
||||||
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.events.size shouldEqual 1
|
chunk.timelineEvents.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.events.size shouldEqual 1
|
chunk.timelineEvents.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.events.size shouldEqual 60
|
chunk1.timelineEvents.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.events.size shouldEqual 40
|
chunk1.timelineEvents.size shouldEqual 40
|
||||||
chunk1.isLastForward.shouldBeTrue()
|
chunk1.isLastForward.shouldBeTrue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,25 +18,6 @@ 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,7 +87,8 @@ 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. You should call Matrix.initialize or let your application implements MatrixConfiguration.Provider.")
|
throw IllegalStateException("Matrix is not initialized properly." +
|
||||||
|
" You should call Matrix.initialize or let your application implements MatrixConfiguration.Provider.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return instance
|
return instance
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
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
|
||||||
@ -29,31 +28,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 = Pattern.compile(MATRIX_USER_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE)
|
private val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
// 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 = Pattern.compile(MATRIX_ROOM_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE)
|
private val PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER = MATRIX_ROOM_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
// 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 = Pattern.compile(MATRIX_ROOM_ALIAS_REGEX, Pattern.CASE_INSENSITIVE)
|
private val PATTERN_CONTAIN_MATRIX_ALIAS = MATRIX_ROOM_ALIAS_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
// 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 = Pattern.compile(MATRIX_EVENT_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE)
|
private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER = MATRIX_EVENT_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
// 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 = Pattern.compile(MATRIX_EVENT_IDENTIFIER_V3_REGEX, Pattern.CASE_INSENSITIVE)
|
private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 = MATRIX_EVENT_IDENTIFIER_V3_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
// 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 = Pattern.compile(MATRIX_EVENT_IDENTIFIER_V4_REGEX, Pattern.CASE_INSENSITIVE)
|
private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4 = MATRIX_EVENT_IDENTIFIER_V4_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
// 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 = Pattern.compile(MATRIX_GROUP_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE)
|
private val PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER = MATRIX_GROUP_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
// 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.
|
||||||
@ -62,16 +61,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 = Pattern.compile(LINK_TO_ROOM_ID_REGEXP, Pattern.CASE_INSENSITIVE)
|
private val PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ID = LINK_TO_ROOM_ID_REGEXP.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
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 = Pattern.compile(LINK_TO_ROOM_ALIAS_REGEXP, Pattern.CASE_INSENSITIVE)
|
private val PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ALIAS = LINK_TO_ROOM_ALIAS_REGEXP.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
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 = Pattern.compile(LINK_TO_APP_ROOM_ID_REGEXP, Pattern.CASE_INSENSITIVE)
|
private val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ID = LINK_TO_APP_ROOM_ID_REGEXP.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
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 = Pattern.compile(LINK_TO_APP_ROOM_ALIAS_REGEXP, Pattern.CASE_INSENSITIVE)
|
private val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ALIAS = LINK_TO_APP_ROOM_ALIAS_REGEXP.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
// list of patterns to find some matrix item.
|
// list of patterns to find some matrix item.
|
||||||
val MATRIX_PATTERNS = listOf(
|
val MATRIX_PATTERNS = listOf(
|
||||||
@ -93,7 +92,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 && PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER.matcher(str).matches()
|
return str != null && str matches PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -103,7 +102,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 && PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER.matcher(str).matches()
|
return str != null && str matches PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -113,7 +112,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 && PATTERN_CONTAIN_MATRIX_ALIAS.matcher(str).matches()
|
return str != null && str matches PATTERN_CONTAIN_MATRIX_ALIAS
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -124,9 +123,9 @@ object MatrixPatterns {
|
|||||||
*/
|
*/
|
||||||
fun isEventId(str: String?): Boolean {
|
fun isEventId(str: String?): Boolean {
|
||||||
return str != null
|
return str != null
|
||||||
&& (PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER.matcher(str).matches()
|
&& (str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER
|
||||||
|| PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3.matcher(str).matches()
|
|| str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3
|
||||||
|| PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4.matcher(str).matches())
|
|| str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -136,6 +135,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 && PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER.matcher(str).matches()
|
return str != null && str matches PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,17 +36,15 @@ 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 active [Session].
|
* Check if there is an authenticated [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 active [Session], if there is an active session.
|
* Get the last authenticated [Session], if there is an active session.
|
||||||
* @return the lastActive session if any, or null
|
* @return the last active session if any, or null
|
||||||
*/
|
*/
|
||||||
fun getLastAuthenticatedSession(): Session?
|
fun getLastAuthenticatedSession(): Session?
|
||||||
|
|
||||||
@ -58,7 +56,4 @@ 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(RuntimeException(error.toString()))
|
data class CryptoError(val error: MXCryptoError) : Failure(error)
|
||||||
|
|
||||||
abstract class FeatureFailure : Failure()
|
abstract class FeatureFailure : Failure()
|
||||||
|
|
||||||
|
@ -37,15 +37,13 @@ object MatrixLinkify {
|
|||||||
}
|
}
|
||||||
val text = spannable.toString()
|
val text = spannable.toString()
|
||||||
var hasMatch = false
|
var hasMatch = false
|
||||||
for (index in MatrixPatterns.MATRIX_PATTERNS.indices) {
|
for (pattern in MatrixPatterns.MATRIX_PATTERNS) {
|
||||||
val pattern = MatrixPatterns.MATRIX_PATTERNS[index]
|
for (match in pattern.findAll(spannable)) {
|
||||||
val matcher = pattern.matcher(spannable)
|
|
||||||
while (matcher.find()) {
|
|
||||||
hasMatch = true
|
hasMatch = true
|
||||||
val startPos = matcher.start(0)
|
val startPos = match.range.first
|
||||||
if (startPos == 0 || text[startPos - 1] != '/') {
|
if (startPos == 0 || text[startPos - 1] != '/') {
|
||||||
val endPos = matcher.end(0)
|
val endPos = match.range.last + 1
|
||||||
val url = text.substring(matcher.start(0), matcher.end(0))
|
val url = text.substring(match.range)
|
||||||
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,7 +20,6 @@ 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
|
||||||
|
|
||||||
@ -35,12 +34,12 @@ 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:
|
||||||
|
// Matches any message whose content is unencrypted and contains the user's current display name
|
||||||
// EventType.ENCRYPTED -> {
|
// EventType.ENCRYPTED -> {
|
||||||
// event.root.getClearContent()?.toModel<MessageContent>()
|
// event.root.getClearContent()?.toModel<MessageContent>()
|
||||||
// }
|
// }
|
||||||
|
@ -40,7 +40,8 @@ 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. If no prefix is present, this parameter defaults to ==.
|
* 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 ==.
|
||||||
*/
|
*/
|
||||||
@Json(name = "is") val iz: String? = null
|
@Json(name = "is") val iz: String? = null
|
||||||
) {
|
) {
|
||||||
|
@ -13,17 +13,17 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package im.vector.matrix.android.api.session
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.session;
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
|
||||||
import java.lang.annotation.Documented;
|
interface InitialSyncProgressService {
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
|
|
||||||
import javax.inject.Scope;
|
fun getLiveStatus() : LiveData<Status?>
|
||||||
|
|
||||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
data class Status(
|
||||||
|
@StringRes val statusText: Int?,
|
||||||
@Scope
|
val percentProgress: Int = 0
|
||||||
@Documented
|
)
|
||||||
@Retention(RUNTIME)
|
}
|
||||||
public @interface SessionScope {}
|
|
@ -24,6 +24,7 @@ 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
|
||||||
@ -46,14 +47,19 @@ 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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Useful shortcut to get access to the userId
|
||||||
|
*/
|
||||||
val myUserId: String
|
val myUserId: String
|
||||||
get() = sessionParams.credentials.userId
|
get() = sessionParams.credentials.userId
|
||||||
|
|
||||||
@ -81,7 +87,7 @@ interface Session :
|
|||||||
/**
|
/**
|
||||||
* This method start the sync thread.
|
* This method start the sync thread.
|
||||||
*/
|
*/
|
||||||
fun startSync()
|
fun startSync(fromForeground : Boolean)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method stop the sync thread.
|
* This method stop the sync thread.
|
||||||
|
@ -28,10 +28,11 @@ interface ContentUploadStateTracker {
|
|||||||
|
|
||||||
sealed class State {
|
sealed class State {
|
||||||
object Idle : State()
|
object Idle : State()
|
||||||
data class ProgressData(val current: Long, val total: Long) : State()
|
object EncryptingThumbnail : 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()
|
||||||
object Failure : State()
|
data class Failure(val throwable: Throwable) : State()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -26,12 +26,14 @@ 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 {
|
||||||
|
|
||||||
@ -85,6 +87,8 @@ 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
|
||||||
@ -96,9 +100,10 @@ interface CryptoService {
|
|||||||
roomId: String,
|
roomId: String,
|
||||||
callback: MatrixCallback<MXEncryptEventContentResult>)
|
callback: MatrixCallback<MXEncryptEventContentResult>)
|
||||||
|
|
||||||
fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult?
|
@Throws(MXCryptoError::class)
|
||||||
|
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,106 +18,65 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.api.session.crypto
|
package im.vector.matrix.android.api.session.crypto
|
||||||
|
|
||||||
import android.text.TextUtils
|
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||||
|
import org.matrix.olm.OlmException
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a crypto error response.
|
* Represents a crypto error response.
|
||||||
*/
|
*/
|
||||||
class MXCryptoError(var code: String,
|
sealed class MXCryptoError : Throwable() {
|
||||||
var message: String) {
|
|
||||||
|
|
||||||
|
data class Base(val errorType: ErrorType,
|
||||||
|
val technicalMessage: String,
|
||||||
/**
|
/**
|
||||||
* Describe the error with more details
|
* Describe the error with more details
|
||||||
*/
|
*/
|
||||||
private var mDetailedErrorDescription: String? = null
|
val detailedErrorDescription: String? = null) : MXCryptoError()
|
||||||
|
|
||||||
/**
|
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,
|
||||||
* @return the detailed error description
|
UNABLE_TO_ENCRYPT,
|
||||||
*/
|
UNABLE_TO_DECRYPT,
|
||||||
val detailedErrorDescription: String?
|
UNKNOWN_INBOUND_SESSION_ID,
|
||||||
get() = if (TextUtils.isEmpty(mDetailedErrorDescription)) {
|
INBOUND_SESSION_MISMATCH_ROOM_ID,
|
||||||
message
|
MISSING_FIELDS,
|
||||||
} else mDetailedErrorDescription
|
BAD_EVENT_FORMAT,
|
||||||
|
MISSING_SENDER_KEY,
|
||||||
/**
|
MISSING_CIPHER_TEXT,
|
||||||
* Create a crypto error
|
BAD_DECRYPTED_FORMAT,
|
||||||
*
|
NOT_INCLUDE_IN_RECIPIENTS,
|
||||||
* @param code the error code (see XX_ERROR_CODE)
|
BAD_RECIPIENT,
|
||||||
* @param shortErrorDescription the short error description
|
BAD_RECIPIENT_KEY,
|
||||||
* @param detailedErrorDescription the detailed error description
|
FORWARDED_MESSAGE,
|
||||||
*/
|
BAD_ROOM,
|
||||||
constructor(code: String, shortErrorDescription: String, detailedErrorDescription: String?) : this(code, shortErrorDescription) {
|
BAD_ENCRYPTED_MESSAGE,
|
||||||
mDetailedErrorDescription = detailedErrorDescription
|
DUPLICATED_MESSAGE_INDEX,
|
||||||
}
|
MISSING_PROPERTY,
|
||||||
|
OLM,
|
||||||
/**
|
UNKNOWN_DEVICES,
|
||||||
* Create a crypto error
|
UNKNOWN_MESSAGE_INDEX
|
||||||
*
|
|
||||||
* @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 {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error codes
|
* Resource for technicalMessage
|
||||||
*/
|
*/
|
||||||
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 DETAILLED_OLM_REASON = "Unable to decrypt %1\$s. OLM error: %2\$s"
|
const val DETAILED_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"
|
||||||
@ -126,7 +85,9 @@ class MXCryptoError(var code: String,
|
|||||||
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" + "We strongly recommend you verify them before continuing."
|
const val UNKNOWN_DEVICES_REASON = "This room contains unknown devices which have not been verified.\n" +
|
||||||
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."
|
"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."
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -40,7 +40,8 @@ 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, callback: MatrixCallback<KeysVersion>)
|
fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo,
|
||||||
|
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
|
||||||
@ -58,7 +59,8 @@ 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?, callback: MatrixCallback<Unit>?)
|
fun backupAllGroupSessions(progressListener: ProgressListener?,
|
||||||
|
callback: MatrixCallback<Unit>?)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check trust on a key backup version.
|
* Check trust on a key backup version.
|
||||||
@ -66,7 +68,8 @@ 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, callback: MatrixCallback<KeysBackupVersionTrust>)
|
fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult,
|
||||||
|
callback: MatrixCallback<KeysBackupVersionTrust>)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the current progress of the backup
|
* Return the current progress of the backup
|
||||||
@ -80,7 +83,8 @@ interface KeysBackupService {
|
|||||||
* @param version the backup version
|
* @param version the backup version
|
||||||
* @param callback
|
* @param callback
|
||||||
*/
|
*/
|
||||||
fun getVersion(version: String, callback: MatrixCallback<KeysVersionResult?>)
|
fun getVersion(version: String,
|
||||||
|
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.
|
||||||
@ -114,7 +118,9 @@ 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?, progressListener: ProgressListener?, callback: MatrixCallback<MegolmBackupCreationInfo>)
|
fun prepareKeysBackupVersion(password: String?,
|
||||||
|
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.
|
||||||
@ -123,7 +129,8 @@ 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, callback: MatrixCallback<Unit>?)
|
fun deleteBackup(version: String,
|
||||||
|
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.
|
||||||
@ -139,7 +146,9 @@ 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, trust: Boolean, callback: MatrixCallback<Unit>)
|
fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult,
|
||||||
|
trust: Boolean,
|
||||||
|
callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set trust on a keys backup version.
|
* Set trust on a keys backup version.
|
||||||
@ -148,7 +157,9 @@ 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, recoveryKey: String, callback: MatrixCallback<Unit>)
|
fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult,
|
||||||
|
recoveryKey: String,
|
||||||
|
callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set trust on a keys backup version.
|
* Set trust on a keys backup version.
|
||||||
@ -157,7 +168,9 @@ 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, password: String, callback: MatrixCallback<Unit>)
|
fun trustKeysBackupVersionWithPassphrase(keysBackupVersion: KeysVersionResult,
|
||||||
|
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.
|
||||||
@ -169,7 +182,11 @@ 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, recoveryKey: String, roomId: String?, sessionId: String?, stepProgressListener: StepProgressListener?, callback: MatrixCallback<ImportRoomKeysResult>)
|
fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult,
|
||||||
|
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.
|
||||||
@ -181,7 +198,12 @@ 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, password: String, roomId: String?, sessionId: String?, stepProgressListener: StepProgressListener?, callback: MatrixCallback<ImportRoomKeysResult>)
|
fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult,
|
||||||
|
password: String,
|
||||||
|
roomId: String?,
|
||||||
|
sessionId: String?,
|
||||||
|
stepProgressListener: StepProgressListener?,
|
||||||
|
callback: MatrixCallback<ImportRoomKeysResult>)
|
||||||
|
|
||||||
val keysBackupVersion: KeysVersionResult?
|
val keysBackupVersion: KeysVersionResult?
|
||||||
val currentBackupVersion: String?
|
val currentBackupVersion: String?
|
||||||
|
@ -21,11 +21,10 @@ 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.MXEventDecryptionResult
|
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||||
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
|
||||||
|
|
||||||
@ -79,6 +78,11 @@ 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.
|
||||||
@ -91,41 +95,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.
|
||||||
@ -140,96 +144,151 @@ 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 mClearEvent?.mSenderCurve25519Key ?: mSenderCurve25519Key
|
return mxDecryptionResult?.senderKey
|
||||||
|
// 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> {
|
||||||
val res = HashMap<String, String>()
|
return mxDecryptionResult?.keysClaimed ?: HashMap()
|
||||||
|
// 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) {
|
//
|
||||||
res["ed25519"] = claimedEd25519Key
|
// if (null != claimedEd25519Key) {
|
||||||
|
// res["ed25519"] = claimedEd25519Key
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return res
|
||||||
}
|
}
|
||||||
|
//
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the event type
|
* @return the event type
|
||||||
*/
|
*/
|
||||||
fun getClearType(): String {
|
fun getClearType(): String {
|
||||||
return mClearEvent?.type ?: type
|
return mxDecryptionResult?.payload?.get("type")?.toString()
|
||||||
|
?: type//get("type")?.toString() ?: type
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the event content
|
* @return the event content
|
||||||
*/
|
*/
|
||||||
fun getClearContent(): Content? {
|
fun getClearContent(): Content? {
|
||||||
return mClearEvent?.content ?: content
|
return mxDecryptionResult?.payload?.get("content") as? 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? {
|
||||||
* Update the linked crypto error
|
val contentMap = this.mxDecryptionResult?.payload?.toMutableMap()
|
||||||
*
|
val adapter = MoshiProvider.providesMoshi().adapter(Map::class.java)
|
||||||
* @param error the new crypto error.
|
return contentMap?.let { JSONObject(adapter.toJson(it)).toString(4) }
|
||||||
*/
|
|
||||||
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
|
||||||
|
}
|
||||||
}
|
}
|
@ -17,7 +17,7 @@ package im.vector.matrix.android.api.session.events.model
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constants defining known event relation types from Matrix specifications.
|
* Constants defining known event relation types from Matrix specifications
|
||||||
*/
|
*/
|
||||||
object RelationType {
|
object RelationType {
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ object RelationType {
|
|||||||
const val ANNOTATION = "m.annotation"
|
const val ANNOTATION = "m.annotation"
|
||||||
/** Lets you define an event which replaces an existing event.*/
|
/** Lets you define an event which replaces an existing event.*/
|
||||||
const val REPLACE = "m.replace"
|
const val REPLACE = "m.replace"
|
||||||
/** ets you define an event which references an existing event.*/
|
/** Lets you define an event which references an existing event.*/
|
||||||
const val REFERENCE = "m.reference"
|
const val REFERENCE = "m.reference"
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
*/
|
*/
|
||||||
val liveRoomSummary: LiveData<RoomSummary>
|
fun liveRoomSummary(): LiveData<RoomSummary>
|
||||||
|
|
||||||
val roomSummary: RoomSummary?
|
fun roomSummary(): RoomSummary?
|
||||||
|
|
||||||
}
|
}
|
@ -210,13 +210,7 @@ class CreateRoomParams {
|
|||||||
* @return the first invited user id
|
* @return the first invited user id
|
||||||
*/
|
*/
|
||||||
fun getFirstInvitedUserId(): String? {
|
fun getFirstInvitedUserId(): String? {
|
||||||
if (0 != getInviteCount()) {
|
return invitedUserIds?.firstOrNull() ?: invite3pids?.firstOrNull()?.address
|
||||||
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") val url: String? = null,
|
@Json(name = "url") override 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
|
||||||
) : MessageEncyptedContent
|
) : MessageEncryptedContent
|
||||||
|
@ -26,3 +26,8 @@ interface MessageContent {
|
|||||||
val relatesTo: RelationDefaultContent?
|
val relatesTo: RelationDefaultContent?
|
||||||
val newContent: Content?
|
val newContent: Content?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun MessageContent?.isReply(): Boolean {
|
||||||
|
return this?.relatesTo?.inReplyTo != null
|
||||||
|
}
|
@ -20,8 +20,18 @@ import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for message which can contains encrypted data
|
* Interface for message which can contains an encrypted file
|
||||||
*/
|
*/
|
||||||
interface MessageEncyptedContent : MessageContent {
|
interface MessageEncryptedContent : 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,6 +16,7 @@
|
|||||||
|
|
||||||
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
|
||||||
@ -47,10 +48,22 @@ 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") val url: String? = null,
|
@Json(name = "url") override 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
|
||||||
) : MessageEncyptedContent
|
) : MessageEncryptedContent {
|
||||||
|
|
||||||
|
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") val url: String? = null,
|
@Json(name = "url") override 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
|
||||||
) : MessageEncyptedContent
|
) : MessageEncryptedContent
|
||||||
|
@ -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") val url: String? = null,
|
@Json(name = "url") override 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
|
||||||
) : MessageEncyptedContent
|
) : MessageEncryptedContent
|
||||||
|
@ -16,7 +16,10 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.api.session.room.model.relation
|
package im.vector.matrix.android.api.session.room.model.relation
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.events.model.RelationType
|
||||||
|
|
||||||
interface RelationContent {
|
interface RelationContent {
|
||||||
|
/** See [RelationType] for known possible values */
|
||||||
val type: String?
|
val type: String?
|
||||||
val eventId: String?
|
val eventId: String?
|
||||||
val inReplyTo: ReplyToContent?
|
val inReplyTo: ReplyToContent?
|
||||||
|
@ -16,8 +16,10 @@
|
|||||||
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.MatrixCallback
|
||||||
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.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,7 +53,8 @@ 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, targetEventId: String): Cancelable
|
fun sendReaction(reaction: String,
|
||||||
|
targetEventId: String): Cancelable
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,7 +63,9 @@ 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, targetEventId: String, myUserId: String)//: Cancelable
|
fun undoReaction(reaction: String,
|
||||||
|
targetEventId: String,
|
||||||
|
myUserId: String)//: Cancelable
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -69,7 +74,30 @@ 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, newBodyText: String, newBodyAutoMarkdown: Boolean, compatibilityBodyText: String = "* $newBodyText"): Cancelable
|
fun editTextMessage(targetEventId: String,
|
||||||
|
msgType: String,
|
||||||
|
newBodyText: String,
|
||||||
|
newBodyAutoMarkdown: Boolean,
|
||||||
|
compatibilityBodyText: String = "* $newBodyText"): Cancelable
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit a reply. This is a special case because replies contains fallback text as a prefix.
|
||||||
|
* This method will take the new body (stripped from fallbacks) and re-add them before sending.
|
||||||
|
* @param replyToEdit The event to edit
|
||||||
|
* @param originalTimelineEvent the message that this reply (being edited) is relating to
|
||||||
|
* @param newBodyText The edited body (stripped from in reply to content)
|
||||||
|
* @param compatibilityBodyText The text that will appear on clients that don't support yet edition
|
||||||
|
*/
|
||||||
|
fun editReply(replyToEdit: TimelineEvent,
|
||||||
|
originalTimelineEvent: TimelineEvent,
|
||||||
|
newBodyText: String,
|
||||||
|
compatibilityBodyText: String = "* $newBodyText"): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get's the edit history of the given event
|
||||||
|
*/
|
||||||
|
fun fetchEditHistory(eventId: String, callback: MatrixCallback<List<Event>>)
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -77,8 +105,13 @@ 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: Event, replyText: String): Cancelable?
|
fun replyToMessage(eventReplied: TimelineEvent,
|
||||||
|
replyText: String,
|
||||||
|
autoMarkdown: Boolean = false): Cancelable?
|
||||||
|
|
||||||
fun getEventSummaryLive(eventId: String): LiveData<EventAnnotationsSummary>
|
fun getEventSummaryLive(eventId: String): LiveData<EventAnnotationsSummary>
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -67,7 +67,8 @@ 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, they will be subject to ordinary power level rules like any other user.
|
* 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.
|
||||||
*/
|
*/
|
||||||
@Json(name = "guest_can_join")
|
@Json(name = "guest_can_join")
|
||||||
var guestCanJoin: Boolean = false,
|
var guestCanJoin: Boolean = false,
|
||||||
|
@ -18,8 +18,13 @@ 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.model.message.isReply
|
||||||
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.api.util.ContentUtils.extractUsefulTextFromReply
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This data class is a wrapper around an Event. It allows to get useful data in the context of a timeline.
|
* This data class is a wrapper around an Event. It allows to get useful data in the context of a timeline.
|
||||||
@ -28,13 +33,12 @@ import im.vector.matrix.android.api.session.room.send.SendState
|
|||||||
*/
|
*/
|
||||||
data class TimelineEvent(
|
data class TimelineEvent(
|
||||||
val root: Event,
|
val root: Event,
|
||||||
val localId: String,
|
val localId: Long,
|
||||||
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
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@ -81,3 +85,21 @@ 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()
|
||||||
|
|
||||||
|
|
||||||
|
fun TimelineEvent.getTextEditableContent(): String? {
|
||||||
|
val originalContent = root.getClearContent().toModel<MessageContent>() ?: return null
|
||||||
|
val isReply = originalContent.isReply() || root.content.toModel<EncryptedEventContent>()?.relatesTo?.inReplyTo?.eventId != null
|
||||||
|
val lastContent = getLastMessageContent()
|
||||||
|
return if (isReply) {
|
||||||
|
return extractUsefulTextFromReply(lastContent?.body ?: "")
|
||||||
|
} else {
|
||||||
|
lastContent?.body ?: ""
|
||||||
|
}
|
||||||
|
}
|
@ -18,7 +18,7 @@ package im.vector.matrix.android.api.session.sync
|
|||||||
|
|
||||||
sealed class SyncState {
|
sealed class SyncState {
|
||||||
object IDLE : SyncState()
|
object IDLE : SyncState()
|
||||||
data class RUNNING(val catchingUp: Boolean) : SyncState()
|
data class RUNNING(val afterPause: Boolean) : SyncState()
|
||||||
object PAUSED : SyncState()
|
object PAUSED : SyncState()
|
||||||
object KILLING : SyncState()
|
object KILLING : SyncState()
|
||||||
object KILLED : SyncState()
|
object KILLED : SyncState()
|
||||||
|
@ -16,20 +16,8 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.api.util
|
package im.vector.matrix.android.api.util
|
||||||
|
|
||||||
class CancelableBag : Cancelable {
|
class CancelableBag : Cancelable, MutableList<Cancelable> by ArrayList() {
|
||||||
|
|
||||||
private val cancelableList = ArrayList<Cancelable>()
|
|
||||||
|
|
||||||
fun add(cancelable: Cancelable) {
|
|
||||||
cancelableList.add(cancelable)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cancel() {
|
override fun cancel() {
|
||||||
cancelableList.forEach { it.cancel() }
|
forEach { it.cancel() }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Cancelable.addTo(cancelables: CancelableBag) {
|
|
||||||
cancelables.add(this)
|
|
||||||
}
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* 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.util
|
||||||
|
|
||||||
|
|
||||||
|
object ContentUtils {
|
||||||
|
fun extractUsefulTextFromReply(repliedBody: String): String {
|
||||||
|
val lines = repliedBody.lines()
|
||||||
|
var wellFormed = repliedBody.startsWith(">")
|
||||||
|
var endOfPreviousFound = false
|
||||||
|
val usefullines = ArrayList<String>()
|
||||||
|
lines.forEach {
|
||||||
|
if (it == "") {
|
||||||
|
endOfPreviousFound = true
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
if (!endOfPreviousFound) {
|
||||||
|
wellFormed = wellFormed && it.startsWith(">")
|
||||||
|
} else {
|
||||||
|
usefullines.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return usefullines.joinToString("\n").takeIf { wellFormed } ?: repliedBody
|
||||||
|
}
|
||||||
|
|
||||||
|
fun extractUsefulTextFromHtmlReply(repliedBody: String): String {
|
||||||
|
if (repliedBody.startsWith("<mx-reply>")) {
|
||||||
|
val closingTagIndex = repliedBody.lastIndexOf("</mx-reply>")
|
||||||
|
if (closingTagIndex != -1)
|
||||||
|
return repliedBody.substring(closingTagIndex + "</mx-reply>".length).trim()
|
||||||
|
}
|
||||||
|
return repliedBody
|
||||||
|
}
|
||||||
|
}
|
@ -21,13 +21,4 @@ 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> {
|
open class MatrixCallbackDelegate<T>(private val callback: MatrixCallback<T>) : MatrixCallback<T> by callback
|
||||||
|
|
||||||
override fun onSuccess(data: T) {
|
|
||||||
callback.onSuccess(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
callback.onFailure(failure)
|
|
||||||
}
|
|
||||||
}
|
|
@ -50,16 +50,10 @@ internal class SessionManager @Inject constructor(private val matrixComponent: M
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getOrCreateSessionComponent(sessionParams: SessionParams): SessionComponent {
|
private fun getOrCreateSessionComponent(sessionParams: SessionParams): SessionComponent {
|
||||||
val userId = sessionParams.credentials.userId
|
return sessionComponents.getOrPut(sessionParams.credentials.userId) {
|
||||||
if (sessionComponents.containsKey(userId)) {
|
DaggerSessionComponent
|
||||||
return sessionComponents[userId]!!
|
|
||||||
}
|
|
||||||
return DaggerSessionComponent
|
|
||||||
.factory()
|
.factory()
|
||||||
.create(matrixComponent, sessionParams)
|
.create(matrixComponent, sessionParams)
|
||||||
.also {
|
|
||||||
sessionComponents[sessionParams.credentials.userId] = it
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -21,6 +21,7 @@ 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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -30,9 +31,11 @@ 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>
|
||||||
|
|
||||||
|
@ -21,10 +21,10 @@ package im.vector.matrix.android.internal.crypto
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
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
|
||||||
@ -72,15 +72,16 @@ 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.*
|
||||||
import org.matrix.olm.OlmManager
|
import org.matrix.olm.OlmManager
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.coroutines.EmptyCoroutineContext
|
import kotlin.math.max
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A `CryptoService` class instance manages the end-to-end crypto for a session.
|
* A `CryptoService` class instance manages the end-to-end crypto for a session.
|
||||||
@ -98,7 +99,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: MyDeviceInfoHolder,
|
private val myDeviceInfoHolder: Lazy<MyDeviceInfoHolder>,
|
||||||
// the crypto store
|
// the crypto store
|
||||||
private val cryptoStore: IMXCryptoStore,
|
private val cryptoStore: IMXCryptoStore,
|
||||||
// Olm device
|
// Olm device
|
||||||
@ -190,12 +191,12 @@ internal class CryptoManager @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getMyDevice(): MXDeviceInfo {
|
override fun getMyDevice(): MXDeviceInfo {
|
||||||
return myDeviceInfoHolder.myDevice
|
return myDeviceInfoHolder.get().myDevice
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDevicesList(callback: MatrixCallback<DevicesListResponse>) {
|
override fun getDevicesList(callback: MatrixCallback<DevicesListResponse>) {
|
||||||
getDevicesTask
|
getDevicesTask
|
||||||
.configureWith(Unit)
|
.toConfigurableTask()
|
||||||
.dispatchTo(callback)
|
.dispatchTo(callback)
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
@ -241,16 +242,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) {
|
||||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
if (isStarted.get() || isStarting.get()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isStarting.set(true)
|
||||||
|
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||||
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()
|
||||||
@ -312,7 +313,7 @@ internal class CryptoManager @Inject constructor(
|
|||||||
* @param syncResponse the syncResponse
|
* @param syncResponse the syncResponse
|
||||||
*/
|
*/
|
||||||
fun onSyncCompleted(syncResponse: SyncResponse) {
|
fun onSyncCompleted(syncResponse: SyncResponse) {
|
||||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||||
if (syncResponse.deviceLists != null) {
|
if (syncResponse.deviceLists != null) {
|
||||||
deviceListManager.handleDeviceListsChanges(syncResponse.deviceLists.changed, syncResponse.deviceLists.left)
|
deviceListManager.handleDeviceListsChanges(syncResponse.deviceLists.changed, syncResponse.deviceLists.left)
|
||||||
}
|
}
|
||||||
@ -337,7 +338,7 @@ internal class CryptoManager @Inject constructor(
|
|||||||
* @return the device info, or null if not found / unsupported algorithm / crypto released
|
* @return the device info, or null if not found / unsupported algorithm / crypto released
|
||||||
*/
|
*/
|
||||||
override fun deviceWithIdentityKey(senderKey: String, algorithm: String): MXDeviceInfo? {
|
override fun deviceWithIdentityKey(senderKey: String, algorithm: String): MXDeviceInfo? {
|
||||||
return if (!TextUtils.equals(algorithm, MXCRYPTO_ALGORITHM_MEGOLM) && !TextUtils.equals(algorithm, MXCRYPTO_ALGORITHM_OLM)) {
|
return if (algorithm != MXCRYPTO_ALGORITHM_MEGOLM && algorithm != MXCRYPTO_ALGORITHM_OLM) {
|
||||||
// We only deal in olm keys
|
// We only deal in olm keys
|
||||||
null
|
null
|
||||||
} else cryptoStore.deviceWithIdentityKey(senderKey)
|
} else cryptoStore.deviceWithIdentityKey(senderKey)
|
||||||
@ -350,8 +351,8 @@ internal class CryptoManager @Inject constructor(
|
|||||||
* @param deviceId the device id
|
* @param deviceId the device id
|
||||||
*/
|
*/
|
||||||
override fun getDeviceInfo(userId: String, deviceId: String?): MXDeviceInfo? {
|
override fun getDeviceInfo(userId: String, deviceId: String?): MXDeviceInfo? {
|
||||||
return if (!TextUtils.isEmpty(userId) && !TextUtils.isEmpty(deviceId)) {
|
return if (userId.isNotEmpty() && !deviceId.isNullOrEmpty()) {
|
||||||
cryptoStore.getUserDevice(deviceId!!, userId)
|
cryptoStore.getUserDevice(deviceId, userId)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
@ -387,12 +388,12 @@ internal class CryptoManager @Inject constructor(
|
|||||||
var isUpdated = false
|
var isUpdated = false
|
||||||
val deviceIds = devicesIdListByUserId[userId]
|
val deviceIds = devicesIdListByUserId[userId]
|
||||||
|
|
||||||
for (deviceId in deviceIds!!) {
|
deviceIds?.forEach { deviceId ->
|
||||||
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 (null != device && device.isUnknown) {
|
if (device?.isUnknown == true) {
|
||||||
device.verified = MXDeviceInfo.DEVICE_VERIFICATION_UNVERIFIED
|
device.verified = MXDeviceInfo.DEVICE_VERIFICATION_UNVERIFIED
|
||||||
isUpdated = true
|
isUpdated = true
|
||||||
}
|
}
|
||||||
@ -436,7 +437,7 @@ internal class CryptoManager @Inject constructor(
|
|||||||
// (for now at least. Maybe we should alert the user somehow?)
|
// (for now at least. Maybe we should alert the user somehow?)
|
||||||
val existingAlgorithm = cryptoStore.getRoomAlgorithm(roomId)
|
val existingAlgorithm = cryptoStore.getRoomAlgorithm(roomId)
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(existingAlgorithm) && !TextUtils.equals(existingAlgorithm, algorithm)) {
|
if (!existingAlgorithm.isNullOrEmpty() && existingAlgorithm != algorithm) {
|
||||||
Timber.e("## setEncryptionInRoom() : Ignoring m.room.encryption event which requests a change of config in $roomId")
|
Timber.e("## setEncryptionInRoom() : Ignoring m.room.encryption event which requests a change of config in $roomId")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -444,7 +445,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 with " + algorithm!!)
|
Timber.e("## setEncryptionInRoom() : Unable to encrypt room ${roomId} with $algorithm")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -532,7 +533,7 @@ internal class CryptoManager @Inject constructor(
|
|||||||
eventType: String,
|
eventType: String,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
callback: MatrixCallback<MXEncryptEventContentResult>) {
|
callback: MatrixCallback<MXEncryptEventContentResult>) {
|
||||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||||
if (!isStarted()) {
|
if (!isStarted()) {
|
||||||
Timber.v("## encryptEventContent() : wait after e2e init")
|
Timber.v("## encryptEventContent() : wait after e2e init")
|
||||||
internalStart(false)
|
internalStart(false)
|
||||||
@ -559,7 +560,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))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -568,8 +569,7 @@ 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(MXCryptoError.UNABLE_TO_ENCRYPT_ERROR_CODE,
|
callback.onFailure(Failure.CryptoError(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_ENCRYPT, reason)))
|
||||||
MXCryptoError.UNABLE_TO_ENCRYPT, reason)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -579,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 null in case of error
|
* @return the MXEventDecryptionResult data, or throw in case of error
|
||||||
*/
|
*/
|
||||||
@Throws(MXDecryptionException::class)
|
@Throws(MXCryptoError::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 },
|
||||||
@ -598,8 +598,8 @@ 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 {
|
||||||
val result = withContext(coroutineDispatchers.crypto) {
|
val result = withContext(coroutineDispatchers.crypto) {
|
||||||
internalDecryptEvent(event, timeline)
|
internalDecryptEvent(event, timeline)
|
||||||
}
|
}
|
||||||
@ -614,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
|
||||||
if (eventContent == null) {
|
return if (eventContent == null) {
|
||||||
Timber.e("## decryptEvent : empty event content")
|
Timber.e("## decryptEvent : empty event content")
|
||||||
return@Try null
|
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON))
|
||||||
}
|
} else {
|
||||||
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, eventContent["algorithm"] as String)
|
val algorithm = eventContent["algorithm"]?.toString()
|
||||||
|
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm)
|
||||||
if (alg == null) {
|
if (alg == null) {
|
||||||
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, eventContent["algorithm"] as String)
|
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm)
|
||||||
Timber.e("## decryptEvent() : $reason")
|
Timber.e("## decryptEvent() : $reason")
|
||||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE, MXCryptoError.UNABLE_TO_DECRYPT, reason))
|
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason))
|
||||||
} else {
|
} else {
|
||||||
alg.decryptEvent(event, timeline)
|
alg.decryptEvent(event, timeline)
|
||||||
}
|
}
|
||||||
@ -647,7 +647,7 @@ internal class CryptoManager @Inject constructor(
|
|||||||
* @param event the event
|
* @param event the event
|
||||||
*/
|
*/
|
||||||
fun onToDeviceEvent(event: Event) {
|
fun onToDeviceEvent(event: Event) {
|
||||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||||
when (event.getClearType()) {
|
when (event.getClearType()) {
|
||||||
EventType.ROOM_KEY, EventType.FORWARDED_ROOM_KEY -> {
|
EventType.ROOM_KEY, EventType.FORWARDED_ROOM_KEY -> {
|
||||||
onRoomKeyEvent(event)
|
onRoomKeyEvent(event)
|
||||||
@ -669,13 +669,13 @@ internal class CryptoManager @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
private fun onRoomKeyEvent(event: Event) {
|
private fun onRoomKeyEvent(event: Event) {
|
||||||
val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>() ?: return
|
val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>() ?: return
|
||||||
if (TextUtils.isEmpty(roomKeyContent.roomId) || TextUtils.isEmpty(roomKeyContent.algorithm)) {
|
if (roomKeyContent.roomId.isNullOrEmpty() || roomKeyContent.algorithm.isNullOrEmpty()) {
|
||||||
Timber.e("## onRoomKeyEvent() : missing fields")
|
Timber.e("## onRoomKeyEvent() : missing fields")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
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)
|
||||||
@ -687,13 +687,13 @@ internal class CryptoManager @Inject constructor(
|
|||||||
* @param event the encryption event.
|
* @param event the encryption event.
|
||||||
*/
|
*/
|
||||||
private fun onRoomEncryptionEvent(roomId: String, event: Event) {
|
private fun onRoomEncryptionEvent(roomId: String, event: Event) {
|
||||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||||
val params = LoadRoomMembersTask.Params(roomId)
|
val params = LoadRoomMembersTask.Params(roomId)
|
||||||
loadRoomMembersTask
|
loadRoomMembersTask
|
||||||
.execute(params)
|
.execute(params)
|
||||||
.map { allLoaded ->
|
.map { _ ->
|
||||||
val userIds = getRoomUserIds(roomId)
|
val userIds = getRoomUserIds(roomId)
|
||||||
setEncryptionInRoom(roomId, event.content!!["algorithm"] as String, true, userIds)
|
setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -736,7 +736,7 @@ internal class CryptoManager @Inject constructor(
|
|||||||
val membership = roomMember?.membership
|
val membership = roomMember?.membership
|
||||||
if (membership == Membership.JOIN) {
|
if (membership == Membership.JOIN) {
|
||||||
// make sure we are tracking the deviceList for this user.
|
// make sure we are tracking the deviceList for this user.
|
||||||
deviceListManager.startTrackingDeviceList(Arrays.asList(userId))
|
deviceListManager.startTrackingDeviceList(listOf(userId))
|
||||||
} else if (membership == Membership.INVITE
|
} else if (membership == Membership.INVITE
|
||||||
&& shouldEncryptForInvitedMembers(roomId)
|
&& shouldEncryptForInvitedMembers(roomId)
|
||||||
&& cryptoConfig.enableEncryptionForInvitedMembers) {
|
&& cryptoConfig.enableEncryptionForInvitedMembers) {
|
||||||
@ -745,7 +745,7 @@ internal class CryptoManager @Inject constructor(
|
|||||||
// know what other servers are in the room at the time they've been invited.
|
// know what other servers are in the room at the time they've been invited.
|
||||||
// They therefore will not send device updates if a user logs in whilst
|
// They therefore will not send device updates if a user logs in whilst
|
||||||
// their state is invite.
|
// their state is invite.
|
||||||
deviceListManager.startTrackingDeviceList(Arrays.asList(userId))
|
deviceListManager.startTrackingDeviceList(listOf(userId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -764,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 = MoshiProvider.getCanonicalJson(Map::class.java, getMyDevice().signalableJSONDictionary())
|
val canonicalJson = JsonCanonicalizer.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
|
||||||
@ -780,7 +780,11 @@ internal class CryptoManager @Inject constructor(
|
|||||||
* @param callback the exported keys
|
* @param callback the exported keys
|
||||||
*/
|
*/
|
||||||
override fun exportRoomKeys(password: String, callback: MatrixCallback<ByteArray>) {
|
override fun exportRoomKeys(password: String, callback: MatrixCallback<ByteArray>) {
|
||||||
exportRoomKeys(password, MXMegolmExportEncryption.DEFAULT_ITERATION_COUNT, callback)
|
GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
|
runCatching {
|
||||||
|
exportRoomKeys(password, MXMegolmExportEncryption.DEFAULT_ITERATION_COUNT)
|
||||||
|
}.fold(callback::onSuccess, callback::onFailure)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -790,31 +794,17 @@ internal class CryptoManager @Inject constructor(
|
|||||||
* @param anIterationCount the encryption iteration count (0 means no encryption)
|
* @param anIterationCount the encryption iteration count (0 means no encryption)
|
||||||
* @param callback the exported keys
|
* @param callback the exported keys
|
||||||
*/
|
*/
|
||||||
private fun exportRoomKeys(password: String, anIterationCount: Int, callback: MatrixCallback<ByteArray>) {
|
private suspend fun exportRoomKeys(password: String, anIterationCount: Int): ByteArray {
|
||||||
GlobalScope.launch(coroutineDispatchers.main) {
|
return withContext(coroutineDispatchers.crypto) {
|
||||||
withContext(coroutineDispatchers.crypto) {
|
val iterationCount = max(0, anIterationCount)
|
||||||
Try {
|
|
||||||
val iterationCount = Math.max(0, anIterationCount)
|
|
||||||
|
|
||||||
val exportedSessions = ArrayList<MegolmSessionData>()
|
val exportedSessions = cryptoStore.getInboundGroupSessions().mapNotNull { it.exportKeys() }
|
||||||
|
|
||||||
val inboundGroupSessions = cryptoStore.getInboundGroupSessions()
|
|
||||||
|
|
||||||
for (session in inboundGroupSessions) {
|
|
||||||
val megolmSessionData = session.exportKeys()
|
|
||||||
|
|
||||||
if (null != megolmSessionData) {
|
|
||||||
exportedSessions.add(megolmSessionData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val adapter = MoshiProvider.providesMoshi()
|
val adapter = MoshiProvider.providesMoshi()
|
||||||
.adapter(List::class.java)
|
.adapter(List::class.java)
|
||||||
|
|
||||||
MXMegolmExportEncryption.encryptMegolmKeyFile(adapter.toJson(exportedSessions), password, iterationCount)
|
MXMegolmExportEncryption.encryptMegolmKeyFile(adapter.toJson(exportedSessions), password, iterationCount)
|
||||||
}
|
}
|
||||||
}.foldToCallback(callback)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -838,7 +828,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))
|
||||||
@ -846,7 +836,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")
|
||||||
@ -870,14 +860,14 @@ 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.UNKNOWN_DEVICES_CODE exception is triggered.
|
* If there are some unknown devices, a MXCryptoError.UnknownDevice 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.
|
||||||
*/
|
*/
|
||||||
fun checkUnknownDevices(userIds: List<String>, callback: MatrixCallback<Unit>) {
|
fun checkUnknownDevices(userIds: List<String>, callback: MatrixCallback<Unit>) {
|
||||||
// force the refresh to ensure that the devices list is up-to-date
|
// force the refresh to ensure that the devices list is up-to-date
|
||||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||||
deviceListManager
|
deviceListManager
|
||||||
.downloadKeys(userIds, true)
|
.downloadKeys(userIds, true)
|
||||||
.fold(
|
.fold(
|
||||||
@ -888,9 +878,7 @@ 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(
|
callback.onFailure(Failure.CryptoError(MXCryptoError.UnknownDevice(unknownDevices)))
|
||||||
Failure.CryptoError(MXCryptoError(MXCryptoError.UNKNOWN_DEVICES_CODE,
|
|
||||||
MXCryptoError.UNABLE_TO_ENCRYPT, MXCryptoError.UNKNOWN_DEVICES_REASON, unknownDevices)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -930,11 +918,8 @@ 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 if (null != roomId) {
|
return roomId?.let { cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(it) }
|
||||||
cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId)
|
?: false
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -947,7 +932,7 @@ internal class CryptoManager @Inject constructor(
|
|||||||
val roomIds = cryptoStore.getRoomsListBlacklistUnverifiedDevices().toMutableList()
|
val roomIds = cryptoStore.getRoomsListBlacklistUnverifiedDevices().toMutableList()
|
||||||
|
|
||||||
if (add) {
|
if (add) {
|
||||||
if (!roomIds.contains(roomId)) {
|
if (roomId !in roomIds) {
|
||||||
roomIds.add(roomId)
|
roomIds.add(roomId)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -992,18 +977,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) {
|
||||||
val algorithm = wireContent["algorithm"].toString()
|
Timber.e("## reRequestRoomKeyForEvent Failed to re-request key, null content")
|
||||||
val senderKey = wireContent["sender_key"].toString()
|
return
|
||||||
val sessionId = wireContent["session_id"].toString()
|
}
|
||||||
|
|
||||||
val requestBody = RoomKeyRequestBody()
|
val requestBody = RoomKeyRequestBody()
|
||||||
|
|
||||||
requestBody.roomId = event.roomId
|
requestBody.roomId = event.roomId
|
||||||
requestBody.algorithm = algorithm
|
requestBody.algorithm = wireContent["algorithm"]?.toString()
|
||||||
requestBody.senderKey = senderKey
|
requestBody.senderKey = wireContent["sender_key"]?.toString()
|
||||||
requestBody.sessionId = sessionId
|
requestBody.sessionId = wireContent["session_id"]?.toString()
|
||||||
|
|
||||||
outgoingRoomKeyRequestManager.resendRoomKeyRequest(requestBody)
|
outgoingRoomKeyRequestManager.resendRoomKeyRequest(requestBody)
|
||||||
}
|
}
|
||||||
@ -1022,7 +1007,7 @@ internal class CryptoManager @Inject constructor(
|
|||||||
*
|
*
|
||||||
* @param listener listener
|
* @param listener listener
|
||||||
*/
|
*/
|
||||||
fun removeRoomKeysRequestListener(listener: RoomKeysRequestListener) {
|
override fun removeRoomKeysRequestListener(listener: RoomKeysRequestListener) {
|
||||||
incomingRoomKeyRequestManager.removeRoomKeysRequestListener(listener)
|
incomingRoomKeyRequestManager.removeRoomKeysRequestListener(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1036,12 +1021,11 @@ internal class CryptoManager @Inject constructor(
|
|||||||
val unknownDevices = MXUsersDevicesMap<MXDeviceInfo>()
|
val unknownDevices = MXUsersDevicesMap<MXDeviceInfo>()
|
||||||
val userIds = devicesInRoom.userIds
|
val userIds = devicesInRoom.userIds
|
||||||
for (userId in userIds) {
|
for (userId in userIds) {
|
||||||
val deviceIds = devicesInRoom.getUserDeviceIds(userId)
|
devicesInRoom.getUserDeviceIds(userId)?.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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1050,7 +1034,7 @@ internal class CryptoManager @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>) {
|
override fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>) {
|
||||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||||
deviceListManager
|
deviceListManager
|
||||||
.downloadKeys(userIds, forceDownload)
|
.downloadKeys(userIds, forceDownload)
|
||||||
.foldToCallback(callback)
|
.foldToCallback(callback)
|
||||||
@ -1058,7 +1042,8 @@ internal class CryptoManager @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun clearCryptoCache(callback: MatrixCallback<Unit>) {
|
override fun clearCryptoCache(callback: MatrixCallback<Unit>) {
|
||||||
clearCryptoDataTask.configureWith(Unit)
|
clearCryptoDataTask
|
||||||
|
.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(false /* TODO*/,
|
return RealmCryptoStore(
|
||||||
realmConfiguration,
|
realmConfiguration,
|
||||||
credentials)
|
credentials)
|
||||||
}
|
}
|
||||||
@ -168,8 +168,10 @@ internal abstract class CryptoModule {
|
|||||||
abstract fun bindSendToDeviceTask(sendToDeviceTask: DefaultSendToDeviceTask): SendToDeviceTask
|
abstract fun bindSendToDeviceTask(sendToDeviceTask: DefaultSendToDeviceTask): SendToDeviceTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindClaimOneTimeKeysForUsersDeviceTask(claimOneTimeKeysForUsersDevice: DefaultClaimOneTimeKeysForUsersDevice): ClaimOneTimeKeysForUsersDeviceTask
|
abstract fun bindClaimOneTimeKeysForUsersDeviceTask(claimOneTimeKeysForUsersDevice: DefaultClaimOneTimeKeysForUsersDevice)
|
||||||
|
: ClaimOneTimeKeysForUsersDeviceTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindDeleteDeviceWithUserPasswordTask(deleteDeviceWithUserPasswordTask: DefaultDeleteDeviceWithUserPasswordTask): DeleteDeviceWithUserPasswordTask
|
abstract fun bindDeleteDeviceWithUserPasswordTask(deleteDeviceWithUserPasswordTask: DefaultDeleteDeviceWithUserPasswordTask)
|
||||||
|
: 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(devices, userId)
|
usersDevicesInfoMap.setObjects(userId, devices)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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 -> DeviceInfo
|
// Map from userId -> deviceId -> MXDeviceInfo
|
||||||
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(devices, userId)
|
stored.setObjects(userId, devices)
|
||||||
} 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!![signKeyId]
|
val signKey = deviceKeys.keys?.get(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!![userId]
|
val signatureMap = deviceKeys.signatures?.get(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)
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
Normal file → Executable file
10
matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXEventDecryptionResult.kt
Normal file → Executable file
@ -1,6 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* 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.
|
||||||
@ -18,7 +17,6 @@
|
|||||||
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.
|
||||||
@ -28,23 +26,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).
|
||||||
*/
|
*/
|
||||||
var clearEvent: JsonDict? = null,
|
val clearEvent: JsonDict,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Key owned by the sender of this event.
|
* Key owned by the sender of this event.
|
||||||
* See MXEvent.senderKey.
|
* See MXEvent.senderKey.
|
||||||
*/
|
*/
|
||||||
var senderCurve25519Key: String? = null,
|
val 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.
|
||||||
*/
|
*/
|
||||||
var claimedEd25519Key: String? = null,
|
val 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.
|
||||||
*/
|
*/
|
||||||
var forwardingCurve25519KeyChain: List<String> = ArrayList()
|
val forwardingCurve25519KeyChain: List<String> = emptyList()
|
||||||
)
|
)
|
||||||
|
@ -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 { it <= ' ' }
|
val line = fileStr.substring(lineStart, lineEnd).trim()
|
||||||
|
|
||||||
// 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 { it <= ' ' }
|
line = fileStr.substring(lineStart).trim()
|
||||||
} else {
|
} else {
|
||||||
line = fileStr.substring(lineStart, lineEnd).trim { it <= ' ' }
|
line = fileStr.substring(lineStart, lineEnd).trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TextUtils.equals(line, TRAILER_LINE)) {
|
if (TextUtils.equals(line, TRAILER_LINE)) {
|
||||||
|
@ -18,23 +18,20 @@
|
|||||||
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.MXDecryptionResult
|
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||||
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.OlmAccount
|
import org.matrix.olm.*
|
||||||
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.*
|
||||||
@ -86,11 +83,6 @@ 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()
|
||||||
@ -119,13 +111,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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,13 +289,13 @@ internal class MXOlmDevice @Inject constructor(
|
|||||||
|
|
||||||
val res = HashMap<String, String>()
|
val res = HashMap<String, String>()
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(payloadString)) {
|
if (!payloadString.isNullOrEmpty()) {
|
||||||
res["payload"] = payloadString!!
|
res["payload"] = payloadString
|
||||||
}
|
}
|
||||||
|
|
||||||
val sessionIdentifier = olmSession.sessionIdentifier()
|
val sessionIdentifier = olmSession.sessionIdentifier()
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(sessionIdentifier)) {
|
if (!sessionIdentifier.isNullOrEmpty()) {
|
||||||
res["session_id"] = sessionIdentifier
|
res["session_id"] = sessionIdentifier
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -513,24 +505,26 @@ 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)
|
||||||
|
|
||||||
if (null != existingInboundSession) {
|
getInboundGroupSession(sessionId, senderKey, roomId).fold(
|
||||||
|
{
|
||||||
|
// 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 = existingInboundSession.firstKnownIndex!!
|
val existingFirstKnown = it.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) {
|
||||||
if (session.olmInboundGroupSession != null) {
|
session.olmInboundGroupSession?.releaseSession()
|
||||||
session.olmInboundGroupSession!!.releaseSession()
|
|
||||||
}
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// sanity check
|
// sanity check
|
||||||
if (null == session.olmInboundGroupSession) {
|
if (null == session.olmInboundGroupSession) {
|
||||||
@ -545,7 +539,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
|
||||||
}
|
}
|
||||||
@ -584,13 +578,13 @@ internal class MXOlmDevice @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// sanity check
|
// sanity check
|
||||||
if (null == session || null == session.olmInboundGroupSession) {
|
if (session?.olmInboundGroupSession == null) {
|
||||||
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
|
||||||
@ -601,21 +595,27 @@ internal class MXOlmDevice @Inject constructor(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
val existingOlmSession = getInboundGroupSession(sessionId, senderKey, roomId)
|
getInboundGroupSession(sessionId, senderKey, roomId)
|
||||||
if (null != existingOlmSession) {
|
.fold(
|
||||||
|
{
|
||||||
|
// 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 (existingOlmSession.firstKnownIndex!! <= session.firstKnownIndex!!) {
|
if (it.firstKnownIndex!! <= session.firstKnownIndex!!) {
|
||||||
//Ignore this, keep existing
|
//Ignore this, keep existing
|
||||||
session.olmInboundGroupSession!!.releaseSession()
|
session.olmInboundGroupSession!!.releaseSession()
|
||||||
continue
|
} else {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sessions.add(session)
|
sessions.add(session)
|
||||||
}
|
}
|
||||||
|
Unit
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
store.storeInboundGroupSessions(sessions)
|
store.storeInboundGroupSessions(sessions)
|
||||||
|
|
||||||
@ -644,29 +644,24 @@ 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): MXDecryptionResult? {
|
senderKey: String): Try<OlmDecryptionResult> {
|
||||||
val result = MXDecryptionResult()
|
return getInboundGroupSession(sessionId, senderKey, roomId)
|
||||||
val session = getInboundGroupSession(sessionId, senderKey, roomId)
|
.flatMap { 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 (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: Exception) {
|
} catch (e: OlmException) {
|
||||||
Timber.e(e, "## decryptGroupMessage () : decryptMessage failed")
|
Timber.e(e, "## decryptGroupMessage () : decryptMessage failed")
|
||||||
errorMessage = e.message ?: ""
|
return@flatMap Try.Failure(MXCryptoError.OlmError(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null != decryptResult) {
|
|
||||||
if (null != timeline) {
|
if (null != timeline) {
|
||||||
if (!inboundGroupSessionMessageIndexes.containsKey(timeline)) {
|
if (!inboundGroupSessionMessageIndexes.containsKey(timeline)) {
|
||||||
inboundGroupSessionMessageIndexes[timeline] = HashMap()
|
inboundGroupSessionMessageIndexes[timeline] = HashMap()
|
||||||
@ -674,51 +669,40 @@ internal class MXOlmDevice @Inject constructor(
|
|||||||
|
|
||||||
val messageIndexKey = senderKey + "|" + sessionId + "|" + decryptResult.mIndex
|
val messageIndexKey = senderKey + "|" + sessionId + "|" + decryptResult.mIndex
|
||||||
|
|
||||||
if (null != inboundGroupSessionMessageIndexes[timeline]!![messageIndexKey]) {
|
if (inboundGroupSessionMessageIndexes[timeline]?.get(messageIndexKey) != null) {
|
||||||
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")
|
||||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.DUPLICATED_MESSAGE_INDEX_ERROR_CODE,
|
return@flatMap Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason))
|
||||||
MXCryptoError.UNABLE_TO_DECRYPT, reason))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inboundGroupSessionMessageIndexes[timeline]!!.put(messageIndexKey, true)
|
inboundGroupSessionMessageIndexes[timeline]!!.put(messageIndexKey, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
store.storeInboundGroupSessions(listOf(session))
|
store.storeInboundGroupSessions(listOf(session))
|
||||||
try {
|
val payload = 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)
|
||||||
val payload = adapter.fromJson(payloadString)
|
adapter.fromJson(payloadString)
|
||||||
result.payload = payload
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e, "## decryptGroupMessage() : RLEncoder.encode failed " + e.message)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null == result.payload) {
|
|
||||||
Timber.e("## decryptGroupMessage() : fails to parse the payload")
|
Timber.e("## decryptGroupMessage() : fails to parse the payload")
|
||||||
return null
|
return@flatMap Try.Failure(
|
||||||
|
MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON))
|
||||||
}
|
}
|
||||||
|
|
||||||
result.keysClaimed = session.keysClaimed
|
return@flatMap Try.just(
|
||||||
result.senderKey = senderKey
|
OlmDecryptionResult(
|
||||||
result.forwardingCurve25519KeyChain = session.forwardingCurve25519KeyChain
|
payload,
|
||||||
} else {
|
session.keysClaimed,
|
||||||
Timber.e("## decryptGroupMessage() : failed to decode the message")
|
senderKey,
|
||||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.OLM_ERROR_CODE, errorMessage, null))
|
session.forwardingCurve25519KeyChain
|
||||||
}
|
)
|
||||||
|
)
|
||||||
} 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")
|
||||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_ERROR_CODE,
|
return@flatMap Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason))
|
||||||
MXCryptoError.UNABLE_TO_DECRYPT, reason))
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Timber.e("## decryptGroupMessage() : Cannot retrieve inbound group session $sessionId")
|
|
||||||
throw MXDecryptionException(inboundGroupSessionWithIdError)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -745,7 +729,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, MoshiProvider.getCanonicalJson<Map<*, *>>(Map::class.java, jsonDictionary))
|
olmUtility!!.verifyEd25519Signature(signature, key, JsonCanonicalizer.getCanonicalJson(Map::class.java, jsonDictionary))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -767,9 +751,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 (!TextUtils.isEmpty(theirDeviceIdentityKey) && !TextUtils.isEmpty(sessionId)) {
|
return if (theirDeviceIdentityKey.isEmpty() || sessionId.isEmpty()) null else {
|
||||||
store.getDeviceSession(sessionId, theirDeviceIdentityKey)
|
store.getDeviceSession(sessionId, theirDeviceIdentityKey)
|
||||||
} else null
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -782,26 +766,27 @@ 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?): OlmInboundGroupSessionWrapper? {
|
fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): Try<OlmInboundGroupSessionWrapper> {
|
||||||
inboundGroupSessionWithIdError = null
|
if (sessionId.isNullOrBlank() || senderKey.isNullOrBlank()) {
|
||||||
|
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)
|
||||||
|
|
||||||
if (null != session) {
|
return 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")
|
||||||
inboundGroupSessionWithIdError = MXCryptoError(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_ERROR_CODE,
|
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, errorDescription))
|
||||||
MXCryptoError.UNABLE_TO_DECRYPT, errorDescription)
|
} else {
|
||||||
|
Try.just(session)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Timber.e("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId")
|
Timber.e("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId")
|
||||||
inboundGroupSessionWithIdError = MXCryptoError(MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE,
|
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON))
|
||||||
MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON, null)
|
|
||||||
}
|
}
|
||||||
return session
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -813,6 +798,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 null != getInboundGroupSession(sessionId, senderKey, roomId)
|
return getInboundGroupSession(sessionId, senderKey, roomId).isSuccess()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,9 @@ 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!![OlmAccount.JSON_KEY_ONE_TIME_KEY]
|
val curve25519Map = oneTimeKeys?.get(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 = MoshiProvider.getCanonicalJson(Map::class.java, k)
|
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, k)
|
||||||
|
|
||||||
k["signatures"] = objectSigner.signObject(canonicalJson)
|
k["signatures"] = objectSigner.signObject(canonicalJson)
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
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
|
||||||
@ -28,9 +27,11 @@ 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.*
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@SessionScope
|
@SessionScope
|
||||||
@ -47,7 +48,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 var sendOutgoingRoomKeyRequestsRunning: Boolean = false
|
private val sendOutgoingRoomKeyRequestsRunning = AtomicBoolean(false)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the client is started. Sets background processes running.
|
* Called when the client is started. Sets background processes running.
|
||||||
@ -101,8 +102,10 @@ 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
|
||||||
@ -110,8 +113,10 @@ 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
|
||||||
@ -126,12 +131,17 @@ 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)
|
||||||
|
|
||||||
if (req.state === OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING || req.state === OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND) {
|
when (req.state) {
|
||||||
|
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 {
|
||||||
@ -142,22 +152,23 @@ 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) {
|
if (sendOutgoingRoomKeyRequestsRunning.get()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Handler().postDelayed(Runnable {
|
BACKGROUND_HANDLER.postDelayed(Runnable {
|
||||||
if (sendOutgoingRoomKeyRequestsRunning) {
|
if (sendOutgoingRoomKeyRequestsRunning.get()) {
|
||||||
Timber.v("## startTimer() : RoomKeyRequestSend already in progress!")
|
Timber.v("## startTimer() : RoomKeyRequestSend already in progress!")
|
||||||
return@Runnable
|
return@Runnable
|
||||||
}
|
}
|
||||||
|
|
||||||
sendOutgoingRoomKeyRequestsRunning = true
|
sendOutgoingRoomKeyRequestsRunning.set(true)
|
||||||
sendOutgoingRoomKeyRequests()
|
sendOutgoingRoomKeyRequests()
|
||||||
}, SEND_KEY_REQUESTS_DELAY_MS.toLong())
|
}, SEND_KEY_REQUESTS_DELAY_MS.toLong())
|
||||||
}
|
}
|
||||||
@ -167,19 +178,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 = false
|
sendOutgoingRoomKeyRequestsRunning.set(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(
|
||||||
HashSet<OutgoingRoomKeyRequest.RequestState>(Arrays.asList<OutgoingRoomKeyRequest.RequestState>(OutgoingRoomKeyRequest.RequestState.UNSENT,
|
setOf(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 = false
|
sendOutgoingRoomKeyRequestsRunning.set(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,7 +224,7 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
|
|||||||
cryptoStore.updateOutgoingRoomKeyRequest(request)
|
cryptoStore.updateOutgoingRoomKeyRequest(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
sendOutgoingRoomKeyRequestsRunning = false
|
sendOutgoingRoomKeyRequestsRunning.set(false)
|
||||||
startTimer()
|
startTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,7 +257,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 = false
|
sendOutgoingRoomKeyRequestsRunning.set(false)
|
||||||
startTimer()
|
startTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,15 +296,20 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
|
|||||||
val contentMap = MXUsersDevicesMap<Any>()
|
val contentMap = MXUsersDevicesMap<Any>()
|
||||||
|
|
||||||
for (recipient in recipients) {
|
for (recipient in recipients) {
|
||||||
contentMap.setObject(message, recipient["userId"], recipient["deviceId"]) // TODO Change this two hard coded key to something better
|
// 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,7 +24,6 @@ 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
|
||||||
@ -54,7 +53,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
|
|||||||
}
|
}
|
||||||
|
|
||||||
val olmSessionResult = MXOlmSessionResult(deviceInfo, sessionId)
|
val olmSessionResult = MXOlmSessionResult(deviceInfo, sessionId)
|
||||||
results.setObject(olmSessionResult, userId, deviceId)
|
results.setObject(userId, deviceId, olmSessionResult)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +67,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(oneTimeKeyAlgorithm, device.userId, device.deviceId)
|
usersDevicesToClaim.setObject(device.userId, device.deviceId, oneTimeKeyAlgorithm)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@ -91,12 +90,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(deviceId, userId)
|
val olmSessionResult = results.getObject(userId, deviceId)
|
||||||
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(deviceId, userId)
|
val key = it.getObject(userId, deviceId)
|
||||||
if (key?.type == oneTimeKeyAlgorithm) {
|
if (key?.type == oneTimeKeyAlgorithm) {
|
||||||
oneTimeKey = key
|
oneTimeKey = key
|
||||||
}
|
}
|
||||||
@ -126,12 +125,14 @@ 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,8 +22,7 @@ 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.di.MoshiProvider
|
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||||
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.*
|
||||||
@ -81,10 +80,7 @@ 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
|
||||||
|
|
||||||
// FIXME We have to canonicalize the JSON
|
val payloadString = convertToUTF8(JsonCanonicalizer.getCanonicalJson(Map::class.java, payloadJson))
|
||||||
//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,11 +33,9 @@ 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 null in case of error
|
* @return the decryption information, or an error
|
||||||
* @throws MXDecryptionException the decryption failure reason
|
|
||||||
*/
|
*/
|
||||||
@Throws(MXDecryptionException::class)
|
suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult>
|
||||||
suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult?
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle a key event.
|
* Handle a key event.
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,9 +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.MXUsersDevicesMap
|
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||||
import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent
|
import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent
|
||||||
@ -39,10 +37,10 @@ import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
|||||||
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.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
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,67 +61,77 @@ 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()
|
||||||
|
|
||||||
@Throws(MXDecryptionException::class)
|
override suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> {
|
||||||
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult? {
|
|
||||||
return decryptEvent(event, timeline, true)
|
return decryptEvent(event, timeline, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(MXDecryptionException::class)
|
private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): Try<MXEventDecryptionResult> {
|
||||||
private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): MXEventDecryptionResult? {
|
if (event.roomId.isNullOrBlank()) {
|
||||||
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()!!
|
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON))
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var eventDecryptionResult: MXEventDecryptionResult? = null
|
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()
|
||||||
var cryptoError: MXCryptoError? = null
|
?: return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON))
|
||||||
var decryptGroupMessageResult: MXDecryptionResult? = null
|
|
||||||
|
|
||||||
try {
|
if (encryptedEventContent.senderKey.isNullOrBlank()
|
||||||
decryptGroupMessageResult = olmDevice.decryptGroupMessage(encryptedEventContent.ciphertext!!, event.roomId!!, timeline, encryptedEventContent.sessionId!!, encryptedEventContent.senderKey!!)
|
|| encryptedEventContent.sessionId.isNullOrBlank()
|
||||||
} catch (e: MXDecryptionException) {
|
|| encryptedEventContent.ciphertext.isNullOrBlank()) {
|
||||||
cryptoError = e.cryptoError
|
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON))
|
||||||
}
|
|
||||||
// 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"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
eventDecryptionResult.forwardingCurve25519KeyChain = decryptGroupMessageResult.forwardingCurve25519KeyChain!!
|
return olmDevice.decryptGroupMessage(encryptedEventContent.ciphertext,
|
||||||
} else if (cryptoError != null) {
|
event.roomId,
|
||||||
if (cryptoError.isOlmError) {
|
timeline,
|
||||||
if (MXCryptoError.UNKNOWN_MESSAGE_INDEX == cryptoError.message) {
|
encryptedEventContent.sessionId,
|
||||||
|
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, cryptoError.message)
|
val reason = String.format(MXCryptoError.OLM_REASON, throwable.olmException.message)
|
||||||
val detailedReason = String.format(MXCryptoError.DETAILLED_OLM_REASON, encryptedEventContent.ciphertext, cryptoError.message)
|
val detailedReason = String.format(MXCryptoError.DETAILED_OLM_REASON, encryptedEventContent.ciphertext, reason)
|
||||||
|
|
||||||
throw MXDecryptionException(MXCryptoError(
|
Try.Failure(MXCryptoError.Base(
|
||||||
MXCryptoError.OLM_ERROR_CODE,
|
MXCryptoError.ErrorType.OLM,
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return eventDecryptionResult
|
Try.Failure(throwable)
|
||||||
|
},
|
||||||
|
{ 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
|
||||||
@ -139,7 +147,8 @@ 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>()
|
||||||
selfMap["userId"] = credentials.userId // TODO Replace this hard coded keys (see OutgoingRoomKeyRequestManager)
|
// TODO Replace this hard coded keys (see OutgoingRoomKeyRequestManager)
|
||||||
|
selfMap["userId"] = credentials.userId
|
||||||
selfMap["deviceId"] = "*"
|
selfMap["deviceId"] = "*"
|
||||||
recipients.add(selfMap)
|
recipients.add(selfMap)
|
||||||
|
|
||||||
@ -171,17 +180,18 @@ 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)) {
|
if (pendingEvents[pendingEventsKey]?.containsKey(timelineId) == false) {
|
||||||
pendingEvents[pendingEventsKey]!![timelineId] = ArrayList()
|
pendingEvents[pendingEventsKey]?.put(timelineId, ArrayList())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pendingEvents[pendingEventsKey]!![timelineId]!!.indexOf(event) < 0) {
|
if (pendingEvents[pendingEventsKey]?.get(timelineId)?.contains(event) == false) {
|
||||||
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]!![timelineId]!!.add(event)
|
pendingEvents[pendingEventsKey]?.get(timelineId)?.add(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,21 +206,20 @@ 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()
|
||||||
var forwardingCurve25519KeyChain: MutableList<String> = ArrayList()
|
val 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 + " sessionId " + roomKeyContent.sessionId
|
Timber.v("## onRoomKeyEvent(), forward adding key : roomId ${roomKeyContent.roomId}" +
|
||||||
+ " sessionKey " + roomKeyContent.sessionKey) // from " + event);
|
" sessionId ${roomKeyContent.sessionId} sessionKey ${roomKeyContent.sessionKey}")
|
||||||
val forwardedRoomKeyContent = event.getClearContent().toModel<ForwardedRoomKeyContent>()
|
val forwardedRoomKeyContent = event.getClearContent().toModel<ForwardedRoomKeyContent>()
|
||||||
?: return
|
?: return
|
||||||
forwardingCurve25519KeyChain = if (forwardedRoomKeyContent.forwardingCurve25519KeyChain == null) {
|
|
||||||
ArrayList()
|
forwardedRoomKeyContent.forwardingCurve25519KeyChain?.let {
|
||||||
} else {
|
forwardingCurve25519KeyChain.addAll(it)
|
||||||
ArrayList(forwardedRoomKeyContent.forwardingCurve25519KeyChain)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (senderKey == null) {
|
if (senderKey == null) {
|
||||||
@ -253,7 +262,13 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val added = olmDevice.addInboundGroupSession(roomKeyContent.sessionId, roomKeyContent.sessionKey, roomKeyContent.roomId, senderKey, forwardingCurve25519KeyChain, keysClaimed, exportFormat)
|
val added = olmDevice.addInboundGroupSession(roomKeyContent.sessionId,
|
||||||
|
roomKeyContent.sessionKey,
|
||||||
|
roomKeyContent.roomId,
|
||||||
|
senderKey,
|
||||||
|
forwardingCurve25519KeyChain,
|
||||||
|
keysClaimed,
|
||||||
|
exportFormat)
|
||||||
|
|
||||||
if (added) {
|
if (added) {
|
||||||
keysBackup.maybeBackupKeys()
|
keysBackup.maybeBackupKeys()
|
||||||
@ -283,8 +298,10 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun hasKeysForKeyRequest(request: IncomingRoomKeyRequest): Boolean {
|
override fun hasKeysForKeyRequest(request: IncomingRoomKeyRequest): Boolean {
|
||||||
return (null != request.requestBody
|
val roomId = request.requestBody?.roomId ?: return false
|
||||||
&& olmDevice.hasInboundSessionKeys(request.requestBody!!.roomId!!, request.requestBody!!.senderKey!!, request.requestBody!!.sessionId!!))
|
val senderKey = request.requestBody?.senderKey ?: return false
|
||||||
|
val sessionId = request.requestBody?.sessionId ?: return false
|
||||||
|
return olmDevice.hasInboundSessionKeys(roomId, senderKey, sessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun shareKeysWithDevice(request: IncomingRoomKeyRequest) {
|
override fun shareKeysWithDevice(request: IncomingRoomKeyRequest) {
|
||||||
@ -292,45 +309,50 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
|
|||||||
if (request.requestBody == null) {
|
if (request.requestBody == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val userId = request.userId!!
|
val userId = request.userId ?: return
|
||||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||||
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 {
|
||||||
val devicesByUser = HashMap<String, List<MXDeviceInfo>>()
|
val devicesByUser = mapOf(userId to listOf(deviceInfo))
|
||||||
devicesByUser[userId] = ArrayList(Arrays.asList(deviceInfo))
|
|
||||||
ensureOlmSessionsForDevicesAction
|
ensureOlmSessionsForDevicesAction
|
||||||
.handle(devicesByUser)
|
.handle(devicesByUser)
|
||||||
.flatMap {
|
.flatMap {
|
||||||
val body = request.requestBody
|
val body = request.requestBody
|
||||||
val olmSessionResult = it.getObject(deviceId, userId)
|
val olmSessionResult = it.getObject(userId, deviceId)
|
||||||
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 " + body!!.senderKey + "|" + body.sessionId
|
Timber.v("## shareKeysWithDevice() : sharing keys for session" +
|
||||||
+ " with device " + userId + ":" + deviceId)
|
" ${body?.senderKey}|${body?.sessionId} with device $userId:$deviceId")
|
||||||
val inboundGroupSession = olmDevice.getInboundGroupSession(body.sessionId, body.senderKey, body.roomId)
|
|
||||||
|
|
||||||
val payloadJson = HashMap<String, Any>()
|
val payloadJson = mutableMapOf<String, Any>("type" to EventType.FORWARDED_ROOM_KEY)
|
||||||
payloadJson["type"] = EventType.FORWARDED_ROOM_KEY
|
|
||||||
payloadJson["content"] = inboundGroupSession!!.exportKeys()!!
|
|
||||||
|
|
||||||
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, Arrays.asList(deviceInfo))
|
olmDevice.getInboundGroupSession(body?.sessionId, body?.senderKey, body?.roomId)
|
||||||
|
.fold(
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
payloadJson["content"] = it.exportKeys() ?: ""
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
|
||||||
val sendToDeviceMap = MXUsersDevicesMap<Any>()
|
val sendToDeviceMap = MXUsersDevicesMap<Any>()
|
||||||
sendToDeviceMap.setObject(encodedPayload, userId, deviceId)
|
sendToDeviceMap.setObject(userId, deviceId, encodedPayload)
|
||||||
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,7 +21,6 @@ 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
|
||||||
@ -37,7 +36,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.di.MoshiProvider
|
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||||
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.*
|
||||||
@ -118,8 +117,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(deviceId, userId)
|
val deviceInfo = devicesInRoom.getObject(userId, deviceId)
|
||||||
if (null == safeSession.sharedWithDevices.getObject(deviceId, userId)) {
|
if (deviceInfo != null && null == safeSession.sharedWithDevices.getObject(userId, deviceId)) {
|
||||||
if (!shareMap.containsKey(userId)) {
|
if (!shareMap.containsKey(userId)) {
|
||||||
shareMap[userId] = ArrayList()
|
shareMap[userId] = ArrayList()
|
||||||
}
|
}
|
||||||
@ -201,7 +200,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(deviceID, userId)
|
val sessionResult = it.getObject(userId, deviceID)
|
||||||
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.
|
||||||
@ -218,7 +217,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(messageEncrypter.encryptMessage(payload, Arrays.asList(sessionResult.deviceInfo)), userId, deviceID)
|
contentMap.setObject(userId, deviceID, messageEncrypter.encryptMessage(payload, Arrays.asList(sessionResult.deviceInfo)))
|
||||||
haveTargets = true
|
haveTargets = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -239,7 +238,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(chainIndex, userId, deviceId)
|
session.sharedWithDevices.setObject(userId, deviceId, chainIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Unit
|
Unit
|
||||||
@ -254,7 +253,8 @@ internal class MXMegolmEncryption(
|
|||||||
/**
|
/**
|
||||||
* process the pending encryptions
|
* process the pending encryptions
|
||||||
*/
|
*/
|
||||||
private suspend fun encryptContent(session: MXOutboundSessionInfo, eventType: String, eventContent: Content) = Try<Content> {
|
private 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(MoshiProvider.getCanonicalJson(Map::class.java, payloadJson))
|
val payloadString = convertToUTF8(JsonCanonicalizer.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,6 +278,7 @@ 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.
|
||||||
@ -303,10 +304,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(deviceId, userId) ?: continue
|
val deviceInfo = it.getObject(userId, deviceId) ?: 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(deviceInfo, userId, deviceId)
|
unknownDevices.setObject(userId, deviceId, deviceInfo)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (deviceInfo.isBlocked) {
|
if (deviceInfo.isBlocked) {
|
||||||
@ -322,15 +323,13 @@ internal class MXMegolmEncryption(
|
|||||||
// Don't bother sending to ourself
|
// Don't bother sending to ourself
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
devicesInRoom.setObject(deviceInfo, userId, deviceId)
|
devicesInRoom.setObject(userId, deviceId, deviceInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (unknownDevices.isEmpty) {
|
if (unknownDevices.isEmpty) {
|
||||||
Try.just(devicesInRoom)
|
Try.just(devicesInRoom)
|
||||||
} else {
|
} else {
|
||||||
val cryptoError = MXCryptoError(MXCryptoError.UNKNOWN_DEVICES_CODE,
|
Try.Failure(MXCryptoError.UnknownDevice(unknownDevices))
|
||||||
MXCryptoError.UNABLE_TO_ENCRYPT, MXCryptoError.UNKNOWN_DEVICES_REASON, unknownDevices)
|
|
||||||
Try.Failure(Failure.CryptoError(cryptoError))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,6 @@ 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(
|
||||||
@ -37,8 +35,6 @@ 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(deviceId, userId)) {
|
if (null == devicesInRoom.getObject(userId, deviceId)) {
|
||||||
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,14 +17,13 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.crypto.algorithms.olm
|
package im.vector.matrix.android.internal.crypto.algorithms.olm
|
||||||
|
|
||||||
import android.text.TextUtils
|
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.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
|
||||||
@ -42,111 +41,120 @@ internal class MXOlmDecryption(
|
|||||||
private val credentials: Credentials)
|
private val credentials: Credentials)
|
||||||
: IMXDecrypting {
|
: IMXDecrypting {
|
||||||
|
|
||||||
@Throws(MXDecryptionException::class)
|
override suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> {
|
||||||
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult? {
|
val olmEventContent = event.content.toModel<OlmEventContent>() ?: run {
|
||||||
val olmEventContent = event.content.toModel<OlmEventContent>()!!
|
Timber.e("## decryptEvent() : bad event format")
|
||||||
|
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_EVENT_FORMAT,
|
||||||
if (null == olmEventContent.ciphertext) {
|
MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON))
|
||||||
Timber.e("## decryptEvent() : missing cipher text")
|
|
||||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_CIPHER_TEXT_ERROR_CODE,
|
|
||||||
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_CIPHER_TEXT_REASON))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!olmEventContent.ciphertext!!.containsKey(olmDevice.deviceCurve25519Key)) {
|
val cipherText = olmEventContent.ciphertext ?: run {
|
||||||
Timber.e("## decryptEvent() : our device " + olmDevice.deviceCurve25519Key
|
Timber.e("## decryptEvent() : missing cipher text")
|
||||||
+ " is not included in recipients. Event")
|
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_CIPHER_TEXT,
|
||||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.NOT_INCLUDE_IN_RECIPIENTS_ERROR_CODE,
|
MXCryptoError.MISSING_CIPHER_TEXT_REASON))
|
||||||
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.NOT_INCLUDED_IN_RECIPIENT_REASON))
|
}
|
||||||
|
|
||||||
|
val senderKey = olmEventContent.senderKey ?: run {
|
||||||
|
Timber.e("## decryptEvent() : missing sender key")
|
||||||
|
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY,
|
||||||
|
MXCryptoError.MISSING_SENDER_KEY_TEXT_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 = olmEventContent.ciphertext!![olmDevice.deviceCurve25519Key] as JsonDict
|
val message = messageAny 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 " + olmEventContent.senderKey)
|
Timber.e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey")
|
||||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_ENCRYPTED_MESSAGE_ERROR_CODE,
|
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE,
|
||||||
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON))
|
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 " + olmEventContent.senderKey)
|
Timber.e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey")
|
||||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_ENCRYPTED_MESSAGE_ERROR_CODE,
|
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE,
|
||||||
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON))
|
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")
|
||||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE,
|
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT,
|
||||||
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_CIPHER_TEXT_REASON))
|
MXCryptoError.MISSING_CIPHER_TEXT_REASON))
|
||||||
}
|
}
|
||||||
|
|
||||||
val olmPayloadContent = OlmPayloadContent.fromJsonString(payloadString)
|
val olmPayloadContent = OlmPayloadContent.fromJsonString(payloadString) ?: run {
|
||||||
|
Timber.e("## decryptEvent() : bad olmPayloadContent format")
|
||||||
|
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT,
|
||||||
|
MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON))
|
||||||
|
}
|
||||||
|
|
||||||
if (TextUtils.isEmpty(olmPayloadContent.recipient)) {
|
if (olmPayloadContent.recipient.isNullOrBlank()) {
|
||||||
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")
|
||||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_PROPERTY_ERROR_CODE,
|
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
|
||||||
MXCryptoError.UNABLE_TO_DECRYPT, reason))
|
reason))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TextUtils.equals(olmPayloadContent.recipient, credentials.userId)) {
|
if (olmPayloadContent.recipient != credentials.userId) {
|
||||||
Timber.e("## decryptEvent() : Event " + event.eventId + ": Intended recipient " + olmPayloadContent.recipient
|
Timber.e("## decryptEvent() : Event ${event.eventId}:" +
|
||||||
+ " does not match our id " + credentials.userId)
|
" Intended recipient ${olmPayloadContent.recipient} does not match our id ${credentials.userId}")
|
||||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_RECIPIENT_ERROR_CODE,
|
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT,
|
||||||
MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient)))
|
String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null == olmPayloadContent.recipient_keys) {
|
val recipientKeys = olmPayloadContent.recipient_keys ?: run {
|
||||||
Timber.e("## decryptEvent() : Olm event (id=" + event.eventId
|
Timber.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'recipient_keys' property; cannot prevent unknown-key attack")
|
||||||
+ ") contains no " + "'recipient_keys' property; cannot prevent unknown-key attack")
|
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
|
||||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_PROPERTY_ERROR_CODE,
|
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys")))
|
||||||
MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys")))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val ed25519 = olmPayloadContent.recipient_keys!!.get("ed25519")
|
val ed25519 = recipientKeys["ed25519"]
|
||||||
|
|
||||||
if (!TextUtils.equals(ed25519, olmDevice.deviceEd25519Key)) {
|
if (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")
|
||||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_RECIPIENT_KEY_ERROR_CODE,
|
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT_KEY,
|
||||||
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.BAD_RECIPIENT_KEY_REASON))
|
MXCryptoError.BAD_RECIPIENT_KEY_REASON))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TextUtils.isEmpty(olmPayloadContent.sender)) {
|
if (olmPayloadContent.sender.isNullOrBlank()) {
|
||||||
Timber.e("## decryptEvent() : Olm event (id=" + event.eventId
|
Timber.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'sender' property; cannot prevent unknown-key attack")
|
||||||
+ ") contains no 'sender' property; cannot prevent unknown-key attack")
|
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
|
||||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_PROPERTY_ERROR_CODE,
|
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender")))
|
||||||
MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender")))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TextUtils.equals(olmPayloadContent.sender, event.senderId)) {
|
if (olmPayloadContent.sender != event.senderId) {
|
||||||
Timber.e("Event " + event.eventId + ": original sender " + olmPayloadContent.sender
|
Timber.e("Event ${event.eventId}: original sender ${olmPayloadContent.sender} does not match reported sender ${event.senderId}")
|
||||||
+ " does not match reported sender " + event.senderId)
|
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.FORWARDED_MESSAGE,
|
||||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.FORWARDED_MESSAGE_ERROR_CODE,
|
String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender)))
|
||||||
MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TextUtils.equals(olmPayloadContent.room_id, event.roomId)) {
|
if (olmPayloadContent.room_id != event.roomId) {
|
||||||
Timber.e("## decryptEvent() : Event " + event.eventId + ": original room " + olmPayloadContent.room_id
|
Timber.e("## decryptEvent() : Event ${event.eventId}: original room ${olmPayloadContent.room_id} does not match reported room ${event.roomId}")
|
||||||
+ " does not match reported room " + event.roomId)
|
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ROOM,
|
||||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_ROOM_ERROR_CODE,
|
String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.room_id)))
|
||||||
MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.room_id)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null == olmPayloadContent.keys) {
|
val keys = olmPayloadContent.keys ?: run {
|
||||||
Timber.e("## decryptEvent failed : null keys")
|
Timber.e("## decryptEvent failed : null keys")
|
||||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE,
|
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT,
|
||||||
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_CIPHER_TEXT_REASON))
|
MXCryptoError.MISSING_CIPHER_TEXT_REASON))
|
||||||
}
|
}
|
||||||
|
|
||||||
val result = MXEventDecryptionResult()
|
return Try.just(MXEventDecryptionResult(
|
||||||
result.clearEvent = payload
|
clearEvent = payload,
|
||||||
result.senderCurve25519Key = olmEventContent.senderKey
|
senderCurve25519Key = senderKey,
|
||||||
result.claimedEd25519Key = olmPayloadContent.keys!!.get("ed25519")
|
claimedEd25519Key = keys["ed25519"]
|
||||||
|
))
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -167,7 +175,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"]
|
||||||
@ -210,7 +218,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.size == 0) {
|
if (sessionIds.isEmpty()) {
|
||||||
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")
|
||||||
@ -228,7 +236,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"]
|
||||||
}
|
}
|
||||||
|
@ -14,32 +14,35 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.crypto.algorithms
|
package im.vector.matrix.android.internal.crypto.algorithms.olm
|
||||||
|
|
||||||
|
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.
|
||||||
*/
|
*/
|
||||||
data class MXDecryptionResult(
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class OlmDecryptionResult(
|
||||||
/**
|
/**
|
||||||
* The decrypted payload (with properties 'type', 'content')
|
* The decrypted payload (with properties 'type', 'content')
|
||||||
*/
|
*/
|
||||||
var payload: JsonDict? = null,
|
@Json(name = "payload") val 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.
|
||||||
*/
|
*/
|
||||||
var keysClaimed: Map<String, String>? = null,
|
@Json(name = "keysClaimed") val 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.
|
||||||
*/
|
*/
|
||||||
var senderKey: String? = null,
|
@Json(name = "senderKey") val senderKey: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Devices which forwarded this session to us (normally empty).
|
* Devices which forwarded this session to us (normally empty).
|
||||||
*/
|
*/
|
||||||
var forwardingCurve25519KeyChain: List<String>? = null
|
@Json(name = "forwardingCurve25519KeyChain") val forwardingCurve25519KeyChain: List<String>? = null
|
||||||
)
|
)
|
@ -24,10 +24,11 @@ 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!!["sha256"] ?: error("")
|
sha256 = this.hashes?.get("sha256") ?: ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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): EncryptionResult? {
|
fun encryptAttachment(attachmentStream: InputStream, mimetype: String): Try<EncryptionResult> {
|
||||||
val t0 = System.currentTimeMillis()
|
val t0 = System.currentTimeMillis()
|
||||||
val secureRandom = SecureRandom()
|
val secureRandom = SecureRandom()
|
||||||
|
|
||||||
@ -115,23 +115,21 @@ 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 result
|
return Try.just(result)
|
||||||
} catch (oom: OutOfMemoryError) {
|
} catch (oom: OutOfMemoryError) {
|
||||||
Timber.e(oom, "## encryptAttachment failed " + oom.message)
|
Timber.e(oom, "## encryptAttachment failed")
|
||||||
|
return Try.Failure(oom)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e, "## encryptAttachment failed " + e.message)
|
Timber.e(e, "## encryptAttachment failed")
|
||||||
}
|
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -199,7 +197,7 @@ object MXEncryptedAttachments {
|
|||||||
|
|
||||||
val currentDigestValue = base64ToUnpaddedBase64(Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT))
|
val currentDigestValue = base64ToUnpaddedBase64(Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT))
|
||||||
|
|
||||||
if (!TextUtils.equals(elementToDecrypt.sha256, currentDigestValue)) {
|
if (elementToDecrypt.sha256 != currentDigestValue) {
|
||||||
Timber.e("## decryptAttachment() : Digest value mismatch")
|
Timber.e("## decryptAttachment() : Digest value mismatch")
|
||||||
outStream.close()
|
outStream.close()
|
||||||
return null
|
return null
|
||||||
|
@ -45,17 +45,18 @@ 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
|
||||||
@ -177,7 +178,7 @@ internal class KeysBackup @Inject constructor(
|
|||||||
megolmBackupAuthData.publicKey = publicKey
|
megolmBackupAuthData.publicKey = publicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, megolmBackupAuthData.signalableJSONDictionary())
|
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, megolmBackupAuthData.signalableJSONDictionary())
|
||||||
|
|
||||||
megolmBackupAuthData.signatures = objectSigner.signObject(canonicalJson)
|
megolmBackupAuthData.signatures = objectSigner.signObject(canonicalJson)
|
||||||
|
|
||||||
@ -388,8 +389,8 @@ internal class KeysBackup @Inject constructor(
|
|||||||
return keysBackupVersionTrust
|
return keysBackupVersionTrust
|
||||||
}
|
}
|
||||||
|
|
||||||
val mySigs: Map<String, *> = authData.signatures!![myUserId] as Map<String, *>
|
val mySigs = authData.signatures?.get(myUserId)
|
||||||
if (mySigs.isEmpty()) {
|
if (mySigs.isNullOrEmpty()) {
|
||||||
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
|
||||||
}
|
}
|
||||||
@ -402,21 +403,22 @@ internal class KeysBackup @Inject constructor(
|
|||||||
deviceId = components[1]
|
deviceId = components[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
var device: MXDeviceInfo? = null
|
|
||||||
if (deviceId != null) {
|
if (deviceId != null) {
|
||||||
device = cryptoStore.getUserDevice(deviceId, myUserId)
|
val 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(device.fingerprint()!!, authData.signalableJSONDictionary(), mySigs[keyId] as String)
|
olmDevice.verifySignature(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
|
||||||
@ -452,12 +454,11 @@ 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!![myUserId]?.toMutableMap()
|
val myUserSignatures = authData.signatures?.get(myUserId)?.toMutableMap() ?: HashMap()
|
||||||
?: HashMap())
|
|
||||||
|
|
||||||
if (trust) {
|
if (trust) {
|
||||||
// Add current device signature
|
// Add current device signature
|
||||||
val canonicalJson = MoshiProvider.getCanonicalJson(Map::class.java, authData.signalableJSONDictionary())
|
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, authData.signalableJSONDictionary())
|
||||||
|
|
||||||
val deviceSignatures = objectSigner.signObject(canonicalJson)
|
val deviceSignatures = objectSigner.signObject(canonicalJson)
|
||||||
|
|
||||||
@ -666,7 +667,8 @@ 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: " + keysBackupVersion?.version)
|
Timber.v("restoreKeysWithRecoveryKey: Those keys will be backed up to backup version: "
|
||||||
|
+ keysBackupVersion?.version)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Import them into the crypto store
|
// Import them into the crypto store
|
||||||
@ -875,7 +877,7 @@ internal class KeysBackup @Inject constructor(
|
|||||||
|
|
||||||
override fun getCurrentVersion(callback: MatrixCallback<KeysVersionResult?>) {
|
override fun getCurrentVersion(callback: MatrixCallback<KeysVersionResult?>) {
|
||||||
getKeysBackupLastVersionTask
|
getKeysBackupLastVersionTask
|
||||||
.configureWith(Unit)
|
.toConfigurableTask()
|
||||||
.dispatchTo(object : MatrixCallback<KeysVersionResult> {
|
.dispatchTo(object : MatrixCallback<KeysVersionResult> {
|
||||||
override fun onSuccess(data: KeysVersionResult) {
|
override fun onSuccess(data: KeysVersionResult) {
|
||||||
callback.onSuccess(data)
|
callback.onSuccess(data)
|
||||||
@ -1027,8 +1029,7 @@ internal class KeysBackup @Inject constructor(
|
|||||||
|
|
||||||
val authData = keysBackupData.getAuthDataAsMegolmBackupAuthData()
|
val authData = keysBackupData.getAuthDataAsMegolmBackupAuthData()
|
||||||
|
|
||||||
if (authData.signatures == null
|
if (authData?.signatures == null || authData.publicKey.isBlank()) {
|
||||||
|| authData.publicKey.isBlank()) {
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1226,7 +1227,8 @@ internal class KeysBackup @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId]!!.sessionIdToKeyBackupData[olmInboundGroupSessionWrapper.olmInboundGroupSession!!.sessionIdentifier()] = keyBackupData
|
keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId]!!
|
||||||
|
.sessionIdToKeyBackupData[olmInboundGroupSessionWrapper.olmInboundGroupSession!!.sessionIdentifier()] = keyBackupData
|
||||||
} catch (e: OlmException) {
|
} catch (e: OlmException) {
|
||||||
Timber.e(e, "OlmException")
|
Timber.e(e, "OlmException")
|
||||||
}
|
}
|
||||||
@ -1278,7 +1280,8 @@ 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 -> // Come back to the ready state so that we will retry on the next received key
|
else ->
|
||||||
|
// 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,7 +18,6 @@
|
|||||||
|
|
||||||
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
|
||||||
@ -107,30 +106,25 @@ data class MXDeviceInfo(
|
|||||||
* @return the fingerprint
|
* @return the fingerprint
|
||||||
*/
|
*/
|
||||||
fun fingerprint(): String? {
|
fun fingerprint(): String? {
|
||||||
return if (null != keys && !TextUtils.isEmpty(deviceId)) {
|
return keys
|
||||||
keys!!["ed25519:$deviceId"]
|
?.takeIf { !deviceId.isBlank() }
|
||||||
} else null
|
?.get("ed25519:$deviceId")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the identity key
|
* @return the identity key
|
||||||
*/
|
*/
|
||||||
fun identityKey(): String? {
|
fun identityKey(): String? {
|
||||||
return if (null != keys && !TextUtils.isEmpty(deviceId)) {
|
return keys
|
||||||
keys!!["curve25519:$deviceId"]
|
?.takeIf { !deviceId.isBlank() }
|
||||||
} else null
|
?.get("curve25519:$deviceId")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the display name
|
* @return the display name
|
||||||
*/
|
*/
|
||||||
fun displayName(): String? {
|
fun displayName(): String? {
|
||||||
return if (null != unsigned) {
|
return unsigned?.get("device_display_name") as? String
|
||||||
unsigned!!["device_display_name"] as String?
|
|
||||||
} else null
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -141,9 +135,7 @@ data class MXDeviceInfo(
|
|||||||
|
|
||||||
map["device_id"] = deviceId
|
map["device_id"] = deviceId
|
||||||
|
|
||||||
if (null != userId) {
|
map["user_id"] = 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 event content
|
* The encrypted event content
|
||||||
*/
|
*/
|
||||||
val eventContent: Content,
|
val eventContent: Content,
|
||||||
/**
|
/**
|
||||||
|
@ -1,139 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,190 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
* 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,27 +111,29 @@ 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? {
|
||||||
var megolmSessionData: MegolmSessionData? = MegolmSessionData()
|
return try {
|
||||||
|
|
||||||
try {
|
|
||||||
if (null == forwardingCurve25519KeyChain) {
|
if (null == forwardingCurve25519KeyChain) {
|
||||||
forwardingCurve25519KeyChain = ArrayList()
|
forwardingCurve25519KeyChain = ArrayList()
|
||||||
}
|
}
|
||||||
|
|
||||||
megolmSessionData!!.senderClaimedEd25519Key = keysClaimed!!["ed25519"]
|
if (keysClaimed == null) {
|
||||||
megolmSessionData.forwardingCurve25519KeyChain = ArrayList(forwardingCurve25519KeyChain!!)
|
return null
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return megolmSessionData
|
MegolmSessionData().also {
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -17,6 +17,7 @@ package im.vector.matrix.android.internal.crypto.model.event
|
|||||||
|
|
||||||
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.room.model.relation.RelationDefaultContent
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class representing an encrypted event content
|
* Class representing an encrypted event content
|
||||||
@ -52,5 +53,8 @@ data class EncryptedEventContent(
|
|||||||
* The session id
|
* The session id
|
||||||
*/
|
*/
|
||||||
@Json(name = "session_id")
|
@Json(name = "session_id")
|
||||||
val sessionId: String? = null
|
val sessionId: String? = null,
|
||||||
|
|
||||||
|
//Relation context is in clear in encrypted message
|
||||||
|
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? = 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,11 +20,14 @@ import com.squareup.moshi.Json
|
|||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents the response to /keys/query request made by claimOneTimeKeysForUsersDevices.
|
* This class represents the response to /keys/claim 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/query request made by claimOneTimeKeysForUsersDevices.
|
* This class represents the response to /keys/claim request made by claimOneTimeKeysForUsersDevices.
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class KeysClaimResponse(
|
data class KeysClaimResponse(
|
||||||
|
@ -18,8 +18,9 @@ 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,6 +31,7 @@ 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
|
||||||
@ -39,16 +40,17 @@ 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 enableFileEncryption: Boolean = false,
|
internal class RealmCryptoStore(private val realmConfiguration: RealmConfiguration,
|
||||||
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
|
||||||
|
|
||||||
@ -88,6 +90,8 @@ internal class RealmCryptoStore(private val enableFileEncryption: Boolean = fals
|
|||||||
}
|
}
|
||||||
|
|
||||||
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()
|
||||||
@ -133,6 +137,9 @@ internal class RealmCryptoStore(private val enableFileEncryption: Boolean = fals
|
|||||||
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().apply {
|
return IncomingRoomKeyRequest().also {
|
||||||
requestId = requestId
|
it.requestId = requestId
|
||||||
userId = userId
|
it.userId = userId
|
||||||
deviceId = deviceId
|
it.deviceId = deviceId
|
||||||
requestBody = RoomKeyRequestBody().apply {
|
it.requestBody = RoomKeyRequestBody().apply {
|
||||||
algorithm = requestBodyAlgorithm
|
algorithm = requestBodyAlgorithm
|
||||||
roomId = requestBodyRoomId
|
roomId = requestBodyRoomId
|
||||||
senderKey = requestBodySenderKey
|
senderKey = requestBodySenderKey
|
||||||
|
@ -23,10 +23,8 @@ 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>> {
|
||||||
@ -46,30 +44,27 @@ internal class DefaultClaimOneTimeKeysForUsersDevice @Inject constructor(private
|
|||||||
apiCall = cryptoApi.claimOneTimeKeysForUsersDevices(body)
|
apiCall = cryptoApi.claimOneTimeKeysForUsersDevices(body)
|
||||||
}.flatMap { keysClaimResponse ->
|
}.flatMap { keysClaimResponse ->
|
||||||
Try {
|
Try {
|
||||||
val map = HashMap<String, Map<String, MXKey>>()
|
val map = MXUsersDevicesMap<MXKey>()
|
||||||
|
|
||||||
if (null != keysClaimResponse.oneTimeKeys) {
|
keysClaimResponse.oneTimeKeys?.let { oneTimeKeys ->
|
||||||
for (userId in keysClaimResponse.oneTimeKeys!!.keys) {
|
for (userId in oneTimeKeys.keys) {
|
||||||
val mapByUserId = keysClaimResponse.oneTimeKeys!![userId]
|
val mapByUserId = oneTimeKeys[userId]
|
||||||
|
|
||||||
val keysMap = HashMap<String, MXKey>()
|
if (mapByUserId != null) {
|
||||||
|
for (deviceId in mapByUserId.keys) {
|
||||||
|
val mxKey = MXKey.from(mapByUserId[deviceId])
|
||||||
|
|
||||||
for (deviceId in mapByUserId!!.keys) {
|
if (mxKey != null) {
|
||||||
try {
|
map.setObject(userId, deviceId, mxKey)
|
||||||
keysMap[deviceId] = MXKey(mapByUserId[deviceId])
|
} else {
|
||||||
} catch (e: Exception) {
|
Timber.e("## claimOneTimeKeysForUsersDevices : fail to create a MXKey")
|
||||||
Timber.e(e, "## claimOneTimeKeysForUsersDevices : fail to create a MXKey ")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keysMap.size != 0) {
|
|
||||||
map[userId] = keysMap
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MXUsersDevicesMap(map)
|
map
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ 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
|
||||||
@ -39,7 +40,7 @@ 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.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -55,7 +56,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: MyDeviceInfoHolder,
|
private val myDeviceInfoHolder: Lazy<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,
|
||||||
@ -70,7 +71,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
|
|||||||
|
|
||||||
// Event received from the sync
|
// Event received from the sync
|
||||||
fun onToDeviceEvent(event: Event) {
|
fun onToDeviceEvent(event: Event) {
|
||||||
CoroutineScope(coroutineDispatchers.crypto).launch {
|
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||||
when (event.getClearType()) {
|
when (event.getClearType()) {
|
||||||
EventType.KEY_VERIFICATION_START -> {
|
EventType.KEY_VERIFICATION_START -> {
|
||||||
onStartRequestReceived(event)
|
onStartRequestReceived(event)
|
||||||
@ -197,7 +198,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
|
|||||||
cryptoStore,
|
cryptoStore,
|
||||||
sendToDeviceTask,
|
sendToDeviceTask,
|
||||||
taskExecutor,
|
taskExecutor,
|
||||||
myDeviceInfoHolder.myDevice.fingerprint()!!,
|
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
|
||||||
startReq.transactionID!!,
|
startReq.transactionID!!,
|
||||||
otherUserId)
|
otherUserId)
|
||||||
addTransaction(tx)
|
addTransaction(tx)
|
||||||
@ -222,7 +223,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
|
|||||||
.fold(
|
.fold(
|
||||||
{ error() },
|
{ error() },
|
||||||
{
|
{
|
||||||
if (it.getUserDeviceIds(otherUserId).contains(startReq.fromDevice)) {
|
if (it.getUserDeviceIds(otherUserId)?.contains(startReq.fromDevice) == true) {
|
||||||
success(it)
|
success(it)
|
||||||
} else {
|
} else {
|
||||||
error()
|
error()
|
||||||
@ -366,7 +367,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
|
|||||||
cryptoStore,
|
cryptoStore,
|
||||||
sendToDeviceTask,
|
sendToDeviceTask,
|
||||||
taskExecutor,
|
taskExecutor,
|
||||||
myDeviceInfoHolder.myDevice.fingerprint()!!,
|
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
|
||||||
txID,
|
txID,
|
||||||
userId,
|
userId,
|
||||||
deviceID)
|
deviceID)
|
||||||
@ -409,7 +410,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(cancelMessage, userId, userDevice)
|
contentMap.setObject(userId, userDevice, cancelMessage)
|
||||||
|
|
||||||
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 + MoshiProvider.getCanonicalJson(KeyVerificationStart::class.java, startReq!!)
|
val concat = getSAS().publicKey + JsonCanonicalizer.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,15 +21,14 @@ 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(
|
||||||
@ -102,8 +101,6 @@ 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(
|
||||||
@ -167,7 +164,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 + MoshiProvider.getCanonicalJson(KeyVerificationStart::class.java, startReq!!)
|
val concat = vKey.key + JsonCanonicalizer.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(keyToDevice, otherUserId, otherDeviceId)
|
contentMap.setObject(otherUserId, otherDeviceId, keyToDevice)
|
||||||
|
|
||||||
sendToDeviceTask.configureWith(SendToDeviceTask.Params(type, contentMap, transactionId))
|
sendToDeviceTask.configureWith(SendToDeviceTask.Params(type, contentMap, transactionId))
|
||||||
.dispatchTo(object : MatrixCallback<Unit> {
|
.dispatchTo(object : MatrixCallback<Unit> {
|
||||||
|
@ -31,8 +31,7 @@ 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).findAll()
|
val results = query.invoke(realm).findAllAsync()
|
||||||
value = results
|
|
||||||
results.addChangeListener(listener)
|
results.addChangeListener(listener)
|
||||||
this.realm = realm
|
this.realm = realm
|
||||||
this.results = results
|
this.results = results
|
||||||
|
@ -17,7 +17,11 @@
|
|||||||
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
|
||||||
@ -29,20 +33,25 @@ internal interface LiveEntityObserver {
|
|||||||
fun isStarted(): Boolean
|
fun isStarted(): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val monarchy: Monarchy)
|
internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val realmConfiguration: RealmConfiguration)
|
||||||
: LiveEntityObserver {
|
: LiveEntityObserver, OrderedRealmCollectionChangeListener<RealmResults<T>> {
|
||||||
|
|
||||||
|
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)) {
|
||||||
monarchy.postToMonarchyThread {
|
BACKGROUND_HANDLER.post {
|
||||||
val queryResults = query.createQuery(it).findAll()
|
val realm = Realm.getInstance(realmConfiguration)
|
||||||
queryResults.addChangeListener { t, changeSet ->
|
backgroundRealm.set(realm)
|
||||||
onChanged(t, changeSet)
|
val queryResults = query.createQuery(realm).findAll()
|
||||||
}
|
queryResults.addChangeListener(this)
|
||||||
results = AtomicReference(queryResults)
|
results = AtomicReference(queryResults)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,8 +59,11 @@ internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val m
|
|||||||
|
|
||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
if (isStarted.compareAndSet(true, false)) {
|
if (isStarted.compareAndSet(true, false)) {
|
||||||
monarchy.postToMonarchyThread {
|
BACKGROUND_HANDLER.post {
|
||||||
results.getAndSet(null).removeAllChangeListeners()
|
results.getAndSet(null).removeAllChangeListeners()
|
||||||
|
backgroundRealm.getAndSet(null).also {
|
||||||
|
it.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,19 +72,4 @@ internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val m
|
|||||||
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,9 +22,11 @@ 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.EventEntity
|
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
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.model.TimelineEventEntityFields
|
||||||
|
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
|
||||||
@ -32,12 +34,15 @@ 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 events.where().equalTo(EventEntityFields.IS_UNLINKED, false).findAll().isEmpty()
|
return timelineEvents.where()
|
||||||
|
.equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, false)
|
||||||
|
.findAll()
|
||||||
|
.isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ChunkEntity.deleteOnCascade() {
|
internal fun ChunkEntity.deleteOnCascade() {
|
||||||
assertIsManaged()
|
assertIsManaged()
|
||||||
this.events.deleteAllFromRealm()
|
this.timelineEvents.deleteAllFromRealm()
|
||||||
this.deleteFromRealm()
|
this.deleteFromRealm()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,22 +55,28 @@ internal fun ChunkEntity.merge(roomId: String,
|
|||||||
val isUnlinked = isCurrentChunkUnlinked && isChunkToMergeUnlinked
|
val isUnlinked = isCurrentChunkUnlinked && isChunkToMergeUnlinked
|
||||||
|
|
||||||
if (isCurrentChunkUnlinked && !isChunkToMergeUnlinked) {
|
if (isCurrentChunkUnlinked && !isChunkToMergeUnlinked) {
|
||||||
this.events.forEach { it.isUnlinked = false }
|
this.timelineEvents.forEach { it.root?.isUnlinked = false }
|
||||||
}
|
}
|
||||||
val eventsToMerge: List<EventEntity>
|
val eventsToMerge: List<TimelineEventEntity>
|
||||||
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.events.sort(EventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
|
eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.ASCENDING)
|
||||||
} else {
|
} else {
|
||||||
this.prevToken = chunkToMerge.prevToken
|
this.prevToken = chunkToMerge.prevToken
|
||||||
this.isLastBackward = chunkToMerge.isLastBackward
|
this.isLastBackward = chunkToMerge.isLastBackward
|
||||||
eventsToMerge = chunkToMerge.events.sort(EventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
|
eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING)
|
||||||
}
|
}
|
||||||
eventsToMerge.forEach {
|
val events = eventsToMerge.mapNotNull { it.root?.asDomain() }
|
||||||
add(roomId, it.asDomain(), direction, isUnlinked = isUnlinked)
|
val eventIds = ArrayList<String>()
|
||||||
|
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,
|
||||||
events: List<Event>,
|
events: List<Event>,
|
||||||
@ -74,8 +85,20 @@ 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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +109,7 @@ internal fun ChunkEntity.add(roomId: String,
|
|||||||
isUnlinked: Boolean = false) {
|
isUnlinked: Boolean = false) {
|
||||||
|
|
||||||
assertIsManaged()
|
assertIsManaged()
|
||||||
if (event.eventId != null && events.fastContains(event.eventId)) {
|
if (event.eventId != null && timelineEvents.find(event.eventId) != null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var currentDisplayIndex = lastDisplayIndex(direction, 0)
|
var currentDisplayIndex = lastDisplayIndex(direction, 0)
|
||||||
@ -101,21 +124,28 @@ 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 && events.isNotEmpty()) {
|
} else if (direction == PaginationDirection.BACKWARDS && timelineEvents.isNotEmpty()) {
|
||||||
val lastEventType = events.last()?.type ?: ""
|
val lastEventType = timelineEvents.last()?.root?.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
|
||||||
}
|
}
|
||||||
val position = if (direction == PaginationDirection.FORWARDS) 0 else this.events.size
|
it.eventId = event.eventId ?: ""
|
||||||
events.add(position, eventEntity)
|
it.roomId = roomId
|
||||||
|
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,9 +21,10 @@ 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)
|
||||||
@ -36,29 +37,39 @@ internal fun RoomEntity.addOrUpdate(chunkEntity: ChunkEntity) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun RoomEntity.addStateEvents(stateEvents: List<Event>,
|
internal fun RoomEntity.addStateEvent(stateEvent: 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))) {
|
||||||
stateEvents.forEach { event ->
|
return
|
||||||
if (event.eventId == null || (filterDuplicates && fastContains(event.eventId))) {
|
} else {
|
||||||
return@forEach
|
val entity = stateEvent.toEntity(roomId).apply {
|
||||||
}
|
|
||||||
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(0, eventEntity)
|
untimelinedStateEvents.add(entity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
sendingTimelineEvents.add(0, eventEntity)
|
val roomMembers = RoomMembers(realm, roomId)
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* 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,7 +19,10 @@ 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 {
|
||||||
@ -45,6 +48,35 @@ 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,10 +17,15 @@
|
|||||||
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 {
|
||||||
@ -30,7 +35,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 ?: UUID.randomUUID().toString()
|
eventEntity.eventId = event.eventId ?: ""
|
||||||
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
|
||||||
@ -46,7 +51,6 @@ 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 {
|
||||||
@ -68,7 +72,17 @@ 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,29 +16,39 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.database.mapper
|
package im.vector.matrix.android.internal.database.mapper
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||||
|
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.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.TimelineEventFactory
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
internal class RoomSummaryMapper @Inject constructor(
|
internal class RoomSummaryMapper @Inject constructor(
|
||||||
private val timelineEventFactory: TimelineEventFactory,
|
val cryptoService: CryptoService
|
||||||
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 {
|
|
||||||
var ev: TimelineEvent? = null
|
val latestEvent = roomSummaryEntity.latestEvent?.asDomain()
|
||||||
monarchy.doWithRealm { realm ->
|
if (latestEvent?.root?.isEncrypted() == true && latestEvent.root.mxDecryptionResult == null) {
|
||||||
ev = timelineEventFactory.create(it, realm)
|
//TODO use a global event decryptor? attache to session and that listen to new sessionId?
|
||||||
|
//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,
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user