1
0
mirror of https://github.com/vector-im/riotX-android synced 2025-10-06 16:22:41 +02:00

Compare commits

...

306 Commits

Author SHA1 Message Date
ganfra
9903a299b9 Sqlite: make it work with latest develop version 2020-04-23 20:34:26 +02:00
ganfra
9a4cad1e45 Merge sql crypto store implementation 2020-04-23 18:58:07 +02:00
ganfra
649b4496a6 Make rx module uses Flow/Rx converter 2020-04-23 18:55:10 +02:00
ganfra
617b76c0d5 Make sdk use sqldelight module 2020-04-23 18:53:06 +02:00
ganfra
9415d9b5c5 Configure project 2020-04-23 16:49:51 +02:00
ganfra
3ba5e97392 Introduce all the tables 2020-04-23 12:54:04 +02:00
Benoit Marty
671c1259af Merge pull request #1239 from waylon531/develop
Use a bigger thread pool
2020-04-22 18:23:37 +02:00
Valere
491f0e6032 Merge pull request #1259 from vector-im/feature/restore_backup_from_ssss
KeyBackup / Use 4S if key in quadS
2020-04-21 14:31:26 +02:00
Valere
63355ca256 code review 2020-04-21 11:06:56 +02:00
Valere
8a4f0a0c00 KeyBackup / Use 4S if key in quadS 2020-04-20 19:21:44 +02:00
Benoit Marty
4d207e6acd Merge pull request #1258 from vector-im/feature/strings
Strings
2020-04-20 17:41:50 +02:00
Valere
1227de3f9c Merge pull request #1256 from vector-im/feature/increase_default_timeout
Increase default timeout to match old riot
2020-04-20 17:13:59 +02:00
Benoit Marty
6cad129625 Still waiting for translation update 2020-04-20 16:13:16 +02:00
Benoit Marty
9ccf51fbc0 Import string from Riot - new strings 2020-04-20 15:20:08 +02:00
Benoit Marty
990867204e Import string from Riot 2020-04-20 15:17:14 +02:00
Onuray Sahin
5795b7e063 Merge pull request #1242 from vector-im/feature/save_media_to_gallery
Save media files to Gallery
2020-04-20 13:24:33 +03:00
Valere
b612a7e63c Merge pull request #1252 from vector-im/feature/fix_device_detection_debounce
Avoid unnecessary /device call
2020-04-20 12:07:17 +02:00
Valere
50c73b68aa cleaning 2020-04-20 11:31:42 +02:00
Valere
c7ac5e2293 Increase default timeout to match old riot 2020-04-20 11:26:03 +02:00
Valere
43cb1fe68b Code review 2020-04-20 11:12:35 +02:00
onurays
f807de9a83 Lint fix. 2020-04-20 11:53:33 +03:00
Onuray Sahin
754f220596 Merge branch 'develop' into feature/save_media_to_gallery 2020-04-20 11:21:14 +03:00
onurays
3f7ca8669a Remove duplicated save icon. 2020-04-20 00:48:52 +03:00
Valere
28c6921a0a Avoid unnecessary /device call 2020-04-17 18:08:29 +02:00
onurays
26bb8ce2be Give user a toast after adding media file to the gallery. 2020-04-17 16:53:40 +03:00
onurays
8434f9326e Save action added to bottom sheet. 2020-04-17 15:27:42 +03:00
Waylon Cude
68f93c6c31 Merge branch 'develop' of https://github.com/vector-im/riotX-android into develop
Signed-off-by: Waylon Cude <waylon.cude@finzdani.net>
2020-04-16 12:19:17 -07:00
Benoit Marty
7961423556 Merge pull request #1244 from vector-im/feature/media_path
Fix download and upload media path
2020-04-16 17:58:08 +02:00
Benoit Marty
ac07fb47d7 Better Kotlin code 2020-04-16 17:42:55 +02:00
Valere
fbcbd6def5 Merge pull request #1243 from vector-im/feature/update_x_signing_copy
Feature/update x signing copy
2020-04-16 17:30:11 +02:00
Valere
3fe15f2d45 code review 2020-04-16 17:02:01 +02:00
Valere
968377a5be Update vector/src/main/res/values/strings_riotX.xml
Co-Authored-By: Benoit Marty <benoitm@matrix.org>
2020-04-16 16:53:24 +02:00
Benoit Marty
5652140f5d Improve ContentUrlResolver 2020-04-16 16:41:45 +02:00
Benoit Marty
e97c95f40a Fix issue with media path (Fixes #1227) 2020-04-16 16:41:45 +02:00
Valere
7ac5f58f32 Show CrossSigning info even if not developer mode 2020-04-16 16:30:45 +02:00
Valere
ce2f4e163d Update password/recovery copy + update icons 2020-04-16 16:30:45 +02:00
Benoit Marty
cc94b6cf7d Merge pull request #1116 from vector-im/feature/worker_manager
Add some documentation on Workers
2020-04-16 16:28:20 +02:00
onurays
ab3cc90ed5 Share menu item added to video media viewer. 2020-04-16 16:46:00 +03:00
Valere
614127e46b Merge pull request #1240 from vector-im/feature/update_security_notice
Feature/update security notice
2020-04-16 15:33:54 +02:00
Valere
66fc38ad4b Remove GlobalScope usage 2020-04-16 15:09:16 +02:00
Valere
f68e84d9da Update toaster to match web 2020-04-16 15:09:16 +02:00
onurays
2b2e6dd6f8 Share menu item added to image media viewer. 2020-04-16 16:04:37 +03:00
Valere
621e78a864 Merge pull request #1235 from vector-im/feature/upgrate_cross_signing
Add migration state to bootstrap
2020-04-16 15:04:06 +02:00
Benoit Marty
828e972c74 Split long lines 2020-04-16 14:35:55 +02:00
Benoit Marty
15bd7d1c5b Change the regex to detect long lines to allow schema with UTF-8 chars 2020-04-16 14:35:23 +02:00
Benoit Marty
79e81dbdde ktlint 2020-04-16 14:01:15 +02:00
Benoit Marty
f93f50b582 Code readability 2020-04-16 14:00:24 +02:00
Benoit Marty
d934f92ebd Fix bad apostrophe 2020-04-16 13:58:54 +02:00
Waylon Cude
d20cf484ff Merge branch 'develop' of https://github.com/vector-im/riotX-android into develop
Signed-off-by: Waylon Cude <waylon.cude@finzdani.net>
2020-04-16 03:10:06 -07:00
Waylon Cude
ec4458e84a Updated CHANGES.md
Signed-off-by: Waylon Cude <waylon.cude@finzdani.net>
2020-04-16 02:50:58 -07:00
waylon531
6c1719e365 Use a bigger thread pool
This patch makes RiotX use an unbounded thread pool to handle
connections. The default thread pool for the android WorkManager has a
fairly anemic number of threads and I suspect this was causing
performance issues especially because of all the long-running jobs that
happen whenever you sync.

I tested this out on my phone and all of the sluggishness appears to
have gone away. I tested both the debug and release builds to make sure
it wasn't just some release optimization. RiotX is so much snappier now!

This fixes #1221

Signed-off-by: Waylon Cude <waylon.cude@finzdani.net>
2020-04-16 02:44:31 -07:00
Benoit Marty
467f48f1a6 Merge remote-tracking branch 'origin/develop' into develop 2020-04-16 11:33:22 +02:00
Valere
a44cb876c9 move strings to resources 2020-04-16 11:30:10 +02:00
Valere
e79c824913 Fix / password visibility initial state bug 2020-04-16 11:30:10 +02:00
Valere
b8e9cc70f2 fix / line too long 2020-04-16 11:30:10 +02:00
Valere
a2f32307f0 Support back from migrate recovery key 2020-04-16 11:30:10 +02:00
Valere
c1d39cefd5 Fix / avoid upgrade secu popup on account creation 2020-04-16 11:30:10 +02:00
Valere
0edc562120 Fix / test compilation 2020-04-16 11:30:10 +02:00
Valere
8ae2f06044 Add change log 2020-04-16 11:30:10 +02:00
Valere
aa496e6efb Add migration state to bootstrap 2020-04-16 11:30:10 +02:00
Benoit Marty
1372192031 Merge pull request #1091 from vector-im/feature/secure
Add a setting to prevent screenshot of the application, disabled by default (#1027)
2020-04-16 11:27:59 +02:00
Benoit Marty
ea03f76847 Merge pull request #1129 from vector-im/feature/update_password
Change password function implemented.
2020-04-16 11:26:33 +02:00
Benoit Marty
d74a5f9979 Typo 2020-04-16 11:23:41 +02:00
Benoit Marty
febadcc4f6 Merge pull request #1238 from vector-im/feature/thumbnail_info
`mimetype` field is optional
2020-04-16 10:57:29 +02:00
Benoit Marty
17ece54cb0 mimetype field is optional 2020-04-16 10:28:23 +02:00
Benoit Marty
da04a74350 Merge pull request #1143 from MatMaul/refresh-summary
Refresh the whole notifs when cleaning one so the summary get updated
2020-04-15 17:29:36 +02:00
Benoit Marty
634c8947bd Merge branch 'develop' into refresh-summary 2020-04-15 17:29:08 +02:00
Benoit Marty
f6f6fa99fb Merge pull request #1223 from vector-im/feature/cross_signing_as_task
Make initialize cross signing as a task
2020-04-15 17:21:25 +02:00
Benoit Marty
dcfbfc4981 Merge pull request #1230 from vector-im/feature/fix_download_file
Catch network errors during file downloading.
2020-04-15 16:54:31 +02:00
Benoit Marty
6201a9b8ef Merge branch 'develop' into feature/fix_download_file 2020-04-15 16:52:40 +02:00
Benoit Marty
1981d2e9ac Merge pull request #1233 from vector-im/feature/fix_local_echo_no_update
Fix / sending event not always updating
2020-04-15 16:51:16 +02:00
Benoit Marty
66f6b1ecac Merge pull request #1224 from vector-im/feature/composer_shield
Add shield in composer
2020-04-15 16:49:50 +02:00
Benoit Marty
affe2b59da Fix crash 2020-04-15 16:40:01 +02:00
Benoit Marty
757f8ec96a Change password: prevent cancellation when processing 2020-04-15 16:39:05 +02:00
Benoit Marty
bf5e2b96df Change password: bigger margins 2020-04-15 16:39:05 +02:00
Benoit Marty
5666965321 Change password: do not trim passwords 2020-04-15 16:39:05 +02:00
Benoit Marty
7bf1f916c4 Change password: lambda 2020-04-15 16:39:05 +02:00
Benoit Marty
d44e43d94b Change password: update wording of button (Nad's request) 2020-04-15 16:39:05 +02:00
Benoit Marty
9fe32fe915 Change password: hide the preference (as Riot-Web does) if it is not possible to change the password. 2020-04-15 16:39:05 +02:00
Benoit Marty
e21cb3082b Change password: update preference summary. Some people think we know the password. Using Riot-web wording 2020-04-15 16:39:05 +02:00
Benoit Marty
c50bc10f92 Code review: improve layout 2020-04-15 16:38:44 +02:00
Benoit Marty
1b416028b4 Code review: inline the error instead of using toast 2020-04-15 16:38:44 +02:00
Benoit Marty
85493b7532 Code review: use isInvalidPassword() extension everywhere. Also be robust if the Fragment is destroyed 2020-04-15 16:38:44 +02:00
onurays
dbabe0232f Do not override the default timeout. 2020-04-15 16:38:44 +02:00
onurays
dfc8e8ec4c AccountService is created. 2020-04-15 16:38:44 +02:00
onurays
f00db49bda Change password function implemented.
Fixes #528
2020-04-15 16:38:44 +02:00
onurays
b4a3eb2cb3 Fix return the exception instead of empty one. 2020-04-15 16:39:41 +03:00
Valere
81012746c4 Code review / added new key for message hint 2020-04-15 15:39:23 +02:00
Valere
1deacfbb34 Code review 2020-04-15 15:37:43 +02:00
Valere
c35d854776 Add shield in composer 2020-04-15 15:37:43 +02:00
Benoit Marty
c0fa259b40 Add a setting to prevent screenshot of the application, disabled by default (#1027) 2020-04-15 15:04:19 +02:00
Benoit Marty
391d3cb6b5 Merge pull request #1186 from duncanturk/improvement-957-catchup-indicator-on-invite
Fix #957 catchup indicator on invite
2020-04-15 15:02:16 +02:00
Benoit Marty
6d56220d98 Merge branch 'develop' into improvement-957-catchup-indicator-on-invite 2020-04-15 15:00:34 +02:00
Benoit Marty
c57fa3f0d0 Merge pull request #1176 from vector-im/feature/target-sdk-29
Increase targetSdk to 29
2020-04-15 14:32:16 +02:00
Benoit Marty
f2bca51046 Merge pull request #1232 from vector-im/feature/translations
string sync
2020-04-15 14:24:39 +02:00
Benoit Marty
6751d88ade Remove .idea exclusion 2020-04-15 13:00:03 +02:00
Benoit Marty
e26a0bc9ae Add Android SDK version 2020-04-15 12:57:15 +02:00
Benoit Marty
6dc517584f Fix merge issue 2020-04-15 12:56:33 +02:00
Benoit Marty
24d0cdef1f Add missing changes of SDK version and build tools 2020-04-15 12:53:28 +02:00
Benoit Marty
6639f89a68 Simpler code 2020-04-15 12:53:08 +02:00
onurays
0ef5ae38d0 Merge branch 'feature/cross_signing_as_task' into feature/sqldelight_crypto_store 2020-04-15 11:16:29 +03:00
Benoit Marty
0c0e9521f5 Fix lint issue 2020-04-15 09:50:25 +02:00
Valere
f2b684aa9e Fix / user and self signing failing
bad copy paste
2020-04-14 18:23:20 +02:00
Valere
68ca0e9d4b Fix / sending event not always updating 2020-04-14 17:35:11 +02:00
Benoit Marty
e54077e020 String copied to Riot project 2020-04-14 17:26:23 +02:00
Benoit Marty
9111800e7a Create specific string for RiotX 2020-04-14 17:23:59 +02:00
Benoit Marty
ef6847671a Import strings from Riot 2020-04-14 17:13:43 +02:00
Benoit Marty
b8cb0588fe Add new resources 2020-04-14 17:06:05 +02:00
onurays
667b371653 Continue supporting old APIs. 2020-04-14 16:29:11 +03:00
onurays
190fbb95ec Merge branch 'develop' into feature/target-sdk-29
# Conflicts:
#	vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt
2020-04-14 13:59:54 +03:00
onurays
f97e08b4e5 Remove .gitignore which belongs to another PR. 2020-04-14 13:12:17 +03:00
onurays
7242cbda40 Catch network errors during file downloading.
Fixes #1229
2020-04-13 17:48:19 +03:00
onurays
c53f9359d7 Use json (de)serializer instead of object (de)serialization. 2020-04-13 13:43:39 +03:00
Valere
9e3011d4c8 Make initialize cross signing as a task 2020-04-10 17:04:34 +02:00
Valere
eb50256af7 Merge pull request #1222 from vector-im/feature/verification_unified_emoji
Uniform emoji representation for all platform
2020-04-10 16:05:42 +02:00
Valere
ccacd20428 Merge branch 'develop' into feature/verification_unified_emoji 2020-04-10 16:05:33 +02:00
Valere
ac46fe9e16 Merge pull request #1218 from vector-im/feature/scan_confirm_update
Feature/scan confirm update
2020-04-10 16:05:02 +02:00
Valere
9cfb83f0d2 Remove outdated translation 2020-04-10 14:31:03 +02:00
Valere
943ba3bebd Fix / string bad argument number - lint 2020-04-10 13:14:12 +02:00
Valere
fccfd00949 Fix / design update 2020-04-10 12:12:21 +02:00
Valere
68323057aa Update change log 2020-04-10 12:12:21 +02:00
Valere
5081361c2d Update Scan confirm flow 2020-04-10 12:12:06 +02:00
Valere
3ba619d45c Merge pull request #1215 from vector-im/feature/ssss_and_backup
Fixes #1214
2020-04-10 12:10:42 +02:00
Valere
f5dc0b38ff Code review 2020-04-10 11:47:54 +02:00
Valere
8357abd455 Added prefix to resources to avoid accidental override 2020-04-10 11:18:41 +02:00
Valere
ede899d78e Fix / Bad smiley emojii 2020-04-10 11:09:16 +02:00
Valere
a703574004 Uniform emoji representation for all platform 2020-04-10 11:02:33 +02:00
onurays
09c98c32f0 Check and insert metadata at init. 2020-04-09 18:49:10 +03:00
onurays
fc90844556 Check and insert metadata at init. 2020-04-09 18:48:46 +03:00
onurays
0ca7e6202c Use json (de)serializer instead of object (de)serialization. 2020-04-09 16:15:34 +03:00
Christopher Rossbach
ef2abbfbd4 Merge branch 'develop' into improvement-957-catchup-indicator-on-invite 2020-04-09 11:11:44 +02:00
Valere
7c0137e2dc Fix / await callback suspend forever 2020-04-09 10:46:52 +02:00
onurays
96162218e4 Lint fixes. 2020-04-09 02:40:43 +03:00
onurays
ffaacdf436 Use SqlCryptoStore implementation instead of RealmCryptoStore. 2020-04-09 02:30:52 +03:00
onurays
39fdda3715 Realm entities removed. 2020-04-09 00:52:01 +03:00
Valere
34dec64d9c Fixes #1214 2020-04-08 17:56:12 +02:00
Valere
3968bb3488 Merge pull request #1213 from vector-im/feature/timeline_sum_item
Feature/timeline sum item
2020-04-08 17:08:53 +02:00
onurays
af171b3b7e Complete storing user cross signing key function. 2020-04-08 18:05:07 +03:00
Valere
6f2d7aebba code review 2020-04-08 16:37:52 +02:00
Valere
366a35913b Fix alignement 2020-04-08 16:31:01 +02:00
Valere
aec49fe542 Change log 2020-04-08 16:31:01 +02:00
Valere
f04d8b0e03 cleaning 2020-04-08 16:31:01 +02:00
Valere
08af61b778 rename layout 2020-04-08 16:31:01 +02:00
Valere
277f35a352 Merge room creation events in one summary 2020-04-08 16:31:01 +02:00
Valere
68512e475f WIP 2020-04-08 16:31:01 +02:00
Valere
0eff00ebee Merge pull request #1211 from vector-im/feature/fix_gossiping_to_early
Fix / Send gossip request on other done received
2020-04-08 16:30:17 +02:00
onurays
3dd9693f59 Changelog added. 2020-04-08 15:32:14 +03:00
Onuray Sahin
8a4a288074 Merge branch 'develop' into feature/target-sdk-29 2020-04-08 14:15:02 +03:00
onurays
5b1f887760 Code review fixes. 2020-04-08 14:10:31 +03:00
onurays
dcb6af6c45 .gitignore file added. 2020-04-08 12:40:29 +03:00
onurays
8b0845a76b Key backup recovery key fields added to metadata. 2020-04-08 11:08:15 +03:00
onurays
8e896600c9 Initial implementation of SQLDelight CryptoStore. 2020-04-08 10:33:06 +03:00
Christopher Rossbach
3ff5952417 Merge branch 'develop' into improvement-957-catchup-indicator-on-invite 2020-04-07 20:53:31 +02:00
Valere
b480eb3688 Update change log 2020-04-07 19:20:49 +02:00
Valere
12abca1b80 Fix / Send gossip request on other done received 2020-04-07 19:09:56 +02:00
Valere
1d8ed387bc Merge pull request #1206 from vector-im/feature/gossip_keybackup_key
Feature/gossip keybackup key
2020-04-07 18:06:05 +02:00
Christopher Rossbach
5521c094f7 fix typing difference 2020-04-07 17:50:01 +02:00
Christopher Rossbach
222b72a014 Merge branch 'develop' into improvement-957-catchup-indicator-on-invite 2020-04-07 17:46:23 +02:00
Valere
8904ca27f2 clean 2020-04-07 15:30:19 +02:00
Valere
6c5da97c16 code review 2020-04-07 15:25:31 +02:00
Valere
d4d73db5ae keybackup gossip test 2020-04-07 15:15:45 +02:00
Valere
1a436f962f update change log
cleaning
2020-04-07 15:15:45 +02:00
Valere
dc61ee61f5 Fix / wrong export format 2020-04-07 15:15:45 +02:00
Valere
5b4b5e7a57 Gossip keybackup key after verification! 2020-04-07 15:15:45 +02:00
Valere
0164f94047 Merge pull request #1198 from vector-im/feature/crosssigning_bootstrap
Feature/crosssigning bootstrap
2020-04-07 15:14:54 +02:00
Valere
153587bd82 Merge branch 'develop' into feature/crosssigning_bootstrap 2020-04-07 15:14:43 +02:00
Valere
326f2e99fb klint 2020-04-06 10:00:59 +02:00
Valere
1dfd6f232a Code quality / line too long 2020-04-03 18:59:21 +02:00
Valere
42d61944b5 Merge pull request #1204 from vector-im/feature/increase_file_log_size
Increase file logger size
2020-04-03 16:40:18 +02:00
Valere
50a8ffeca1 Merge pull request #1203 from vector-im/feature/fix_e2e_null_alg
Feature/fix e2e null alg
2020-04-03 15:57:56 +02:00
Valere
f605bb8270 Code review 2020-04-03 15:57:05 +02:00
Valere
7ffb6113a4 Increase file logger size 2020-04-03 13:53:34 +02:00
Valere
156e6114c1 Updade change log 2020-04-03 13:43:40 +02:00
Valere
c91bc82cd9 Fix / Ensure encryption set even if faield to fetch members 2020-04-03 13:35:09 +02:00
Valere
8b481e2294 Remove dead code 2020-04-03 13:34:38 +02:00
Valere
92bf3f1349 Update change log
+ code quality
2020-04-03 11:14:14 +02:00
Valere
6474735662 Fix / devtools was not showing all json numbers 2020-04-03 10:31:44 +02:00
Valere
45c5626267 Add generate key option 2020-04-02 18:30:43 +02:00
Valere
c27264761d Back /Skip navigation 2020-04-02 18:11:52 +02:00
Valere
c6abfa14ea Fix / Bind continue button 2020-04-02 16:54:30 +02:00
Valere
2f237cf17b klint 2020-04-02 16:51:40 +02:00
Valere
bf5ba99653 Full bootstrap flow initial commit 2020-04-02 16:51:03 +02:00
Valere
8ecdac7c31 Fixes #1191 2020-03-31 17:08:11 +02:00
Valere
a40dd31543 Bootstrap bottomsheet 2020-03-27 17:09:18 +01:00
Christopher Rossbach
dff89cb2e1 document changes
Signed-off-by: Christopher Rossbach <31703168+duncanturk@users.noreply.github.com>
2020-03-27 14:41:48 +01:00
Christopher Rossbach
cdbb657961 Fix #957 by incrementing the catchup count on home icon.
Signed-off-by: Christopher Rossbach <31703168+duncanturk@users.noreply.github.com>
2020-03-27 14:24:42 +01:00
Onuray Sahin
443d45db6a Merge branch 'develop' into feature/target-sdk-29 2020-03-27 12:21:39 +03:00
Valere
a995615f87 Fix / protect against crash during migration 2020-03-26 18:20:12 +01:00
Valere
024c62515c Merge pull request #1181 from vector-im/feature/update_shield_logic
Update shield logic for DM
2020-03-26 16:58:10 +01:00
Valere
75e66a6550 Merge branch 'develop' into feature/update_shield_logic 2020-03-26 16:58:01 +01:00
Onuray Sahin
e2f7890bb8 Merge branch 'develop' into feature/target-sdk-29 2020-03-26 17:39:22 +03:00
onurays
0a77d5014e Fix nullability issues. 2020-03-26 17:19:29 +03:00
Valere
91464a071e Merge pull request #1180 from vector-im/feature/complete_security_design_update
Feature/complete security design update
2020-03-26 14:31:09 +01:00
Valere
5244612ef6 Update shield logic for DM 2020-03-26 14:26:34 +01:00
Valere
e51439ade0 updage change log 2020-03-26 14:10:19 +01:00
Valere
acd90657c7 Fix / prevent cancel when in conclusion fragment 2020-03-26 14:09:41 +01:00
onurays
e5482d48c0 Remove deprecated & unused ImageTools class. 2020-03-26 16:02:50 +03:00
onurays
5816a04a37 Use MediaStore instead of deprecated addCompletedDownload since Android Q. 2020-03-26 15:58:31 +03:00
onurays
4b7da9ae6b Replace deprecated getExternalStoragePublicDirectory with getExternalFilesDir. 2020-03-26 15:57:21 +03:00
onurays
f7cbc01023 Replace deprecated PreferenceManager with androidx version. 2020-03-26 15:56:33 +03:00
Valere
1de57bbf3b Fix / cancel copy when verifying other 2020-03-26 13:49:48 +01:00
Valere
42a8c561db Fix / Verification was not cancelled on back in ready state 2020-03-26 13:49:48 +01:00
Valere
5bef9aef6a Fix / add specific copy for other verif cancel confirm 2020-03-26 13:49:48 +01:00
Valere
f8c1ec985f re-prompt to verify on restart 2020-03-26 13:49:48 +01:00
onurays
12429d8091 Merge develop into the branch. 2020-03-26 14:39:50 +03:00
Onuray Sahin
3bb5e127d6 Merge pull request #1155 from vector-im/feature/multipicker
Multiple attachment picker implementation
2020-03-26 14:30:02 +03:00
Valere
1d46b523b9 Merge pull request #1172 from vector-im/feature/ensure_olm_account_unicity
Feature/ensure olm account unicity
2020-03-26 12:11:09 +01:00
Valere
6721f337bd Merge branch 'develop' into feature/ensure_olm_account_unicity 2020-03-26 12:11:00 +01:00
Valere
535cdf0ef5 Merge pull request #1162 from vector-im/feature/xs_detect_new_session
Feature/xs detect new session
2020-03-26 12:06:18 +01:00
Valere
19990b27bb Code review 2020-03-26 10:52:02 +01:00
Valere
4b3c5d5135 update change log 2020-03-26 10:35:43 +01:00
Valere
b6fe80faf4 Fix / device key could be rotated
MXOlmDevice constructor access IMXStore before is open (dagger)
2020-03-26 10:35:43 +01:00
Valere
638970fa77 Merge pull request #1175 from vector-im/feature/fix_ssss_symetric_get_secret
Fixes #1174
2020-03-26 10:34:49 +01:00
onurays
c63f3edb06 Initial fixes to support targetSdk 29. 2020-03-26 12:06:50 +03:00
Valere
9a6fe1af4e Fixes #1174 2020-03-26 09:49:34 +01:00
onurays
a01482dca4 Use Timber log instead of printStackTrace. 2020-03-25 18:51:55 +03:00
onurays
5db1010e47 Catch exceptions if the file cannot be decoded. 2020-03-25 18:39:35 +03:00
onurays
6130a0a654 Remove unused toString(). 2020-03-25 18:34:04 +03:00
onurays
3c1e1090e7 Avoid UNCHECKED_CAST. 2020-03-25 18:20:34 +03:00
onurays
5cb47dae35 Return a failure message if the file cannot be opened. 2020-03-25 18:03:20 +03:00
onurays
f68e98b2c7 Do not resize video thumbnail. 2020-03-25 17:35:18 +03:00
Valere
420a55da76 klint 2020-03-25 13:53:06 +01:00
onurays
f9aed28732 grantUriPermission to handle incoming sharing. 2020-03-25 15:51:15 +03:00
Valere
e30c17eab7 Update Changes log 2020-03-24 18:09:54 +01:00
Valere
2c9a8865bf Cancel all on going to settings 2020-03-24 17:48:16 +01:00
Valere
bddd70afdb Fix / IllegalState after token invalidation 2020-03-24 17:48:01 +01:00
Valere
c4388348f7 Show cancelled screen instead of dismissing 2020-03-24 17:38:46 +01:00
Valere
ee7828a445 Code quality 2020-03-24 16:21:22 +01:00
Valere
37ac45c90a Fix / handling of back 2020-03-24 15:51:09 +01:00
Valere
63d3bf93f2 Fix / for now keep old toaster 2020-03-24 15:50:49 +01:00
Valere
2de8865730 Fix / Key Request recipients 2020-03-24 15:49:41 +01:00
Valere
fcffe1f3c3 Clear alert when device is verified 2020-03-24 15:21:03 +01:00
Valere
cfcec04029 code cleaning 2020-03-24 14:57:51 +01:00
Valere
b56a41bec7 Custom alert design 2020-03-24 14:56:57 +01:00
onurays
6bf89aeac9 Remove JPEG_ prefix from file name. 2020-03-24 12:37:37 +03:00
onurays
e583c03751 Add documentation. 2020-03-24 12:32:37 +03:00
onurays
d20b1cb64a Add documentation. 2020-03-24 12:15:14 +03:00
Valere
22642e71a3 cleaning 2020-03-24 10:06:36 +01:00
Valere
6e85b20b0e Update copy for riotX default device Name 2020-03-24 10:06:30 +01:00
Valere
fcd290410e Also cancel pending request on back 2020-03-24 10:06:15 +01:00
onurays
727d86236b ImageUtils created with helper functions. 2020-03-24 11:31:27 +03:00
onurays
2651f82337 Refactor duplicated code. 2020-03-24 11:03:41 +03:00
Valere
3b62402cfe Fix / ensure keys trust is updated before checking devices 2020-03-23 19:15:29 +01:00
Valere
6cc8d1b205 Fix / concurrent start broke QR verification 2020-03-23 18:43:52 +01:00
Valere
49e5fafb2d New sign in detection flow 2020-03-23 16:27:32 +01:00
Valere
e36367c040 Fix / sending secret encryption + refactoring 2020-03-23 16:27:17 +01:00
onurays
f7fd23b153 App integration to the new multipicker library. 2020-03-23 16:31:32 +03:00
Valere
4f70c40b1a Refactor + share secret window implementation 2020-03-23 11:13:33 +01:00
onurays
5b875e0571 CameraPicker & incoming share implementation. 2020-03-22 18:27:59 +03:00
onurays
6db0de321c Initial implementation of multipicker. 2020-03-20 12:12:59 +03:00
Valere
0c1f30208d Fix / Epoxy divider not showing 2020-03-19 11:23:15 +01:00
Valere
08cfe79625 Merge pull request #1153 from vector-im/feature/fix_841
Fixes #841
2020-03-18 14:50:56 +01:00
Valere
22a599633d Merge pull request #1152 from vector-im/feature/concurrent_start_verification
Verif / handle concurrent start
2020-03-18 14:50:33 +01:00
Valere
14acbb2b4d Update changes 2020-03-18 12:06:37 +01:00
Valere
6f5bebedf8 Fixes #841 2020-03-18 11:53:38 +01:00
Valere
6fe77eba72 code review 2020-03-18 11:25:49 +01:00
Valere
286a5081ff Verif / handle concurrent start
Fixes #794
2020-03-18 10:07:57 +01:00
Valere
56c241c9bd Merge pull request #1140 from vector-im/feature/gossiping_work
Feature/gossiping work
2020-03-17 15:48:11 +01:00
Valere
68151d838f Update changes 2020-03-17 15:10:27 +01:00
Valere
572b174cfe code quality 2020-03-17 14:42:55 +01:00
Valere
b71d8185a2 Fix / gossiping sent to soon results in not getting keys
Overall improovment of logs
2020-03-17 14:40:05 +01:00
Valere
8051d9e3be cleaning 2020-03-17 14:40:05 +01:00
Valere
1bf8fef292 Fix realm migration 2020-03-17 14:40:05 +01:00
Valere
b8a9397e73 hide reRequest behind developer mode 2020-03-17 14:40:05 +01:00
Valere
009d691d5b post merge fix 2020-03-17 14:40:05 +01:00
Valere
6933159245 Remove refresh menu 2020-03-17 14:40:05 +01:00
Valere
75549c41e0 View source in audit + clean 2020-03-17 14:40:05 +01:00
Valere
5e2f888eaf Request secret from mobile to web 2020-03-17 14:40:05 +01:00
Valere
d3d6d44665 Post rebase fix 2020-03-17 14:40:05 +01:00
Valere
fc6225a7ac Gossiping refactoring 2020-03-17 14:40:05 +01:00
Valere
3639007985 Cancel transaction if failed to decrypt other part events 2020-03-17 14:40:05 +01:00
Valere
d5137897c1 Fix / crash No JsonAdapter for GossipingToDeviceObject 2020-03-17 14:40:05 +01:00
Valere
b67735c31a Incoming Secret Share request support
crypto DB migration
2020-03-17 14:40:05 +01:00
Valere
8ff31ac49d cleaning klint 2020-03-17 14:40:05 +01:00
Valere
5e0235e48d Add option to recover from backup + hide if not applicable 2020-03-17 14:40:05 +01:00
Valere
9e63a3219c Add Re-Request keys for fail to decrypt 2020-03-17 14:40:05 +01:00
Valere
757e90986e Key Req Dev tool initial commit 2020-03-17 14:40:05 +01:00
Valere
06fc5c2dd9 Log tunning 2020-03-17 14:40:05 +01:00
Valere
20dbe2dd0d version ++ 2020-03-17 14:18:08 +01:00
Valere
6f56c74e9d Merge branch 'release/0.18.1' into develop 2020-03-17 11:46:55 +01:00
Valere
78d90c8f04 prepare release 0.18.1 2020-03-17 11:46:42 +01:00
Valere
75fa2904d3 Merge pull request #1149 from vector-im/feature/quick_fix_backup_update_banner
QuickFix / key banner not clickable
2020-03-17 11:12:43 +01:00
Valere
a9ed55e6a2 Fixes #757 2020-03-17 10:50:34 +01:00
Valere
b37600f536 QuickFix / key banner not clickable 2020-03-17 10:43:48 +01:00
Valere
ef2783e9f4 Merge pull request #1142 from vector-im/feature/fix_upload_error
Fix / correctly update local echo failed state
2020-03-17 09:48:43 +01:00
Valere
8827b4b5ef Fix / correctly update local echo failed state
+ endure Workmanager queues are reset after a clear cache
2020-03-17 09:47:27 +01:00
Valere
7664b716b3 Merge pull request #1146 from vector-im/feature/workaround_crash_select_gif_keyboard
WorkAround / crash android 10 gif from keyboard
2020-03-17 09:20:49 +01:00
Valere
067a22883c WorkAround / crash android 10 gif from keyboard
fixes #1136
2020-03-16 16:01:55 +01:00
Valere
eb74523905 Fix / typo 2020-03-16 15:30:41 +01:00
Valere
18d82b1bea Merge pull request #1138 from vector-im/feature/join_command
Implementation of /join command (#12)
2020-03-16 10:15:25 +01:00
Mathieu Velten
687884431e Refresh the whole notifs when cleaning one so the summary get updated
Fix summary notification staying after "mark as read"
2020-03-13 23:42:28 +01:00
Valere
3c9c2e61d7 missing change log entry 2020-03-13 17:19:00 +01:00
onurays
e3246a1f2c Rename JoinedToAnotherRoom to JoinRoomCommandSucces. 2020-03-13 18:41:04 +03:00
Valere
642651a069 Merge pull request #1133 from vector-im/feature/quick_fix_redecrypt_remote_echo
Fix Message transitions in encrypted rooms
2020-03-13 12:18:59 +01:00
onurays
85a987ca8d Fix typo. 2020-03-13 14:02:42 +03:00
onurays
5cb7e455b1 Implementation of /join command. 2020-03-13 14:00:46 +03:00
Valere
7c1428e097 preserver relations as non encrypted 2020-03-12 10:05:55 +01:00
Valere
32fd4c1be9 save decryption result at encryption 2020-03-11 19:39:40 +01:00
Valere
f53fc205e1 Fix Message transitions in encrypted rooms
Fixes #518
2020-03-11 18:07:18 +01:00
Valere
54eca7525e Version ++ 2020-03-11 16:24:49 +01:00
Valere
005a11b765 Merge branch 'release/0.18.0' into develop 2020-03-11 13:41:48 +01:00
Benoit Marty
a8e19f3cc9 Understanding things 2020-03-06 15:57:49 +01:00
Benoit Marty
cb4752812a Hide private key 2020-03-06 15:53:55 +01:00
Benoit Marty
ccd9d2961d Cleanup 2020-03-06 15:45:39 +01:00
Benoit Marty
d1db17f244 Add doc on workers 2020-03-06 15:45:26 +01:00
Benoit Marty
aa4327c4da Add doc and log error from the workers 2020-03-06 15:44:55 +01:00
703 changed files with 29711 additions and 8009 deletions

1
.gitignore vendored
View File

@@ -14,3 +14,4 @@
/tmp
ktlint
.idea

View File

@@ -0,0 +1 @@
{"databases":[{"packageName":"im.vector.matrix.sqldelight.auth","compilationUnits":[{"name":"main","sourceFolders":[{"path":"src/main/sqldelight/auth","dependency":false}]}],"outputDirectory":"build/generated/sqldelight/code/AuthDatabase","className":"AuthDatabase","dependencies":[]},{"packageName":"im.vector.matrix.sqldelight.crypto","compilationUnits":[{"name":"main","sourceFolders":[{"path":"src/main/sqldelight/crypto","dependency":false}]}],"outputDirectory":"build/generated/sqldelight/code/CryptoDatabase","className":"CryptoDatabase","dependencies":[]},{"packageName":"im.vector.matrix.sqldelight.session","compilationUnits":[{"name":"main","sourceFolders":[{"path":"src/main/sqldelight/session","dependency":false}]}],"outputDirectory":"build/generated/sqldelight/code/SessionDatabase","className":"SessionDatabase","dependencies":[]}]}

View File

@@ -23,10 +23,10 @@ android:
- platform-tools
# The BuildTools version used by your project
- build-tools-28.0.3
- build-tools-29.0.3
# The SDK version used to compile your project
- android-28
- android-29
before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock

View File

@@ -1,3 +1,56 @@
Changes in RiotX 0.19.0 (2020-XX-XX)
===================================================
Features ✨:
- Cross-Signing | Support SSSS secret sharing (#944)
- Cross-Signing | Verify new session from existing session (#1134)
- Cross-Signing | Bootstraping cross signing with 4S from mobile (#985)
Improvements 🙌:
- Verification DM / Handle concurrent .start after .ready (#794)
- Cross-Signing | Update Shield Logic for DM (#963)
- Cross-Signing | Complete security new session design update (#1135)
- Cross-Signing | Setup key backup as part of SSSS bootstrapping (#1201)
- Cross-Signing | Gossip key backup recovery key (#1200)
- Show room encryption status as a bubble tile (#1078)
- Cross-Signing | Restore history after recover from passphrase (#1214)
- Cross-Sign | QR code scan confirmation screens design update (#1187)
- Emoji Verification | It's not the same butterfly! (#1220)
Bugfix 🐛:
- Missing avatar/displayname after verification request message (#841)
- Crypto | RiotX sometimes rotate the current device keys (#1170)
- RiotX can't restore cross signing keys saved by web in SSSS (#1174)
- Cross- Signing | After signin in new session, verification paper trail in DM is off (#1191)
- Failed to encrypt message in room (message stays in red), [thanks to pwr22] (#925)
- Cross-Signing | web <-> riotX After QR code scan, gossiping fails (#1210)
Translations 🗣:
-
SDK API changes ⚠️:
- Implementation of SqlCryptoStore on top of SQLDelight
Build 🧱:
-
Other changes:
- Increase File Logger capacities ( + use dev log preferences)
Changes in RiotX 0.18.1 (2020-03-17)
===================================================
Improvements 🙌:
- Implementation of /join command
Bugfix 🐛:
- Message transitions in encrypted rooms are jarring #518
- Images that failed to send are waiting to be sent forever #1145
- Fix / Crashed when trying to send a gif from the Gboard #1136
- Fix / Cannot click on key backup banner when new keys are available
Changes in RiotX 0.18.0 (2020-03-11)
===================================================

View File

@@ -16,7 +16,7 @@ buildscript {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1'
classpath 'com.google.android.gms:oss-licenses-plugin:0.9.5'
classpath 'com.squareup.sqldelight:gradle-plugin:1.2.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
@@ -53,7 +53,7 @@ allprojects {
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
// Warnings are potential errors, so stop ignoring them
kotlinOptions.allWarningsAsErrors = true
kotlinOptions.allWarningsAsErrors = false
}
}

View File

@@ -3,11 +3,11 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 28
compileSdkVersion 29
defaultConfig {
minSdkVersion 16
targetSdkVersion 28
targetSdkVersion 29
versionCode 1
versionName "1.0"
@@ -38,6 +38,8 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.3.3'
// Paging
implementation "androidx.paging:paging-runtime-ktx:2.1.0"

View File

@@ -27,44 +27,34 @@ import im.vector.matrix.android.api.session.room.notification.RoomNotificationSt
import im.vector.matrix.android.api.session.room.send.UserDraft
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.api.util.toOptional
import io.reactivex.Observable
import io.reactivex.Single
import kotlinx.coroutines.rx2.asObservable
class RxRoom(private val room: Room) {
fun liveRoomSummary(): Observable<Optional<RoomSummary>> {
return room.getRoomSummaryLive()
.asObservable()
.startWithCallable { room.roomSummary().toOptional() }
return room.getRoomSummaryLive().asObservable()
}
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMemberSummary>> {
return room.getRoomMembersLive(queryParams).asObservable()
.startWithCallable {
room.getRoomMembers(queryParams)
}
}
fun liveAnnotationSummary(eventId: String): Observable<Optional<EventAnnotationsSummary>> {
fun liveRoomMember(userId: String): Observable<Optional<RoomMemberSummary>> {
return room.getRoomMemberLive(userId).asObservable()
}
fun liveAnnotationSummary(eventId: String): Observable<EventAnnotationsSummary> {
return room.getEventAnnotationsSummaryLive(eventId).asObservable()
.startWithCallable {
room.getEventAnnotationsSummary(eventId).toOptional()
}
}
fun liveTimelineEvent(eventId: String): Observable<Optional<TimelineEvent>> {
return room.getTimeLineEventLive(eventId).asObservable()
.startWithCallable {
room.getTimeLineEvent(eventId).toOptional()
}
}
fun liveStateEvent(eventType: String, stateKey: String): Observable<Optional<Event>> {
return room.getStateEventLive(eventType, stateKey).asObservable()
.startWithCallable {
room.getStateEvent(eventType, stateKey).toOptional()
}
}
fun liveReadMarker(): Observable<Optional<String>> {
@@ -93,7 +83,7 @@ class RxRoom(private val room: Room) {
}
fun liveNotificationState(): Observable<RoomNotificationState> {
return room.getLiveRoomNotificationState().asObservable()
return room.getRoomNotificationStateLive().asObservable()
}
}

View File

@@ -23,6 +23,7 @@ import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams
import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.session.pushers.Pusher
import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
import im.vector.matrix.android.api.session.room.model.Breadcrumb
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.sync.SyncState
@@ -34,28 +35,20 @@ import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
import io.reactivex.Observable
import io.reactivex.Single
import kotlinx.coroutines.rx2.asObservable
class RxSession(private val session: Session) {
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
return session.getRoomSummariesLive(queryParams).asObservable()
.startWithCallable {
session.getRoomSummaries(queryParams)
}
}
fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Observable<List<GroupSummary>> {
return session.getGroupSummariesLive(queryParams).asObservable()
.startWithCallable {
session.getGroupSummaries(queryParams)
}
}
fun liveBreadcrumbs(): Observable<List<RoomSummary>> {
fun liveBreadcrumbs(): Observable<List<Breadcrumb>> {
return session.getBreadcrumbsLive().asObservable()
.startWithCallable {
session.getBreadcrumbs()
}
}
fun liveSyncState(): Observable<SyncState> {
@@ -68,9 +61,6 @@ class RxSession(private val session: Session) {
fun liveUser(userId: String): Observable<Optional<User>> {
return session.getUserLive(userId).asObservable()
.startWithCallable {
session.getUser(userId).toOptional()
}
}
fun liveUsers(): Observable<List<User>> {
@@ -125,9 +115,6 @@ class RxSession(private val session: Session) {
fun liveAccountData(types: Set<String>): Observable<List<UserAccountDataEvent>> {
return session.getLiveAccountDataEvents(types).asObservable()
.startWithCallable {
session.getAccountDataEvents(types)
}
}
}

View File

@@ -19,12 +19,12 @@ androidExtensions {
}
android {
compileSdkVersion 28
compileSdkVersion 29
testOptions.unitTests.includeAndroidResources = true
defaultConfig {
minSdkVersion 16
targetSdkVersion 28
targetSdkVersion 29
versionCode 1
versionName "0.0.1"
// Multidex is useful for tests
@@ -71,6 +71,10 @@ android {
kotlinOptions {
jvmTarget = "1.8"
}
packagingOptions {
pickFirst 'META-INF/kotlinx-coroutines-core.kotlin_module'
}
}
static def gitRevision() {
@@ -97,6 +101,7 @@ dependencies {
def coroutines_version = "1.3.2"
def markwon_version = '3.1.0'
def daggerVersion = '2.25.4'
def work_version = '2.3.3'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
@@ -118,15 +123,20 @@ dependencies {
implementation "ru.noties.markwon:core:$markwon_version"
// Image
implementation 'androidx.exifinterface:exifinterface:1.1.0'
implementation 'androidx.exifinterface:exifinterface:1.3.0-alpha01'
implementation 'id.zelory:compressor:3.0.0'
// Database
implementation 'com.github.Zhuinden:realm-monarchy:0.5.1'
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
implementation "com.squareup.sqldelight:android-driver:1.2.2"
implementation 'net.zetetic:android-database-sqlcipher:4.3.0'
implementation project(':matrix-sdk-sqldelight')
implementation "com.squareup.sqldelight:coroutines-extensions:1.2.1"
implementation "com.squareup.sqldelight:android-paging-extensions:1.2.1"
// Work
implementation "androidx.work:work-runtime-ktx:2.3.3"
implementation "androidx.work:work-runtime-ktx:$work_version"
// FP
implementation "io.arrow-kt:arrow-core:$arrow_version"

View File

@@ -18,8 +18,5 @@ package im.vector.matrix.android
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.asCoroutineDispatcher
import java.util.concurrent.Executors
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main,
Executors.newSingleThreadExecutor().asCoroutineDispatcher())
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main, Main, Main, Main)

View File

@@ -38,6 +38,7 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
import im.vector.matrix.android.api.session.sync.SyncState
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
@@ -265,8 +266,26 @@ class CommonTestHelper(context: Context) {
* @param latch
* @throws InterruptedException
*/
fun await(latch: CountDownLatch) {
assertTrue(latch.await(TestConstants.timeOutMillis, TimeUnit.MILLISECONDS))
fun await(latch: CountDownLatch, timout: Long? = TestConstants.timeOutMillis) {
assertTrue(latch.await(timout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS))
}
fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) {
GlobalScope.launch {
while (true) {
delay(1000)
if (condition()) {
latch.countDown()
return@launch
}
}
}
}
fun waitWithLatch(timout: Long? = TestConstants.timeOutMillis, block: (CountDownLatch) -> Unit) {
val latch = CountDownLatch(1)
block(latch)
await(latch, timout)
}
// Transform a method with a MatrixCallback to a synchronous method

View File

@@ -22,8 +22,8 @@ object TestConstants {
const val TESTS_HOME_SERVER_URL = "http://10.0.2.2:8080"
// Time out to use when waiting for server response. 10s
private const val AWAIT_TIME_OUT_MILLIS = 10_000
// Time out to use when waiting for server response. 20s
private const val AWAIT_TIME_OUT_MILLIS = 20_000
// Time out to use when waiting for server response, when the debugger is connected. 10 minutes
private const val AWAIT_TIME_OUT_WITH_DEBUGGER_MILLIS = 10 * 60_000

View File

@@ -0,0 +1,289 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto.gossiping
import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4
import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerificationTransaction
import im.vector.matrix.android.api.session.crypto.verification.SasVerificationTransaction
import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
import im.vector.matrix.android.api.session.crypto.verification.VerificationService
import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.common.CommonTestHelper
import im.vector.matrix.android.common.SessionTestParams
import im.vector.matrix.android.common.TestConstants
import im.vector.matrix.android.internal.crypto.GossipingRequestState
import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestState
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertNotNull
import junit.framework.TestCase.assertTrue
import junit.framework.TestCase.fail
import org.junit.Assert
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
class KeyShareTests : InstrumentedTest {
private val mTestHelper = CommonTestHelper(context())
@Test
fun test_DoNotSelfShareIfNotTrusted() {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
// Create an encrypted room and add a message
val roomId = mTestHelper.doSync<String> {
aliceSession.createRoom(
CreateRoomParams(RoomDirectoryVisibility.PRIVATE).enableEncryptionWithAlgorithm(true),
it
)
}
val room = aliceSession.getRoom(roomId)
assertNotNull(room)
Thread.sleep(4_000)
assertTrue(room?.isEncrypted() == true)
val sentEventId = mTestHelper.sendTextMessage(room!!, "My Message", 1).first().eventId
// Open a new sessionx
val aliceSession2 = mTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
val roomSecondSessionPOV = aliceSession2.getRoom(roomId)
val receivedEvent = roomSecondSessionPOV?.getTimeLineEvent(sentEventId)
assertNotNull(receivedEvent)
assert(receivedEvent!!.isEncrypted())
try {
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
fail("should fail")
} catch (failure: Throwable) {
}
val outgoingRequestBefore = aliceSession2.cryptoService().getOutgoingRoomKeyRequest()
// Try to request
aliceSession2.cryptoService().requestRoomKeyForEvent(receivedEvent.root)
val waitLatch = CountDownLatch(1)
val eventMegolmSessionId = receivedEvent.root.content.toModel<EncryptedEventContent>()?.sessionId
var outGoingRequestId: String? = null
mTestHelper.retryPeriodicallyWithLatch(waitLatch) {
aliceSession2.cryptoService().getOutgoingRoomKeyRequest()
.filter { req ->
// filter out request that was known before
!outgoingRequestBefore.any { req.requestId == it.requestId }
}
.let {
val outgoing = it.firstOrNull { it.sessionId == eventMegolmSessionId }
outGoingRequestId = outgoing?.requestId
outgoing != null
}
}
mTestHelper.await(waitLatch)
Log.v("TEST", "=======> Outgoing requet Id is $outGoingRequestId")
val outgoingRequestAfter = aliceSession2.cryptoService().getOutgoingRoomKeyRequest()
// We should have a new request
Assert.assertTrue(outgoingRequestAfter.size > outgoingRequestBefore.size)
Assert.assertNotNull(outgoingRequestAfter.first { it.sessionId == eventMegolmSessionId })
// The first session should see an incoming request
// the request should be refused, because the device is not trusted
mTestHelper.waitWithLatch { latch ->
mTestHelper.retryPeriodicallyWithLatch(latch) {
// DEBUG LOGS
aliceSession.cryptoService().getIncomingRoomKeyRequest().let {
Log.v("TEST", "Incoming request Session 1 (looking for $outGoingRequestId)")
Log.v("TEST", "=========================")
it.forEach { keyRequest ->
Log.v("TEST", "[ts${keyRequest.localCreationTimestamp}] requestId ${keyRequest.requestId}, for sessionId ${keyRequest.requestBody?.sessionId} is ${keyRequest.state}")
}
Log.v("TEST", "=========================")
}
val incoming = aliceSession.cryptoService().getIncomingRoomKeyRequest().firstOrNull { it.requestId == outGoingRequestId }
incoming?.state == GossipingRequestState.REJECTED
}
}
try {
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
fail("should fail")
} catch (failure: Throwable) {
}
// Mark the device as trusted
aliceSession.cryptoService().setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId,
aliceSession2.sessionParams.credentials.deviceId ?: "")
// Re request
aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
mTestHelper.waitWithLatch { latch ->
mTestHelper.retryPeriodicallyWithLatch(latch) {
aliceSession.cryptoService().getIncomingRoomKeyRequest().let {
Log.v("TEST", "Incoming request Session 1")
Log.v("TEST", "=========================")
it.forEach {
Log.v("TEST", "requestId ${it.requestId}, for sessionId ${it.requestBody?.sessionId} is ${it.state}")
}
Log.v("TEST", "=========================")
it.any { it.requestBody?.sessionId == eventMegolmSessionId && it.state == GossipingRequestState.ACCEPTED }
}
}
}
Thread.sleep(6_000)
mTestHelper.waitWithLatch { latch ->
mTestHelper.retryPeriodicallyWithLatch(latch) {
aliceSession2.cryptoService().getOutgoingRoomKeyRequest().let {
it.any { it.requestBody?.sessionId == eventMegolmSessionId && it.state == OutgoingGossipingRequestState.CANCELLED }
}
}
}
try {
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
} catch (failure: Throwable) {
fail("should have been able to decrypt")
}
mTestHelper.signOutAndClose(aliceSession)
mTestHelper.signOutAndClose(aliceSession2)
}
@Test
fun test_ShareSSSSSecret() {
val aliceSession1 = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
mTestHelper.doSync<Unit> {
aliceSession1.cryptoService().crossSigningService()
.initializeCrossSigning(UserPasswordAuth(
user = aliceSession1.myUserId,
password = TestConstants.PASSWORD
), it)
}
// Also bootstrap keybackup on first session
val creationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> {
aliceSession1.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it)
}
val version = mTestHelper.doSync<KeysVersion> {
aliceSession1.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it)
}
// Save it for gossiping
aliceSession1.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version)
val aliceSession2 = mTestHelper.logIntoAccount(aliceSession1.myUserId, SessionTestParams(true))
val aliceVerificationService1 = aliceSession1.cryptoService().verificationService()
val aliceVerificationService2 = aliceSession2.cryptoService().verificationService()
// force keys download
mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
aliceSession1.cryptoService().downloadKeys(listOf(aliceSession1.myUserId), true, it)
}
mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
aliceSession2.cryptoService().downloadKeys(listOf(aliceSession2.myUserId), true, it)
}
var session1ShortCode: String? = null
var session2ShortCode: String? = null
aliceVerificationService1.addListener(object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
Log.d("#TEST", "AA: tx incoming?:${tx.isIncoming} state ${tx.state}")
if (tx is SasVerificationTransaction) {
if (tx.state == VerificationTxState.OnStarted) {
(tx as IncomingSasVerificationTransaction).performAccept()
}
if (tx.state == VerificationTxState.ShortCodeReady) {
session1ShortCode = tx.getDecimalCodeRepresentation()
tx.userHasVerifiedShortCode()
}
}
}
})
aliceVerificationService2.addListener(object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
Log.d("#TEST", "BB: tx incoming?:${tx.isIncoming} state ${tx.state}")
if (tx is SasVerificationTransaction) {
if (tx.state == VerificationTxState.ShortCodeReady) {
session2ShortCode = tx.getDecimalCodeRepresentation()
tx.userHasVerifiedShortCode()
}
}
}
})
val txId: String = "m.testVerif12"
aliceVerificationService2.beginKeyVerification(VerificationMethod.SAS, aliceSession1.myUserId, aliceSession1.sessionParams.credentials.deviceId
?: "", txId)
mTestHelper.waitWithLatch { latch ->
mTestHelper.retryPeriodicallyWithLatch(latch) {
aliceSession1.cryptoService().getDeviceInfo(aliceSession1.myUserId, aliceSession2.sessionParams.credentials.deviceId ?: "")?.isVerified == true
}
}
assertNotNull(session1ShortCode)
Log.d("#TEST", "session1ShortCode: $session1ShortCode")
assertNotNull(session2ShortCode)
Log.d("#TEST", "session2ShortCode: $session2ShortCode")
assertEquals(session1ShortCode, session2ShortCode)
// SSK and USK private keys should have been shared
mTestHelper.waitWithLatch(60_000) { latch ->
mTestHelper.retryPeriodicallyWithLatch(latch) {
Log.d("#TEST", "CAN XS :${aliceSession2.cryptoService().crossSigningService().getMyCrossSigningKeys()}")
aliceSession2.cryptoService().crossSigningService().canCrossSign()
}
}
// Test that key backup key has been shared to
mTestHelper.waitWithLatch(60_000) { latch ->
val keysBackupService = aliceSession2.cryptoService().keysBackupService()
mTestHelper.retryPeriodicallyWithLatch(latch) {
Log.d("#TEST", "Recovery :${ keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey}")
keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey == creationInfo.recoveryKey
}
}
}
}

View File

@@ -34,7 +34,6 @@ import im.vector.matrix.android.common.assertDictEquals
import im.vector.matrix.android.common.assertListEquals
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
import im.vector.matrix.android.internal.crypto.MegolmSessionData
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
@@ -326,46 +325,46 @@ class KeysBackupTest : InstrumentedTest {
* - Restore must be successful
* - *** There must be no more pending key share requests
*/
@Test
fun restoreKeysBackupAndKeyShareRequestTest() {
fail("Check with Valere for this test. I think we do not send key share request")
val testData = createKeysBackupScenarioWithPassword(null)
// - Check the SDK sent key share requests
val cryptoStore2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store
val unsentRequest = cryptoStore2
.getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.UNSENT))
val sentRequest = cryptoStore2
.getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.SENT))
// Request is either sent or unsent
assertTrue(unsentRequest != null || sentRequest != null)
// - Restore the e2e backup from the homeserver
val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
null,
null,
null,
it
)
}
checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
// - There must be no more pending key share requests
val unsentRequestAfterRestoration = cryptoStore2
.getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.UNSENT))
val sentRequestAfterRestoration = cryptoStore2
.getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.SENT))
// Request is either sent or unsent
assertTrue(unsentRequestAfterRestoration == null && sentRequestAfterRestoration == null)
testData.cleanUp(mTestHelper)
}
// @Test
// fun restoreKeysBackupAndKeyShareRequestTest() {
// fail("Check with Valere for this test. I think we do not send key share request")
//
// val testData = createKeysBackupScenarioWithPassword(null)
//
// // - Check the SDK sent key share requests
// val cryptoStore2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store
// val unsentRequest = cryptoStore2
// .getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.UNSENT))
// val sentRequest = cryptoStore2
// .getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.SENT))
//
// // Request is either sent or unsent
// assertTrue(unsentRequest != null || sentRequest != null)
//
// // - Restore the e2e backup from the homeserver
// val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
// testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
// testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
// null,
// null,
// null,
// it
// )
// }
//
// checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
//
// // - There must be no more pending key share requests
// val unsentRequestAfterRestoration = cryptoStore2
// .getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.UNSENT))
// val sentRequestAfterRestoration = cryptoStore2
// .getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.SENT))
//
// // Request is either sent or unsent
// assertTrue(unsentRequestAfterRestoration == null && sentRequestAfterRestoration == null)
//
// testData.cleanUp(mTestHelper)
// }
/**
* - Do an e2e backup to the homeserver with a recovery key

View File

@@ -71,7 +71,7 @@ class QuadSTests : InstrumentedTest {
val TEST_KEY_ID = "my.test.Key"
mTestHelper.doSync<SsssKeyCreationInfo> {
quadS.generateKey(TEST_KEY_ID, "Test Key", emptyKeySigner, it)
quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner, it)
}
// Assert Account data is updated
@@ -177,7 +177,7 @@ class QuadSTests : InstrumentedTest {
val TEST_KEY_ID = "my.test.Key"
mTestHelper.doSync<SsssKeyCreationInfo> {
quadS.generateKey(TEST_KEY_ID, "Test Key", emptyKeySigner, it)
quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner, it)
}
// Test that we don't need to wait for an account data sync to access directly the keyid from DB
@@ -322,7 +322,7 @@ class QuadSTests : InstrumentedTest {
val quadS = session.sharedSecretStorageService
val creationInfo = mTestHelper.doSync<SsssKeyCreationInfo> {
quadS.generateKey(keyId, keyId, emptyKeySigner, it)
quadS.generateKey(keyId, null, keyId, emptyKeySigner, it)
}
assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId")

View File

@@ -16,6 +16,7 @@
package im.vector.matrix.android.internal.crypto.verification
import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4
import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.api.session.Session
@@ -23,6 +24,7 @@ import im.vector.matrix.android.api.session.crypto.verification.CancelCode
import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerificationTransaction
import im.vector.matrix.android.api.session.crypto.verification.OutgoingSasVerificationTransaction
import im.vector.matrix.android.api.session.crypto.verification.SasMode
import im.vector.matrix.android.api.session.crypto.verification.SasVerificationTransaction
import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
import im.vector.matrix.android.api.session.crypto.verification.VerificationService
import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction
@@ -355,6 +357,7 @@ class SASTest : InstrumentedTest {
val aliceAcceptedLatch = CountDownLatch(1)
val aliceListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
Log.v("TEST", "== aliceTx state ${tx.state} => ${(tx as? OutgoingSasVerificationTransaction)?.uxState}")
if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnAccepted) {
val at = tx as SASDefaultVerificationTransaction
accepted = at.accepted
@@ -367,7 +370,9 @@ class SASTest : InstrumentedTest {
val bobListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
Log.v("TEST", "== bobTx state ${tx.state} => ${(tx as? IncomingSasVerificationTransaction)?.uxState}")
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
bobVerificationService.removeListener(this)
val at = tx as IncomingSasVerificationTransaction
at.performAccept()
}
@@ -515,4 +520,96 @@ class SASTest : InstrumentedTest {
assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
cryptoTestData.cleanUp(mTestHelper)
}
@Test
fun test_ConcurrentStart() {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession!!.cryptoService().verificationService()
val req = aliceVerificationService.requestKeyVerificationInDMs(
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
bobSession.myUserId,
cryptoTestData.roomId
)
var requestID : String? = null
mTestHelper.waitWithLatch {
mTestHelper.retryPeriodicallyWithLatch(it) {
val prAlicePOV = aliceVerificationService.getExistingVerificationRequest(bobSession.myUserId)?.firstOrNull()
requestID = prAlicePOV?.transactionId
Log.v("TEST", "== alicePOV is $prAlicePOV")
prAlicePOV?.transactionId != null && prAlicePOV.localId == req.localId
}
}
Log.v("TEST", "== requestID is $requestID")
mTestHelper.waitWithLatch {
mTestHelper.retryPeriodicallyWithLatch(it) {
val prBobPOV = bobVerificationService.getExistingVerificationRequest(aliceSession.myUserId)?.firstOrNull()
Log.v("TEST", "== prBobPOV is $prBobPOV")
prBobPOV?.transactionId == requestID
}
}
bobVerificationService.readyPendingVerification(
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
aliceSession.myUserId,
requestID!!
)
// wait for alice to get the ready
mTestHelper.waitWithLatch {
mTestHelper.retryPeriodicallyWithLatch(it) {
val prAlicePOV = aliceVerificationService.getExistingVerificationRequest(bobSession.myUserId)?.firstOrNull()
Log.v("TEST", "== prAlicePOV is $prAlicePOV")
prAlicePOV?.transactionId == requestID && prAlicePOV?.isReady != null
}
}
// Start concurrent!
aliceVerificationService.beginKeyVerificationInDMs(
VerificationMethod.SAS,
requestID!!,
cryptoTestData.roomId,
bobSession.myUserId,
bobSession.sessionParams.credentials.deviceId!!,
null)
bobVerificationService.beginKeyVerificationInDMs(
VerificationMethod.SAS,
requestID!!,
cryptoTestData.roomId,
aliceSession.myUserId,
aliceSession.sessionParams.credentials.deviceId!!,
null)
// we should reach SHOW SAS on both
var alicePovTx: SasVerificationTransaction?
var bobPovTx: SasVerificationTransaction?
mTestHelper.waitWithLatch {
mTestHelper.retryPeriodicallyWithLatch(it) {
alicePovTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, requestID!!) as? SasVerificationTransaction
Log.v("TEST", "== alicePovTx is $alicePovTx")
alicePovTx?.state == VerificationTxState.ShortCodeReady
}
}
// wait for alice to get the ready
mTestHelper.waitWithLatch {
mTestHelper.retryPeriodicallyWithLatch(it) {
bobPovTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, requestID!!) as? SasVerificationTransaction
Log.v("TEST", "== bobPovTx is $bobPovTx")
bobPovTx?.state == VerificationTxState.ShortCodeReady
}
}
cryptoTestData.cleanUp(mTestHelper)
}
}

View File

@@ -18,14 +18,18 @@ package im.vector.matrix.android.internal.crypto.verification.qrcode
import androidx.test.ext.junit.runners.AndroidJUnit4
import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.api.session.crypto.verification.PendingVerificationRequest
import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
import im.vector.matrix.android.api.session.crypto.verification.VerificationService
import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
import im.vector.matrix.android.common.CommonTestHelper
import im.vector.matrix.android.common.CryptoTestHelper
import im.vector.matrix.android.common.TestConstants
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
import im.vector.matrix.android.api.session.crypto.verification.PendingVerificationRequest
import org.amshove.kluent.shouldBe
import org.junit.Assert.assertEquals
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -229,4 +233,46 @@ class VerificationTest : InstrumentedTest {
cryptoTestData.cleanUp(mTestHelper)
}
@Test
fun test_alice_sends_text_message_and_bob_can_decrypt() {
val lock = CountDownLatch(1)
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession!!
val roomFromBobPOV = bobSession.getRoom(cryptoTestData.roomId)!!
aliceSession.getRoom(cryptoTestData.roomId)!!.sendTextMessage("test")
val bobEventsListener = object : Timeline.Listener {
override fun onTimelineFailure(throwable: Throwable) {
// noop
}
override fun onNewTimelineEvents(eventIds: List<String>) {
// noop
}
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
if (snapshot.isNotEmpty() && snapshot.first().root.isEncrypted()) {
lock.countDown()
}
}
}
val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(20))
bobTimeline.start()
bobTimeline.addListener(bobEventsListener)
mTestHelper.await(lock)
bobTimeline.getTimelineEventAtIndex(0)?.root?.let {
val decryptionResult = bobSession.cryptoService().decryptEvent(it, "")
val text = (decryptionResult.clearEvent["content"] as Map<*, *>)["body"]
assertEquals("test", text)
}
}
}

View File

@@ -33,6 +33,7 @@ import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
import org.matrix.olm.OlmManager
import java.io.InputStream
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.Executors
import javax.inject.Inject
data class MatrixConfiguration(
@@ -61,7 +62,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
Monarchy.init(context)
DaggerMatrixComponent.factory().create(context, matrixConfiguration).inject(this)
if (context.applicationContext !is Configuration.Provider) {
WorkManager.initialize(context, Configuration.Builder().build())
WorkManager.initialize(context, Configuration.Builder().setExecutor(Executors.newCachedThreadPool()).build())
}
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
}

View File

@@ -23,5 +23,14 @@ data class MXCryptoConfig(
// Tell whether the encryption of the event content is enabled for the invited members.
// SDK clients can disable this by settings it to false.
// Note that the encryption for the invited members will be blocked if the history visibility is "joined".
var enableEncryptionForInvitedMembers: Boolean = true
var enableEncryptionForInvitedMembers: Boolean = true,
/**
* If set to true, the SDK will automatically ignore room key request (gossiping)
* coming from your other untrusted sessions (or blocked).
* If set to false, the request will be forwarded to the application layer; in this
* case the application can decide to prompt the user.
*/
var discardRoomKeyRequestsFromUntrustedDevices : Boolean = true
)

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.extensions
inline fun <A> tryThis(operation: () -> A): A? {
return try {
operation()
} catch (any: Throwable) {
null
}
}

View File

@@ -31,3 +31,9 @@ fun Throwable.shouldBeRetried(): Boolean {
return this is Failure.NetworkConnection
|| (this is Failure.ServerError && error.code == MatrixError.M_LIMIT_EXCEEDED)
}
fun Throwable.isInvalidPassword(): Boolean {
return this is Failure.ServerError
&& error.code == MatrixError.M_FORBIDDEN
&& error.message == "Invalid password"
}

View File

@@ -21,6 +21,7 @@ import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.failure.GlobalError
import im.vector.matrix.android.api.pushrules.PushRuleService
import im.vector.matrix.android.api.session.account.AccountService
import im.vector.matrix.android.api.session.accountdata.AccountDataService
import im.vector.matrix.android.api.session.cache.CacheService
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
@@ -59,7 +60,8 @@ interface Session :
InitialSyncProgressService,
HomeServerCapabilitiesService,
SecureStorageService,
AccountDataService {
AccountDataService,
AccountService {
/**
* The params associated to the session

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.account
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable
/**
* This interface defines methods to manage the account. It's implemented at the session level.
*/
interface AccountService {
/**
* Ask the homeserver to change the password.
* @param password Current password.
* @param newPassword New password
*/
fun changePassword(password: String, newPassword: String, callback: MatrixCallback<Unit>): Cancelable
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.account.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
/**
* Class to pass request parameters to update the password.
*/
@JsonClass(generateAdapter = true)
internal data class ChangePasswordParams(
@Json(name = "auth")
val auth: UserPasswordAuth? = null,
@Json(name = "new_password")
val newPassword: String? = null
) {
companion object {
fun create(userId: String, oldPassword: String, newPassword: String): ChangePasswordParams {
return ChangePasswordParams(
auth = UserPasswordAuth(user = userId, password = oldPassword),
newPassword = newPassword
)
}
}
}

View File

@@ -16,12 +16,12 @@
package im.vector.matrix.android.api.session.accountdata
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
import kotlinx.coroutines.flow.Flow
interface AccountDataService {
/**
@@ -32,7 +32,7 @@ interface AccountDataService {
/**
* Observe the account data with the provided type
*/
fun getLiveAccountDataEvent(type: String): LiveData<Optional<UserAccountDataEvent>>
fun getLiveAccountDataEvent(type: String): Flow<Optional<UserAccountDataEvent>>
/**
* Retrieve the account data with the provided types. The return list can have a different size that
@@ -44,7 +44,7 @@ interface AccountDataService {
/**
* Observe the account data with the provided types. If an empty set is provided, all the AccountData are observed
*/
fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<UserAccountDataEvent>>
fun getLiveAccountDataEvents(types: Set<String>): Flow<List<UserAccountDataEvent>>
/**
* Update the account data with the provided type and the provided account data content

View File

@@ -16,6 +16,7 @@
package im.vector.matrix.android.api.session.content
import android.net.Uri
import android.os.Parcelable
import androidx.exifinterface.media.ExifInterface
import kotlinx.android.parcel.Parcelize
@@ -29,8 +30,7 @@ data class ContentAttachmentData(
val width: Long? = 0,
val exifOrientation: Int = ExifInterface.ORIENTATION_UNDEFINED,
val name: String? = null,
val queryUri: String,
val path: String,
val queryUri: Uri,
private val mimeType: String?,
val type: Type
) : Parcelable {

View File

@@ -26,6 +26,11 @@ interface ContentUrlResolver {
SCALE("scale")
}
/**
* URL to use to upload content
*/
val uploadUrl: String
/**
* Get the actual URL for accessing the full-size image of a Matrix media content URI.
*

View File

@@ -22,12 +22,14 @@ import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.listeners.ProgressListener
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
import im.vector.matrix.android.api.session.crypto.keyshare.RoomKeysRequestListener
import im.vector.matrix.android.api.session.crypto.keyshare.GossipingRequestListener
import im.vector.matrix.android.api.session.crypto.verification.VerificationService
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.IncomingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.crypto.NewSessionListener
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
@@ -86,13 +88,15 @@ interface CryptoService {
fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo?
fun requestRoomKeyForEvent(event: Event)
fun reRequestRoomKeyForEvent(event: Event)
fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody)
fun addRoomKeysRequestListener(listener: RoomKeysRequestListener)
fun addRoomKeysRequestListener(listener: GossipingRequestListener)
fun removeRoomKeysRequestListener(listener: RoomKeysRequestListener)
fun removeRoomKeysRequestListener(listener: GossipingRequestListener)
fun getDevicesList(callback: MatrixCallback<DevicesListResponse>)
@@ -129,4 +133,8 @@ interface CryptoService {
fun addNewSessionListener(newSessionListener: NewSessionListener)
fun removeSessionListener(listener: NewSessionListener)
fun getOutgoingRoomKeyRequest(): List<OutgoingRoomKeyRequest>
fun getIncomingRoomKeyRequest(): List<IncomingRoomKeyRequest>
fun getGossipingEventsTrail(): List<Event>
}

View File

@@ -22,6 +22,7 @@ import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustResult
import im.vector.matrix.android.internal.crypto.crosssigning.UserTrustResult
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
import im.vector.matrix.android.internal.crypto.store.PrivateKeysInfo
interface CrossSigningService {
@@ -52,6 +53,8 @@ interface CrossSigningService {
fun getMyCrossSigningKeys(): MXCrossSigningInfo?
fun getCrossSigningPrivateKeys(): PrivateKeysInfo?
fun canCrossSign(): Boolean
fun trustUser(otherUserId: String,
@@ -68,4 +71,7 @@ interface CrossSigningService {
fun checkDeviceTrust(otherUserId: String,
otherDeviceId: String,
locallyTrusted: Boolean?): DeviceTrustResult
fun onSecretSSKGossip(sskPrivateKey: String)
fun onSecretUSKGossip(uskPrivateKey: String)
}

View File

@@ -21,3 +21,5 @@ const val MASTER_KEY_SSSS_NAME = "m.cross_signing.master"
const val USER_SIGNING_KEY_SSSS_NAME = "m.cross_signing.user_signing"
const val SELF_SIGNING_KEY_SSSS_NAME = "m.cross_signing.self_signing"
const val KEYBACKUP_SECRET_SSSS_NAME = "m.megolm_backup.v1"

View File

@@ -24,6 +24,7 @@ import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCre
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
import im.vector.matrix.android.internal.crypto.store.SavedKeyBackupKeyInfo
interface KeysBackupService {
/**
@@ -172,6 +173,8 @@ interface KeysBackupService {
password: String,
callback: MatrixCallback<Unit>)
fun onSecretKeyGossip(secret: String)
/**
* Restore a backup with a recovery key from a given backup version stored on the homeserver.
*
@@ -210,4 +213,10 @@ interface KeysBackupService {
val isEnabled: Boolean
val isStucked: Boolean
val state: KeysBackupState
// For gossiping
fun saveBackupRecoveryKey(recoveryKey: String?, version: String?)
fun getKeyBackupRecoveryKeyInfo() : SavedKeyBackupKeyInfo?
fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String, callback: MatrixCallback<Boolean>)
}

View File

@@ -17,12 +17,13 @@
package im.vector.matrix.android.api.session.crypto.keyshare
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequestCancellation
import im.vector.matrix.android.internal.crypto.IncomingRequestCancellation
import im.vector.matrix.android.internal.crypto.IncomingSecretShareRequest
/**
* Room keys events listener
*/
interface RoomKeysRequestListener {
interface GossipingRequestListener {
/**
* An room key request has been received.
*
@@ -30,10 +31,16 @@ interface RoomKeysRequestListener {
*/
fun onRoomKeyRequest(request: IncomingRoomKeyRequest)
/**
* Returns the secret value to be shared
* @return true if is handled
*/
fun onSecretShareRequest(request: IncomingSecretShareRequest) : Boolean
/**
* A room key request cancellation has been received.
*
* @param request the cancellation request
*/
fun onRoomKeyRequestCancellation(request: IncomingRoomKeyRequestCancellation)
fun onRoomKeyRequestCancellation(request: IncomingRequestCancellation)
}

View File

@@ -16,7 +16,10 @@
package im.vector.matrix.android.api.session.crypto.verification
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
data class EmojiRepresentation(val emoji: String,
@StringRes val nameResId: Int)
@StringRes val nameResId: Int,
@DrawableRes val drawableRes: Int? = null
)

View File

@@ -17,6 +17,7 @@
package im.vector.matrix.android.api.session.crypto.verification
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.LocalEcho
/**
@@ -59,6 +60,8 @@ interface VerificationService {
roomId: String,
localId: String? = LocalEcho.createLocalEchoId()): PendingVerificationRequest
fun cancelVerificationRequest(request: PendingVerificationRequest)
/**
* Request a key verification from another user using toDevice events.
*/
@@ -136,4 +139,6 @@ interface VerificationService {
return age in tooInThePast..tooInTheFuture
}
}
fun onPotentiallyInterestingEventRoomFailToDecrypt(event: Event)
}

View File

@@ -43,6 +43,7 @@ sealed class VerificationTxState {
// Will be used to ask the user if the other user has correctly scanned
object QrScannedByOther : VerificationQrTxState()
object WaitingOtherReciprocateConfirm : VerificationQrTxState()
// Terminal states
abstract class TerminalTxState : VerificationTxState()

View File

@@ -142,12 +142,12 @@ data class Event(
}
fun toContentStringWithIndent(): String {
val contentMap = toContent().toMutableMap()
val contentMap = toContent()
return JSONObject(contentMap).toString(4)
}
fun toClearContentStringWithIndent(): String? {
val contentMap = this.mxDecryptionResult?.payload?.toMutableMap()
val contentMap = this.mxDecryptionResult?.payload
val adapter = MoshiProvider.providesMoshi().adapter(Map::class.java)
return contentMap?.let { JSONObject(adapter.toJson(it)).toString(4) }
}

View File

@@ -18,6 +18,7 @@ package im.vector.matrix.android.api.session.group
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.session.group.model.GroupSummary
import kotlinx.coroutines.flow.Flow
/**
* This interface defines methods to get groups. It's implemented at the session level.
@@ -48,5 +49,5 @@ interface GroupService {
* Get a live list of group summaries. This list is refreshed as soon as the data changes.
* @return the [LiveData] of [GroupSummary]
*/
fun getGroupSummariesLive(groupSummaryQueryParams: GroupSummaryQueryParams): LiveData<List<GroupSummary>>
fun getGroupSummariesLive(groupSummaryQueryParams: GroupSummaryQueryParams): Flow<List<GroupSummary>>
}

View File

@@ -25,9 +25,7 @@ import im.vector.matrix.android.api.session.room.model.Membership
data class GroupSummary(
val groupId: String,
val membership: Membership,
val displayName: String = "",
val shortDescription: String = "",
val avatarUrl: String = "",
val roomIds: List<String> = emptyList(),
val userIds: List<String> = emptyList()
val displayName: String? = null,
val shortDescription: String? = null,
val avatarUrl: String? = null
)

View File

@@ -17,6 +17,10 @@
package im.vector.matrix.android.api.session.homeserver
data class HomeServerCapabilities(
/**
* True if it is possible to change the password of the account.
*/
val canChangePassword: Boolean = true,
/**
* Max size of file which can be uploaded to the homeserver in bytes. [MAX_UPLOAD_FILE_SIZE_UNKNOWN] if unknown or not retrieved yet
*/

View File

@@ -18,6 +18,7 @@ package im.vector.matrix.android.api.session.pushers
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable
import kotlinx.coroutines.flow.Flow
import java.util.UUID
interface PushersService {
@@ -72,9 +73,9 @@ interface PushersService {
fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback<Unit>): Cancelable
/**
* Get the current pushers, as a LiveData
* Get the current pushers, as a Flow
*/
fun getPushersLive(): LiveData<List<Pusher>>
fun getPushersLive(): Flow<List<Pusher>>
/**
* Get the current pushers

View File

@@ -30,6 +30,7 @@ import im.vector.matrix.android.api.session.room.state.StateService
import im.vector.matrix.android.api.session.room.timeline.TimelineService
import im.vector.matrix.android.api.session.room.typing.TypingService
import im.vector.matrix.android.api.util.Optional
import kotlinx.coroutines.flow.Flow
/**
* This interface defines methods to interact within a room.
@@ -56,7 +57,7 @@ interface Room :
* A live [RoomSummary] associated with the room
* You can observe this summary to get dynamic data from this room.
*/
fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>>
fun getRoomSummaryLive(): Flow<Optional<RoomSummary>>
/**
* A current snapshot of [RoomSummary] associated with the room

View File

@@ -18,10 +18,12 @@ package im.vector.matrix.android.api.session.room
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.room.model.Breadcrumb
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.Optional
import kotlinx.coroutines.flow.Flow
/**
* This interface defines methods to get rooms. It's implemented at the session level.
@@ -67,21 +69,21 @@ interface RoomService {
/**
* Get a live list of room summaries. This list is refreshed as soon as the data changes.
* @return the [LiveData] of List[RoomSummary]
* @return the [Flow] of List[RoomSummary]
*/
fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData<List<RoomSummary>>
fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): Flow<List<RoomSummary>>
/**
* Get a snapshot list of Breadcrumbs
* @return the immutable list of [RoomSummary]
* @return the immutable list of [Breadcrumb]
*/
fun getBreadcrumbs(): List<RoomSummary>
fun getBreadcrumbs(): List<Breadcrumb>
/**
* Get a live list of Breadcrumbs
* @return the [LiveData] of [RoomSummary]
* @return the [Flow] of [Breadcrumb]
*/
fun getBreadcrumbsLive(): LiveData<List<RoomSummary>>
fun getBreadcrumbsLive(): Flow<List<Breadcrumb>>
/**
* Inform the Matrix SDK that a room is displayed.
@@ -102,5 +104,5 @@ interface RoomService {
searchOnServer: Boolean,
callback: MatrixCallback<Optional<String>>): Cancelable
fun getExistingDirectRoomWithUser(otherUserId: String) : Room?
fun getExistingDirectRoomWithUser(otherUserId: String): Room?
}

View File

@@ -28,6 +28,7 @@ fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {
* [im.vector.matrix.android.api.session.room.Room] and [im.vector.matrix.android.api.session.room.RoomService]
*/
data class RoomSummaryQueryParams(
val fromGroupId: String?,
val displayName: QueryStringValue,
val canonicalAlias: QueryStringValue,
val memberships: List<Membership>
@@ -35,11 +36,13 @@ data class RoomSummaryQueryParams(
class Builder {
var fromGroupId: String? = null
var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
var canonicalAlias: QueryStringValue = QueryStringValue.NoCondition
var memberships: List<Membership> = Membership.all()
fun build() = RoomSummaryQueryParams(
fromGroupId = fromGroupId,
displayName = displayName,
canonicalAlias = canonicalAlias,
memberships = memberships

View File

@@ -20,6 +20,8 @@ import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.Optional
import kotlinx.coroutines.flow.Flow
/**
* This interface defines methods to handling membership. It's implemented at the room level.
@@ -40,6 +42,14 @@ interface MembershipService {
*/
fun getRoomMember(userId: String): RoomMemberSummary?
/**
* Return a live version of an optionnal roomMember with the given userId.
* @param userId the userId param to look for
*
* @return a [Flow] of [Optional] [RoomMemberSummary] with userId
*/
fun getRoomMemberLive(userId: String): Flow<Optional<RoomMemberSummary>>
/**
* Return all the roomMembers of the room with params
* @param queryParams the params to query for
@@ -50,9 +60,10 @@ interface MembershipService {
/**
* Return all the roomMembers of the room filtered by memberships
* @param queryParams the params to query for
* @return a [LiveData] of roomMember list.
* @return a [Flow] of roomMember list.
*/
fun getRoomMembersLive(queryParams: RoomMemberQueryParams): LiveData<List<RoomMemberSummary>>
fun getRoomMembersLive(queryParams: RoomMemberQueryParams): Flow<List<RoomMemberSummary>>
fun getNumberOfJoinedMembers(): Int

View File

@@ -0,0 +1,18 @@
package im.vector.matrix.android.api.session.room.model
import im.vector.matrix.android.api.session.room.send.UserDraft
/**
* This data class holds data about a breadcrumb.
*/
data class Breadcrumb(
val roomId: String,
val displayName: String = "",
val avatarUrl: String = "",
val notificationCount: Int = 0,
val highlightCount: Int = 0,
val hasUnreadMessages: Boolean = false,
val userDrafts: List<UserDraft> = emptyList(),
var isEncrypted: Boolean,
val typingRoomMemberIds: List<String> = emptyList()
)

View File

@@ -31,7 +31,6 @@ data class RoomSummary constructor(
val topic: String = "",
val avatarUrl: String = "",
val canonicalAlias: String? = null,
val aliases: List<String> = emptyList(),
val isDirect: Boolean = false,
val joinedMembersCount: Int? = 0,
val invitedMembersCount: Int? = 0,

View File

@@ -24,7 +24,7 @@ data class AudioInfo(
/**
* The mimetype of the audio e.g. "audio/aac".
*/
@Json(name = "mimetype") val mimeType: String,
@Json(name = "mimetype") val mimeType: String?,
/**
* The size of the audio clip in bytes.

View File

@@ -29,7 +29,8 @@ data class MessageLocationContent(
@Json(name = "msgtype") override val msgType: String,
/**
* Required. A description of the location e.g. 'Big Ben, London, UK', or some kind of content description for accessibility e.g. 'location attachment'.
* Required. A description of the location e.g. 'Big Ben, London, UK', or some kind
* of content description for accessibility e.g. 'location attachment'.
*/
@Json(name = "body") override val body: String,

View File

@@ -39,5 +39,5 @@ data class ThumbnailInfo(
/**
* The mimetype of the image, e.g. "image/jpeg".
*/
@Json(name = "mimetype") val mimeType: String
@Json(name = "mimetype") val mimeType: String?
)

View File

@@ -22,6 +22,7 @@ import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.Optional
import kotlinx.coroutines.flow.Flow
/**
* In some cases, events may wish to reference other events.
@@ -113,12 +114,12 @@ interface RelationService {
* @param eventId the eventId to look for EventAnnotationsSummary
* @return the EventAnnotationsSummary found
*/
fun getEventAnnotationsSummary(eventId: String): EventAnnotationsSummary?
fun getEventAnnotationsSummary(eventId: String): EventAnnotationsSummary
/**
* Get a LiveData of EventAnnotationsSummary for the specified eventId
* @param eventId the eventId to look for EventAnnotationsSummary
* @return the LiveData of EventAnnotationsSummary
* @return a [Flow] of EventAnnotationsSummary
*/
fun getEventAnnotationsSummaryLive(eventId: String): LiveData<Optional<EventAnnotationsSummary>>
fun getEventAnnotationsSummaryLive(eventId: String): Flow<EventAnnotationsSummary>
}

View File

@@ -16,13 +16,13 @@
package im.vector.matrix.android.api.session.room.notification
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable
import kotlinx.coroutines.flow.Flow
interface RoomPushRuleService {
fun getLiveRoomNotificationState(): LiveData<RoomNotificationState>
fun getRoomNotificationStateLive(): Flow<RoomNotificationState>
fun setRoomNotificationState(roomNotificationState: RoomNotificationState, matrixCallback: MatrixCallback<Unit>): Cancelable
}

View File

@@ -20,6 +20,7 @@ import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.room.model.ReadReceipt
import im.vector.matrix.android.api.util.Optional
import kotlinx.coroutines.flow.Flow
/**
* This interface defines methods to handle read receipts and read marker in a room. It's implemented at the room level.
@@ -55,16 +56,16 @@ interface ReadService {
/**
* Returns a live read marker id for the room.
*/
fun getReadMarkerLive(): LiveData<Optional<String>>
fun getReadMarkerLive(): Flow<Optional<String>>
/**
* Returns a live read receipt id for the room.
*/
fun getMyReadReceiptLive(): LiveData<Optional<String>>
fun getMyReadReceiptLive(): Flow<Optional<String>>
/**
* Returns a live list of read receipts for a given event
* @param eventId: the event
*/
fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>>
fun getEventReadReceiptsLive(eventId: String): Flow<List<ReadReceipt>>
}

View File

@@ -16,9 +16,9 @@
package im.vector.matrix.android.api.session.room.send
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable
import kotlinx.coroutines.flow.Flow
interface DraftService {
@@ -36,5 +36,5 @@ interface DraftService {
* Return the current drafts if any, as a live data
* The draft list can contain one draft for {regular, reply, quote} and an arbitrary number of {edit} drafts
*/
fun getDraftsLive(): LiveData<List<UserDraft>>
fun getDraftsLive(): Flow<List<UserDraft>>
}

View File

@@ -16,10 +16,10 @@
package im.vector.matrix.android.api.session.room.state
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.util.Optional
import kotlinx.coroutines.flow.Flow
interface StateService {
@@ -30,5 +30,5 @@ interface StateService {
fun getStateEvent(eventType: String, stateKey: String): Event?
fun getStateEventLive(eventType: String, stateKey: String): LiveData<Optional<Event>>
fun getStateEventLive(eventType: String, stateKey: String): Flow<Optional<Event>>
}

View File

@@ -16,8 +16,8 @@
package im.vector.matrix.android.api.session.room.timeline
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.util.Optional
import kotlinx.coroutines.flow.Flow
/**
* This interface defines methods to interact with the timeline. It's implemented at the room level.
@@ -38,5 +38,5 @@ interface TimelineService {
fun getTimeLineEvent(eventId: String): TimelineEvent?
fun getTimeLineEventLive(eventId: String): LiveData<Optional<TimelineEvent>>
fun getTimeLineEventLive(eventId: String): Flow<Optional<TimelineEvent>>
}

View File

@@ -19,3 +19,7 @@ package im.vector.matrix.android.api.session.securestorage
interface KeySigner {
fun sign(canonicalJson: String): Map<String, Map<String, String>>?
}
class EmptyKeySigner : KeySigner {
override fun sign(canonicalJson: String): Map<String, Map<String, String>>? = null
}

View File

@@ -35,12 +35,14 @@ interface SharedSecretStorageService {
* Use the SsssKeyCreationInfo object returned by the callback to get more information about the created key (recovery key ...)
*
* @param keyId the ID of the key
* @param key keep null if you want to generate a random key
* @param keyName a human readable name
* @param keySigner Used to add a signature to the key (client should check key signature before storing secret)
*
* @param callback Get key creation info
*/
fun generateKey(keyId: String,
key: SsssKeySpec?,
keyName: String,
keySigner: KeySigner?,
callback: MatrixCallback<SsssKeyCreationInfo>)
@@ -111,6 +113,8 @@ interface SharedSecretStorageService {
fun checkShouldBeAbleToAccessSecrets(secretNames: List<String>, keyId: String?) : IntegrityResult
fun requestSecret(name: String, myOtherDeviceId: String)
data class KeyRef(
val keyId: String?,
val keySpec: SsssKeySpec?

View File

@@ -19,5 +19,6 @@ package im.vector.matrix.android.api.session.securestorage
data class SsssKeyCreationInfo(
val keyId: String = "",
var content: SecretStorageKeyContent?,
val recoveryKey: String = ""
val recoveryKey: String = "",
val keySpec: SsssKeySpec
)

View File

@@ -22,6 +22,7 @@ import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.Optional
import kotlinx.coroutines.flow.Flow
/**
* This interface defines methods to get users. It's implemented at the session level.
@@ -48,27 +49,27 @@ interface UserService {
/**
* Observe a live user from a userId
* @param userId the userId to look for.
* @return a LiveData of user with userId
* @return a Flow of user with userId
*/
fun getUserLive(userId: String): LiveData<Optional<User>>
fun getUserLive(userId: String): Flow<Optional<User>>
/**
* Observe a live list of users sorted alphabetically
* @return a Livedata of users
* @return a Flow of users
*/
fun getUsersLive(): LiveData<List<User>>
fun getUsersLive(): Flow<List<User>>
/**
* Observe a live [PagedList] of users sorted alphabetically. You can filter the users.
* @param filter the filter. It will look into userId and displayName.
* @return a Livedata of users
* @return a Flow of users
*/
fun getPagedUsersLive(filter: String? = null): LiveData<PagedList<User>>
fun getPagedUsersLive(filter: String? = null): Flow<PagedList<User>>
/**
* Get list of ignored users
*/
fun getIgnoredUsersLive(): LiveData<List<User>>
fun getIgnoredUsersLive(): Flow<List<User>>
/**
* Ignore users

View File

@@ -18,6 +18,7 @@ package im.vector.matrix.android.api.util
import im.vector.matrix.android.BuildConfig
import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.session.room.model.Breadcrumb
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
@@ -148,6 +149,8 @@ fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, displayName, ava
fun RoomSummary.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl)
fun Breadcrumb.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl)
fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl)
// If no name is available, use room alias as Riot-Web does

View File

@@ -17,16 +17,19 @@
package im.vector.matrix.android.internal.auth
import android.content.Context
import com.squareup.sqldelight.android.AndroidSqliteDriver
import dagger.Binds
import dagger.Module
import dagger.Provides
import im.vector.matrix.android.api.auth.AuthenticationService
import im.vector.matrix.android.internal.auth.db.AuthRealmMigration
import im.vector.matrix.android.internal.auth.db.AuthRealmModule
import im.vector.matrix.android.internal.auth.db.RealmPendingSessionStore
import im.vector.matrix.android.internal.auth.db.RealmSessionParamsStore
import im.vector.matrix.android.internal.database.RealmKeysUtils
import im.vector.matrix.android.internal.di.AuthDatabase
import im.vector.matrix.android.internal.auth.realm.AuthRealmMigration
import im.vector.matrix.android.internal.auth.realm.AuthRealmModule
import im.vector.matrix.android.internal.auth.sqlite.AuthSchema
import im.vector.matrix.android.internal.auth.sqlite.SqlitePendingSessionStore
import im.vector.matrix.android.internal.auth.sqlite.SqliteSessionParamsStore
import im.vector.matrix.android.internal.database.DatabaseKeysUtils
import im.vector.matrix.android.internal.di.MatrixScope
import im.vector.matrix.sqldelight.auth.AuthDatabase
import io.realm.RealmConfiguration
import java.io.File
@@ -39,16 +42,16 @@ internal abstract class AuthModule {
@JvmStatic
@Provides
@AuthDatabase
fun providesRealmConfiguration(context: Context, realmKeysUtils: RealmKeysUtils): RealmConfiguration {
@im.vector.matrix.android.internal.di.RealmAuthDatabase
@MatrixScope
fun providesRealmConfiguration(context: Context, databaseKeysUtils: DatabaseKeysUtils): RealmConfiguration {
val old = File(context.filesDir, "matrix-sdk-auth")
if (old.exists()) {
old.renameTo(File(context.filesDir, "matrix-sdk-auth.realm"))
}
return RealmConfiguration.Builder()
.apply {
realmKeysUtils.configureEncryption(this, DB_ALIAS)
databaseKeysUtils.configureEncryption(this, DB_ALIAS)
}
.name("matrix-sdk-auth.realm")
.modules(AuthRealmModule())
@@ -56,13 +59,22 @@ internal abstract class AuthModule {
.migration(AuthRealmMigration)
.build()
}
@JvmStatic
@Provides
@MatrixScope
fun providesAuthDatabase(context: Context, authSchema: AuthSchema, databaseKeysUtils: DatabaseKeysUtils): AuthDatabase {
val supportFactory = databaseKeysUtils.createEncryptedSQLiteOpenHelperFactory(DB_ALIAS)
val driver = AndroidSqliteDriver(authSchema, context, "matrix-sdk-auth.db", factory = supportFactory)
return AuthDatabase.invoke(driver)
}
}
@Binds
abstract fun bindSessionParamsStore(sessionParamsStore: RealmSessionParamsStore): SessionParamsStore
abstract fun bindSessionParamsStore(sessionParamsStore: SqliteSessionParamsStore): SessionParamsStore
@Binds
abstract fun bindPendingSessionStore(pendingSessionStore: RealmPendingSessionStore): PendingSessionStore
abstract fun bindPendingSessionStore(pendingSessionStore: SqlitePendingSessionStore): PendingSessionStore
@Binds
abstract fun bindAuthenticationService(authenticationService: DefaultAuthenticationService): AuthenticationService

View File

@@ -20,13 +20,7 @@ import android.net.Uri
import dagger.Lazy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.AuthenticationService
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.auth.data.LoginFlowResult
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.auth.data.Versions
import im.vector.matrix.android.api.auth.data.isLoginAndRegistrationSupportedBySdk
import im.vector.matrix.android.api.auth.data.isSupportedBySdk
import im.vector.matrix.android.api.auth.data.*
import im.vector.matrix.android.api.auth.login.LoginWizard
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
import im.vector.matrix.android.api.failure.Failure
@@ -35,7 +29,7 @@ import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.SessionManager
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
import im.vector.matrix.android.internal.auth.data.RiotConfig
import im.vector.matrix.android.internal.auth.db.PendingSessionData
import im.vector.matrix.android.internal.auth.registration.PendingSessionData
import im.vector.matrix.android.internal.auth.login.DefaultLoginWizard
import im.vector.matrix.android.internal.auth.registration.DefaultRegistrationWizard
import im.vector.matrix.android.internal.di.Unauthenticated
@@ -114,7 +108,7 @@ internal class DefaultAuthenticationService @Inject constructor(
}
private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
return withContext(coroutineDispatchers.io) {
return withContext(coroutineDispatchers.computation) {
val authAPI = buildAuthAPI(homeServerConnectionConfig)
// First check the homeserver version

View File

@@ -16,7 +16,7 @@
package im.vector.matrix.android.internal.auth
import im.vector.matrix.android.internal.auth.db.PendingSessionData
import im.vector.matrix.android.internal.auth.registration.PendingSessionData
/**
* Store for elements when doing login or registration

View File

@@ -30,7 +30,7 @@ import im.vector.matrix.android.internal.auth.PendingSessionStore
import im.vector.matrix.android.internal.auth.SessionCreator
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
import im.vector.matrix.android.internal.auth.db.PendingSessionData
import im.vector.matrix.android.internal.auth.registration.PendingSessionData
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationParams
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationResponse
import im.vector.matrix.android.internal.auth.registration.RegisterAddThreePidTask

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package im.vector.matrix.android.internal.auth.db
package im.vector.matrix.android.internal.auth.realm
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.sessionId

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package im.vector.matrix.android.internal.auth.db
package im.vector.matrix.android.internal.auth.realm
import io.realm.annotations.RealmModule

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package im.vector.matrix.android.internal.auth.db
package im.vector.matrix.android.internal.auth.realm
import io.realm.RealmObject

View File

@@ -14,11 +14,12 @@
* limitations under the License.
*/
package im.vector.matrix.android.internal.auth.db
package im.vector.matrix.android.internal.auth.realm
import com.squareup.moshi.Moshi
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.internal.auth.login.ResetPasswordData
import im.vector.matrix.android.internal.auth.registration.PendingSessionData
import im.vector.matrix.android.internal.auth.registration.ThreePidData
import javax.inject.Inject

View File

@@ -14,17 +14,18 @@
* limitations under the License.
*/
package im.vector.matrix.android.internal.auth.db
package im.vector.matrix.android.internal.auth.realm
import im.vector.matrix.android.internal.auth.PendingSessionStore
import im.vector.matrix.android.internal.auth.registration.PendingSessionData
import im.vector.matrix.android.internal.database.awaitTransaction
import im.vector.matrix.android.internal.di.AuthDatabase
import im.vector.matrix.android.internal.di.RealmAuthDatabase
import io.realm.Realm
import io.realm.RealmConfiguration
import javax.inject.Inject
internal class RealmPendingSessionStore @Inject constructor(private val mapper: PendingSessionMapper,
@AuthDatabase
@RealmAuthDatabase
private val realmConfiguration: RealmConfiguration
) : PendingSessionStore {

View File

@@ -14,14 +14,14 @@
* limitations under the License.
*/
package im.vector.matrix.android.internal.auth.db
package im.vector.matrix.android.internal.auth.realm
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.auth.data.sessionId
import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.database.awaitTransaction
import im.vector.matrix.android.internal.di.AuthDatabase
import im.vector.matrix.android.internal.di.RealmAuthDatabase
import io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.exceptions.RealmPrimaryKeyConstraintException
@@ -29,7 +29,7 @@ import timber.log.Timber
import javax.inject.Inject
internal class RealmSessionParamsStore @Inject constructor(private val mapper: SessionParamsMapper,
@AuthDatabase
@RealmAuthDatabase
private val realmConfiguration: RealmConfiguration
) : SessionParamsStore {

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package im.vector.matrix.android.internal.auth.db
package im.vector.matrix.android.internal.auth.realm
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey

View File

@@ -14,8 +14,9 @@
* limitations under the License.
*/
package im.vector.matrix.android.internal.auth.db
package im.vector.matrix.android.internal.auth.realm
import com.squareup.moshi.JsonDataException
import com.squareup.moshi.Moshi
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
@@ -28,6 +29,15 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
private val credentialsAdapter = moshi.adapter(Credentials::class.java)
private val homeServerConnectionConfigAdapter = moshi.adapter(HomeServerConnectionConfig::class.java)
fun map(credentialsJson: String, homeServerConnectionConfigJson: String, isTokenValid: Boolean): SessionParams {
val credentials = credentialsAdapter.fromJson(credentialsJson)
val homeServerConnectionConfig = homeServerConnectionConfigAdapter.fromJson(homeServerConnectionConfigJson)
if (credentials == null || homeServerConnectionConfig == null) {
throw JsonDataException()
}
return SessionParams(credentials, homeServerConnectionConfig, isTokenValid)
}
fun map(entity: SessionParamsEntity?): SessionParams? {
if (entity == null) {
return null

View File

@@ -29,7 +29,6 @@ import im.vector.matrix.android.internal.auth.AuthAPI
import im.vector.matrix.android.internal.auth.PendingSessionStore
import im.vector.matrix.android.internal.auth.SessionCreator
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
import im.vector.matrix.android.internal.auth.db.PendingSessionData
import im.vector.matrix.android.internal.network.RetrofitFactory
import im.vector.matrix.android.internal.task.launchToCallback
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers

View File

@@ -14,12 +14,12 @@
* limitations under the License.
*/
package im.vector.matrix.android.internal.auth.db
package im.vector.matrix.android.internal.auth.registration
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.internal.auth.login.ResetPasswordData
import im.vector.matrix.android.internal.auth.registration.ThreePidData
import java.util.UUID
import java.util.*
/**
* This class holds all pending data when creating a session, either by login or by register

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.matrix.android.internal.auth.sqlite
import com.squareup.sqldelight.db.SqlDriver
import im.vector.matrix.sqldelight.auth.AuthDatabase
import io.realm.Realm
import io.realm.RealmConfiguration
import javax.inject.Inject
internal class AuthSchema @Inject constructor(@im.vector.matrix.android.internal.di.RealmAuthDatabase private val realmConfiguration: RealmConfiguration) : SqlDriver.Schema by AuthDatabase.Schema {
override fun create(driver: SqlDriver) {
AuthDatabase.Schema.create(driver)
AuthDatabase(driver).apply {
val sessionParamsQueries = this.sessionParamsQueries
sessionParamsQueries.transaction {
Realm.getInstance(realmConfiguration).use {
it.where(im.vector.matrix.android.internal.auth.realm.SessionParamsEntity::class.java).findAll().forEach { realmSessionParams ->
sessionParamsQueries.insert(realmSessionParams.toSqlModel())
}
}
}
}
}
private fun im.vector.matrix.android.internal.auth.realm.SessionParamsEntity.toSqlModel(): im.vector.matrix.sqldelight.auth.SessionParamsEntity {
return im.vector.matrix.sqldelight.auth.SessionParamsEntity.Impl(
sessionId,
userId,
credentialsJson,
homeServerConnectionConfigJson,
isTokenValid
)
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.matrix.android.internal.auth.sqlite
import com.squareup.moshi.Moshi
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.internal.auth.login.ResetPasswordData
import im.vector.matrix.android.internal.auth.registration.PendingSessionData
import im.vector.matrix.android.internal.auth.registration.ThreePidData
import im.vector.matrix.sqldelight.auth.PendingSessionEntity
import javax.inject.Inject
internal class SqlitePendingSessionMapper @Inject constructor(moshi: Moshi) {
private val homeServerConnectionConfigAdapter = moshi.adapter(HomeServerConnectionConfig::class.java)
private val resetPasswordDataAdapter = moshi.adapter(ResetPasswordData::class.java)
private val threePidDataAdapter = moshi.adapter(ThreePidData::class.java)
fun map(entity: PendingSessionEntity?): PendingSessionData? {
if (entity == null) {
return null
}
val homeServerConnectionConfig = homeServerConnectionConfigAdapter.fromJson(entity.home_server_connection_config_json)!!
val resetPasswordData = entity.reset_password_data_json?.let { resetPasswordDataAdapter.fromJson(it) }
val threePidData = entity.current_three_pid_data_json?.let { threePidDataAdapter.fromJson(it) }
return PendingSessionData(
homeServerConnectionConfig = homeServerConnectionConfig,
clientSecret = entity.client_secret,
sendAttempt = entity.send_attempts,
resetPasswordData = resetPasswordData,
currentSession = entity.current_session,
isRegistrationStarted = entity.is_registration_started,
currentThreePidData = threePidData)
}
fun map(sessionData: PendingSessionData?): PendingSessionEntity? {
if (sessionData == null) {
return null
}
val homeServerConnectionConfigJson = homeServerConnectionConfigAdapter.toJson(sessionData.homeServerConnectionConfig)
val resetPasswordDataJson = resetPasswordDataAdapter.toJson(sessionData.resetPasswordData)
val currentThreePidDataJson = threePidDataAdapter.toJson(sessionData.currentThreePidData)
return PendingSessionEntity.Impl(
home_server_connection_config_json = homeServerConnectionConfigJson,
client_secret = sessionData.clientSecret,
send_attempts = sessionData.sendAttempt,
reset_password_data_json = resetPasswordDataJson,
current_session = sessionData.currentSession,
is_registration_started = sessionData.isRegistrationStarted,
current_three_pid_data_json = currentThreePidDataJson
)
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.matrix.android.internal.auth.sqlite
import im.vector.matrix.android.internal.auth.PendingSessionStore
import im.vector.matrix.android.internal.auth.registration.PendingSessionData
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.sqldelight.auth.PendingSessionQueries
import im.vector.matrix.sqldelight.auth.AuthDatabase
import kotlinx.coroutines.withContext
import javax.inject.Inject
internal class SqlitePendingSessionStore @Inject constructor(database: AuthDatabase,
private val mapper: SqlitePendingSessionMapper,
private val coroutineDispatchers: MatrixCoroutineDispatchers) : PendingSessionStore {
private val pendingSessionQueries: PendingSessionQueries = database.pendingSessionQueries
override suspend fun savePendingSessionData(pendingSessionData: PendingSessionData) = withContext(coroutineDispatchers.dbTransaction) {
val pendingSessionEntity = mapper.map(pendingSessionData)
if (pendingSessionEntity != null) {
pendingSessionQueries.transaction {
pendingSessionQueries.delete()
pendingSessionQueries.insertOrUpdate(pendingSessionEntity)
}
}
}
override fun getPendingSessionData(): PendingSessionData? {
val pendingSessionEntity = pendingSessionQueries.get().executeAsOneOrNull()
return mapper.map(pendingSessionEntity)
}
override suspend fun delete() = withContext(coroutineDispatchers.dbTransaction) {
pendingSessionQueries.delete()
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.matrix.android.internal.auth.sqlite
import com.squareup.moshi.JsonDataException
import com.squareup.moshi.Moshi
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.auth.data.sessionId
import im.vector.matrix.sqldelight.auth.SessionParamsEntity
import javax.inject.Inject
internal class SqliteSessionParamsMapper @Inject constructor(moshi: Moshi) {
val credentialsAdapter = moshi.adapter(Credentials::class.java)
private val homeServerConnectionConfigAdapter = moshi.adapter(HomeServerConnectionConfig::class.java)
fun map(credentialsJson: String, homeServerConnectionConfigJson: String, isTokenValid: Boolean): SessionParams {
val credentials = credentialsAdapter.fromJson(credentialsJson)
val homeServerConnectionConfig = homeServerConnectionConfigAdapter.fromJson(homeServerConnectionConfigJson)
if (credentials == null || homeServerConnectionConfig == null) {
throw JsonDataException()
}
return SessionParams(credentials, homeServerConnectionConfig, isTokenValid)
}
fun map(sessionParams: SessionParams?): SessionParamsEntity? {
if (sessionParams == null) {
return null
}
val credentialsJson = credentialsAdapter.toJson(sessionParams.credentials)
val homeServerConnectionConfigJson = homeServerConnectionConfigAdapter.toJson(sessionParams.homeServerConnectionConfig)
if (credentialsJson == null || homeServerConnectionConfigJson == null) {
return null
}
return SessionParamsEntity.Impl(
session_id = sessionParams.credentials.sessionId(),
user_id = sessionParams.credentials.userId,
credentials_json = credentialsJson,
home_server_connection_config_json = homeServerConnectionConfigJson,
is_token_valid = sessionParams.isTokenValid)
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.matrix.android.internal.auth.sqlite
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.auth.data.sessionId
import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.sqldelight.auth.SessionParamsQueries
import im.vector.matrix.sqldelight.auth.AuthDatabase
import javax.inject.Inject
internal class SqliteSessionParamsStore @Inject constructor(database: AuthDatabase,
private val mapper: SqliteSessionParamsMapper) : SessionParamsStore {
private val sessionParamsQueries: SessionParamsQueries = database.sessionParamsQueries
override fun get(sessionId: String): SessionParams? {
return sessionParamsQueries.getSessionParamsWithId(sessionId) { credentials_json, home_server_connection_config_json, is_token_valid ->
mapper.map(credentials_json, home_server_connection_config_json, is_token_valid)
}.executeAsOneOrNull()
}
override fun getLast(): SessionParams? {
return getAll().lastOrNull()
}
override fun getAll(): List<SessionParams> {
return sessionParamsQueries.getAllSessionParams { credentials_json, home_server_connection_config_json, is_token_valid ->
mapper.map(credentials_json, home_server_connection_config_json, is_token_valid)
}.executeAsList()
}
override suspend fun save(sessionParams: SessionParams) {
val sessionParamsEntity = mapper.map(sessionParams)
if (sessionParamsEntity != null) {
sessionParamsQueries.insert(sessionParamsEntity)
}
}
override suspend fun setTokenInvalid(sessionId: String) {
sessionParamsQueries.setTokenInvalid(sessionId)
}
override suspend fun updateCredentials(newCredentials: Credentials) {
val newCredentialsJson = mapper.credentialsAdapter.toJson(newCredentials)
sessionParamsQueries.updateCredentials(newCredentialsJson, newCredentials.sessionId())
}
override suspend fun delete(sessionId: String) {
sessionParamsQueries.delete(sessionId)
}
override suspend fun deleteAll() {
sessionParamsQueries.deleteAll()
}
}

View File

@@ -0,0 +1,16 @@
package im.vector.matrix.android.internal.concurrency
import java.util.concurrent.Executor
import java.util.concurrent.Executors
import java.util.concurrent.ThreadFactory
internal class NamedThreadFactory(private val name: String) : ThreadFactory {
override fun newThread(runnable: Runnable): Thread {
return Thread(runnable, name)
}
}
internal fun newNamedSingleThreadExecutor(name: String): Executor {
return Executors.newSingleThreadExecutor(NamedThreadFactory(name))
}

View File

@@ -0,0 +1,118 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.failure.shouldBeRetried
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.LocalEcho
import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.rest.ShareRequestCancellation
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import im.vector.matrix.android.internal.worker.getSessionComponent
import org.greenrobot.eventbus.EventBus
import timber.log.Timber
import javax.inject.Inject
internal class CancelGossipRequestWorker(context: Context,
params: WorkerParameters)
: CoroutineWorker(context, params) {
@JsonClass(generateAdapter = true)
internal data class Params(
val sessionId: String,
val requestId: String,
val recipients: Map<String, List<String>>
) {
companion object {
fun fromRequest(sessionId: String, request: OutgoingGossipingRequest): Params {
return Params(
sessionId = sessionId,
requestId = request.requestId,
recipients = request.recipients
)
}
}
}
@Inject lateinit var sendToDeviceTask: SendToDeviceTask
@Inject lateinit var cryptoStore: IMXCryptoStore
@Inject lateinit var eventBus: EventBus
@Inject lateinit var credentials: Credentials
override suspend fun doWork(): Result {
val errorOutputData = Data.Builder().putBoolean("failed", true).build()
val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.success(errorOutputData)
val sessionComponent = getSessionComponent(params.sessionId)
?: return Result.success(errorOutputData).also {
// TODO, can this happen? should I update local echo?
Timber.e("Unknown Session, cannot send message, sessionId: ${params.sessionId}")
}
sessionComponent.inject(this)
val localId = LocalEcho.createLocalEchoId()
val contentMap = MXUsersDevicesMap<Any>()
val toDeviceContent = ShareRequestCancellation(
requestingDeviceId = credentials.deviceId,
requestId = params.requestId
)
cryptoStore.saveGossipingEvent(Event(
type = EventType.ROOM_KEY_REQUEST,
content = toDeviceContent.toContent(),
senderId = credentials.userId
).also {
it.ageLocalTs = System.currentTimeMillis()
})
params.recipients.forEach { userToDeviceMap ->
userToDeviceMap.value.forEach { deviceId ->
contentMap.setObject(userToDeviceMap.key, deviceId, toDeviceContent)
}
}
try {
cryptoStore.updateOutgoingGossipingRequestState(params.requestId, OutgoingGossipingRequestState.CANCELLING)
sendToDeviceTask.execute(
SendToDeviceTask.Params(
eventType = EventType.ROOM_KEY_REQUEST,
contentMap = contentMap,
transactionId = localId
)
)
cryptoStore.updateOutgoingGossipingRequestState(params.requestId, OutgoingGossipingRequestState.CANCELLED)
return Result.success()
} catch (exception: Throwable) {
return if (exception.shouldBeRetried()) {
Result.retry()
} else {
cryptoStore.updateOutgoingGossipingRequestState(params.requestId, OutgoingGossipingRequestState.FAILED_TO_CANCEL)
Result.success(errorOutputData)
}
}
}
}

View File

@@ -16,9 +16,15 @@
package im.vector.matrix.android.internal.crypto
import android.content.Context
import android.util.Log
import com.squareup.sqldelight.ColumnAdapter
import com.squareup.sqldelight.android.AndroidSqliteDriver
import com.squareup.sqldelight.logs.LogSqliteDriver
import dagger.Binds
import dagger.Module
import dagger.Provides
import im.vector.matrix.android.BuildConfig
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
import im.vector.matrix.android.internal.crypto.api.CryptoApi
@@ -55,9 +61,9 @@ import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreRoomSessio
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreMigration
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
import im.vector.matrix.android.internal.crypto.store.db.SqlCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultClaimOneTimeKeysForUsersDevice
import im.vector.matrix.android.internal.crypto.tasks.DefaultDeleteDeviceTask
@@ -66,6 +72,7 @@ import im.vector.matrix.android.internal.crypto.tasks.DefaultDownloadKeysForUser
import im.vector.matrix.android.internal.crypto.tasks.DefaultEncryptEventTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultGetDeviceInfoTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultGetDevicesTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultInitializeCrossSigningTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultSendToDeviceTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultSendVerificationMessageTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultSetDeviceNameTask
@@ -78,19 +85,22 @@ import im.vector.matrix.android.internal.crypto.tasks.DownloadKeysForUsersTask
import im.vector.matrix.android.internal.crypto.tasks.EncryptEventTask
import im.vector.matrix.android.internal.crypto.tasks.GetDeviceInfoTask
import im.vector.matrix.android.internal.crypto.tasks.GetDevicesTask
import im.vector.matrix.android.internal.crypto.tasks.InitializeCrossSigningTask
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
import im.vector.matrix.android.internal.crypto.tasks.SendVerificationMessageTask
import im.vector.matrix.android.internal.crypto.tasks.SetDeviceNameTask
import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask
import im.vector.matrix.android.internal.crypto.tasks.UploadSignaturesTask
import im.vector.matrix.android.internal.crypto.tasks.UploadSigningKeysTask
import im.vector.matrix.android.internal.database.RealmKeysUtils
import im.vector.matrix.android.internal.di.CryptoDatabase
import im.vector.matrix.android.internal.database.DatabaseKeysUtils
import im.vector.matrix.android.internal.di.RealmCryptoDatabase
import im.vector.matrix.android.internal.di.SessionFilesDirectory
import im.vector.matrix.android.internal.di.UserMd5
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.cache.ClearCacheTask
import im.vector.matrix.android.internal.session.cache.RealmClearCacheTask
import im.vector.matrix.sqldelight.crypto.CrossSigningInfoEntity
import im.vector.matrix.sqldelight.crypto.CryptoDatabase
import io.realm.RealmConfiguration
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
@@ -106,15 +116,15 @@ internal abstract class CryptoModule {
@JvmStatic
@Provides
@CryptoDatabase
@RealmCryptoDatabase
@SessionScope
fun providesRealmConfiguration(@SessionFilesDirectory directory: File,
@UserMd5 userMd5: String,
realmKeysUtils: RealmKeysUtils): RealmConfiguration {
databaseKeysUtils: DatabaseKeysUtils): RealmConfiguration {
return RealmConfiguration.Builder()
.directory(directory)
.apply {
realmKeysUtils.configureEncryption(this, getKeyAlias(userMd5))
databaseKeysUtils.configureEncryption(this, getKeyAlias(userMd5))
}
.name("crypto_store.realm")
.modules(RealmCryptoStoreModule())
@@ -123,6 +133,31 @@ internal abstract class CryptoModule {
.build()
}
@JvmStatic
@Provides
@SessionScope
fun providesCryptoDatabase(context: Context, @SessionFilesDirectory directory: File): CryptoDatabase {
val name = "${directory.name}-matrix-sdk-crypto.db"
val driver = if (BuildConfig.DEBUG) {
LogSqliteDriver(AndroidSqliteDriver(CryptoDatabase.Schema, context, name)) { log ->
Log.d("SQLite", log)
}
} else {
AndroidSqliteDriver(CryptoDatabase.Schema, context, name)
}
val listOfStringAdapter = object : ColumnAdapter<List<String>, String> {
override fun decode(databaseValue: String) = databaseValue.split(",")
override fun encode(value: List<String>) = value.joinToString(separator = ",")
}
return CryptoDatabase(
driver = driver,
crossSigningInfoEntityAdapter = CrossSigningInfoEntity.Adapter(
usagesAdapter = listOfStringAdapter
)
)
}
@JvmStatic
@Provides
@SessionScope
@@ -132,8 +167,8 @@ internal abstract class CryptoModule {
@JvmStatic
@Provides
@CryptoDatabase
fun providesClearCacheTask(@CryptoDatabase
@RealmCryptoDatabase
fun providesClearCacheTask(@RealmCryptoDatabase
realmConfiguration: RealmConfiguration): ClearCacheTask {
return RealmClearCacheTask(realmConfiguration)
}
@@ -241,8 +276,11 @@ internal abstract class CryptoModule {
abstract fun bindCrossSigningService(service: DefaultCrossSigningService): CrossSigningService
@Binds
abstract fun bindCryptoStore(store: RealmCryptoStore): IMXCryptoStore
abstract fun bindCryptoStore(store: SqlCryptoStore): IMXCryptoStore
@Binds
abstract fun bindComputeShieldTrustTask(task: DefaultComputeTrustTask): ComputeTrustTask
@Binds
abstract fun bindInitializeCrossSigningTask(task: DefaultInitializeCrossSigningTask): InitializeCrossSigningTask
}

View File

@@ -23,7 +23,6 @@ import android.os.Handler
import android.os.Looper
import androidx.lifecycle.LiveData
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.NoOpMatrixCallback
@@ -33,7 +32,9 @@ import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.listeners.ProgressListener
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.crypto.keyshare.RoomKeysRequestListener
import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
import im.vector.matrix.android.api.session.crypto.keyshare.GossipingRequestListener
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
@@ -50,29 +51,19 @@ import im.vector.matrix.android.internal.crypto.algorithms.olm.MXOlmEncryptionFa
import im.vector.matrix.android.internal.crypto.crosssigning.DefaultCrossSigningService
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
import im.vector.matrix.android.internal.crypto.keysbackup.DefaultKeysBackupService
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
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.*
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent
import im.vector.matrix.android.internal.crypto.model.event.SecretSendEventContent
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import im.vector.matrix.android.internal.crypto.model.toRest
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.DeleteDeviceTask
import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceWithUserPasswordTask
import im.vector.matrix.android.internal.crypto.tasks.GetDeviceInfoTask
import im.vector.matrix.android.internal.crypto.tasks.GetDevicesTask
import im.vector.matrix.android.internal.crypto.tasks.SetDeviceNameTask
import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask
import im.vector.matrix.android.internal.crypto.tasks.*
import im.vector.matrix.android.internal.crypto.verification.DefaultVerificationService
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.whereType
import im.vector.matrix.android.internal.database.repository.CurrentStateEventDataSource
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.extensions.foldToCallback
import im.vector.matrix.android.internal.session.SessionScope
@@ -80,16 +71,12 @@ import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembers
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
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.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 im.vector.matrix.android.internal.util.fetchCopied
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import im.vector.matrix.sqldelight.session.SessionDatabase
import kotlinx.coroutines.*
import org.matrix.olm.OlmManager
import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean
@@ -115,6 +102,7 @@ internal class DefaultCryptoService @Inject constructor(
private val myDeviceInfoHolder: Lazy<MyDeviceInfoHolder>,
// the crypto store
private val cryptoStore: IMXCryptoStore,
// Olm device
private val olmDevice: MXOlmDevice,
// Set of parameters used to configure/customize the end-to-end crypto.
@@ -134,9 +122,9 @@ internal class DefaultCryptoService @Inject constructor(
private val crossSigningService: DefaultCrossSigningService,
//
private val incomingRoomKeyRequestManager: IncomingRoomKeyRequestManager,
private val incomingGossipingRequestManager: IncomingGossipingRequestManager,
//
private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager,
private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
// Actions
private val setDeviceVerificationAction: SetDeviceVerificationAction,
private val megolmSessionDataImporter: MegolmSessionDataImporter,
@@ -152,7 +140,7 @@ internal class DefaultCryptoService @Inject constructor(
private val setDeviceNameTask: SetDeviceNameTask,
private val uploadKeysTask: UploadKeysTask,
private val loadRoomMembersTask: LoadRoomMembersTask,
private val monarchy: Monarchy,
private val sessionDatabase: SessionDatabase,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val taskExecutor: TaskExecutor,
private val cryptoCoroutineScope: CoroutineScope
@@ -171,16 +159,16 @@ internal class DefaultCryptoService @Inject constructor(
fun onStateEvent(roomId: String, event: Event) {
when {
event.getClearType() == EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
event.getClearType() == EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
event.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
}
}
fun onLiveEvent(roomId: String, event: Event) {
when {
event.getClearType() == EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
event.getClearType() == EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
event.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
}
}
@@ -188,6 +176,7 @@ internal class DefaultCryptoService @Inject constructor(
override fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>) {
setDeviceNameTask
.configureWith(SetDeviceNameTask.Params(deviceId, deviceName)) {
this.executionThread = TaskThread.CRYPTO
this.callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
// bg refresh of crypto device
@@ -206,6 +195,7 @@ internal class DefaultCryptoService @Inject constructor(
override fun deleteDevice(deviceId: String, callback: MatrixCallback<Unit>) {
deleteDeviceTask
.configureWith(DeleteDeviceTask.Params(deviceId)) {
this.executionThread = TaskThread.CRYPTO
this.callback = callback
}
.executeBy(taskExecutor)
@@ -214,6 +204,7 @@ internal class DefaultCryptoService @Inject constructor(
override fun deleteDeviceWithUserPassword(deviceId: String, authSession: String?, password: String, callback: MatrixCallback<Unit>) {
deleteDeviceWithUserPasswordTask
.configureWith(DeleteDeviceWithUserPasswordTask.Params(deviceId, authSession, password)) {
this.executionThread = TaskThread.CRYPTO
this.callback = callback
}
.executeBy(taskExecutor)
@@ -230,6 +221,7 @@ internal class DefaultCryptoService @Inject constructor(
override fun getDevicesList(callback: MatrixCallback<DevicesListResponse>) {
getDevicesTask
.configureWith {
// this.executionThread = TaskThread.CRYPTO
this.callback = callback
}
.executeBy(taskExecutor)
@@ -238,6 +230,7 @@ internal class DefaultCryptoService @Inject constructor(
override fun getDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>) {
getDeviceInfoTask
.configureWith(GetDeviceInfoTask.Params(deviceId)) {
this.executionThread = TaskThread.CRYPTO
this.callback = callback
}
.executeBy(taskExecutor)
@@ -300,14 +293,13 @@ internal class DefaultCryptoService @Inject constructor(
runCatching {
uploadDeviceKeys()
oneTimeKeysUploader.maybeUploadOneTimeKeys()
outgoingRoomKeyRequestManager.start()
keysBackupService.checkAndStartKeysBackup()
if (isInitialSync) {
// refresh the devices list for each known room members
deviceListManager.invalidateAllDeviceLists()
deviceListManager.refreshOutdatedDeviceLists()
} else {
incomingRoomKeyRequestManager.processReceivedRoomKeyRequests()
incomingGossipingRequestManager.processReceivedGossipingRequests()
}
}.fold(
{
@@ -328,8 +320,6 @@ internal class DefaultCryptoService @Inject constructor(
fun close() = runBlocking(coroutineDispatchers.crypto) {
cryptoCoroutineScope.coroutineContext.cancelChildren(CancellationException("Closing crypto module"))
outgoingRoomKeyRequestManager.stop()
olmDevice.release()
cryptoStore.close()
}
@@ -368,7 +358,7 @@ internal class DefaultCryptoService @Inject constructor(
// Make sure we process to-device messages before generating new one-time-keys #2782
deviceListManager.refreshOutdatedDeviceLists()
oneTimeKeysUploader.maybeUploadOneTimeKeys()
incomingRoomKeyRequestManager.processReceivedRoomKeyRequests()
incomingGossipingRequestManager.processReceivedGossipingRequests()
}
}
}
@@ -499,7 +489,7 @@ internal class DefaultCryptoService @Inject constructor(
val alg: IMXEncrypting = when (algorithm) {
MXCRYPTO_ALGORITHM_MEGOLM -> megolmEncryptionFactory.create(roomId)
else -> olmEncryptionFactory.create(roomId)
else -> olmEncryptionFactory.create(roomId)
}
synchronized(roomEncryptors) {
@@ -533,12 +523,10 @@ internal class DefaultCryptoService @Inject constructor(
* @return true if the room is encrypted with algorithm MXCRYPTO_ALGORITHM_MEGOLM
*/
override fun isRoomEncrypted(roomId: String): Boolean {
val encryptionEvent = monarchy.fetchCopied { realm ->
EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION)
.contains(EventEntityFields.CONTENT, "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"")
.findFirst()
}
return encryptionEvent != null
return sessionDatabase.eventQueries
.findWithContent(roomId = roomId, content = "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"")
.executeAsList()
.firstOrNull() != null
}
/**
@@ -627,7 +615,7 @@ internal class DefaultCryptoService @Inject constructor(
*/
@Throws(MXCryptoError::class)
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
return internalDecryptEvent(event, timeline)
return internalDecryptEvent(event, timeline)
}
/**
@@ -688,15 +676,26 @@ internal class DefaultCryptoService @Inject constructor(
* @param event the event
*/
fun onToDeviceEvent(event: Event) {
// event have already been decrypted
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
when (event.getClearType()) {
EventType.ROOM_KEY, EventType.FORWARDED_ROOM_KEY -> {
cryptoStore.saveGossipingEvent(event)
// Keys are imported directly, not waiting for end of sync
onRoomKeyEvent(event)
}
EventType.ROOM_KEY_REQUEST -> {
incomingRoomKeyRequestManager.onRoomKeyRequestEvent(event)
EventType.REQUEST_SECRET,
EventType.ROOM_KEY_REQUEST -> {
// save audit trail
cryptoStore.saveGossipingEvent(event)
// Requests are stacked, and will be handled one by one at the end of the sync (onSyncComplete)
incomingGossipingRequestManager.onGossipingRequestEvent(event)
}
else -> {
EventType.SEND_SECRET -> {
cryptoStore.saveGossipingEvent(event)
onSecretSendReceived(event)
}
else -> {
// ignore
}
}
@@ -710,18 +709,59 @@ internal class DefaultCryptoService @Inject constructor(
*/
private fun onRoomKeyEvent(event: Event) {
val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>() ?: return
Timber.v("## GOSSIP onRoomKeyEvent() : type<${event.type}> , sessionId<${roomKeyContent.sessionId}>")
if (roomKeyContent.roomId.isNullOrEmpty() || roomKeyContent.algorithm.isNullOrEmpty()) {
Timber.e("## onRoomKeyEvent() : missing fields")
Timber.e("## GOSSIP onRoomKeyEvent() : missing fields")
return
}
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(roomKeyContent.roomId, roomKeyContent.algorithm)
if (alg == null) {
Timber.e("## onRoomKeyEvent() : Unable to handle keys for ${roomKeyContent.algorithm}")
Timber.e("## GOSSIP onRoomKeyEvent() : Unable to handle keys for ${roomKeyContent.algorithm}")
return
}
alg.onRoomKeyEvent(event, keysBackupService)
}
private fun onSecretSendReceived(event: Event) {
Timber.i("## GOSSIP onSecretSend() : onSecretSendReceived ${event.content?.get("sender_key")}")
if (!event.isEncrypted()) {
// secret send messages must be encrypted
Timber.e("## GOSSIP onSecretSend() :Received unencrypted secret send event")
return
}
// Was that sent by us?
if (event.senderId != credentials.userId) {
Timber.e("## GOSSIP onSecretSend() : Ignore secret from other user ${event.senderId}")
return
}
val secretContent = event.getClearContent().toModel<SecretSendEventContent>() ?: return
val existingRequest = cryptoStore
.getOutgoingSecretKeyRequests().firstOrNull { it.requestId == secretContent.requestId }
if (existingRequest == null) {
Timber.i("## GOSSIP onSecretSend() : Ignore secret that was not requested: ${secretContent.requestId}")
return
}
when (existingRequest.secretName) {
SELF_SIGNING_KEY_SSSS_NAME -> {
crossSigningService.onSecretSSKGossip(secretContent.secretValue)
return
}
USER_SIGNING_KEY_SSSS_NAME -> {
crossSigningService.onSecretUSKGossip(secretContent.secretValue)
return
}
else -> {
// Ask to application layer?
Timber.v("## onSecretSend() : secret not handled by SDK")
}
}
}
/**
* Handle an m.room.encryption event.
*
@@ -735,25 +775,21 @@ internal class DefaultCryptoService @Inject constructor(
val userIds = getRoomUserIds(roomId)
setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds)
} catch (throwable: Throwable) {
Timber.e(throwable)
Timber.e(throwable, "## onRoomEncryptionEvent ERROR FAILED TO SETUP CRYPTO ")
}
}
}
private fun getRoomUserIds(roomId: String): List<String> {
var userIds: List<String> = emptyList()
monarchy.doWithRealm { realm ->
// Check whether the event content must be encrypted for the invited members.
val encryptForInvitedMembers = isEncryptionEnabledForInvitedUser()
&& shouldEncryptForInvitedMembers(roomId)
// Check whether the event content must be encrypted for the invited members.
val encryptForInvitedMembers = isEncryptionEnabledForInvitedUser()
&& shouldEncryptForInvitedMembers(roomId)
userIds = if (encryptForInvitedMembers) {
RoomMemberHelper(realm, roomId).getActiveRoomMemberIds()
} else {
RoomMemberHelper(realm, roomId).getJoinedRoomMemberIds()
}
return if (encryptForInvitedMembers) {
RoomMemberHelper(sessionDatabase, roomId).getActiveRoomMemberIds()
} else {
RoomMemberHelper(sessionDatabase, roomId).getJoinedRoomMemberIds()
}
return userIds
}
/**
@@ -997,14 +1033,14 @@ internal class DefaultCryptoService @Inject constructor(
setRoomBlacklistUnverifiedDevices(roomId, false)
}
// TODO Check if this method is still necessary
// TODO Check if this method is still necessary
/**
* Cancel any earlier room key request
*
* @param requestBody requestBody
*/
override fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody) {
outgoingRoomKeyRequestManager.cancelRoomKeyRequest(requestBody)
outgoingGossipingRequestManager.cancelRoomKeyRequest(requestBody)
}
/**
@@ -1013,38 +1049,54 @@ internal class DefaultCryptoService @Inject constructor(
* @param event the event to decrypt again.
*/
override fun reRequestRoomKeyForEvent(event: Event) {
val wireContent = event.content
if (wireContent == null) {
val wireContent = event.content.toModel<EncryptedEventContent>() ?: return Unit.also {
Timber.e("## reRequestRoomKeyForEvent Failed to re-request key, null content")
return
}
val requestBody = RoomKeyRequestBody(
algorithm = wireContent["algorithm"]?.toString(),
algorithm = wireContent.algorithm,
roomId = event.roomId,
senderKey = wireContent["sender_key"]?.toString(),
sessionId = wireContent["session_id"]?.toString()
senderKey = wireContent.senderKey,
sessionId = wireContent.sessionId
)
outgoingRoomKeyRequestManager.resendRoomKeyRequest(requestBody)
outgoingGossipingRequestManager.resendRoomKeyRequest(requestBody)
}
override fun requestRoomKeyForEvent(event: Event) {
val wireContent = event.content.toModel<EncryptedEventContent>() ?: return Unit.also {
Timber.e("## requestRoomKeyForEvent Failed to request key, null content eventId: ${event.eventId}")
}
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
if (!isStarted()) {
Timber.v("## requestRoomKeyForEvent() : wait after e2e init")
internalStart(false)
}
roomDecryptorProvider
.getOrCreateRoomDecryptor(event.roomId, wireContent.algorithm)
?.requestKeysForEvent(event) ?: run {
Timber.v("## requestRoomKeyForEvent() : No room decryptor for roomId:${event.roomId} algorithm:${wireContent.algorithm}")
}
}
}
/**
* Add a RoomKeysRequestListener listener.
* Add a GossipingRequestListener listener.
*
* @param listener listener
*/
override fun addRoomKeysRequestListener(listener: RoomKeysRequestListener) {
incomingRoomKeyRequestManager.addRoomKeysRequestListener(listener)
override fun addRoomKeysRequestListener(listener: GossipingRequestListener) {
incomingGossipingRequestManager.addRoomKeysRequestListener(listener)
}
/**
* Add a RoomKeysRequestListener listener.
* Add a GossipingRequestListener listener.
*
* @param listener listener
*/
override fun removeRoomKeysRequestListener(listener: RoomKeysRequestListener) {
incomingRoomKeyRequestManager.removeRoomKeysRequestListener(listener)
override fun removeRoomKeysRequestListener(listener: GossipingRequestListener) {
incomingGossipingRequestManager.removeRoomKeysRequestListener(listener)
}
/**
@@ -1084,11 +1136,23 @@ internal class DefaultCryptoService @Inject constructor(
override fun removeSessionListener(listener: NewSessionListener) {
roomDecryptorProvider.removeSessionListener(listener)
}
/* ==========================================================================================
* DEBUG INFO
* ========================================================================================== */
/* ==========================================================================================
* DEBUG INFO
* ========================================================================================== */
override fun toString(): String {
return "DefaultCryptoService of " + credentials.userId + " (" + credentials.deviceId + ")"
}
override fun getOutgoingRoomKeyRequest(): List<OutgoingRoomKeyRequest> {
return cryptoStore.getOutgoingRoomKeyRequests()
}
override fun getIncomingRoomKeyRequest(): List<IncomingRoomKeyRequest> {
return cryptoStore.getIncomingRoomKeyRequests()
}
override fun getGossipingEventsTrail(): List<Event> {
return cryptoStore.getGossipingEventsTrail()
}
}

View File

@@ -361,13 +361,13 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
// Handle cross signing keys update
val masterKey = response.masterKeys?.get(userId)?.toCryptoModel().also {
Timber.d("## CrossSigning : Got keys for $userId : MSK ${it?.unpaddedBase64PublicKey}")
Timber.v("## CrossSigning : Got keys for $userId : MSK ${it?.unpaddedBase64PublicKey}")
}
val selfSigningKey = response.selfSigningKeys?.get(userId)?.toCryptoModel()?.also {
Timber.d("## CrossSigning : Got keys for $userId : SSK ${it.unpaddedBase64PublicKey}")
Timber.v("## CrossSigning : Got keys for $userId : SSK ${it.unpaddedBase64PublicKey}")
}
val userSigningKey = response.userSigningKeys?.get(userId)?.toCryptoModel()?.also {
Timber.d("## CrossSigning : Got keys for $userId : USK ${it.unpaddedBase64PublicKey}")
Timber.v("## CrossSigning : Got keys for $userId : USK ${it.unpaddedBase64PublicKey}")
}
cryptoStore.storeUserCrossSigningKeys(
userId,

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto
enum class GossipRequestType {
KEY,
SECRET
}
enum class GossipingRequestState {
NONE,
PENDING,
REJECTED,
ACCEPTING,
ACCEPTED,
FAILED_TO_ACCEPTED,
// USER_REJECTED,
UNABLE_TO_PROCESS,
CANCELLED_BY_REQUESTER,
RE_REQUESTED
}
enum class OutgoingGossipingRequestState {
UNSENT,
SENDING,
SENT,
CANCELLING,
CANCELLED,
FAILED_TO_SEND,
FAILED_TO_CANCEL
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto
import androidx.work.BackoffPolicy
import androidx.work.Data
import androidx.work.ExistingWorkPolicy
import androidx.work.ListenableWorker
import androidx.work.OneTimeWorkRequest
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.di.WorkManagerProvider
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.util.CancelableWork
import im.vector.matrix.android.internal.worker.startChain
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@SessionScope
internal class GossipingWorkManager @Inject constructor(
private val workManagerProvider: WorkManagerProvider
) {
inline fun <reified W : ListenableWorker> createWork(data: Data, startChain: Boolean): OneTimeWorkRequest {
return workManagerProvider.matrixOneTimeWorkRequestBuilder<W>()
.setConstraints(WorkManagerProvider.workConstraints)
.startChain(startChain)
.setInputData(data)
.setBackoffCriteria(BackoffPolicy.LINEAR, 10_000L, TimeUnit.MILLISECONDS)
.build()
}
// Prevent sending queue to stay broken after app restart
// The unique queue id will stay the same as long as this object is instanciated
val queueSuffixApp = System.currentTimeMillis()
fun postWork(workRequest: OneTimeWorkRequest, policy: ExistingWorkPolicy = ExistingWorkPolicy.APPEND): Cancelable {
workManagerProvider.workManager
.beginUniqueWork(this::class.java.name + "_$queueSuffixApp", policy, workRequest)
.enqueue()
return CancelableWork(workManagerProvider.workManager, workRequest.id)
}
}

View File

@@ -0,0 +1,401 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.crypto.MXCryptoConfig
import im.vector.matrix.android.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
import im.vector.matrix.android.api.session.crypto.keyshare.GossipingRequestListener
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.internal.crypto.crosssigning.toBase64NoPadding
import im.vector.matrix.android.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
import im.vector.matrix.android.internal.crypto.model.rest.GossipingDefaultContent
import im.vector.matrix.android.internal.crypto.model.rest.GossipingToDeviceObject
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.di.SessionId
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import timber.log.Timber
import javax.inject.Inject
@SessionScope
internal class IncomingGossipingRequestManager @Inject constructor(
@SessionId private val sessionId: String,
private val credentials: Credentials,
private val cryptoStore: IMXCryptoStore,
private val cryptoConfig: MXCryptoConfig,
private val gossipingWorkManager: GossipingWorkManager,
private val roomDecryptorProvider: RoomDecryptorProvider) {
// list of IncomingRoomKeyRequests/IncomingRoomKeyRequestCancellations
// we received in the current sync.
private val receivedGossipingRequests = ArrayList<IncomingShareRequestCommon>()
private val receivedRequestCancellations = ArrayList<IncomingRequestCancellation>()
// the listeners
private val gossipingRequestListeners: MutableSet<GossipingRequestListener> = HashSet()
init {
receivedGossipingRequests.addAll(cryptoStore.getPendingIncomingGossipingRequests())
}
// Recently verified devices (map of deviceId and timestamp)
private val recentlyVerifiedDevices = HashMap<String, Long>()
/**
* Called when a session has been verified.
* This information can be used by the manager to decide whether or not to fullfil gossiping requests
*/
fun onVerificationCompleteForDevice(deviceId: String) {
// For now we just keep an in memory cache
synchronized(recentlyVerifiedDevices) {
recentlyVerifiedDevices[deviceId] = System.currentTimeMillis()
}
}
private fun hasBeenVerifiedLessThanFiveMinutesFromNow(deviceId: String): Boolean {
val verifTimestamp: Long?
synchronized(recentlyVerifiedDevices) {
verifTimestamp = recentlyVerifiedDevices[deviceId]
}
if (verifTimestamp == null) return false
val age = System.currentTimeMillis() - verifTimestamp
return age < FIVE_MINUTES_IN_MILLIS
}
/**
* Called when we get an m.room_key_request event
* It must be called on CryptoThread
*
* @param event the announcement event.
*/
fun onGossipingRequestEvent(event: Event) {
Timber.v("## GOSSIP onGossipingRequestEvent type ${event.type} from user ${event.senderId}")
val roomKeyShare = event.getClearContent().toModel<GossipingDefaultContent>()
val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it }
when (roomKeyShare?.action) {
GossipingToDeviceObject.ACTION_SHARE_REQUEST -> {
if (event.getClearType() == EventType.REQUEST_SECRET) {
IncomingSecretShareRequest.fromEvent(event)?.let {
if (event.senderId == credentials.userId && it.deviceId == credentials.deviceId) {
// ignore, it was sent by me as *
Timber.v("## GOSSIP onGossipingRequestEvent type ${event.type} ignore remote echo")
} else {
// save in DB
cryptoStore.storeIncomingGossipingRequest(it, ageLocalTs)
receivedGossipingRequests.add(it)
}
}
} else if (event.getClearType() == EventType.ROOM_KEY_REQUEST) {
IncomingRoomKeyRequest.fromEvent(event)?.let {
if (event.senderId == credentials.userId && it.deviceId == credentials.deviceId) {
// ignore, it was sent by me as *
Timber.v("## GOSSIP onGossipingRequestEvent type ${event.type} ignore remote echo")
} else {
cryptoStore.storeIncomingGossipingRequest(it, ageLocalTs)
receivedGossipingRequests.add(it)
}
}
}
}
GossipingToDeviceObject.ACTION_SHARE_CANCELLATION -> {
IncomingRequestCancellation.fromEvent(event)?.let {
receivedRequestCancellations.add(it)
}
}
else -> {
Timber.e("## GOSSIP onGossipingRequestEvent() : unsupported action ${roomKeyShare?.action}")
}
}
}
/**
* Process any m.room_key_request or m.secret.request events which were queued up during the
* current sync.
* It must be called on CryptoThread
*/
fun processReceivedGossipingRequests() {
val roomKeyRequestsToProcess = receivedGossipingRequests.toList()
receivedGossipingRequests.clear()
for (request in roomKeyRequestsToProcess) {
if (request is IncomingRoomKeyRequest) {
processIncomingRoomKeyRequest(request)
} else if (request is IncomingSecretShareRequest) {
processIncomingSecretShareRequest(request)
}
}
var receivedRequestCancellations: List<IncomingRequestCancellation>? = null
synchronized(this.receivedRequestCancellations) {
if (this.receivedRequestCancellations.isNotEmpty()) {
receivedRequestCancellations = this.receivedRequestCancellations.toList()
this.receivedRequestCancellations.clear()
}
}
receivedRequestCancellations?.forEach { request ->
Timber.v("## GOSSIP processReceivedGossipingRequests() : m.room_key_request cancellation $request")
// we should probably only notify the app of cancellations we told it
// about, but we don't currently have a record of that, so we just pass
// everything through.
if (request.userId == credentials.userId && request.deviceId == credentials.deviceId) {
// ignore remote echo
return@forEach
}
val matchingIncoming = cryptoStore.getIncomingRoomKeyRequest(request.userId ?: "", request.deviceId ?: "", request.requestId ?: "")
if (matchingIncoming == null) {
// ignore that?
return@forEach
} else {
// If it was accepted from this device, keep the information, do not mark as cancelled
if (matchingIncoming.state != GossipingRequestState.ACCEPTED) {
onRoomKeyRequestCancellation(request)
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.CANCELLED_BY_REQUESTER)
}
}
}
}
private fun processIncomingRoomKeyRequest(request: IncomingRoomKeyRequest) {
val userId = request.userId
val deviceId = request.deviceId
val body = request.requestBody
val roomId = body!!.roomId
val alg = body.algorithm
Timber.v("## GOSSIP processIncomingRoomKeyRequest from $userId:$deviceId for $roomId / ${body.sessionId} id ${request.requestId}")
if (userId == null || credentials.userId != userId) {
// TODO: determine if we sent this device the keys already: in
Timber.w("## GOSSIP processReceivedGossipingRequests() : Ignoring room key request from other user for now")
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
return
}
// 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)
if (null == decryptor) {
Timber.w("## GOSSIP processReceivedGossipingRequests() : room key request for unknown $alg in room $roomId")
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
return
}
if (!decryptor.hasKeysForKeyRequest(request)) {
Timber.w("## GOSSIP processReceivedGossipingRequests() : room key request for unknown session ${body.sessionId!!}")
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
return
}
if (credentials.deviceId == deviceId && credentials.userId == userId) {
Timber.v("## GOSSIP processReceivedGossipingRequests() : oneself device - ignored")
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
return
}
request.share = Runnable {
decryptor.shareKeysWithDevice(request)
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTED)
}
request.ignore = Runnable {
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
}
// if the device is verified already, share the keys
val device = cryptoStore.getUserDevice(userId, deviceId!!)
if (device != null) {
if (device.isVerified) {
Timber.v("## GOSSIP processReceivedGossipingRequests() : device is already verified: sharing keys")
request.share?.run()
return
}
if (device.isBlocked) {
Timber.v("## GOSSIP processReceivedGossipingRequests() : device is blocked -> ignored")
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
return
}
}
// As per config we automatically discard untrusted devices request
if (cryptoConfig.discardRoomKeyRequestsFromUntrustedDevices) {
Timber.v("## processReceivedGossipingRequests() : discardRoomKeyRequestsFromUntrustedDevices")
// At this point the device is unknown, we don't want to bother user with that
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
return
}
// Pass to application layer to decide what to do
onRoomKeyRequest(request)
}
private fun processIncomingSecretShareRequest(request: IncomingSecretShareRequest) {
val secretName = request.secretName ?: return Unit.also {
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
Timber.v("## GOSSIP processIncomingSecretShareRequest() : Missing secret name")
}
val userId = request.userId
if (userId == null || credentials.userId != userId) {
Timber.e("## GOSSIP processIncomingSecretShareRequest() : Ignoring secret share request from other users")
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
return
}
val deviceId = request.deviceId
?: return Unit.also {
Timber.e("## GOSSIP processIncomingSecretShareRequest() : Malformed request, no ")
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
}
val device = cryptoStore.getUserDevice(userId, deviceId)
?: return Unit.also {
Timber.e("## GOSSIP processIncomingSecretShareRequest() : Received secret share request from unknown device ${request.deviceId}")
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
}
if (!device.isVerified || device.isBlocked) {
Timber.v("## GOSSIP processIncomingSecretShareRequest() : Ignoring secret share request from untrusted/blocked session $device")
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
return
}
val isDeviceLocallyVerified = cryptoStore.getUserDevice(userId, deviceId)?.trustLevel?.isLocallyVerified()
// Should SDK always Silently reject any request for the master key?
when (secretName) {
SELF_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.selfSigned
USER_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.user
KEYBACKUP_SECRET_SSSS_NAME -> cryptoStore.getKeyBackupRecoveryKeyInfo()?.recoveryKey
?.let {
extractCurveKeyFromRecoveryKey(it)?.toBase64NoPadding()
}
else -> null
}?.let { secretValue ->
Timber.i("## GOSSIP processIncomingSecretShareRequest() : Sharing secret $secretName with $device locally trusted")
if (isDeviceLocallyVerified == true && hasBeenVerifiedLessThanFiveMinutesFromNow(deviceId)) {
val params = SendGossipWorker.Params(
sessionId = sessionId,
secretValue = secretValue,
request = request
)
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTING)
val workRequest = gossipingWorkManager.createWork<SendGossipWorker>(WorkerParamsFactory.toData(params), true)
gossipingWorkManager.postWork(workRequest)
} else {
Timber.v("## GOSSIP processIncomingSecretShareRequest() : Can't share secret $secretName with $device, verification too old")
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
}
return
}
Timber.v("## GOSSIP processIncomingSecretShareRequest() : $secretName unknown at SDK level, asking to app layer")
request.ignore = Runnable {
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
}
request.share = { secretValue ->
val params = SendGossipWorker.Params(
sessionId = userId,
secretValue = secretValue,
request = request
)
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTING)
val workRequest = gossipingWorkManager.createWork<SendGossipWorker>(WorkerParamsFactory.toData(params), true)
gossipingWorkManager.postWork(workRequest)
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTED)
}
onShareRequest(request)
}
/**
* Dispatch onRoomKeyRequest
*
* @param request the request
*/
private fun onRoomKeyRequest(request: IncomingRoomKeyRequest) {
synchronized(gossipingRequestListeners) {
for (listener in gossipingRequestListeners) {
try {
listener.onRoomKeyRequest(request)
} catch (e: Exception) {
Timber.e(e, "## onRoomKeyRequest() failed")
}
}
}
}
/**
* Ask for a value to the listeners, and take the first one
*/
private fun onShareRequest(request: IncomingSecretShareRequest) {
synchronized(gossipingRequestListeners) {
for (listener in gossipingRequestListeners) {
try {
if (listener.onSecretShareRequest(request)) {
return
}
} catch (e: Exception) {
Timber.e(e, "## GOSSIP onRoomKeyRequest() failed")
}
}
}
// Not handled, ignore
request.ignore?.run()
}
/**
* A room key request cancellation has been received.
*
* @param request the cancellation request
*/
private fun onRoomKeyRequestCancellation(request: IncomingRequestCancellation) {
synchronized(gossipingRequestListeners) {
for (listener in gossipingRequestListeners) {
try {
listener.onRoomKeyRequestCancellation(request)
} catch (e: Exception) {
Timber.e(e, "## GOSSIP onRoomKeyRequestCancellation() failed")
}
}
}
}
fun addRoomKeysRequestListener(listener: GossipingRequestListener) {
synchronized(gossipingRequestListeners) {
gossipingRequestListeners.add(listener)
}
}
fun removeRoomKeysRequestListener(listener: GossipingRequestListener) {
synchronized(gossipingRequestListeners) {
gossipingRequestListeners.remove(listener)
}
}
companion object {
private const val FIVE_MINUTES_IN_MILLIS = 5 * 60 * 1000
}
}

View File

@@ -18,12 +18,12 @@ package im.vector.matrix.android.internal.crypto
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.internal.crypto.model.rest.RoomKeyShareCancellation
import im.vector.matrix.android.internal.crypto.model.rest.ShareRequestCancellation
/**
* IncomingRoomKeyRequestCancellation describes the incoming room key cancellation.
* IncomingRequestCancellation describes the incoming room key cancellation.
*/
data class IncomingRoomKeyRequestCancellation(
data class IncomingRequestCancellation(
/**
* The user id
*/
@@ -37,22 +37,24 @@ data class IncomingRoomKeyRequestCancellation(
/**
* The request id
*/
override val requestId: String? = null
) : IncomingRoomKeyRequestCommon {
override val requestId: String? = null,
override val localCreationTimestamp: Long?
) : IncomingShareRequestCommon {
companion object {
/**
* Factory
*
* @param event the event
*/
fun fromEvent(event: Event): IncomingRoomKeyRequestCancellation? {
fun fromEvent(event: Event): IncomingRequestCancellation? {
return event.getClearContent()
.toModel<RoomKeyShareCancellation>()
.toModel<ShareRequestCancellation>()
?.let {
IncomingRoomKeyRequestCancellation(
IncomingRequestCancellation(
userId = event.senderId,
deviceId = it.requestingDeviceId,
requestId = it.requestId
requestId = it.requestId,
localCreationTimestamp = event.ageLocalTs ?: System.currentTimeMillis()
)
}
}

View File

@@ -46,6 +46,8 @@ data class IncomingRoomKeyRequest(
*/
val requestBody: RoomKeyRequestBody? = null,
val state: GossipingRequestState = GossipingRequestState.NONE,
/**
* The runnable to call to accept to share the keys
*/
@@ -56,8 +58,9 @@ data class IncomingRoomKeyRequest(
* The runnable to call to ignore the key share request.
*/
@Transient
var ignore: Runnable? = null
) : IncomingRoomKeyRequestCommon {
var ignore: Runnable? = null,
override val localCreationTimestamp: Long?
) : IncomingShareRequestCommon {
companion object {
/**
* Factory
@@ -72,7 +75,8 @@ data class IncomingRoomKeyRequest(
userId = event.senderId,
deviceId = it.requestingDeviceId,
requestId = it.requestId,
requestBody = it.body ?: RoomKeyRequestBody()
requestBody = it.body ?: RoomKeyRequestBody(),
localCreationTimestamp = event.ageLocalTs ?: System.currentTimeMillis()
)
}
}

View File

@@ -1,203 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.keyshare.RoomKeysRequestListener
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShare
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.session.SessionScope
import timber.log.Timber
import javax.inject.Inject
@SessionScope
internal class IncomingRoomKeyRequestManager @Inject constructor(
private val credentials: Credentials,
private val cryptoStore: IMXCryptoStore,
private val roomDecryptorProvider: RoomDecryptorProvider) {
// list of IncomingRoomKeyRequests/IncomingRoomKeyRequestCancellations
// we received in the current sync.
private val receivedRoomKeyRequests = ArrayList<IncomingRoomKeyRequest>()
private val receivedRoomKeyRequestCancellations = ArrayList<IncomingRoomKeyRequestCancellation>()
// the listeners
private val roomKeysRequestListeners: MutableSet<RoomKeysRequestListener> = HashSet()
init {
receivedRoomKeyRequests.addAll(cryptoStore.getPendingIncomingRoomKeyRequests())
}
/**
* Called when we get an m.room_key_request event
* It must be called on CryptoThread
*
* @param event the announcement event.
*/
fun onRoomKeyRequestEvent(event: Event) {
when (val roomKeyShareAction = event.getClearContent()?.get("action") as? String) {
RoomKeyShare.ACTION_SHARE_REQUEST -> IncomingRoomKeyRequest.fromEvent(event)?.let { receivedRoomKeyRequests.add(it) }
RoomKeyShare.ACTION_SHARE_CANCELLATION -> IncomingRoomKeyRequestCancellation.fromEvent(event)?.let { receivedRoomKeyRequestCancellations.add(it) }
else -> Timber.e("## onRoomKeyRequestEvent() : unsupported action $roomKeyShareAction")
}
}
/**
* Process any m.room_key_request events which were queued up during the
* current sync.
* It must be called on CryptoThread
*/
fun processReceivedRoomKeyRequests() {
val roomKeyRequestsToProcess = receivedRoomKeyRequests.toList()
receivedRoomKeyRequests.clear()
for (request in roomKeyRequestsToProcess) {
val userId = request.userId
val deviceId = request.deviceId
val body = request.requestBody
val roomId = body!!.roomId
val alg = body.algorithm
Timber.v("m.room_key_request from $userId:$deviceId for $roomId / ${body.sessionId} id ${request.requestId}")
if (userId == null || credentials.userId != userId) {
// TODO: determine if we sent this device the keys already: in
Timber.w("## 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?
// 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)
if (null == decryptor) {
Timber.w("## processReceivedRoomKeyRequests() : room key request for unknown $alg in room $roomId")
continue
}
if (!decryptor.hasKeysForKeyRequest(request)) {
Timber.w("## processReceivedRoomKeyRequests() : room key request for unknown session ${body.sessionId!!}")
cryptoStore.deleteIncomingRoomKeyRequest(request)
continue
}
if (credentials.deviceId == deviceId && credentials.userId == userId) {
Timber.v("## processReceivedRoomKeyRequests() : oneself device - ignored")
cryptoStore.deleteIncomingRoomKeyRequest(request)
continue
}
request.share = Runnable {
decryptor.shareKeysWithDevice(request)
cryptoStore.deleteIncomingRoomKeyRequest(request)
}
request.ignore = Runnable {
cryptoStore.deleteIncomingRoomKeyRequest(request)
}
// if the device is verified already, share the keys
val device = cryptoStore.getUserDevice(userId, deviceId!!)
if (device != null) {
if (device.isVerified) {
Timber.v("## processReceivedRoomKeyRequests() : device is already verified: sharing keys")
cryptoStore.deleteIncomingRoomKeyRequest(request)
request.share?.run()
continue
}
if (device.isBlocked) {
Timber.v("## processReceivedRoomKeyRequests() : device is blocked -> ignored")
cryptoStore.deleteIncomingRoomKeyRequest(request)
continue
}
}
// If cross signing is available on account we automatically discard untrust devices request
if (cryptoStore.getMyCrossSigningInfo() != null) {
// At this point the device is unknown, we don't want to bother user with that
cryptoStore.deleteIncomingRoomKeyRequest(request)
continue
}
cryptoStore.storeIncomingRoomKeyRequest(request)
onRoomKeyRequest(request)
}
var receivedRoomKeyRequestCancellations: List<IncomingRoomKeyRequestCancellation>? = null
synchronized(this.receivedRoomKeyRequestCancellations) {
if (this.receivedRoomKeyRequestCancellations.isNotEmpty()) {
receivedRoomKeyRequestCancellations = this.receivedRoomKeyRequestCancellations.toList()
this.receivedRoomKeyRequestCancellations.clear()
}
}
if (null != receivedRoomKeyRequestCancellations) {
for (request in receivedRoomKeyRequestCancellations!!) {
Timber.v("## ## processReceivedRoomKeyRequests() : m.room_key_request cancellation for " + request.userId
+ ":" + request.deviceId + " id " + request.requestId)
// we should probably only notify the app of cancellations we told it
// about, but we don't currently have a record of that, so we just pass
// everything through.
onRoomKeyRequestCancellation(request)
cryptoStore.deleteIncomingRoomKeyRequest(request)
}
}
}
/**
* Dispatch onRoomKeyRequest
*
* @param request the request
*/
private fun onRoomKeyRequest(request: IncomingRoomKeyRequest) {
synchronized(roomKeysRequestListeners) {
for (listener in roomKeysRequestListeners) {
try {
listener.onRoomKeyRequest(request)
} catch (e: Exception) {
Timber.e(e, "## onRoomKeyRequest() failed")
}
}
}
}
/**
* A room key request cancellation has been received.
*
* @param request the cancellation request
*/
private fun onRoomKeyRequestCancellation(request: IncomingRoomKeyRequestCancellation) {
synchronized(roomKeysRequestListeners) {
for (listener in roomKeysRequestListeners) {
try {
listener.onRoomKeyRequestCancellation(request)
} catch (e: Exception) {
Timber.e(e, "## onRoomKeyRequestCancellation() failed")
}
}
}
}
fun addRoomKeysRequestListener(listener: RoomKeysRequestListener) {
synchronized(roomKeysRequestListeners) {
roomKeysRequestListeners.add(listener)
}
}
fun removeRoomKeysRequestListener(listener: RoomKeysRequestListener) {
synchronized(roomKeysRequestListeners) {
roomKeysRequestListeners.remove(listener)
}
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto
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.internal.crypto.model.rest.SecretShareRequest
/**
* IncomingRoomKeyRequest class defines the incoming room keys request.
*/
data class IncomingSecretShareRequest(
/**
* The user id
*/
override val userId: String? = null,
/**
* The device id
*/
override val deviceId: String? = null,
/**
* The request id
*/
override val requestId: String? = null,
/**
* The request body
*/
val secretName: String? = null,
/**
* The runnable to call to accept to share the keys
*/
@Transient
var share: ((String) -> Unit)? = null,
/**
* The runnable to call to ignore the key share request.
*/
@Transient
var ignore: Runnable? = null,
override val localCreationTimestamp: Long?
) : IncomingShareRequestCommon {
companion object {
/**
* Factory
*
* @param event the event
*/
fun fromEvent(event: Event): IncomingSecretShareRequest? {
return event.getClearContent()
.toModel<SecretShareRequest>()
?.let {
IncomingSecretShareRequest(
userId = event.senderId,
deviceId = it.requestingDeviceId,
requestId = it.requestId,
secretName = it.secretName,
localCreationTimestamp = event.ageLocalTs ?: System.currentTimeMillis()
)
}
}
}
}

View File

@@ -16,7 +16,7 @@
package im.vector.matrix.android.internal.crypto
interface IncomingRoomKeyRequestCommon {
interface IncomingShareRequestCommon {
/**
* The user id
*/
@@ -31,4 +31,6 @@ interface IncomingRoomKeyRequestCommon {
* The request id
*/
val requestId: String?
val localCreationTimestamp: Long?
}

View File

@@ -59,9 +59,6 @@ internal class MXOlmDevice @Inject constructor(
var deviceEd25519Key: String? = null
private set
// The OLM lib account instance.
private var olmAccount: OlmAccount? = null
// The OLM lib utility instance.
private var olmUtility: OlmUtility? = null
@@ -86,19 +83,10 @@ internal class MXOlmDevice @Inject constructor(
init {
// Retrieve the account from the store
olmAccount = store.getAccount()
if (null == olmAccount) {
Timber.v("MXOlmDevice : create a new olm account")
// Else, create it
try {
olmAccount = OlmAccount()
store.storeAccount(olmAccount!!)
} catch (e: Exception) {
Timber.e(e, "MXOlmDevice : cannot initialize olmAccount")
}
} else {
Timber.v("MXOlmDevice : use an existing account")
try {
store.getOrCreateOlmAccount()
} catch (e: Exception) {
Timber.e(e, "MXOlmDevice : cannot initialize olmAccount")
}
try {
@@ -109,13 +97,13 @@ internal class MXOlmDevice @Inject constructor(
}
try {
deviceCurve25519Key = olmAccount!!.identityKeys()[OlmAccount.JSON_KEY_IDENTITY_KEY]
deviceCurve25519Key = store.getOlmAccount().identityKeys()[OlmAccount.JSON_KEY_IDENTITY_KEY]
} catch (e: Exception) {
Timber.e(e, "## MXOlmDevice : cannot find ${OlmAccount.JSON_KEY_IDENTITY_KEY} with error")
}
try {
deviceEd25519Key = olmAccount!!.identityKeys()[OlmAccount.JSON_KEY_FINGER_PRINT_KEY]
deviceEd25519Key = store.getOlmAccount().identityKeys()[OlmAccount.JSON_KEY_FINGER_PRINT_KEY]
} catch (e: Exception) {
Timber.e(e, "## MXOlmDevice : cannot find ${OlmAccount.JSON_KEY_FINGER_PRINT_KEY} with error")
}
@@ -126,7 +114,7 @@ internal class MXOlmDevice @Inject constructor(
*/
fun getOneTimeKeys(): Map<String, Map<String, String>>? {
try {
return olmAccount!!.oneTimeKeys()
return store.getOlmAccount().oneTimeKeys()
} catch (e: Exception) {
Timber.e(e, "## getOneTimeKeys() : failed")
}
@@ -138,14 +126,13 @@ internal class MXOlmDevice @Inject constructor(
* @return The maximum number of one-time keys the olm account can store.
*/
fun getMaxNumberOfOneTimeKeys(): Long {
return olmAccount?.maxOneTimeKeys() ?: -1
return store.getOlmAccount().maxOneTimeKeys()
}
/**
* Release the instance
*/
fun release() {
olmAccount?.releaseAccount()
olmUtility?.releaseUtility()
}
@@ -157,7 +144,7 @@ internal class MXOlmDevice @Inject constructor(
*/
fun signMessage(message: String): String? {
try {
return olmAccount!!.signMessage(message)
return store.getOlmAccount().signMessage(message)
} catch (e: Exception) {
Timber.e(e, "## signMessage() : failed")
}
@@ -170,8 +157,8 @@ internal class MXOlmDevice @Inject constructor(
*/
fun markKeysAsPublished() {
try {
olmAccount!!.markOneTimeKeysAsPublished()
store.storeAccount(olmAccount!!)
store.getOlmAccount().markOneTimeKeysAsPublished()
store.saveOlmAccount()
} catch (e: Exception) {
Timber.e(e, "## markKeysAsPublished() : failed")
}
@@ -184,8 +171,8 @@ internal class MXOlmDevice @Inject constructor(
*/
fun generateOneTimeKeys(numKeys: Int) {
try {
olmAccount!!.generateOneTimeKeys(numKeys)
store.storeAccount(olmAccount!!)
store.getOlmAccount().generateOneTimeKeys(numKeys)
store.saveOlmAccount()
} catch (e: Exception) {
Timber.e(e, "## generateOneTimeKeys() : failed")
}
@@ -205,7 +192,7 @@ internal class MXOlmDevice @Inject constructor(
try {
olmSession = OlmSession()
olmSession.initOutboundSession(olmAccount!!, theirIdentityKey, theirOneTimeKey)
olmSession.initOutboundSession(store.getOlmAccount(), theirIdentityKey, theirOneTimeKey)
val olmSessionWrapper = OlmSessionWrapper(olmSession, 0)
@@ -245,7 +232,7 @@ internal class MXOlmDevice @Inject constructor(
try {
try {
olmSession = OlmSession()
olmSession.initInboundSessionFrom(olmAccount!!, theirDeviceIdentityKey, ciphertext)
olmSession.initInboundSessionFrom(store.getOlmAccount(), theirDeviceIdentityKey, ciphertext)
} catch (e: Exception) {
Timber.e(e, "## createInboundSession() : the session creation failed")
return null
@@ -254,8 +241,8 @@ internal class MXOlmDevice @Inject constructor(
Timber.v("## createInboundSession() : sessionId: ${olmSession.sessionIdentifier()}")
try {
olmAccount!!.removeOneTimeKeys(olmSession)
store.storeAccount(olmAccount!!)
store.getOlmAccount().removeOneTimeKeys(olmSession)
store.saveOlmAccount()
} catch (e: Exception) {
Timber.e(e, "## createInboundSession() : removeOneTimeKeys failed")
}
@@ -654,7 +641,7 @@ internal class MXOlmDevice @Inject constructor(
throw MXCryptoError.OlmError(e)
}
if (null != timeline) {
if (timeline?.isNotBlank() == true) {
val timelineSet = inboundGroupSessionMessageIndexes.getOrPut(timeline) { mutableSetOf() }
val messageIndexKey = senderKey + "|" + sessionId + "|" + decryptResult.mIndex
@@ -770,7 +757,7 @@ internal class MXOlmDevice @Inject constructor(
return session
}
} else {
Timber.w("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId")
Timber.v("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId")
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON)
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto
interface OutgoingGossipingRequest {
var recipients: Map<String, List<String>>
var requestId: String
var state: OutgoingGossipingRequestState
// transaction id for the cancellation, if any
// var cancellationTxnId: String?
}

View File

@@ -0,0 +1,162 @@
/*
* 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
import im.vector.matrix.android.api.session.events.model.LocalEcho
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.di.SessionId
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@SessionScope
internal class OutgoingGossipingRequestManager @Inject constructor(
@SessionId private val sessionId: String,
private val cryptoStore: IMXCryptoStore,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val cryptoCoroutineScope: CoroutineScope,
private val gossipingWorkManager: GossipingWorkManager) {
/**
* Send off a room key request, if we haven't already done so.
*
*
* The `requestBody` is compared (with a deep-equality check) against
* previous queued or sent requests and if it matches, no change is made.
* Otherwise, a request is added to the pending list, and a job is started
* in the background to send it.
*
* @param requestBody requestBody
* @param recipients recipients
*/
fun sendRoomKeyRequest(requestBody: RoomKeyRequestBody, recipients: Map<String, List<String>>) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
cryptoStore.getOrAddOutgoingRoomKeyRequest(requestBody, recipients)?.let {
// Don't resend if it's already done, you need to cancel first (reRequest)
if (it.state == OutgoingGossipingRequestState.SENDING || it.state == OutgoingGossipingRequestState.SENT) {
Timber.v("## GOSSIP sendOutgoingRoomKeyRequest() : we already request for that session: $it")
return@launch
}
sendOutgoingGossipingRequest(it)
}
}
}
fun sendSecretShareRequest(secretName: String, recipients: Map<String, List<String>>) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
// A bit dirty, but for better stability give other party some time to mark
// devices trusted :/
delay(1500)
cryptoStore.getOrAddOutgoingSecretShareRequest(secretName, recipients)?.let {
// TODO check if there is already one that is being sent?
if (it.state == OutgoingGossipingRequestState.SENDING || it.state == OutgoingGossipingRequestState.SENT) {
Timber.v("## GOSSIP sendSecretShareRequest() : we already request for that session: $it")
return@launch
}
sendOutgoingGossipingRequest(it)
}
}
}
/**
* Cancel room key requests, if any match the given details
*
* @param requestBody requestBody
*/
fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
cancelRoomKeyRequest(requestBody, false)
}
}
/**
* Cancel room key requests, if any match the given details, and resend
*
* @param requestBody requestBody
*/
fun resendRoomKeyRequest(requestBody: RoomKeyRequestBody) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
cancelRoomKeyRequest(requestBody, true)
}
}
/**
* Cancel room key requests, if any match the given details, and resend
*
* @param requestBody requestBody
* @param andResend true to resend the key request
*/
private fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody, andResend: Boolean) {
val req = cryptoStore.getOutgoingRoomKeyRequest(requestBody)
?: // no request was made for this key
return Unit.also {
Timber.v("## GOSSIP cancelRoomKeyRequest() Unknown request")
}
sendOutgoingRoomKeyRequestCancellation(req, andResend)
}
/**
* Send the outgoing key request.
*
* @param request the request
*/
private fun sendOutgoingGossipingRequest(request: OutgoingGossipingRequest) {
Timber.v("## GOSSIP sendOutgoingRoomKeyRequest() : Requesting keys $request")
val params = SendGossipRequestWorker.Params(
sessionId = sessionId,
keyShareRequest = request as? OutgoingRoomKeyRequest,
secretShareRequest = request as? OutgoingSecretRequest
)
cryptoStore.updateOutgoingGossipingRequestState(request.requestId, OutgoingGossipingRequestState.SENDING)
val workRequest = gossipingWorkManager.createWork<SendGossipRequestWorker>(WorkerParamsFactory.toData(params), true)
gossipingWorkManager.postWork(workRequest)
}
/**
* Given a OutgoingRoomKeyRequest, cancel it and delete the request record
*
* @param request the request
*/
private fun sendOutgoingRoomKeyRequestCancellation(request: OutgoingRoomKeyRequest, resend: Boolean = false) {
Timber.v("$request")
val params = CancelGossipRequestWorker.Params.fromRequest(sessionId, request)
cryptoStore.updateOutgoingGossipingRequestState(request.requestId, OutgoingGossipingRequestState.CANCELLING)
val workRequest = gossipingWorkManager.createWork<CancelGossipRequestWorker>(WorkerParamsFactory.toData(params), true)
gossipingWorkManager.postWork(workRequest)
if (resend) {
val reSendParams = SendGossipRequestWorker.Params(
sessionId = sessionId,
keyShareRequest = request.copy(requestId = LocalEcho.createLocalEchoId())
)
val reSendWorkRequest = gossipingWorkManager.createWork<SendGossipRequestWorker>(WorkerParamsFactory.toData(reSendParams), true)
gossipingWorkManager.postWork(reSendWorkRequest)
}
}
}

View File

@@ -17,22 +17,26 @@
package im.vector.matrix.android.internal.crypto
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
/**
* Represents an outgoing room key request
*/
class OutgoingRoomKeyRequest(
@JsonClass(generateAdapter = true)
data class OutgoingRoomKeyRequest(
// RequestBody
var requestBody: RoomKeyRequestBody?, // list of recipients for the request
var recipients: List<Map<String, String>>, // Unique id for this request. Used for both
var requestBody: RoomKeyRequestBody?,
// list of recipients for the request
override var recipients: Map<String, List<String>>,
// Unique id for this request. Used for both
// an id within the request for later pairing with a cancellation, and for
// the transaction id when sending the to_device messages to our local
var requestId: String, // current state of this request
var state: RequestState) {
// transaction id for the cancellation, if any
var cancellationTxnId: String? = null
override var requestId: String, // current state of this request
override var state: OutgoingGossipingRequestState
// transaction id for the cancellation, if any
// override var cancellationTxnId: String? = null
) : OutgoingGossipingRequest {
/**
* Used only for log.
@@ -53,66 +57,4 @@ class OutgoingRoomKeyRequest(
get() = if (null != requestBody) {
requestBody!!.sessionId
} else null
/**
* possible states for a room key request
*
*
* The state machine looks like:
* <pre>
*
* |
* V
* UNSENT -----------------------------+
* | |
* | (send successful) | (cancellation requested)
* V |
* SENT |
* |-------------------------------- | --------------+
* | | |
* | | | (cancellation requested with intent
* | | | to resend a new request)
* | (cancellation requested) | |
* V | V
* CANCELLATION_PENDING | CANCELLATION_PENDING_AND_WILL_RESEND
* | | |
* | (cancellation sent) | | (cancellation sent. Create new request
* | | | in the UNSENT state)
* V | |
* (deleted) <---------------------------+----------------+
* </pre>
*/
enum class RequestState {
/**
* request not yet sent
*/
UNSENT,
/**
* request sent, awaiting reply
*/
SENT,
/**
* reply received, cancellation not yet sent
*/
CANCELLATION_PENDING,
/**
* Cancellation not yet sent, once sent, a new request will be done
*/
CANCELLATION_PENDING_AND_WILL_RESEND,
/**
* sending failed
*/
FAILED;
companion object {
fun from(state: Int) = when (state) {
0 -> UNSENT
1 -> SENT
2 -> CANCELLATION_PENDING
3 -> CANCELLATION_PENDING_AND_WILL_RESEND
else /*4*/ -> FAILED
}
}
}
}

View File

@@ -1,320 +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
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
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShareCancellation
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShareRequest
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.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
@SessionScope
internal class OutgoingRoomKeyRequestManager @Inject constructor(
private val cryptoStore: IMXCryptoStore,
private val sendToDeviceTask: SendToDeviceTask,
private val taskExecutor: TaskExecutor) {
// running
private var isClientRunning: Boolean = false
// transaction counter
private var txnCtr: Int = 0
// sanity check to ensure that we don't end up with two concurrent runs
// of sendOutgoingRoomKeyRequestsTimer
private val sendOutgoingRoomKeyRequestsRunning = AtomicBoolean(false)
/**
* Called when the client is started. Sets background processes running.
*/
fun start() {
isClientRunning = true
startTimer()
}
/**
* Called when the client is stopped. Stops any running background processes.
*/
fun stop() {
isClientRunning = false
stopTimer()
}
/**
* Make up a new transaction id
*
* @return {string} a new, unique, transaction id
*/
private fun makeTxnId(): String {
return "m" + System.currentTimeMillis() + "." + txnCtr++
}
/**
* Send off a room key request, if we haven't already done so.
*
*
* The `requestBody` is compared (with a deep-equality check) against
* previous queued or sent requests and if it matches, no change is made.
* Otherwise, a request is added to the pending list, and a job is started
* in the background to send it.
*
* @param requestBody requestBody
* @param recipients recipients
*/
fun sendRoomKeyRequest(requestBody: RoomKeyRequestBody?, recipients: List<Map<String, String>>) {
val req = cryptoStore.getOrAddOutgoingRoomKeyRequest(
OutgoingRoomKeyRequest(requestBody, recipients, makeTxnId(), OutgoingRoomKeyRequest.RequestState.UNSENT))
if (req?.state == OutgoingRoomKeyRequest.RequestState.UNSENT) {
startTimer()
}
}
/**
* Cancel room key requests, if any match the given details
*
* @param requestBody requestBody
*/
fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody) {
BACKGROUND_HANDLER.post {
cancelRoomKeyRequest(requestBody, false)
}
}
/**
* Cancel room key requests, if any match the given details, and resend
*
* @param requestBody requestBody
*/
fun resendRoomKeyRequest(requestBody: RoomKeyRequestBody) {
BACKGROUND_HANDLER.post {
cancelRoomKeyRequest(requestBody, true)
}
}
/**
* Cancel room key requests, if any match the given details, and resend
*
* @param requestBody requestBody
* @param andResend true to resend the key request
*/
private fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody, andResend: Boolean) {
val req = cryptoStore.getOutgoingRoomKeyRequest(requestBody)
?: // no request was made for this key
return
Timber.v("cancelRoomKeyRequest: requestId: " + req.requestId + " state: " + req.state + " andResend: " + andResend)
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)
}
}
}
/**
* Start the background timer to send queued requests, if the timer isn't already running.
*/
private fun startTimer() {
if (sendOutgoingRoomKeyRequestsRunning.get()) {
return
}
BACKGROUND_HANDLER.postDelayed(Runnable {
if (sendOutgoingRoomKeyRequestsRunning.get()) {
Timber.v("## startTimer() : RoomKeyRequestSend already in progress!")
return@Runnable
}
sendOutgoingRoomKeyRequestsRunning.set(true)
sendOutgoingRoomKeyRequests()
}, SEND_KEY_REQUESTS_DELAY_MS.toLong())
}
private fun stopTimer() {
BACKGROUND_HANDLER.removeCallbacksAndMessages(null)
}
// look for and send any queued requests. Runs itself recursively until
// there are no more requests, or there is an error (in which case, the
// timer will be restarted before the promise resolves).
private fun sendOutgoingRoomKeyRequests() {
if (!isClientRunning) {
sendOutgoingRoomKeyRequestsRunning.set(false)
return
}
Timber.v("## sendOutgoingRoomKeyRequests() : Looking for queued outgoing room key requests")
val outgoingRoomKeyRequest = cryptoStore.getOutgoingRoomKeyRequestByState(
setOf(OutgoingRoomKeyRequest.RequestState.UNSENT,
OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING,
OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND))
if (null == outgoingRoomKeyRequest) {
Timber.v("## sendOutgoingRoomKeyRequests() : No more outgoing room key requests")
sendOutgoingRoomKeyRequestsRunning.set(false)
return
}
if (OutgoingRoomKeyRequest.RequestState.UNSENT === outgoingRoomKeyRequest.state) {
sendOutgoingRoomKeyRequest(outgoingRoomKeyRequest)
} else {
sendOutgoingRoomKeyRequestCancellation(outgoingRoomKeyRequest)
}
}
/**
* Send the outgoing key request.
*
* @param request the request
*/
private fun sendOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest) {
Timber.v("## sendOutgoingRoomKeyRequest() : Requesting keys " + request.requestBody
+ " from " + request.recipients + " id " + request.requestId)
val requestMessage = RoomKeyShareRequest(
requestingDeviceId = cryptoStore.getDeviceId(),
requestId = request.requestId,
body = request.requestBody
)
sendMessageToDevices(requestMessage, request.recipients, request.requestId, object : MatrixCallback<Unit> {
private fun onDone(state: OutgoingRoomKeyRequest.RequestState) {
if (request.state !== OutgoingRoomKeyRequest.RequestState.UNSENT) {
Timber.v("## sendOutgoingRoomKeyRequest() : Cannot update room key request from UNSENT as it was already updated to ${request.state}")
} else {
request.state = state
cryptoStore.updateOutgoingRoomKeyRequest(request)
}
sendOutgoingRoomKeyRequestsRunning.set(false)
startTimer()
}
override fun onSuccess(data: Unit) {
Timber.v("## sendOutgoingRoomKeyRequest succeed")
onDone(OutgoingRoomKeyRequest.RequestState.SENT)
}
override fun onFailure(failure: Throwable) {
Timber.e("## sendOutgoingRoomKeyRequest failed")
onDone(OutgoingRoomKeyRequest.RequestState.FAILED)
}
})
}
/**
* Given a OutgoingRoomKeyRequest, cancel it and delete the request record
*
* @param request the request
*/
private fun sendOutgoingRoomKeyRequestCancellation(request: OutgoingRoomKeyRequest) {
Timber.v("## sendOutgoingRoomKeyRequestCancellation() : Sending cancellation for key request for " + request.requestBody
+ " to " + request.recipients
+ " cancellation id " + request.cancellationTxnId)
val roomKeyShareCancellation = RoomKeyShareCancellation(
requestingDeviceId = cryptoStore.getDeviceId(),
requestId = request.cancellationTxnId
)
sendMessageToDevices(roomKeyShareCancellation, request.recipients, request.cancellationTxnId, object : MatrixCallback<Unit> {
private fun onDone() {
cryptoStore.deleteOutgoingRoomKeyRequest(request.requestId)
sendOutgoingRoomKeyRequestsRunning.set(false)
startTimer()
}
override fun onSuccess(data: Unit) {
Timber.v("## sendOutgoingRoomKeyRequestCancellation() : done")
val resend = request.state === OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND
onDone()
// Resend the request with a new ID
if (resend) {
sendRoomKeyRequest(request.requestBody, request.recipients)
}
}
override fun onFailure(failure: Throwable) {
Timber.e("## sendOutgoingRoomKeyRequestCancellation failed")
onDone()
}
})
}
/**
* Send a SendToDeviceObject to a list of recipients
*
* @param message the message
* @param recipients the recipients.
* @param transactionId the transaction id
* @param callback the asynchronous callback.
*/
private fun sendMessageToDevices(message: Any,
recipients: List<Map<String, String>>,
transactionId: String?,
callback: MatrixCallback<Unit>) {
val contentMap = MXUsersDevicesMap<Any>()
for (recipient in recipients) {
// 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)) {
this.callback = callback
this.callbackThread = TaskThread.CALLER
this.executionThread = TaskThread.CALLER
}
.executeBy(taskExecutor)
}
companion object {
private const val SEND_KEY_REQUESTS_DELAY_MS = 500
private val BACKGROUND_HANDLER = createBackgroundHandler("OutgoingRoomKeyRequest")
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto
import com.squareup.moshi.JsonClass
/**
* Represents an outgoing room key request
*/
@JsonClass(generateAdapter = true)
class OutgoingSecretRequest(
// Secret Name
val secretName: String?,
// list of recipients for the request
override var recipients: Map<String, List<String>>,
// Unique id for this request. Used for both
// an id within the request for later pairing with a cancellation, and for
// the transaction id when sending the to_device messages to our local
override var requestId: String,
// current state of this request
override var state: OutgoingGossipingRequestState) : OutgoingGossipingRequest {
// transaction id for the cancellation, if any
}

View File

@@ -0,0 +1,148 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.failure.shouldBeRetried
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.LocalEcho
import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.rest.GossipingToDeviceObject
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShareRequest
import im.vector.matrix.android.internal.crypto.model.rest.SecretShareRequest
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import im.vector.matrix.android.internal.worker.getSessionComponent
import org.greenrobot.eventbus.EventBus
import timber.log.Timber
import javax.inject.Inject
internal class SendGossipRequestWorker(context: Context,
params: WorkerParameters)
: CoroutineWorker(context, params) {
@JsonClass(generateAdapter = true)
internal data class Params(
val sessionId: String,
val keyShareRequest: OutgoingRoomKeyRequest? = null,
val secretShareRequest: OutgoingSecretRequest? = null
)
@Inject lateinit var sendToDeviceTask: SendToDeviceTask
@Inject lateinit var cryptoStore: IMXCryptoStore
@Inject lateinit var eventBus: EventBus
@Inject lateinit var credentials: Credentials
override suspend fun doWork(): Result {
val errorOutputData = Data.Builder().putBoolean("failed", true).build()
val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.success(errorOutputData)
val sessionComponent = getSessionComponent(params.sessionId)
?: return Result.success(errorOutputData).also {
// TODO, can this happen? should I update local echo?
Timber.e("Unknown Session, cannot send message, sessionId: ${params.sessionId}")
}
sessionComponent.inject(this)
val localId = LocalEcho.createLocalEchoId()
val contentMap = MXUsersDevicesMap<Any>()
val eventType: String
val requestId: String
when {
params.keyShareRequest != null -> {
eventType = EventType.ROOM_KEY_REQUEST
requestId = params.keyShareRequest.requestId
val toDeviceContent = RoomKeyShareRequest(
requestingDeviceId = credentials.deviceId,
requestId = params.keyShareRequest.requestId,
action = GossipingToDeviceObject.ACTION_SHARE_REQUEST,
body = params.keyShareRequest.requestBody
)
cryptoStore.saveGossipingEvent(Event(
type = eventType,
content = toDeviceContent.toContent(),
senderId = credentials.userId
).also {
it.ageLocalTs = System.currentTimeMillis()
})
params.keyShareRequest.recipients.forEach { userToDeviceMap ->
userToDeviceMap.value.forEach { deviceId ->
contentMap.setObject(userToDeviceMap.key, deviceId, toDeviceContent)
}
}
}
params.secretShareRequest != null -> {
eventType = EventType.REQUEST_SECRET
requestId = params.secretShareRequest.requestId
val toDeviceContent = SecretShareRequest(
requestingDeviceId = credentials.deviceId,
requestId = params.secretShareRequest.requestId,
action = GossipingToDeviceObject.ACTION_SHARE_REQUEST,
secretName = params.secretShareRequest.secretName
)
cryptoStore.saveGossipingEvent(Event(
type = eventType,
content = toDeviceContent.toContent(),
senderId = credentials.userId
).also {
it.ageLocalTs = System.currentTimeMillis()
})
params.secretShareRequest.recipients.forEach { userToDeviceMap ->
userToDeviceMap.value.forEach { deviceId ->
contentMap.setObject(userToDeviceMap.key, deviceId, toDeviceContent)
}
}
}
else -> {
return Result.success(errorOutputData).also {
Timber.e("Unknown empty gossiping request: $params")
}
}
}
try {
cryptoStore.updateOutgoingGossipingRequestState(requestId, OutgoingGossipingRequestState.SENDING)
sendToDeviceTask.execute(
SendToDeviceTask.Params(
eventType = eventType,
contentMap = contentMap,
transactionId = localId
)
)
cryptoStore.updateOutgoingGossipingRequestState(requestId, OutgoingGossipingRequestState.SENT)
return Result.success()
} catch (exception: Throwable) {
return if (exception.shouldBeRetried()) {
Result.retry()
} else {
cryptoStore.updateOutgoingGossipingRequestState(requestId, OutgoingGossipingRequestState.FAILED_TO_SEND)
Result.success(errorOutputData)
}
}
}
}

View File

@@ -0,0 +1,141 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.failure.shouldBeRetried
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.LocalEcho
import im.vector.matrix.android.api.session.events.model.toContent
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.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.event.SecretSendEventContent
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import im.vector.matrix.android.internal.worker.getSessionComponent
import org.greenrobot.eventbus.EventBus
import timber.log.Timber
import javax.inject.Inject
internal class SendGossipWorker(context: Context,
params: WorkerParameters)
: CoroutineWorker(context, params) {
@JsonClass(generateAdapter = true)
internal data class Params(
val sessionId: String,
val secretValue: String,
val request: IncomingSecretShareRequest
)
@Inject lateinit var sendToDeviceTask: SendToDeviceTask
@Inject lateinit var cryptoStore: IMXCryptoStore
@Inject lateinit var eventBus: EventBus
@Inject lateinit var credentials: Credentials
@Inject lateinit var messageEncrypter: MessageEncrypter
@Inject lateinit var ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction
override suspend fun doWork(): Result {
val errorOutputData = Data.Builder().putBoolean("failed", true).build()
val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.success(errorOutputData)
val sessionComponent = getSessionComponent(params.sessionId)
?: return Result.success(errorOutputData).also {
// TODO, can this happen? should I update local echo?
Timber.e("Unknown Session, cannot send message, sessionId: ${params.sessionId}")
}
sessionComponent.inject(this)
val localId = LocalEcho.createLocalEchoId()
val eventType: String = EventType.SEND_SECRET
val toDeviceContent = SecretSendEventContent(
requestId = params.request.requestId ?: "",
secretValue = params.secretValue
)
val requestingUserId = params.request.userId ?: ""
val requestingDeviceId = params.request.deviceId ?: ""
val deviceInfo = cryptoStore.getUserDevice(requestingUserId, requestingDeviceId)
?: return Result.success(errorOutputData).also {
cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.FAILED_TO_ACCEPTED)
Timber.e("Unknown deviceInfo, cannot send message, sessionId: ${params.request.deviceId}")
}
val sendToDeviceMap = MXUsersDevicesMap<Any>()
val devicesByUser = mapOf(requestingUserId to listOf(deviceInfo))
val usersDeviceMap = ensureOlmSessionsForDevicesAction.handle(devicesByUser)
val olmSessionResult = usersDeviceMap.getObject(requestingUserId, requestingDeviceId)
if (olmSessionResult?.sessionId == null) {
// no session with this device, probably because there
// were no one-time keys.
return Result.success(errorOutputData).also {
cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.FAILED_TO_ACCEPTED)
Timber.e("no session with this device, probably because there were no one-time keys.")
}
}
val payloadJson = mapOf(
"type" to EventType.SEND_SECRET,
"content" to toDeviceContent.toContent()
)
try {
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
sendToDeviceMap.setObject(requestingUserId, requestingDeviceId, encodedPayload)
} catch (failure: Throwable) {
Timber.e("## Fail to encrypt gossip + ${failure.localizedMessage}")
}
cryptoStore.saveGossipingEvent(Event(
type = eventType,
content = toDeviceContent.toContent(),
senderId = credentials.userId
).also {
it.ageLocalTs = System.currentTimeMillis()
})
try {
sendToDeviceTask.execute(
SendToDeviceTask.Params(
eventType = EventType.ENCRYPTED,
contentMap = sendToDeviceMap,
transactionId = localId
)
)
cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.ACCEPTED)
return Result.success()
} catch (exception: Throwable) {
return if (exception.shouldBeRetried()) {
Result.retry()
} else {
cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.FAILED_TO_ACCEPTED)
Result.success(errorOutputData)
}
}
}
}

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