Compare commits

...

205 Commits

Author SHA1 Message Date
2948018453 Clean code after review 2019-07-17 14:56:00 +02:00
b7e0b400fb Timeline : set bigger initial load size 2019-07-16 17:48:32 +02:00
a8f06f609b Use latest retrofit version to properly cancel requests
Fix cancelation requests
2019-07-16 17:46:52 +02:00
d469299f42 RoomMembers: should fix state events issues 2019-07-16 17:46:52 +02:00
9182f2ce4e RoomMembers/User : get a better and faster handling (still need to fix one small issue) 2019-07-12 13:59:37 +02:00
10e4d0190f Try to insert users directly to see if perfs are better [WIP] 2019-07-11 18:55:13 +02:00
b77310fe92 Merge pull request #337 from vector-im/feature/debug_suffix
Add ".debug" to the applicationId to be able to install the app along with the prod version
2019-07-11 18:33:43 +02:00
919dec4a56 Add ".debug" to the applicationId to be able to install the app along with the prod version 2019-07-11 17:59:07 +02:00
43b3680774 Prepare next release 2019-07-11 17:44:58 +02:00
bfb5fce809 Update CHANGES.md 2019-07-11 17:43:56 +02:00
1f3731aae7 Merge branch 'master' into develop 2019-07-11 17:42:11 +02:00
52dced43ff Fix version code issue 2019-07-11 16:49:06 +02:00
ff80c3c8d5 Add script to sign the APK. 2019-07-11 16:41:45 +02:00
34e4d27573 Add missing space in pipeline 2019-07-11 16:00:45 +02:00
6522148e63 Merge branch 'release/0.1.0' 2019-07-11 15:54:48 +02:00
252b2ea30a Merge pull request #334 from vector-im/feature/general_perf
Feature/general perf
2019-07-11 15:52:00 +02:00
f493ce44f2 RealmLiveEntity: passes the results and changeSet instead of filtering as it's more efficient 2019-07-11 15:30:01 +02:00
c4c5069ee5 Merge pull request #332 from vector-im/feature/login_warning
Improve login screen
2019-07-11 15:25:11 +02:00
423125b5d9 Merge pull request #333 from vector-im/feature/timeout
Create a TimeOutInterceptor to set specific timeout on some request
2019-07-11 15:24:54 +02:00
9e3d29b7d7 Create a TimeOutInterceptor to set specific timeout on some request: login and sync (Fixes #170) 2019-07-11 15:16:25 +02:00
f65becf7c0 Rework login screen before release 2019-07-11 14:38:30 +02:00
80a61cf6b5 Improve dependency download safe path 2019-07-11 14:03:20 +02:00
77056aff94 Merge pull request #330 from vector-im/feature/edit_emote
Edit emote
2019-07-11 13:34:46 +02:00
65e123d87f Split long lines 2019-07-11 13:32:28 +02:00
d0b145d031 Edit emote 2019-07-11 12:29:02 +02:00
98306e223b Merge pull request #322 from vector-im/feature/clean
Improve reply feature
2019-07-11 11:46:00 +02:00
c9fe1adb77 Add a debug button to test crash of the app 2019-07-11 10:36:59 +02:00
1b95336ad3 EventEntity|TimelineEventEntity : remove UUID as primary key and use auto-incremented Long 2019-07-11 10:25:30 +02:00
f007fb04b8 Timeline: clean listeners 2019-07-11 10:25:30 +02:00
141434e8f8 Try getting things off the main thread 2019-07-11 10:25:30 +02:00
b8669d5ed2 Sync: use a single threaded executor to ensure we have only one sync at a time 2019-07-11 10:23:24 +02:00
7a08a11b19 Fix compilation of test 2019-07-10 18:17:03 +02:00
54b1d18812 Merge remote-tracking branch 'origin/feature/clean' into feature/clean 2019-07-10 18:07:03 +02:00
3aa30e5f15 Fix reply of reply 2019-07-10 18:06:44 +02:00
ddf4a81905 Do not display the banner when keys backup is sending keys 2019-07-10 18:04:27 +02:00
794fd650a4 Mutualize code, and also, when replying to an edited event, use the last text in the reply prefix content 2019-07-10 17:37:22 +02:00
9a57a02996 Cleaner code: add TimelineEvent to special modes 2019-07-10 17:05:32 +02:00
7e8cd07e1e Do not send edition if text is identical 2019-07-10 16:32:44 +02:00
d613abf4b4 i18n edited_suffix 2019-07-10 15:29:52 +02:00
06699eaefc Cleaner code 2019-07-10 14:40:08 +02:00
e5082f662c Fix actually done TODO 2019-07-10 14:19:59 +02:00
c8ab53e39c Video visibility fix 2019-07-10 14:11:49 +02:00
d424a135a9 Merge pull request #324 from vector-im/feature/quick_react_e2e
Quick react on e2e was not displayed
2019-07-10 14:08:46 +02:00
e6409d4c60 Create a common canReact() method 2019-07-10 12:10:55 +02:00
19c7de687e We can react on e2e room text event 2019-07-10 11:51:09 +02:00
1918302297 Reply with formatted content 2019-07-10 11:29:47 +02:00
92e3a02389 Create data class instead of Pair 2019-07-10 10:34:32 +02:00
0a54801fcc Code clarity 2019-07-10 10:16:21 +02:00
228ee52563 Remove extra space in <mx-reply> 2019-07-10 10:07:45 +02:00
e6c74dc1fe Convert a Task to a ConfigurableTask without parameter 2019-07-09 18:41:08 +02:00
fe82ad2002 Format 2019-07-09 18:31:04 +02:00
f66739491a Merge pull request #321 from vector-im/feature/workManager_clean
Fix bug on WorkManager: clean by tag
2019-07-09 18:30:07 +02:00
c5dc9d4a9a Fix test 2019-07-09 18:29:32 +02:00
8f858f8119 Fix / line too long 2019-07-09 18:20:00 +02:00
6e036c24b8 Make the test be runnable 2019-07-09 18:14:58 +02:00
5e832e07cd Code cleanup 2019-07-09 18:04:19 +02:00
e9700e04d8 Move method to JsonCanonicalizer and fix test compilation 2019-07-09 18:04:19 +02:00
c19b1f917f Javadoc 2019-07-09 18:04:19 +02:00
4281b5967a Create object for work constraint 2019-07-09 18:04:19 +02:00
aa743d8469 Ensure we do not cancel Work from other lib or SDK client 2019-07-09 18:04:19 +02:00
a09850b16c Merge pull request #316 from vector-im/feature/initial_sync_progress
Feature/initial sync progress
2019-07-09 17:58:24 +02:00
6cb94dd4d6 Fine tune task weights + more measure 2019-07-09 17:42:53 +02:00
c9931e3ba3 Block interaction on initial sync 2019-07-09 17:36:08 +02:00
fc302c1b5a FIx / crash notification drawer empty nam 2019-07-09 17:35:50 +02:00
34ac987494 Cleanup 2019-07-09 16:36:46 +02:00
24b2387703 Merge pull request #319 from vector-im/feature/code_quality
Feature/code quality
2019-07-09 16:29:44 +02:00
8a0c9ae9b0 Rename PreferencesManager to VectorPreferences for code clarity 2019-07-09 16:29:24 +02:00
a79227424f Convert PreferencesManager file to Kotlin 2019-07-09 16:07:16 +02:00
ffe0b9712c Convert file to Kotlin 2019-07-09 15:50:15 +02:00
d92c090c30 Code quality: HashMap / HashSet 2019-07-09 15:40:49 +02:00
1a4157a663 review 2019-07-09 15:38:44 +02:00
fa81d1a9c7 Fix / revert bad refactor rename 2019-07-09 15:38:44 +02:00
dba4df6836 clean 2019-07-09 15:38:44 +02:00
4aae1f78d8 moved new strings + @StringRes annotation 2019-07-09 15:38:44 +02:00
8159a52bd7 cleaning 2019-07-09 15:38:44 +02:00
95d83db90c WIP 2019-07-09 15:38:44 +02:00
ac5b0af63e Code quality: remove rule for map() 2019-07-09 15:37:20 +02:00
e80473903e Code quality: import static 2019-07-09 15:35:27 +02:00
d08778c674 Code quality: equalTo 2019-07-09 15:33:31 +02:00
0919b9460d Code quality: split long lines 2019-07-09 15:26:32 +02:00
66a018c79e Code quality: trim() 2019-07-09 15:11:20 +02:00
dcd64de4b8 Check sdk modules 2019-07-09 15:07:11 +02:00
a0bd206308 Merge pull request #318 from vector-im/feature/send_state
Fix some bugs on e2e rooms
2019-07-09 15:03:39 +02:00
ba589e7961 Add missing permission request 2019-07-09 15:03:21 +02:00
5dc83d64c1 Fix compilation issue 2019-07-09 15:03:21 +02:00
9a4eb8e9a4 add getFileUrl extension 2019-07-09 15:03:21 +02:00
058e7153a1 Fix bug 2019-07-09 15:03:21 +02:00
d7b2371854 Add long click listener to file items 2019-07-09 15:03:21 +02:00
b0c939866f Download file - typo 2019-07-09 15:03:21 +02:00
a07f8b615e Download file - WIP 2019-07-09 15:03:21 +02:00
12bd85e0a9 Decrypt video file 2019-07-09 15:02:31 +02:00
1b82ed5abb Fix regression 2019-07-09 15:02:31 +02:00
c13ab62187 Fix issue when sending video in encrypted room 2019-07-09 15:02:31 +02:00
ea77686746 Send file: cleanup 2019-07-09 15:02:31 +02:00
8a5612be3d Send file: improve UI feedback 2019-07-09 15:02:31 +02:00
d24ce27903 Add missing call to contentUploadStateTracker.setFailure 2019-07-09 15:02:31 +02:00
2099965508 Avoid returning Result.failure() from appendable worker. 2019-07-09 15:02:31 +02:00
829e8da8dc lastFailureMessage is val, not var 2019-07-09 15:02:31 +02:00
e149ee53de Fix bad mime type for encrypted thumbnail 2019-07-09 15:02:31 +02:00
b73d3b15f8 Merge pull request #317 from vector-im/feature/realm_entity_rework
Feature/realm entity rework
2019-07-09 15:01:05 +02:00
61d7f23870 remove dead code 2019-07-09 15:00:37 +02:00
b5650b2b8f Pagination : avoid breaking timeline when paginating twice from same token (race condition) 2019-07-09 14:44:59 +02:00
8777d13d8b Fix / view source, decrypted source was not correct 2019-07-09 14:22:40 +02:00
d52613d723 Trick / Remove home progress blank paddings 2019-07-09 11:17:36 +02:00
7ce476f858 Merge pull request #313 from vector-im/feature/notif_optim
Improve notification drawer manager: Dagger, throttle, and icon for API 9
2019-07-08 17:44:10 +02:00
dd07f5c2a6 TimelineEvent : update sender data when loading room members and prune event (+ remove RoomSummaryMapper param) 2019-07-08 15:32:24 +02:00
7e6e09bc19 fix / compilation 2019-07-08 15:30:11 +02:00
1d11a163af Notification resolver try to decrypt 2019-07-08 15:08:49 +02:00
57bd103de8 Fix / decrypt room summary latest event 2019-07-08 14:58:49 +02:00
25bc5001f9 RoomSummary / Use encrypted message screen 2019-07-08 14:57:37 +02:00
e4c52484b1 Fix / ensure equals check for encryption result 2019-07-08 14:57:02 +02:00
a30da07fd1 Fix / timeline auto refresh on new session 2019-07-08 14:12:46 +02:00
ee27d3e047 Fix / clear unknown session map before re-request decrypt 2019-07-08 12:49:22 +02:00
7096094224 wip crypto 2019-07-08 12:05:41 +02:00
443fb41d18 Cleanup 2019-07-08 11:21:26 +02:00
94b4351e19 wip async crypto + persist 2019-07-08 11:18:27 +02:00
e90aeff417 ThrottleLast the notification drawer manager 2019-07-08 11:08:23 +02:00
e50dd265d4 merge develop 2019-07-08 10:58:41 +02:00
4521ea14ee Merge branch 'develop' into feature/realm_entity_rework 2019-07-08 10:55:20 +02:00
535b41d818 Rename Debouncer to FirstThrottler 2019-07-08 10:49:32 +02:00
21357a1ec7 private fun 2019-07-08 10:32:38 +02:00
8c872caf78 Inject IconLoader and BitmapLoader 2019-07-08 10:30:45 +02:00
62a81a556e Refresh notification drawer in a background thread. It also fixes the person and room avatar display 2019-07-08 10:26:22 +02:00
568e8c8bc0 Do not load user icon before Android Pie 2019-07-08 10:10:39 +02:00
98a7652403 Put back local echo 2019-07-05 19:13:34 +02:00
78951b9155 Timeline event: handle displayName/avatar [WIP] 2019-07-05 19:07:33 +02:00
8c86a653b2 Merge pull request #309 from vector-im/feature/crypto_cleanup
Rework Crypto using Try
2019-07-05 19:03:59 +02:00
ea0526821e Top left Back does not go to previous Activity anymore (Fixes #275) 2019-07-05 18:44:09 +02:00
c503445092 Branch back relation summaries 2019-07-05 18:38:20 +02:00
205af8b122 Merge pull request #280 from Dominaezzz/kotlinify-1
Enhance CancelableBag
2019-07-05 18:34:28 +02:00
3abb7c8de6 Merge pull request #308 from Dominaezzz/kotlinify-2
Some "Kotlinification"
2019-07-05 18:11:18 +02:00
a40510da3b Merge pull request #310 from vector-im/feature/buildkite_pr
Build every branch which is not master, to be able to build PR from external repository
2019-07-05 18:06:14 +02:00
a6ab4a349d Build every branch which is not master, to be able to build PR from external repository 2019-07-05 18:02:13 +02:00
79a704d240 Timeline : Uncomment liveChunk to make pagination working 2019-07-05 17:27:24 +02:00
e5adf174a8 Fix crash when invalid urls for image 2019-07-05 17:00:57 +02:00
f01e796271 Timeline is back 2019-07-05 17:00:13 +02:00
302d23ba96 Create a realm locker to fast up next Realm.getInstance calls 2019-07-05 16:28:15 +02:00
03050c3f25 Cleanup 2019-07-05 16:11:54 +02:00
cbfd2af74b Start branching TimelineEventEntity 2019-07-05 16:07:12 +02:00
f3fab0dc08 Rename ErrorTypes 2019-07-05 15:52:37 +02:00
4a512d2425 Create enum for errorType and fix a few issues 2019-07-05 15:43:28 +02:00
07f80f43bd Display clear type 2019-07-05 15:15:55 +02:00
87dec337d8 Rework Crypto using Try 2019-07-05 14:41:32 +02:00
b37877746a Introduce TimelineEventEntity to begin with the rework 2019-07-05 14:39:15 +02:00
b0e5612bdc Convert java-esque code to Kotlin
Signed-off-by: Dominic Fischer <dominicfischer7@gmail.com>
2019-07-05 12:32:21 +01:00
25b0cd0e4b Remove some work from UI thread 2019-07-04 19:02:37 +02:00
2800d86a57 Merge pull request #302 from vector-im/feature/invitation
Quick action in invitation and composer input type
2019-07-04 18:47:44 +02:00
01bc0de2c2 Set again input type for composer, lost after merge 2019-07-04 18:43:36 +02:00
857a4c5a26 Quick implementation of accept/reject invitation from notification 2019-07-04 18:14:39 +02:00
063c35380a Fix regression on invitation full screen display 2019-07-04 16:44:37 +02:00
5322251bc0 Fix wording for direct message tab 2019-07-04 15:37:19 +02:00
c21b9df9a5 Fix issue with notification from previous account displayed after logout 2019-07-04 15:23:59 +02:00
f2a52f0253 Merge pull request #297 from vector-im/feature/crypto_stabilization
Safely remove all usage of `!![`
2019-07-04 15:17:26 +02:00
baaf493cb4 Merge pull request #299 from vector-im/feature/dix_concurrent_sync
Fix / Push worker could launch concurrent syncs
2019-07-04 15:10:18 +02:00
6cbd6d3a33 Valere's review 2019-07-04 14:59:29 +02:00
72e5aa981a Merge pull request #298 from vector-im/feature/quote
Fix issue when quoting event in e2e rooms (Fixes #295)
2019-07-04 14:49:53 +02:00
c0f085cdf8 SyncTask now handles by itself the sync token 2019-07-04 14:46:59 +02:00
10bc2297d4 Fix / Push worker could launch concurrent syncs 2019-07-04 14:04:36 +02:00
8fa5e63b07 Fix issue: reply to e2e event does not contain the base message 2019-07-04 12:52:43 +02:00
9d0c50907c Fix issue when quoting event in e2e rooms (Fixes #295) 2019-07-04 12:39:59 +02:00
e5958983d8 Safely remove all usage of !![ 2019-07-04 11:44:09 +02:00
ab23ec3f35 Fix https://github.com/matrix-org/riot-android-rageshakes/issues/5851 (DI) 2019-07-04 10:20:50 +02:00
a79a6443e7 Realm: update realm dependencie 2019-07-03 20:08:27 +02:00
9ff24cbf2a Merge branch 'feature/fix_issues' into develop 2019-07-03 19:46:34 +02:00
2eee25bbc1 Fix / crash not called on UI Thread 2019-07-03 19:36:25 +02:00
2a2431e490 Merge pull request #290 from vector-im/feature/fix_crash_npe_cryptomanager
Fix / Rageshake crashes + cleaning
2019-07-03 18:47:45 +02:00
4041e2e8ca code review 2019-07-03 18:40:42 +02:00
031c4e5746 Crash on loggout
https://github.com/matrix-org/riot-android-rageshakes/issues/5881
2019-07-03 18:40:04 +02:00
b4ea85fc76 Fix / Rageshake crashes + cleaning !!
https://github.com/matrix-org/riot-android-rageshakes/issues/5880
https://github.com/matrix-org/riot-android-rageshakes/issues/5877
https://github.com/matrix-org/riot-android-rageshakes/issues/5873
https://github.com/matrix-org/riot-android-rageshakes/issues/5871
2019-07-03 18:40:04 +02:00
480f14902d Rx: observe on computation by default 2019-07-03 18:28:56 +02:00
20c8e8d922 Change the test to apply Google Service plugin to be able to run sonar 2019-07-03 18:18:07 +02:00
9cdecced57 Merge pull request #291 from vector-im/feature/start_crypto_earlier
Start crypto manager before handling first sync events
2019-07-03 18:05:44 +02:00
60d46538de Merge pull request #292 from vector-im/feature/sonar_fix
Feature sonar fix and convert remaining Java files to Kotlin
2019-07-03 18:03:23 +02:00
223295c2f1 Convert MXUsersDevicesMap to kotlin - Fix issue 2019-07-03 18:01:28 +02:00
f789fb275d Convert MXUsersDevicesMap to kotlin 2019-07-03 17:34:22 +02:00
a7c12aeb93 Start crypto manager before handling first sync events 2019-07-03 17:17:58 +02:00
0ca9a5f68b Convert MXKey to kotlin 2019-07-03 16:45:08 +02:00
842345df9b Merge pull request #284 from vector-im/feature/better_incoming_key_verif_mgmt
Moved incoming key/verif to active session holder
2019-07-03 15:54:15 +02:00
7d5c31c510 Fix Javadoc issues 2019-07-03 15:52:53 +02:00
1ee1c31b9c Fix bugs detected by Sonar 2019-07-03 15:42:35 +02:00
e9eada77f9 Add comment to run sonar analysis and fix compilation issue 2019-07-03 15:42:35 +02:00
93ce0cc5e9 Realm: avoid using monarchy thread for custom work 2019-07-03 14:48:45 +02:00
eefd09d022 Dagger: don't create MatrixCoroutineDispatchers multiple time!! 2019-07-03 14:48:03 +02:00
ef597cc67a RoomSummary: set unreadNotification to 0 by default 2019-07-03 14:47:33 +02:00
5d171e0240 Moved incoming key/verif to active session holder 2019-07-03 12:56:08 +02:00
39070820be Merge pull request #283 from vector-im/feature/check_pushrule_on_sync_only
Check Push rule on sync only + fix bad room name in notif
2019-07-03 12:37:49 +02:00
1fdad38b9d Check Push rule on sync only + fix bad room name in notif 2019-07-03 11:59:45 +02:00
f41c0311fa Fix done TODO 2019-07-03 11:58:50 +02:00
a476ac71da Import translations from Riot 2019-07-03 10:20:07 +02:00
bc2d321a84 Merge branch 'feature/Perf' into develop 2019-07-02 23:07:16 +02:00
9adeab6bae Perf: revert constraintLayout version as it breaks at the moment 2019-07-02 23:06:40 +02:00
0f3a63e366 Enhance CancelableBag
Signed-off-by: Dominic Fischer <dominicfischer7@gmail.com>
2019-07-02 21:46:44 +01:00
d90698fe92 Merge pull request #279 from vector-im/feature/clear_glide_cache
Clear media cache from the settings and clear cache when signing out
2019-07-02 21:56:31 +02:00
af0af6e260 Fix bad background color in dark theme 2019-07-02 21:49:52 +02:00
6e71fb565a Fix bad layout for button when keyboard is opened (Fixes #268) 2019-07-02 21:23:57 +02:00
6c66ab1568 Fix code quality 2019-07-02 21:17:41 +02:00
0d329f0338 Clear media cache from the settings and clear cache when signing out 2019-07-02 20:21:40 +02:00
2f66321c2a RoomSummary: don't fetch last event by default as it takes some time 2019-07-02 19:59:01 +02:00
5b102485bc Perf: timeline should reuse one background looper thread 2019-07-02 19:12:20 +02:00
37199da52f Merge branch 'develop' into feature/Perf 2019-07-02 18:29:59 +02:00
11bf00030d Merge branch 'develop' into feature/Perf 2019-07-02 17:00:09 +02:00
9378d30601 Merge branch 'develop' into feature/Perf 2019-07-02 11:25:39 +02:00
41ed4b23d8 Update dependencies (tested ok) 2019-07-02 09:39:45 +02:00
de9a5a3d12 Perf: eventHtmlRenderer is slow to build, get only one instance 2019-07-01 20:19:50 +02:00
19202cfca6 Perf: try to get better 2019-07-01 20:05:48 +02:00
325 changed files with 6725 additions and 4837 deletions

View File

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

View File

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

View File

@ -2,8 +2,6 @@
buildscript {
ext.kotlin_version = '1.3.21'
ext.koin_version = '1.0.2'
// TODO ext.koin_version = '2.0.0-GA'
repositories {
google()
jcenter()
@ -26,15 +24,27 @@ buildscript {
allprojects {
repositories {
maven { url "http://dl.bintray.com/piasy/maven" }
maven { url 'https://jitpack.io' }
// 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'
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/' }
google()
jcenter()
// For Olm SDK
maven {
url 'https://jitpack.io'
}
}
}
@ -44,6 +54,10 @@ task clean(type: Delete) {
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 {
properties {
property "sonar.projectName", "RiotX-Android"
@ -69,3 +83,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", "**/*.*"
// }
// }
//}

View File

@ -35,11 +35,11 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
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:rxandroid:2.1.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

View File

@ -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.timeline.TimelineEvent
import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
class RxRoom(private val room: Room) {
fun liveRoomSummary(): Observable<RoomSummary> {
return room.liveRoomSummary.asObservable()
return room.liveRoomSummary().asObservable().observeOn(Schedulers.computation())
}
fun liveRoomMemberIds(): Observable<List<String>> {
return room.getRoomMemberIdsLive().asObservable()
return room.getRoomMemberIdsLive().asObservable().observeOn(Schedulers.computation())
}
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> {
return room.liveTimeLineEvent(eventId).asObservable()
return room.liveTimeLineEvent(eventId).asObservable().observeOn(Schedulers.computation())
}
}

View File

@ -22,23 +22,24 @@ 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.sync.SyncState
import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
class RxSession(private val session: Session) {
fun liveRoomSummaries(): Observable<List<RoomSummary>> {
return session.liveRoomSummaries().asObservable()
return session.liveRoomSummaries().asObservable().observeOn(Schedulers.computation())
}
fun liveGroupSummaries(): Observable<List<GroupSummary>> {
return session.liveGroupSummaries().asObservable()
return session.liveGroupSummaries().asObservable().observeOn(Schedulers.computation())
}
fun liveSyncState(): Observable<SyncState> {
return session.syncState().asObservable()
return session.syncState().asObservable().observeOn(Schedulers.computation())
}
fun livePushers(): Observable<List<Pusher>> {
return session.livePushers().asObservable()
return session.livePushers().asObservable().observeOn(Schedulers.computation())
}
}

View File

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

View File

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

View File

@ -19,25 +19,18 @@ package im.vector.matrix.android.auth
import androidx.test.annotation.UiThreadTest
import androidx.test.rule.GrantPermissionRule
import androidx.test.runner.AndroidJUnit4
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.OkReplayRuleChainNoActivity
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 org.junit.ClassRule
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.koin.standalone.StandAloneContext.loadKoinModules
import org.koin.standalone.inject
import org.koin.test.KoinTest
@RunWith(AndroidJUnit4::class)
internal class AuthenticatorTest : InstrumentedTest, KoinTest {
internal class AuthenticatorTest : InstrumentedTest {
lateinit var authenticator: Authenticator
lateinit var okReplayInterceptor: OkReplayInterceptor

View File

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

View File

@ -59,7 +59,7 @@ internal class ChunkEntityTest : InstrumentedTest {
val chunk: ChunkEntity = realm.createObject()
val fakeEvent = createFakeMessageEvent()
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()
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)
chunk2.addAll("roomId", createFakeListOfEvents(30), 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)
chunk2.addAll("roomId", eventsForChunk2, PaginationDirection.BACKWARDS)
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
chunk1.events.size shouldEqual 40
chunk1.timelineEvents.size shouldEqual 40
chunk1.isLastForward.shouldBeTrue()
}
}

View File

@ -18,25 +18,6 @@ package im.vector.matrix.android.session.room.timeline
import com.zhuinden.monarchy.Monarchy
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 {

View File

@ -87,7 +87,8 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
val matrixConfiguration = (appContext as MatrixConfiguration.Provider).providesMatrixConfiguration()
instance = Matrix(appContext, matrixConfiguration)
} else {
throw IllegalStateException("Matrix is not initialized properly. 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

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.api
import java.util.regex.Pattern
/**
* 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.
// 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 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.
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.
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.
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.
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
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.
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.
// Android does not support in URL so extract it.
@ -62,16 +61,16 @@ object MatrixPatterns {
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 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 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 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 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.
val MATRIX_PATTERNS = listOf(
@ -93,7 +92,7 @@ object MatrixPatterns {
* @return true if the string is a valid user id
*/
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
*/
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.
*/
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 {
return str != null
&& (PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER.matcher(str).matches()
|| PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3.matcher(str).matches()
|| PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4.matcher(str).matches())
&& (str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER
|| str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3
|| str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4)
}
/**
@ -136,6 +135,6 @@ object MatrixPatterns {
* @return true if the string is a valid group id.
*/
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
}
}

View File

@ -36,17 +36,15 @@ interface Authenticator {
*/
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.
*/
fun hasAuthenticatedSessions(): Boolean
//TODO remove this method. Shouldn't be managed like that.
/**
* Get the last active [Session], if there is an active session.
* @return the lastActive session if any, or null
* Get the last authenticated [Session], if there is an active session.
* @return the last active session if any, or null
*/
fun getLastAuthenticatedSession(): Session?
@ -58,7 +56,4 @@ interface Authenticator {
* @return the associated session if any, or null
*/
fun getSession(sessionParams: SessionParams): Session?
}

View File

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

View File

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

View File

@ -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.toModel
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 java.util.regex.Pattern
@ -35,16 +34,16 @@ class ContainsDisplayNameCondition : Condition(Kind.contains_display_name) {
}
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) {
EventType.MESSAGE -> {
EventType.MESSAGE -> {
event.content.toModel<MessageContent>()
}
// EventType.ENCRYPTED -> {
// event.root.getClearContent()?.toModel<MessageContent>()
// }
else -> null
//TODO the spec says:
// Matches any message whose content is unencrypted and contains the user's current display name
// EventType.ENCRYPTED -> {
// event.root.getClearContent()?.toModel<MessageContent>()
// }
else -> null
} ?: return false
return caseInsensitiveFind(displayName, message.body)

View File

@ -40,7 +40,8 @@ data class PushCondition(
/**
* Required for room_member_count conditions.
* 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
) {

View File

@ -13,17 +13,17 @@
* See the License for the specific language governing permissions and
* 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;
import java.lang.annotation.Retention;
interface InitialSyncProgressService {
import javax.inject.Scope;
fun getLiveStatus() : LiveData<Status?>
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Scope
@Documented
@Retention(RUNTIME)
public @interface SessionScope {}
data class Status(
@StringRes val statusText: Int?,
val percentProgress: Int = 0
)
}

View File

@ -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.ContentUrlResolver
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.pushers.PushersService
import im.vector.matrix.android.api.session.room.RoomDirectoryService
@ -46,15 +47,17 @@ interface Session :
CacheService,
SignOutService,
FilterService,
FileService,
PushRuleService,
PushersService {
PushersService,
InitialSyncProgressService {
/**
* The params associated to the session
*/
val sessionParams: SessionParams
val myUserId : String
val myUserId: String
get() = sessionParams.credentials.userId

View File

@ -28,10 +28,11 @@ interface ContentUploadStateTracker {
sealed class 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 Failure : State()
data class Failure(val throwable: Throwable) : State()
}
}

View File

@ -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.internal.crypto.MXEventDecryptionResult
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.MXDeviceInfo
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.rest.DevicesListResponse
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import java.io.File
interface CryptoService {
@ -85,6 +87,8 @@ interface CryptoService {
fun addRoomKeysRequestListener(listener: RoomKeysRequestListener)
fun removeRoomKeysRequestListener(listener: RoomKeysRequestListener)
fun getDevicesList(callback: MatrixCallback<DevicesListResponse>)
fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int
@ -96,9 +100,10 @@ interface CryptoService {
roomId: String,
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?

View File

@ -18,106 +18,65 @@
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.
*/
class MXCryptoError(var code: String,
var message: String) {
sealed class MXCryptoError : Throwable() {
/**
* Describe the error with more details
*/
private var mDetailedErrorDescription: String? = null
data class Base(val errorType: ErrorType,
val technicalMessage: String,
/**
* Describe the error with more details
*/
val detailedErrorDescription: String? = null) : MXCryptoError()
/**
* Data exception.
* Some exceptions provide some data to describe the exception
*/
var mExceptionData: Any? = null
data class OlmError(val olmException: OlmException) : MXCryptoError()
/**
* @return true if the current error is an olm one.
*/
val isOlmError: Boolean
get() = OLM_ERROR_CODE == code
data class UnknownDevice(val deviceList: MXUsersDevicesMap<MXDeviceInfo>) : MXCryptoError()
/**
* @return the detailed error description
*/
val detailedErrorDescription: String?
get() = if (TextUtils.isEmpty(mDetailedErrorDescription)) {
message
} else mDetailedErrorDescription
/**
* Create a crypto error
*
* @param code the error code (see XX_ERROR_CODE)
* @param shortErrorDescription the short error description
* @param detailedErrorDescription the detailed error description
*/
constructor(code: String, shortErrorDescription: String, detailedErrorDescription: String?) : this(code, shortErrorDescription) {
mDetailedErrorDescription = detailedErrorDescription
}
/**
* Create a crypto error
*
* @param code the error code (see XX_ERROR_CODE)
* @param shortErrorDescription the short error description
* @param detailedErrorDescription the detailed error description
* @param exceptionData the exception data
*/
constructor(code: String, shortErrorDescription: String, detailedErrorDescription: String?, exceptionData: Any) : this(code, shortErrorDescription) {
mDetailedErrorDescription = detailedErrorDescription
mExceptionData = exceptionData
enum class ErrorType {
ENCRYPTING_NOT_ENABLED,
UNABLE_TO_ENCRYPT,
UNABLE_TO_DECRYPT,
UNKNOWN_INBOUND_SESSION_ID,
INBOUND_SESSION_MISMATCH_ROOM_ID,
MISSING_FIELDS,
BAD_EVENT_FORMAT,
MISSING_SENDER_KEY,
MISSING_CIPHER_TEXT,
BAD_DECRYPTED_FORMAT,
NOT_INCLUDE_IN_RECIPIENTS,
BAD_RECIPIENT,
BAD_RECIPIENT_KEY,
FORWARDED_MESSAGE,
BAD_ROOM,
BAD_ENCRYPTED_MESSAGE,
DUPLICATED_MESSAGE_INDEX,
MISSING_PROPERTY,
OLM,
UNKNOWN_DEVICES,
UNKNOWN_MESSAGE_INDEX
}
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_DECRYPT_REASON = "Unable to decrypt %1\$s. Algorithm: %2\$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 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 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 BAD_DECRYPTED_FORMAT_TEXT_REASON = "Bad decrypted event format"
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_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 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 UNKNOWN_DEVICES_REASON = "This room contains unknown devices which have not been verified.\n" + "We strongly recommend you verify them before continuing."
const val NO_MORE_ALGORITHM_REASON = "Room was previously configured to use encryption, but is no longer." + " Perhaps the homeserver is hiding the configuration event."
const val UNKNOWN_DEVICES_REASON = "This room contains unknown devices which have not been verified.\n" +
"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."
}
}
}

View File

@ -40,7 +40,8 @@ interface KeysBackupService {
* @param keysBackupCreationInfo the info object from [prepareKeysBackupVersion].
* @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
@ -58,7 +59,8 @@ interface KeysBackupService {
* @param progressListener the callback to follow the progress
* @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.
@ -66,7 +68,8 @@ interface KeysBackupService {
* @param keysBackupVersion the backup version to check.
* @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
@ -80,7 +83,8 @@ interface KeysBackupService {
* @param version the backup version
* @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.
@ -114,7 +118,9 @@ interface KeysBackupService {
* @param progressListener a progress listener, as generating private key from password may take a while
* @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.
@ -123,7 +129,8 @@ interface KeysBackupService {
* @param version the backup version to delete.
* @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.
@ -139,7 +146,9 @@ interface KeysBackupService {
* @param trust the trust to set to the keys backup.
* @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.
@ -148,7 +157,9 @@ interface KeysBackupService {
* @param recoveryKey the recovery key to challenge with the key backup public key.
* @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.
@ -157,7 +168,9 @@ interface KeysBackupService {
* @param password the pass phrase to challenge with the keyBackupVersion public key.
* @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.
@ -169,7 +182,11 @@ interface KeysBackupService {
* @param stepProgressListener the step progress listener
* @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.
@ -181,7 +198,12 @@ interface KeysBackupService {
* @param stepProgressListener the step progress listener
* @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 currentBackupVersion: String?

View File

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

View File

@ -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>)
}

View File

@ -47,8 +47,8 @@ interface Room :
* A live [RoomSummary] associated with the 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?
}

View File

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

View File

@ -42,7 +42,7 @@ data class MessageAudioContent(
/**
* Required. Required if the file is not encrypted. The URL (typically MXC URI) to the audio clip.
*/
@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.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.
*/
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageEncyptedContent
) : MessageEncryptedContent

View File

@ -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?
}
}
/**
* Get the url of the encrypted file or of the file
*/
fun MessageEncryptedContent.getFileUrl() = encryptedFileInfo?.url ?: url

View File

@ -16,6 +16,7 @@
package im.vector.matrix.android.api.session.room.model.message
import android.content.ClipDescription
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
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.
*/
@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.new_content") override val newContent: Content? = 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
}
}

View File

@ -43,7 +43,7 @@ data class MessageImageContent(
/**
* 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.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.
*/
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageEncyptedContent
) : MessageEncryptedContent

View File

@ -42,7 +42,7 @@ data class MessageVideoContent(
/**
* Required. Required if the file is unencrypted. The URL (typically MXC URI) to the video clip.
*/
@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.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.
*/
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageEncyptedContent
) : MessageEncryptedContent

View File

@ -16,8 +16,8 @@
package im.vector.matrix.android.api.session.room.model.relation
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.util.Cancelable
/**
@ -51,7 +51,8 @@ interface RelationService {
* @param reaction the reaction (preferably emoji)
* @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 +61,9 @@ interface RelationService {
* @param targetEventId the id of the event being reacted
* @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 +72,11 @@ interface RelationService {
* @param newBodyText The edited body
* @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
/**
@ -77,8 +84,11 @@ interface RelationService {
* https://matrix.org/docs/spec/client_server/r0.4.0.html#id350
* @param eventReplied the event referenced by the reply
* @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>
}

View File

@ -67,7 +67,8 @@ data class PublicRoom(
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")
var guestCanJoin: Boolean = false,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -21,11 +21,11 @@ import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.api.MatrixPatterns
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.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
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.sync.SyncTokenStore
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")
}
// And the response result
usersDevicesInfoMap.setObjects(devices, userId)
usersDevicesInfoMap.setObjects(userId, devices)
}
}
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>> {
Timber.v("## downloadKeys() : forceDownload $forceDownload : $userIds")
// Map from userid -> deviceid -> DeviceInfo
// Map from userId -> deviceId -> MXDeviceInfo
val stored = MXUsersDevicesMap<MXDeviceInfo>()
// 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)
// should always be true
if (devices != null) {
stored.setObjects(devices, userId)
stored.setObjects(userId, devices)
} else {
downloadUsers.add(userId)
}
@ -380,14 +380,14 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
}
val signKeyId = "ed25519:" + deviceKeys.deviceId
val signKey = deviceKeys.keys!![signKeyId]
val signKey = deviceKeys.keys?.get(signKeyId)
if (null == signKey) {
Timber.e("## validateDeviceKeys() : Device " + userId + ":" + deviceKeys.deviceId + " has no ed25519 key")
return false
}
val signatureMap = deviceKeys.signatures!![userId]
val signatureMap = deviceKeys.signatures?.get(userId)
if (null == signatureMap) {
Timber.e("## validateDeviceKeys() : Device " + userId + ":" + deviceKeys.deviceId + " has no map for " + userId)
@ -413,7 +413,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
if (!isVerified) {
Timber.e("## validateDeviceKeys() : Unable to verify signature on device " + userId + ":"
+ deviceKeys.deviceId + " with error " + errorMessage)
+ deviceKeys.deviceId + " with error " + errorMessage)
return false
}
@ -424,8 +424,8 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
//
// Should we warn the user about it somehow?
Timber.e("## validateDeviceKeys() : WARNING:Ed25519 key for device " + userId + ":"
+ deviceKeys.deviceId + " has changed : "
+ previouslyStoredDeviceKeys.fingerprint() + " -> " + signKey)
+ deviceKeys.deviceId + " has changed : "
+ previouslyStoredDeviceKeys.fingerprint() + " -> " + signKey)
Timber.e("## validateDeviceKeys() : $previouslyStoredDeviceKeys -> $deviceKeys")
Timber.e("## validateDeviceKeys() : " + previouslyStoredDeviceKeys.keys + " -> " + deviceKeys.keys)

View File

@ -84,7 +84,7 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
Timber.e("## processReceivedRoomKeyRequests() : Ignoring room key request from other user for now")
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
// the keys for the requested events, and can drop the requests.
val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, alg)

View File

@ -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()
}
}

View File

@ -1,6 +1,5 @@
/*
* 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.
@ -18,7 +17,6 @@
package im.vector.matrix.android.internal.crypto
import im.vector.matrix.android.api.util.JsonDict
import java.util.*
/**
* 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).
*/
var clearEvent: JsonDict? = null,
val clearEvent: JsonDict,
/**
* Key owned by the sender of this event.
* See MXEvent.senderKey.
*/
var senderCurve25519Key: String? = null,
val senderCurve25519Key: String? = null,
/**
* Ed25519 key claimed by the sender of this event.
* See MXEvent.claimedEd25519Key.
*/
var claimedEd25519Key: String? = null,
val claimedEd25519Key: String? = null,
/**
* List of curve25519 keys involved in telling us about the senderCurve25519Key and
* claimedEd25519Key. See MXEvent.forwardingCurve25519KeyChain.
*/
var forwardingCurve25519KeyChain: List<String> = ArrayList()
val forwardingCurve25519KeyChain: List<String> = emptyList()
)

View File

@ -229,7 +229,7 @@ object MXMegolmExportEncryption {
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
lineStart = lineEnd + 1
@ -247,9 +247,9 @@ object MXMegolmExportEncryption {
val line: String
if (lineEnd < 0) {
line = fileStr.substring(lineStart).trim { it <= ' ' }
line = fileStr.substring(lineStart).trim()
} else {
line = fileStr.substring(lineStart, lineEnd).trim { it <= ' ' }
line = fileStr.substring(lineStart, lineEnd).trim()
}
if (TextUtils.equals(line, TRAILER_LINE)) {

View File

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

View File

@ -44,7 +44,9 @@ internal class ObjectSigner @Inject constructor(private val credentials: Credent
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

View File

@ -22,8 +22,8 @@ import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.crypto.model.MXKey
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
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.util.JsonCanonicalizer
import org.matrix.olm.OlmAccount
import timber.log.Timber
import java.util.*
@ -150,7 +150,7 @@ internal class OneTimeKeysUploader @Inject constructor(
val oneTimeKeys = olmDevice.getOneTimeKeys()
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) {
for (key_id in curve25519Map.keys) {
@ -158,7 +158,7 @@ internal class OneTimeKeysUploader @Inject constructor(
k["key"] = curve25519Map.getValue(key_id)
// 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)

View File

@ -17,7 +17,6 @@
package im.vector.matrix.android.internal.crypto
import android.os.Handler
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.EventType
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.session.SessionScope
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.util.createBackgroundHandler
import timber.log.Timber
import java.util.*
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
@SessionScope
@ -47,7 +48,7 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
// sanity check to ensure that we don't end up with two concurrent runs
// of sendOutgoingRoomKeyRequestsTimer
private var sendOutgoingRoomKeyRequestsRunning: Boolean = false
private val sendOutgoingRoomKeyRequestsRunning = AtomicBoolean(false)
/**
* Called when the client is started. Sets background processes running.
@ -101,7 +102,9 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
* @param requestBody requestBody
*/
fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody) {
cancelRoomKeyRequest(requestBody, false)
BACKGROUND_HANDLER.post {
cancelRoomKeyRequest(requestBody, false)
}
}
/**
@ -110,7 +113,9 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
* @param requestBody requestBody
*/
fun resendRoomKeyRequest(requestBody: RoomKeyRequestBody) {
cancelRoomKeyRequest(requestBody, true)
BACKGROUND_HANDLER.post {
cancelRoomKeyRequest(requestBody, true)
}
}
/**
@ -121,25 +126,31 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
*/
private fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody, andResend: Boolean) {
val req = cryptoStore.getOutgoingRoomKeyRequest(requestBody)
?: // no request was made for this key
return
?: // no request was made for this key
return
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) {
// nothing to do here
} else if (req.state === OutgoingRoomKeyRequest.RequestState.UNSENT || req.state === OutgoingRoomKeyRequest.RequestState.FAILED) {
Timber.v("## cancelRoomKeyRequest() : deleting unnecessary room key request for $requestBody")
cryptoStore.deleteOutgoingRoomKeyRequest(req.requestId)
} else if (req.state === OutgoingRoomKeyRequest.RequestState.SENT) {
if (andResend) {
req.state = OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND
} else {
req.state = OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING
when (req.state) {
OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING,
OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND -> {
// nothing to do here
}
OutgoingRoomKeyRequest.RequestState.UNSENT,
OutgoingRoomKeyRequest.RequestState.FAILED -> {
Timber.v("## cancelRoomKeyRequest() : deleting unnecessary room key request for $requestBody")
cryptoStore.deleteOutgoingRoomKeyRequest(req.requestId)
}
OutgoingRoomKeyRequest.RequestState.SENT -> {
if (andResend) {
req.state = OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND
} else {
req.state = OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING
}
req.cancellationTxnId = makeTxnId()
cryptoStore.updateOutgoingRoomKeyRequest(req)
sendOutgoingRoomKeyRequestCancellation(req)
}
req.cancellationTxnId = makeTxnId()
cryptoStore.updateOutgoingRoomKeyRequest(req)
sendOutgoingRoomKeyRequestCancellation(req)
}
}
@ -148,16 +159,16 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
* Start the background timer to send queued requests, if the timer isn't already running.
*/
private fun startTimer() {
if (sendOutgoingRoomKeyRequestsRunning) {
if (sendOutgoingRoomKeyRequestsRunning.get()) {
return
}
Handler().postDelayed(Runnable {
if (sendOutgoingRoomKeyRequestsRunning) {
BACKGROUND_HANDLER.postDelayed(Runnable {
if (sendOutgoingRoomKeyRequestsRunning.get()) {
Timber.v("## startTimer() : RoomKeyRequestSend already in progress!")
return@Runnable
}
sendOutgoingRoomKeyRequestsRunning = true
sendOutgoingRoomKeyRequestsRunning.set(true)
sendOutgoingRoomKeyRequests()
}, SEND_KEY_REQUESTS_DELAY_MS.toLong())
}
@ -167,19 +178,19 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
// timer will be restarted before the promise resolves).
private fun sendOutgoingRoomKeyRequests() {
if (!isClientRunning) {
sendOutgoingRoomKeyRequestsRunning = false
sendOutgoingRoomKeyRequestsRunning.set(false)
return
}
Timber.v("## sendOutgoingRoomKeyRequests() : Looking for queued outgoing room key requests")
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_AND_WILL_RESEND)))
OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND))
if (null == outgoingRoomKeyRequest) {
Timber.e("## sendOutgoingRoomKeyRequests() : No more outgoing room key requests")
sendOutgoingRoomKeyRequestsRunning = false
sendOutgoingRoomKeyRequestsRunning.set(false)
return
}
@ -213,7 +224,7 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
cryptoStore.updateOutgoingRoomKeyRequest(request)
}
sendOutgoingRoomKeyRequestsRunning = false
sendOutgoingRoomKeyRequestsRunning.set(false)
startTimer()
}
@ -246,7 +257,7 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
sendMessageToDevices(roomKeyShareCancellation, request.recipients, request.cancellationTxnId, object : MatrixCallback<Unit> {
private fun onDone() {
cryptoStore.deleteOutgoingRoomKeyRequest(request.requestId)
sendOutgoingRoomKeyRequestsRunning = false
sendOutgoingRoomKeyRequestsRunning.set(false)
startTimer()
}
@ -285,15 +296,20 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
val contentMap = MXUsersDevicesMap<Any>()
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))
.dispatchTo(callback)
.executeOn(TaskThread.CALLER)
.callbackOn(TaskThread.CALLER)
.executeBy(taskExecutor)
}
companion object {
private const val SEND_KEY_REQUESTS_DELAY_MS = 500
private val BACKGROUND_HANDLER = createBackgroundHandler("OutgoingRoomKeyRequest")
}
}

View File

@ -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.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
import im.vector.matrix.android.internal.session.SessionScope
import timber.log.Timber
import java.util.*
import javax.inject.Inject
@ -54,7 +53,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
}
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
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
@ -91,12 +90,12 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
val deviceIds = it.getUserDeviceIds(userId)
if (null != deviceIds) {
for (deviceId in deviceIds) {
val olmSessionResult = results.getObject(deviceId, userId)
val olmSessionResult = results.getObject(userId, deviceId)
if (olmSessionResult!!.sessionId != null) {
// We already have a result for this device
continue
}
val key = it.getObject(deviceId, userId)
val key = it.getObject(userId, deviceId)
if (key?.type == oneTimeKeyAlgorithm) {
oneTimeKey = key
}
@ -126,11 +125,13 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
var isVerified = false
var errorMessage: String? = null
try {
olmDevice.verifySignature(deviceInfo.fingerprint()!!, oneTimeKey.signalableJSONDictionary(), signature)
isVerified = true
} catch (e: Exception) {
errorMessage = e.message
if (signature != null) {
try {
olmDevice.verifySignature(deviceInfo.fingerprint()!!, oneTimeKey.signalableJSONDictionary(), signature)
isVerified = true
} catch (e: Exception) {
errorMessage = e.message
}
}
// Check one-time key signature

View File

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

View File

@ -17,9 +17,9 @@
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.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.keysbackup.KeysBackup
@ -33,11 +33,9 @@ internal interface IMXDecrypting {
*
* @param event the raw event.
* @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
* @throws MXDecryptionException the decryption failure reason
* @return the decryption information, or an error
*/
@Throws(MXDecryptionException::class)
suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult?
suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult>
/**
* Handle a key event.

View File

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

View File

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

View File

@ -21,7 +21,6 @@ package im.vector.matrix.android.internal.crypto.algorithms.megolm
import android.text.TextUtils
import arrow.core.Try
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.events.model.Content
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.store.IMXCryptoStore
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 timber.log.Timber
import java.util.*
@ -118,8 +117,8 @@ internal class MXMegolmEncryption(
for (userId in userIds) {
val deviceIds = devicesInRoom.getUserDeviceIds(userId)
for (deviceId in deviceIds!!) {
val deviceInfo = devicesInRoom.getObject(deviceId, userId)
if (null == safeSession.sharedWithDevices.getObject(deviceId, userId)) {
val deviceInfo = devicesInRoom.getObject(userId, deviceId)
if (deviceInfo != null && null == safeSession.sharedWithDevices.getObject(userId, deviceId)) {
if (!shareMap.containsKey(userId)) {
shareMap[userId] = ArrayList()
}
@ -201,7 +200,7 @@ internal class MXMegolmEncryption(
for (userId in userIds) {
val devicesToShareWith = devicesByUser[userId]
for ((deviceID) in devicesToShareWith!!) {
val sessionResult = it.getObject(deviceID, userId)
val sessionResult = it.getObject(userId, deviceID)
if (sessionResult?.sessionId == null) {
// no session with this device, probably because there
// were no one-time keys.
@ -218,7 +217,7 @@ internal class MXMegolmEncryption(
}
Timber.v("## shareUserDevicesKey() : Sharing keys with device $userId:$deviceID")
//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
}
}
@ -239,7 +238,7 @@ internal class MXMegolmEncryption(
for (userId in devicesByUser.keys) {
val devicesToShareWith = devicesByUser[userId]
for ((deviceId) in devicesToShareWith!!) {
session.sharedWithDevices.setObject(chainIndex, userId, deviceId)
session.sharedWithDevices.setObject(userId, deviceId, chainIndex)
}
}
Unit
@ -254,29 +253,31 @@ internal class MXMegolmEncryption(
/**
* process the pending encryptions
*/
private suspend fun encryptContent(session: MXOutboundSessionInfo, eventType: String, eventContent: Content) = Try<Content> {
// Everything is in place, encrypt all pending events
val payloadJson = HashMap<String, Any>()
payloadJson["room_id"] = roomId
payloadJson["type"] = eventType
payloadJson["content"] = eventContent
private fun encryptContent(session: MXOutboundSessionInfo, eventType: String, eventContent: Content): Try<Content> {
return Try<Content> {
// Everything is in place, encrypt all pending events
val payloadJson = HashMap<String, Any>()
payloadJson["room_id"] = roomId
payloadJson["type"] = eventType
payloadJson["content"] = eventContent
// Get canonical Json from
// Get canonical Json from
val payloadString = convertToUTF8(MoshiProvider.getCanonicalJson(Map::class.java, payloadJson))
val ciphertext = olmDevice.encryptGroupMessage(session.sessionId, payloadString!!)
val payloadString = convertToUTF8(JsonCanonicalizer.getCanonicalJson(Map::class.java, payloadJson))
val ciphertext = olmDevice.encryptGroupMessage(session.sessionId, payloadString!!)
val map = HashMap<String, Any>()
map["algorithm"] = MXCRYPTO_ALGORITHM_MEGOLM
map["sender_key"] = olmDevice.deviceCurve25519Key!!
map["ciphertext"] = ciphertext!!
map["session_id"] = session.sessionId
val map = HashMap<String, Any>()
map["algorithm"] = MXCRYPTO_ALGORITHM_MEGOLM
map["sender_key"] = olmDevice.deviceCurve25519Key!!
map["ciphertext"] = ciphertext!!
map["session_id"] = session.sessionId
// Include our device ID so that recipients can send us a
// m.new_device message if they don't have our session key.
map["device_id"] = credentials.deviceId!!
session.useCount++
map
// Include our device ID so that recipients can send us a
// m.new_device message if they don't have our session key.
map["device_id"] = credentials.deviceId!!
session.useCount++
map
}
}
/**
@ -303,10 +304,10 @@ internal class MXMegolmEncryption(
for (userId in it.userIds) {
val deviceIds = it.getUserDeviceIds(userId) ?: continue
for (deviceId in deviceIds) {
val deviceInfo = it.getObject(deviceId, userId) ?: continue
val deviceInfo = it.getObject(userId, deviceId) ?: continue
if (warnOnUnknownDevicesRepository.warnOnUnknownDevices() && deviceInfo.isUnknown) {
// The device is not yet known by the user
unknownDevices.setObject(deviceInfo, userId, deviceId)
unknownDevices.setObject(userId, deviceId, deviceInfo)
continue
}
if (deviceInfo.isBlocked) {
@ -322,15 +323,13 @@ internal class MXMegolmEncryption(
// Don't bother sending to ourself
continue
}
devicesInRoom.setObject(deviceInfo, userId, deviceId)
devicesInRoom.setObject(userId, deviceId, deviceInfo)
}
}
if (unknownDevices.isEmpty) {
Try.just(devicesInRoom)
} else {
val cryptoError = MXCryptoError(MXCryptoError.UNKNOWN_DEVICES_CODE,
MXCryptoError.UNABLE_TO_ENCRYPT, MXCryptoError.UNKNOWN_DEVICES_REASON, unknownDevices)
Try.Failure(Failure.CryptoError(cryptoError))
Try.Failure(MXCryptoError.UnknownDevice(unknownDevices))
}
}
}

View File

@ -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.store.IMXCryptoStore
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
internal class MXMegolmEncryptionFactory @Inject constructor(
@ -37,8 +35,6 @@ internal class MXMegolmEncryptionFactory @Inject constructor(
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
private val credentials: Credentials,
private val sendToDeviceTask: SendToDeviceTask,
// FIXME Why taskExecutor is not used?
private val taskExecutor: TaskExecutor,
private val messageEncrypter: MessageEncrypter,
private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository) {

View File

@ -64,7 +64,7 @@ internal class MXOutboundSessionInfo(
val deviceIds = sharedWithDevices.getUserDeviceIds(userId)
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")
return true
}

View File

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

View File

@ -14,32 +14,35 @@
* 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
/**
* This class represents the decryption result.
*/
data class MXDecryptionResult(
@JsonClass(generateAdapter = true)
data class OlmDecryptionResult(
/**
* 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:
* 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.
*/
var senderKey: String? = null,
@Json(name = "senderKey") val senderKey: String? = null,
/**
* Devices which forwarded this session to us (normally empty).
*/
var forwardingCurve25519KeyChain: List<String>? = null
)
@Json(name = "forwardingCurve25519KeyChain") val forwardingCurve25519KeyChain: List<String>? = null
)

View File

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

View File

@ -16,8 +16,8 @@
package im.vector.matrix.android.internal.crypto.attachments
import android.text.TextUtils
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.EncryptedFileKey
import timber.log.Timber
@ -51,7 +51,7 @@ object MXEncryptedAttachments {
* @param mimetype the mime type
* @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 secureRandom = SecureRandom()
@ -115,23 +115,21 @@ object MXEncryptedAttachments {
encryptedByteArray = outStream.toByteArray()
)
outStream.close()
Timber.v("Encrypt in " + (System.currentTimeMillis() - t0) + " ms")
return result
return Try.just(result)
} catch (oom: OutOfMemoryError) {
Timber.e(oom, "## encryptAttachment failed " + oom.message)
Timber.e(oom, "## encryptAttachment failed")
return Try.Failure(oom)
} catch (e: Exception) {
Timber.e(e, "## encryptAttachment failed " + e.message)
Timber.e(e, "## encryptAttachment failed")
return Try.Failure(e)
} finally {
try {
outStream.close()
} catch (e: Exception) {
Timber.e(e, "## encryptAttachment() : fail to close outStream")
}
}
try {
outStream.close()
} catch (e: Exception) {
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))
if (!TextUtils.equals(elementToDecrypt.sha256, currentDigestValue)) {
if (elementToDecrypt.sha256 != currentDigestValue) {
Timber.e("## decryptAttachment() : Digest value mismatch")
outStream.close()
return null

View File

@ -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.extractCurveKeyFromRecoveryKey
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.store.IMXCryptoStore
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.extensions.foldToCallback
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.TaskExecutor
import im.vector.matrix.android.internal.task.TaskThread
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 kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@ -177,7 +178,7 @@ internal class KeysBackup @Inject constructor(
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)
@ -388,8 +389,8 @@ internal class KeysBackup @Inject constructor(
return keysBackupVersionTrust
}
val mySigs: Map<String, *> = authData.signatures!![myUserId] as Map<String, *>
if (mySigs.isEmpty()) {
val mySigs = authData.signatures?.get(myUserId)
if (mySigs.isNullOrEmpty()) {
Timber.v("getKeysBackupTrust: Ignoring key backup because it lacks any signatures from this user")
return keysBackupVersionTrust
}
@ -402,20 +403,21 @@ internal class KeysBackup @Inject constructor(
deviceId = components[1]
}
var device: MXDeviceInfo? = null
if (deviceId != null) {
device = cryptoStore.getUserDevice(deviceId, myUserId)
val device = cryptoStore.getUserDevice(deviceId, myUserId)
var isSignatureValid = false
if (device == null) {
Timber.v("getKeysBackupTrust: Signature from unknown device $deviceId")
} else {
try {
olmDevice.verifySignature(device.fingerprint()!!, authData.signalableJSONDictionary(), mySigs[keyId] as String)
isSignatureValid = true
} catch (e: OlmException) {
Timber.v("getKeysBackupTrust: Bad signature from device " + device.deviceId + " " + e.localizedMessage)
val fingerprint = device.fingerprint()
if (fingerprint != null) {
try {
olmDevice.verifySignature(fingerprint, authData.signalableJSONDictionary(), mySigs[keyId] as String)
isSignatureValid = true
} catch (e: OlmException) {
Timber.v("getKeysBackupTrust: Bad signature from device " + device.deviceId + " " + e.localizedMessage)
}
}
if (isSignatureValid && device.isVerified) {
@ -452,12 +454,11 @@ internal class KeysBackup @Inject constructor(
val myUserId = credentials.userId
// Get current signatures, or create an empty set
val myUserSignatures = (authData.signatures!![myUserId]?.toMutableMap()
?: HashMap())
val myUserSignatures = authData.signatures?.get(myUserId)?.toMutableMap() ?: HashMap()
if (trust) {
// 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)
@ -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
val backUp = keysVersionResult.version != keysBackupVersion?.version
if (backUp) {
Timber.v("restoreKeysWithRecoveryKey: Those keys will be backed up to backup version: " + keysBackupVersion?.version)
Timber.v("restoreKeysWithRecoveryKey: Those keys will be backed up to backup version: "
+ keysBackupVersion?.version)
}
// Import them into the crypto store
@ -875,7 +877,7 @@ internal class KeysBackup @Inject constructor(
override fun getCurrentVersion(callback: MatrixCallback<KeysVersionResult?>) {
getKeysBackupLastVersionTask
.configureWith(Unit)
.toConfigurableTask()
.dispatchTo(object : MatrixCallback<KeysVersionResult> {
override fun onSuccess(data: KeysVersionResult) {
callback.onSuccess(data)
@ -1027,8 +1029,7 @@ internal class KeysBackup @Inject constructor(
val authData = keysBackupData.getAuthDataAsMegolmBackupAuthData()
if (authData.signatures == null
|| authData.publicKey.isBlank()) {
if (authData?.signatures == null || authData.publicKey.isBlank()) {
return null
}
@ -1226,7 +1227,8 @@ internal class KeysBackup @Inject constructor(
}
try {
keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId]!!.sessionIdToKeyBackupData[olmInboundGroupSessionWrapper.olmInboundGroupSession!!.sessionIdentifier()] = keyBackupData
keysBackupData.roomIdToRoomKeysBackupData[olmInboundGroupSessionWrapper.roomId]!!
.sessionIdToKeyBackupData[olmInboundGroupSessionWrapper.olmInboundGroupSession!!.sessionIdentifier()] = keyBackupData
} catch (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
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
}
}

View File

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

View File

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

View File

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

View File

@ -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;
}
}

View File

@ -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
}
}
}

View File

@ -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";
}
}
}

View File

@ -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"
}
}

View File

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

View File

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

View File

@ -20,11 +20,14 @@ import com.squareup.moshi.Json
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)
data class KeysClaimBody(
/**
* The time (in milliseconds) to wait when downloading keys from remote servers. 10 seconds is the recommended default.
*/
@Json(name = "timeout")
var timeout: Int? = null,

View File

@ -20,7 +20,7 @@ import com.squareup.moshi.Json
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)
data class KeysClaimResponse(

View File

@ -18,8 +18,9 @@ package im.vector.matrix.android.internal.crypto.model.rest
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 device ID may also be *, meaning all known devices for the user.
*/

View File

@ -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.getOrCreate
import im.vector.matrix.android.internal.session.SessionScope
import io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.Sort
import io.realm.kotlin.where
@ -39,16 +40,17 @@ import org.matrix.olm.OlmException
import timber.log.Timber
import kotlin.collections.set
// enableFileEncryption is used to migrate the previous store
@SessionScope
internal class RealmCryptoStore(private val enableFileEncryption: Boolean = false,
private val realmConfiguration: RealmConfiguration,
internal class RealmCryptoStore(private val realmConfiguration: RealmConfiguration,
private val credentials: Credentials) : IMXCryptoStore {
/* ==========================================================================================
* 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
private var olmAccount: OlmAccount? = null
@ -88,6 +90,8 @@ internal class RealmCryptoStore(private val enableFileEncryption: Boolean = fals
}
override fun open() {
realmLocker = Realm.getInstance(realmConfiguration)
// Ensure CryptoMetadataEntity is inserted in DB
doWithRealm(realmConfiguration) { realm ->
var currentMetadata = realm.where<CryptoMetadataEntity>().findFirst()
@ -133,6 +137,9 @@ internal class RealmCryptoStore(private val enableFileEncryption: Boolean = fals
inboundGroupSessionToRelease.clear()
olmAccount?.releaseAccount()
realmLocker?.close()
realmLocker = null
}
override fun storeDeviceId(deviceId: String) {

View File

@ -16,9 +16,9 @@
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.model.rest.RoomKeyRequestBody
import io.realm.RealmObject
internal open class IncomingRoomKeyRequestEntity(
var requestId: String? = null,
@ -32,11 +32,11 @@ internal open class IncomingRoomKeyRequestEntity(
) : RealmObject() {
fun toIncomingRoomKeyRequest(): IncomingRoomKeyRequest {
return IncomingRoomKeyRequest().apply {
requestId = requestId
userId = userId
deviceId = deviceId
requestBody = RoomKeyRequestBody().apply {
return IncomingRoomKeyRequest().also {
it.requestId = requestId
it.userId = userId
it.deviceId = deviceId
it.requestBody = RoomKeyRequestBody().apply {
algorithm = requestBodyAlgorithm
roomId = requestBodyRoomId
senderKey = requestBodySenderKey

View File

@ -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.KeysClaimResponse
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 timber.log.Timber
import java.util.*
import javax.inject.Inject
internal interface ClaimOneTimeKeysForUsersDeviceTask : Task<ClaimOneTimeKeysForUsersDeviceTask.Params, MXUsersDevicesMap<MXKey>> {
@ -46,30 +44,27 @@ internal class DefaultClaimOneTimeKeysForUsersDevice @Inject constructor(private
apiCall = cryptoApi.claimOneTimeKeysForUsersDevices(body)
}.flatMap { keysClaimResponse ->
Try {
val map = HashMap<String, Map<String, MXKey>>()
val map = MXUsersDevicesMap<MXKey>()
if (null != keysClaimResponse.oneTimeKeys) {
for (userId in keysClaimResponse.oneTimeKeys!!.keys) {
val mapByUserId = keysClaimResponse.oneTimeKeys!![userId]
keysClaimResponse.oneTimeKeys?.let { oneTimeKeys ->
for (userId in oneTimeKeys.keys) {
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) {
try {
keysMap[deviceId] = MXKey(mapByUserId[deviceId])
} catch (e: Exception) {
Timber.e(e, "## claimOneTimeKeysForUsersDevices : fail to create a MXKey ")
if (mxKey != null) {
map.setObject(userId, deviceId, mxKey)
} else {
Timber.e("## claimOneTimeKeysForUsersDevices : fail to create a MXKey")
}
}
}
if (keysMap.size != 0) {
map[userId] = keysMap
}
}
}
MXUsersDevicesMap(map)
map
}
}
}

View File

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

View File

@ -29,8 +29,8 @@ import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationMac
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
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.util.JsonCanonicalizer
import timber.log.Timber
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,
// 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) ?: ""
//we need to send this to other device now
state = SasVerificationTxState.SendingAccept

View File

@ -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.events.model.EventType
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.KeyVerificationKey
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.store.IMXCryptoStore
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.util.JsonCanonicalizer
import timber.log.Timber
internal class OutgoingSASVerificationRequest(
@ -61,22 +60,22 @@ internal class OutgoingSASVerificationRequest(
override val uxState: OutgoingSasVerificationRequest.UxState
get() {
return when (state) {
SasVerificationTxState.None -> OutgoingSasVerificationRequest.UxState.WAIT_FOR_START
SasVerificationTxState.None -> OutgoingSasVerificationRequest.UxState.WAIT_FOR_START
SasVerificationTxState.SendingStart,
SasVerificationTxState.Started,
SasVerificationTxState.OnAccepted,
SasVerificationTxState.SendingKey,
SasVerificationTxState.KeySent,
SasVerificationTxState.OnKeyReceived -> OutgoingSasVerificationRequest.UxState.WAIT_FOR_KEY_AGREEMENT
SasVerificationTxState.OnKeyReceived -> OutgoingSasVerificationRequest.UxState.WAIT_FOR_KEY_AGREEMENT
SasVerificationTxState.ShortCodeReady -> OutgoingSasVerificationRequest.UxState.SHOW_SAS
SasVerificationTxState.ShortCodeAccepted,
SasVerificationTxState.SendingMac,
SasVerificationTxState.MacSent,
SasVerificationTxState.Verifying -> OutgoingSasVerificationRequest.UxState.WAIT_FOR_VERIFICATION
SasVerificationTxState.Verified -> OutgoingSasVerificationRequest.UxState.VERIFIED
SasVerificationTxState.OnCancelled -> OutgoingSasVerificationRequest.UxState.CANCELLED_BY_ME
SasVerificationTxState.Cancelled -> OutgoingSasVerificationRequest.UxState.CANCELLED_BY_OTHER
else -> OutgoingSasVerificationRequest.UxState.UNKNOWN
SasVerificationTxState.Verifying -> OutgoingSasVerificationRequest.UxState.WAIT_FOR_VERIFICATION
SasVerificationTxState.Verified -> OutgoingSasVerificationRequest.UxState.VERIFIED
SasVerificationTxState.OnCancelled -> OutgoingSasVerificationRequest.UxState.CANCELLED_BY_ME
SasVerificationTxState.Cancelled -> OutgoingSasVerificationRequest.UxState.CANCELLED_BY_OTHER
else -> OutgoingSasVerificationRequest.UxState.UNKNOWN
}
}
@ -102,8 +101,6 @@ internal class OutgoingSASVerificationRequest(
startMessage.shortAuthenticationStrings = KNOWN_SHORT_CODES
startReq = startMessage
val contentMap = MXUsersDevicesMap<Any>()
contentMap.setObject(startMessage, otherUserId, otherDeviceId)
state = SasVerificationTxState.SendingStart
sendToOther(
@ -167,7 +164,7 @@ internal class OutgoingSASVerificationRequest(
// in Bobs m.key.verification.key and the content of Alices m.key.verification.start message.
//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) ?: ""
if (accepted!!.commitment.equals(otherCommitment)) {

View File

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

View File

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

View File

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

View File

@ -22,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.toEntity
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.EventEntityFields
import im.vector.matrix.android.internal.database.query.fastContains
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import im.vector.matrix.android.internal.database.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.session.room.timeline.PaginationDirection
import io.realm.Sort
@ -32,12 +34,15 @@ import io.realm.Sort
// By default if a chunk is empty we consider it unlinked
internal fun ChunkEntity.isUnlinked(): Boolean {
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() {
assertIsManaged()
this.events.deleteAllFromRealm()
this.timelineEvents.deleteAllFromRealm()
this.deleteFromRealm()
}
@ -50,21 +55,27 @@ internal fun ChunkEntity.merge(roomId: String,
val isUnlinked = 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) {
this.nextToken = chunkToMerge.nextToken
this.isLastForward = chunkToMerge.isLastForward
eventsToMerge = chunkToMerge.events.sort(EventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.ASCENDING)
} else {
this.prevToken = chunkToMerge.prevToken
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 {
add(roomId, it.asDomain(), direction, isUnlinked = isUnlinked)
val events = eventsToMerge.mapNotNull { it.root?.asDomain() }
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,
@ -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)
isUnlinked: Boolean = false) {
assertIsManaged()
val eventIds = ArrayList<String>()
events.forEach { event ->
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) {
assertIsManaged()
if (event.eventId != null && events.fastContains(event.eventId)) {
if (event.eventId != null && timelineEvents.find(event.eventId) != null) {
return
}
var currentDisplayIndex = lastDisplayIndex(direction, 0)
@ -101,33 +124,40 @@ internal fun ChunkEntity.add(roomId: String,
if (direction == PaginationDirection.FORWARDS && EventType.isStateEvent(event.getClearType())) {
currentStateIndex += 1
forwardsStateIndex = currentStateIndex
} else if (direction == PaginationDirection.BACKWARDS && events.isNotEmpty()) {
val lastEventType = events.last()?.type ?: ""
} else if (direction == PaginationDirection.BACKWARDS && timelineEvents.isNotEmpty()) {
val lastEventType = timelineEvents.last()?.root?.type ?: ""
if (EventType.isStateEvent(lastEventType)) {
currentStateIndex -= 1
backwardsStateIndex = currentStateIndex
}
}
val eventEntity = event.toEntity(roomId).apply {
this.stateIndex = currentStateIndex
this.isUnlinked = isUnlinked
this.displayIndex = currentDisplayIndex
this.sendState = SendState.SYNCED
val localId = TimelineEventEntity.nextId(realm)
val eventEntity = TimelineEventEntity(localId).also {
it.root = event.toEntity(roomId).apply {
this.stateIndex = currentStateIndex
this.isUnlinked = isUnlinked
this.displayIndex = currentDisplayIndex
this.sendState = SendState.SYNCED
}
it.eventId = event.eventId ?: ""
it.roomId = roomId
it.annotations = EventAnnotationsSummaryEntity.where(realm, it.eventId).findFirst()
}
val position = if (direction == PaginationDirection.FORWARDS) 0 else this.events.size
events.add(position, eventEntity)
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 {
return when (direction) {
PaginationDirection.FORWARDS -> forwardsDisplayIndex
PaginationDirection.BACKWARDS -> backwardsDisplayIndex
} ?: defaultValue
PaginationDirection.FORWARDS -> forwardsDisplayIndex
PaginationDirection.BACKWARDS -> backwardsDisplayIndex
} ?: defaultValue
}
internal fun ChunkEntity.lastStateIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
return when (direction) {
PaginationDirection.FORWARDS -> forwardsStateIndex
PaginationDirection.BACKWARDS -> backwardsStateIndex
} ?: defaultValue
PaginationDirection.FORWARDS -> forwardsStateIndex
PaginationDirection.BACKWARDS -> backwardsStateIndex
} ?: defaultValue
}

View File

@ -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.model.ChunkEntity
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.extensions.assertIsManaged
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
internal fun RoomEntity.deleteOnCascade(chunkEntity: ChunkEntity) {
chunks.remove(chunkEntity)
@ -36,29 +37,39 @@ internal fun RoomEntity.addOrUpdate(chunkEntity: ChunkEntity) {
}
}
internal fun RoomEntity.addStateEvents(stateEvents: List<Event>,
stateIndex: Int = Int.MIN_VALUE,
filterDuplicates: Boolean = false,
isUnlinked: Boolean = false) {
internal fun RoomEntity.addStateEvent(stateEvent: Event,
stateIndex: Int = Int.MIN_VALUE,
filterDuplicates: Boolean = false,
isUnlinked: Boolean = false) {
assertIsManaged()
stateEvents.forEach { event ->
if (event.eventId == null || (filterDuplicates && fastContains(event.eventId))) {
return@forEach
}
val eventEntity = event.toEntity(roomId).apply {
if (stateEvent.eventId == null || (filterDuplicates && fastContains(stateEvent.eventId))) {
return
} else {
val entity = stateEvent.toEntity(roomId).apply {
this.stateIndex = stateIndex
this.isUnlinked = isUnlinked
this.sendState = SendState.SYNCED
}
untimelinedStateEvents.add(0, eventEntity)
untimelinedStateEvents.add(entity)
}
}
internal fun RoomEntity.addSendingEvent(event: Event) {
assertIsManaged()
val senderId = event.senderId ?: return
val eventEntity = event.toEntity(roomId).apply {
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)
}

View File

@ -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)
}

View File

@ -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.EventAnnotationsSummary
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.ReactionAggregatedSummaryEntity
import io.realm.RealmList
internal object EventAnnotationsSummaryMapper {
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 {

View File

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

View File

@ -16,29 +16,39 @@
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.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.session.room.timeline.TimelineEventFactory
import java.util.*
import javax.inject.Inject
internal class RoomSummaryMapper @Inject constructor(
private val timelineEventFactory: TimelineEventFactory,
private val monarchy: Monarchy) {
val cryptoService: CryptoService
) {
fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary {
val tags = roomSummaryEntity.tags.map {
RoomTag(it.tagName, it.tagOrder)
}
val latestEvent = roomSummaryEntity.latestEvent?.let {
var ev: TimelineEvent? = null
monarchy.doWithRealm { realm ->
ev = timelineEventFactory.create(it, realm)
val latestEvent = roomSummaryEntity.latestEvent?.asDomain()
if (latestEvent?.root?.isEncrypted() == true && latestEvent.root.mxDecryptionResult == null) {
//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(
roomId = roomSummaryEntity.roomId,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,41 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.database.model
import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.Index
import io.realm.annotations.LinkingObjects
internal open class TimelineEventEntity(var localId: Long = 0,
@Index var eventId: String = "",
@Index var roomId: String = "",
var root: EventEntity? = null,
var annotations: EventAnnotationsSummaryEntity? = null,
var senderName: String? = null,
var isUniqueDisplayName: Boolean = false,
var senderAvatar: String? = null,
var senderMembershipEvent: EventEntity? = null
) : RealmObject() {
@LinkingObjects("timelineEvents")
val chunk: RealmResults<ChunkEntity>? = null
companion object
}

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