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

Compare commits

...

358 Commits

Author SHA1 Message Date
Benoit Marty
f9c0256afd Merge branch 'release/0.9.1' 2019-12-05 18:17:55 +01:00
Benoit Marty
6d8850b3d6 Prepare version 0.9.1 2019-12-05 18:17:36 +01:00
Benoit Marty
d88edd578f Merge pull request #740 from vector-im/feature/hot_fix_filter
Feature/hot fix filter
2019-12-05 18:14:46 +01:00
ganfra
eb9775e307 Fix some crypto realm issues 2019-12-05 17:14:56 +01:00
ganfra
aa9d66b991 Allow returning a value from an async transaction 2019-12-05 17:14:38 +01:00
Benoit Marty
fb8ba32fb4 Version++ 2019-12-05 09:46:36 +01:00
Benoit Marty
8e9ac8198d Merge branch 'release/0.9.0' 2019-12-05 09:44:06 +01:00
Benoit Marty
7a05207ae4 Merge branch 'release/0.9.0' into develop 2019-12-05 09:44:06 +01:00
Benoit Marty
6b39cf3b70 Prepare release 0.9.0 2019-12-05 09:43:58 +01:00
Benoit Marty
994759e11a Merge pull request #737 from vector-im/feature/otk_upload
Improve and cleanup OneTimeKey uploader
2019-12-05 09:38:05 +01:00
Benoit Marty
f31c1b69cb Remove delay when waiting for first sync to finish and add number of sent keys in the log 2019-12-04 16:52:55 +01:00
Benoit Marty
bdb9d2fbb8 Improve and cleanup OneTimeKey uploader
Fix boolean reset if request fails
Implement https://github.com/matrix-org/matrix-js-sdk/pull/493
2019-12-04 16:34:25 +01:00
Benoit Marty
9fb50dde32 Merge pull request #726 from vector-im/feature/sign_x_stabilization
Registration stabilization
2019-12-04 16:26:48 +01:00
Benoit Marty
a145aae0aa Avoid using !! 2019-12-04 15:38:16 +01:00
Benoit Marty
3623072f08 Attempt to properly cancel the crypto module when user signs out (#724)
Attempt to properly cancel the crypto module when user signs out (#724)
2019-12-04 15:38:16 +01:00
Benoit Marty
2717ad475a Merge pull request #699 from vector-im/feature/read_marker_rework
Feature/read marker rework
2019-12-04 14:12:41 +01:00
Benoit Marty
a6f8fe9317 Fix lint issue 2019-12-04 12:08:18 +01:00
Benoit Marty
f9eb80b4ec Simplify layout 2019-12-04 11:42:18 +01:00
Benoit Marty
9510d71cd3 Proposal for simple layout 2019-12-04 11:42:18 +01:00
Benoit Marty
e7a47ae32a Some cleanup 2019-12-04 11:42:18 +01:00
ganfra
7890b929a7 Update CHANGES 2019-12-04 11:42:18 +01:00
ganfra
0376de08f4 Clean files 2019-12-04 11:41:54 +01:00
ganfra
90c472fef9 Read marker: fix mark all as read 2019-12-04 11:41:54 +01:00
ganfra
8e873672a9 Read marker: change design 2019-12-04 11:41:54 +01:00
ganfra
bba52e77d1 Read marker: fix merged items 2019-12-04 11:41:54 +01:00
ganfra
64d73ae8e6 Read marker: handle the jump to read marker 2019-12-04 11:41:54 +01:00
ganfra
d9982076f9 Read marker: continue rework [WIP] 2019-12-04 11:39:51 +01:00
ganfra
ab489df83d Read marker: don't show unread on events we own 2019-12-04 11:33:06 +01:00
ganfra
5e07e96bdb Read marker: start reworking how we manage it [WIP] 2019-12-04 11:33:06 +01:00
Benoit Marty
c495aa4914 Merge pull request #731 from vector-im/feature/fix_pills
Fix issue with pill: also send the text after the last pills
2019-12-03 18:19:36 +01:00
Benoit Marty
ff267ba9bc Update changelog 2019-12-03 16:36:44 +01:00
Benoit Marty
69f923383c Rename some classes with "Item" suffix, as a convention (ooi) 2019-12-03 16:32:25 +01:00
Benoit Marty
c69852c849 Make url clickable on the preview of event in the bottom sheet - avoid instantiating objects in the bind() method 2019-12-03 16:17:49 +01:00
Benoit Marty
6d7f2670df Make url clickable on the preview of event in the bottom sheet 2019-12-03 16:02:07 +01:00
Benoit Marty
71de8fdad3 Display pills Avatar in the message preview 2019-12-03 15:08:44 +01:00
Benoit Marty
998d9f2c59 Bugfix: Text after the last pill was not send 2019-12-03 14:48:41 +01:00
Benoit Marty
4f3da353e4 Add ellipsis char for action with another step 2019-12-03 13:23:44 +01:00
Benoit Marty
4154cb2b85 Improve wording of the title of read receipt list 2019-12-03 13:21:42 +01:00
Benoit Marty
3c6eb4bccf Rework FilterEntityQueries to fix issue of ghost Realm reference 2019-12-03 11:10:43 +01:00
Benoit Marty
7b4398404b Update wording for modular screens 2019-12-03 11:10:43 +01:00
Benoit Marty
9b882978ed Update modular link 2019-12-03 11:10:43 +01:00
Benoit Marty
49178dc633 Reduce some log level 2019-12-03 11:10:43 +01:00
Benoit Marty
490ce4b51d Fix issue of closing Realm in another thread (#725) 2019-12-03 10:05:10 +01:00
Benoit Marty
5b63856d96 Add log to detect if a realm instance is not properly closed 2019-12-02 18:33:31 +01:00
Benoit Marty
538c4d1a64 typo 2019-12-02 18:15:21 +01:00
Benoit Marty
1cadbb8eed Ensure credentials can be stored, even if they already exist 2019-12-02 13:48:20 +01:00
Benoit Marty
3f4f7457c7 Merge pull request #689 from vector-im/feature/signin_signup
Login and Registration
2019-12-02 13:47:28 +01:00
Valere
ebf21fe9d8 Merge pull request #687 from vector-im/feature/dat_pill
Send mention pills from composer
2019-11-29 16:28:34 +01:00
Benoit Marty
a343da594f Import Strings from Riot 2019-11-29 16:22:04 +01:00
Benoit Marty
938289e8eb ktlint 2019-11-29 15:44:08 +01:00
Benoit Marty
e23763e6db Update password from email twice 2019-11-29 15:43:39 +01:00
Benoit Marty
c06b8486ea Update wording 2019-11-29 15:36:54 +01:00
Valere
67fe776d91 Update Changes 2019-11-29 13:27:50 +01:00
Benoit Marty
10cc270273 ktlint 2019-11-29 13:27:26 +01:00
Benoit Marty
46d96429e0 Create ooi extension 2019-11-29 13:27:26 +01:00
Benoit Marty
9f9c418085 Pills: cleanup and robustness 2019-11-29 13:27:26 +01:00
Benoit Marty
c412006f0e Pills: render the avatar 2019-11-29 13:27:26 +01:00
Benoit Marty
5d3c376267 Pills: remove pills when a char is deleted 2019-11-29 13:27:26 +01:00
Benoit Marty
a3f8f138a6 Create showKeyBoard() extension 2019-11-29 13:27:26 +01:00
Benoit Marty
4b273e8746 Pills: simplify and improve the algorithm 2019-11-29 13:27:26 +01:00
Benoit Marty
f11cd47df3 Pills: cleanup 2019-11-29 13:27:26 +01:00
Benoit Marty
f984758d37 Pills: Daggerization 2019-11-29 13:27:26 +01:00
Valere
97766404d6 klint 2019-11-29 13:27:26 +01:00
Valere
38b93c527b Ensure received pills spans do not overlap 2019-11-29 13:27:26 +01:00
Valere
62bae67080 Code review 2019-11-29 13:27:26 +01:00
Valere
2a4cdec020 klint cleaning 2019-11-29 13:27:26 +01:00
Valere
6bd7257cf2 Send mention pills from composer 2019-11-29 13:27:26 +01:00
Benoit Marty
500eb113b6 Login screens: add some animation for Fragment transition (WIP) 2019-11-28 20:36:29 +01:00
Benoit Marty
1bec8c29b8 Add missing items for Status theme 2019-11-28 15:52:32 +01:00
Benoit Marty
0ecb23199c Login screens: add background color 2019-11-28 13:25:56 +01:00
Benoit Marty
33925fcf57 Login screens: fix crash on back navigation 2019-11-28 12:09:28 +01:00
Benoit Marty
bf9ce4f690 Merge pull request #714 from vector-im/feature/upgrade_gif_drawable
Force android-gif-drawable version
2019-11-28 10:24:40 +01:00
Benoit Marty
d2a4163dff Merge pull request #691 from vector-im/anoa/typo
Small typo fix
2019-11-28 09:55:16 +01:00
Valere
a0d7aef92e Force android-gif-drawable version
BigImageViewer update (updated the android-gif-drawable dep)
2019-11-28 09:39:04 +01:00
Benoit Marty
29f32cf8eb Login screens: fix regression on Back press 2019-11-27 16:48:28 +01:00
Benoit Marty
bb1c988a49 Login screens: Update the local pendingSessionData synchronously, store asynchronously 2019-11-27 16:36:35 +01:00
Benoit Marty
f063abe068 Login screens: keep PendingSessionData member up to date 2019-11-27 16:15:51 +01:00
Benoit Marty
db87d8f644 Login screens: Realm migration for Auth DB 2019-11-27 15:52:02 +01:00
Benoit Marty
efa858a337 Login screens: reorder reset action for clarity 2019-11-27 15:11:02 +01:00
Benoit Marty
fd90f3b9fc Login screens: reset SDK when home server url is deleted 2019-11-27 15:08:36 +01:00
Benoit Marty
aa51764068 Login screens: save isRegistrationStarted in DB 2019-11-27 15:04:00 +01:00
Benoit Marty
0a19ded167 Login screens: extract some classes 2019-11-27 14:34:07 +01:00
Benoit Marty
2e3763e8b4 Login screens: persist all data during login or registration 2019-11-27 14:26:06 +01:00
Benoit Marty
0c4e0890b1 Use Realm.use { } 2019-11-27 10:49:33 +01:00
Benoit Marty
e532d97ec1 Login screens: persist login mode 2019-11-27 10:04:41 +01:00
Benoit Marty
fbde8d7d18 ktlint 2019-11-26 18:04:42 +01:00
Benoit Marty
f96bea742e Login screens: split long lines 2019-11-26 18:04:00 +01:00
Benoit Marty
86bfdd011e Login screens: cleanup and ignore lint issue 2019-11-26 18:03:09 +01:00
Benoit Marty
d5c2c1938c Login screens: move user choices to the ViewState 2019-11-26 17:59:01 +01:00
Benoit Marty
7ce8a13ddf Login screens: prepare for saving user state 2019-11-26 13:11:31 +01:00
Benoit Marty
9bd4dbb65f Login screens: trim homeserver url 2019-11-26 12:40:17 +01:00
Benoit Marty
ee875b359b Login screens: update wording 2019-11-26 12:25:37 +01:00
Benoit Marty
3eb2e1655f Login screens: ensure homeserver version is supported - fix bug for SSO 2019-11-26 12:16:39 +01:00
Benoit Marty
9b207dd5dc Login screens: ensure homeserver version is supported 2019-11-26 11:39:33 +01:00
Benoit Marty
3f1540b54e Update wording 2019-11-26 09:51:35 +01:00
Benoit Marty
2b30925163 Login screens: doc for sign in 2019-11-26 09:49:53 +01:00
Benoit Marty
4690754f5f Merge pull request #702 from vector-im/feature/quick_fix
2 quick fixes
2019-11-25 18:48:43 +01:00
Benoit Marty
a9526cdd92 Login screens: use homeserver and identity server Uri provided along with the credential is any 2019-11-25 18:32:24 +01:00
Benoit Marty
ab4d42fb20 Login screens: mutualize the code which create session from the credentials 2019-11-25 18:16:57 +01:00
Benoit Marty
0014e8ef06 Login screens: rename variables 2019-11-25 17:32:27 +01:00
Benoit Marty
311d8ddf7b Login screens: fix layout issue 2019-11-25 17:24:31 +01:00
Benoit Marty
6cb3c222a9 Login screens: handle mandatory dummy stage automatically 2019-11-25 16:47:17 +01:00
Benoit Marty
f84ec08847 Code cleanup, restore comment, and fix regression on delay 2019-11-25 14:11:38 +01:00
ganfra
9d0188cbf1 Create user from userId during initialSync 2019-11-22 20:28:52 +01:00
ganfra
73462a3045 Clean some coroutine code 2019-11-22 20:04:11 +01:00
Benoit Marty
3eebf965e5 Fix emoji filtering not working 2019-11-22 15:19:09 +01:00
Benoit Marty
bba58d25e1 Do not show long click help if only invitation are displayed 2019-11-22 14:54:22 +01:00
Benoit Marty
fedb45b019 Login screens: add doc on signin 2019-11-22 14:35:39 +01:00
Benoit Marty
9b83f08654 Login screens: fix compilation issue (lint) 2019-11-22 14:24:22 +01:00
Benoit Marty
91fcf428dd Login screens: login with unknown email 2019-11-22 14:21:14 +01:00
Benoit Marty
7e1a279fd9 Update changelog (Fixes #34, Fixes #613) 2019-11-22 12:19:15 +01:00
Benoit Marty
8de1fa835b Improve M_LIMIT_EXCEEDED error rendering 2019-11-22 12:15:19 +01:00
Benoit Marty
af45c554fd Login screens: fix scroll issue 2019-11-22 12:15:19 +01:00
Benoit Marty
11bc7051fd Login screens: splash scrollable 2019-11-22 12:15:19 +01:00
Benoit Marty
489a594027 Login screens: ensure forms are scrollable on small screens 2019-11-22 12:15:19 +01:00
Benoit Marty
3f83c161e4 Login screens: fix code quality issues 2019-11-22 12:15:19 +01:00
Benoit Marty
e0a36b794f Login screens: fix lint issues 2019-11-22 12:15:19 +01:00
Benoit Marty
d2b516bdc2 Login screens: fix issue with reset password fragment navigation 2019-11-22 12:15:19 +01:00
Benoit Marty
37166caea2 Login screens: create sub method 2019-11-22 12:15:19 +01:00
Benoit Marty
9fa131c297 Login screens: reset password: display a better popup when link is not clicked yet 2019-11-22 12:15:19 +01:00
Benoit Marty
71ae3c4a8c Login screens: reset password: display a warning when the process is not finished 2019-11-22 12:15:19 +01:00
Benoit Marty
f87526e615 Login screens: reset password: add documentation 2019-11-22 12:15:19 +01:00
Benoit Marty
51f53e2ae9 Login screens: reset password: fix a few errors 2019-11-22 12:15:19 +01:00
Benoit Marty
ef35f0a044 Login screens: disable submit button when input is empty 2019-11-22 12:15:19 +01:00
Benoit Marty
5db3f51ddb Login screens: fix bad view binding 2019-11-22 12:15:19 +01:00
Benoit Marty
49f7ce3554 Login screens: better API 2019-11-22 12:15:19 +01:00
Benoit Marty
a3111dc2d8 Login screens: rename a few classes and packages 2019-11-22 12:15:19 +01:00
Benoit Marty
be95542110 Login screens: dummy stage shoud not be mandatory 2019-11-22 12:15:19 +01:00
Benoit Marty
6723a566c2 Login screens: refacto: create an AuthenticationWizard 2019-11-22 12:15:19 +01:00
Benoit Marty
90027cc4d5 Login screens: reset password WIP 2019-11-22 12:15:19 +01:00
Benoit Marty
810b226f21 Do not trim login nor password 2019-11-22 12:15:19 +01:00
Benoit Marty
42c5adf08d ktlint 2019-11-22 12:15:19 +01:00
Benoit Marty
5edfb78721 Cleanup errors and close keyboard at each login step 2019-11-22 12:15:19 +01:00
Benoit Marty
491a38a79f Login screens: send again 3pid 2019-11-22 12:15:19 +01:00
Benoit Marty
051f77087e Email format validation 2019-11-22 12:15:19 +01:00
Benoit Marty
1a603742d0 Cleanup 2019-11-22 12:15:19 +01:00
Benoit Marty
edb65f1787 Fix some errors 2019-11-22 12:15:19 +01:00
Benoit Marty
9af8355c07 Fix wording 2019-11-22 12:15:19 +01:00
Benoit Marty
dd44078297 Login screens: fix several issue with check email screen 2019-11-22 12:15:19 +01:00
Benoit Marty
a1f96a5b5a Login screens: typo 2019-11-22 12:15:19 +01:00
Benoit Marty
5770023593 Login screens: code cleanup 2019-11-22 12:15:19 +01:00
Benoit Marty
2789268c23 Login screens: MSISDN: check format and compute country code 2019-11-22 12:15:19 +01:00
Benoit Marty
eb4355890e Login screens: setup autofill 2019-11-22 12:15:19 +01:00
Benoit Marty
4e41156db3 Login screens: doc: registration forbidden 2019-11-22 12:15:19 +01:00
Benoit Marty
1a0b8b35f8 Login screens: Doc: adapt log to correct logs and fix typo 2019-11-22 12:15:19 +01:00
Benoit Marty
5f9cdcb4b4 Login screens: Doc: add msisdn stage 2019-11-22 12:15:19 +01:00
Benoit Marty
2e4c3f850a Cleanup 2019-11-22 12:15:19 +01:00
Benoit Marty
127916a8d9 Login screens: add MSISDN 2019-11-22 12:15:19 +01:00
Benoit Marty
248a584e1a Login screens: Add Msisdn - WIP 2019-11-22 12:15:19 +01:00
Benoit Marty
b8a3ad0c43 Login screens: Wait for email validation screen 2019-11-22 12:15:19 +01:00
Benoit Marty
1f161b7e23 Login screens: Add 3Pid step 1 2019-11-22 12:15:19 +01:00
Benoit Marty
23315ede92 Login screens: update wording 2019-11-22 12:15:19 +01:00
Benoit Marty
20ad3abb60 Login screens: set initial device name 2019-11-22 12:15:19 +01:00
Benoit Marty
ac377fceba Login screens: mutualize registration callback 2019-11-22 12:15:19 +01:00
Benoit Marty
abbe56acfa Login screens: UI: display errors properly 2019-11-22 12:15:19 +01:00
Benoit Marty
f74cabd145 Login screens: UI: style to prepare for landscape 2019-11-22 12:15:19 +01:00
Benoit Marty
0e2237226f Login screens: back button management for registration 2019-11-22 12:15:19 +01:00
Benoit Marty
62d5aba796 Login screens: back button management for SSO 2019-11-22 12:15:19 +01:00
Benoit Marty
f12e6c941d Login screens: sigin button for SSO 2019-11-22 12:15:19 +01:00
Benoit Marty
7caa8ce3bc Login screens: disabled registration 2019-11-22 12:15:19 +01:00
Benoit Marty
20f969d563 Login screens: fix issue on terms 2019-11-22 12:15:19 +01:00
Benoit Marty
a8f24e5c39 Login screens: a11y 2019-11-22 12:15:19 +01:00
Benoit Marty
8ae9544b48 Login screens: Loading on Captcha step 2019-11-22 12:15:19 +01:00
Benoit Marty
3758334824 Login screens: cleanup the Fragment stack after completing stage 2019-11-22 12:15:19 +01:00
Benoit Marty
6d8e5b892e Login screens: Show disclaimer dialog only in HomeActivity, now that RiotX supports registration 2019-11-22 12:15:19 +01:00
Benoit Marty
c18c140ec9 Login screens: Animate the logo in screen transition 2019-11-22 12:15:19 +01:00
Benoit Marty
1dc7dfc896 Login screens: registration fallback 2019-11-22 12:15:19 +01:00
Benoit Marty
1c03163a33 Login screens: prepare email and msisdn 2019-11-22 12:15:19 +01:00
Benoit Marty
9aa270c7ad Login screens: Perform dummy action when user does not want to enter an email -> account created! 2019-11-22 12:15:19 +01:00
Benoit Marty
3f80076fb1 Login screens: Terms step for registration 2019-11-22 12:15:19 +01:00
Benoit Marty
dfbf448bb7 Login screens: Captcha step for registration 2019-11-22 12:15:19 +01:00
Benoit Marty
95fc20dca0 Login screens: Registration: login/password step 2019-11-22 12:15:19 +01:00
Benoit Marty
381084b2ab Login screens: USER_IN_USE error 2019-11-22 12:15:19 +01:00
Benoit Marty
41ac2c6d70 Login screens: Registration WIP 2019-11-22 12:15:19 +01:00
Benoit Marty
08ea3d049e Login screens: Simple Input form (UI) 2019-11-22 12:15:19 +01:00
Benoit Marty
f24889230c Login screens: Captch screen (UI) 2019-11-22 12:15:19 +01:00
Benoit Marty
b5f9549a8b Login screens: Fix issues on button style 2019-11-22 12:15:19 +01:00
Benoit Marty
e3e38d4c8a Login screens: Fix issues on modular tile 2019-11-22 12:15:19 +01:00
Benoit Marty
416bef7903 Login screens: button theme 2019-11-22 12:15:19 +01:00
Benoit Marty
823acebf78 Login screens: harmonize styles for containers 2019-11-22 12:15:19 +01:00
Benoit Marty
3e91125872 Fix issues 2019-11-22 12:15:19 +01:00
Benoit Marty
9a628c7b5d ktlint 2019-11-22 12:15:19 +01:00
Benoit Marty
fb46a14172 Fix compilation issue after rebase 2019-11-22 12:15:19 +01:00
Benoit Marty
ca4e75a1a0 Login screens: Fix a few bugs 2019-11-22 12:15:19 +01:00
Benoit Marty
2871e4f5b1 Login screens: forget password screens 2019-11-22 12:15:19 +01:00
Benoit Marty
b7bfb20a2e Login screens: login and registration fallback 2019-11-22 12:15:19 +01:00
Benoit Marty
a1aa16715d Login screens: move elements from ViewState to ViewModel 2019-11-22 12:15:19 +01:00
Benoit Marty
55add4734d Login screens: Fix Other rendering issue 2019-11-22 12:15:19 +01:00
Benoit Marty
2849e1f846 Login screens: Splash: update icons 2019-11-22 12:15:19 +01:00
Benoit Marty
5b9876a20c Login screens: Fix navigation issue 2019-11-22 12:15:19 +01:00
Benoit Marty
adf299081d Login screens: re-click on an item submit it 2019-11-22 12:15:19 +01:00
Benoit Marty
d50b690523 Login screens: improve LoginFragment 2019-11-22 12:15:19 +01:00
Benoit Marty
c6b0ae63ea Login screens: handle loading Views and global navigation - WIP 2019-11-22 12:15:19 +01:00
Benoit Marty
3c93807fe6 Login screens: add some doc 2019-11-22 12:15:19 +01:00
Benoit Marty
7f1f98c2e5 Login screens: reset state when navigating back 2019-11-22 12:15:19 +01:00
Benoit Marty
6525314af8 Login screens: server ur form 2019-11-22 12:15:19 +01:00
Benoit Marty
da8d6fb4f4 Login screens: signup signin selection 2019-11-22 12:15:19 +01:00
Benoit Marty
fa6a9cab7e Login screens: server selection 2019-11-22 12:15:19 +01:00
Benoit Marty
bdfc4ad8a7 Login screens: splash screen 2019-11-22 12:15:19 +01:00
Benoit Marty
6ab7209e4d Handle navigation with VectorSharedAction 2019-11-22 12:14:48 +01:00
Benoit Marty
4485d1c685 Registration flow: SDK side 2019-11-22 12:14:48 +01:00
Benoit Marty
8b63f78d76 Add documentation on the sign up flow 2019-11-22 12:14:48 +01:00
Matthew Hodgson
2e87e0b4c1 fix typo 2019-11-21 01:41:59 +00:00
Andrew Morgan
507134407b Update CHANGES.md 2019-11-19 15:10:41 +00:00
Andrew Morgan
7663cd4e23 Merge branch 'develop' of github.com:vector-im/riotx-android into anoa/typo 2019-11-19 15:10:24 +00:00
Benoit Marty
ec2954200e Version++ 2019-11-19 09:58:48 +01:00
Benoit Marty
eb32c5455f Merge branch 'release/0.8.0' 2019-11-19 09:47:57 +01:00
Benoit Marty
fc367b3c3e Merge branch 'release/0.8.0' into develop 2019-11-19 09:47:56 +01:00
Benoit Marty
57dcd569f3 Prepare release 0.8.0 2019-11-19 09:47:12 +01:00
Andrew Morgan
3673520ef6 Small typo fix 2019-11-18 13:30:03 +00:00
Benoit Marty
fe17050580 Merge pull request #685 from vector-im/feature/timeline_items
Feature/timeline items
2019-11-15 16:11:37 +01:00
ganfra
ec40a8c969 Update CHANGES 2019-11-14 13:26:25 +01:00
ganfra
6b1b3bec85 Clean code for klint 2019-11-14 13:25:04 +01:00
ganfra
6bd6ececb7 Timeline: handle sticker events 2019-11-14 13:23:12 +01:00
ganfra
c7db695e67 Timeline: handle join rules event 2019-11-14 12:21:55 +01:00
ganfra
4cefdfedce Home: use detach/attach instead of hide/show 2019-11-14 11:18:45 +01:00
ganfra
6ce241163e Merge pull request #679 from vector-im/feature/perf_again
Feature/perf again
2019-11-13 19:43:07 +01:00
ganfra
79350899c5 Read receipts: use primary key to query 2019-11-13 19:21:14 +01:00
ganfra
f265724a3c Login sso: handle failure 2019-11-13 19:20:03 +01:00
ganfra
2e50d2a36e Clean code for klint 2019-11-13 11:38:30 +01:00
ganfra
643c062858 Merge branch 'develop' into feature/perf_again 2019-11-13 10:44:59 +01:00
ganfra
0e0db67aef Timeline: clear some resources when unbind 2019-11-12 20:34:19 +01:00
ganfra
6dc5b126d6 Optimize room list processing 2019-11-12 19:53:07 +01:00
ganfra
d2acabddd9 RoomDetail: fix enter/exit mode again after merge 2019-11-12 19:25:57 +01:00
ganfra
ec71b53c1e RoomSummary: don't map read receipts 2019-11-12 19:25:21 +01:00
Benoit Marty
fc3d4187d1 Merge pull request #676 from vector-im/feature/long_click_room
Add help to reveal the long click on a room
2019-11-12 18:29:42 +01:00
Benoit Marty
a25f309990 Merge pull request #678 from vector-im/feature/block_user_menu
Add action to block user in the message action bottom sheet
2019-11-12 18:17:18 +01:00
Benoit Marty
5449592422 Add action to block user in the message action bottom sheet (following Nad's design) 2019-11-12 17:52:03 +01:00
ganfra
19b415871d Extract displaymode, clear adapter on roomList/timeline and use commitNow when possible 2019-11-12 15:13:20 +01:00
ganfra
6463f3439f Update Mvrx to 1.3 2019-11-12 15:11:52 +01:00
Benoit Marty
f2320f9571 Merge pull request #665 from vector-im/feature/color_theme
Ensure color is retrieved from current theme, even when theme change
2019-11-12 12:29:27 +01:00
Benoit Marty
fc91694bdd Merge pull request #673 from vector-im/feature/us
Update strings
2019-11-12 12:08:55 +01:00
Benoit Marty
dbb41108ef Improve layout 2019-11-12 11:50:16 +01:00
Benoit Marty
08c864bad7 Add help to reveal the long click on a room 2019-11-12 11:39:47 +01:00
Benoit Marty
9c5c65a243 Cleanup 2019-11-12 10:36:53 +01:00
ganfra
b6199b1f27 Fix some issues with fragments managers (Home fragments) and lifecycle 2019-11-08 19:49:08 +01:00
ganfra
38da54119a Merge branch 'develop' into feature/perf_again 2019-11-08 18:39:06 +01:00
Benoit Marty
65b09ad4f0 Merge pull request #675 from vector-im/feature/cleanup
Cleanup ViewModel for code clarity
2019-11-08 18:19:22 +01:00
Benoit Marty
603b8fae45 Add missing binding 2019-11-08 17:30:04 +01:00
Benoit Marty
50e2e6a823 Cleanup 2019-11-08 17:23:21 +01:00
Benoit Marty
bb237e3bbb Fix ViewModel for Signout 2019-11-08 17:21:46 +01:00
ganfra
1bd2c0d220 Merge branch 'develop' into feature/perf_again 2019-11-08 17:09:22 +01:00
ganfra
bcb811a7e8 Realm perf: use Dispatchers.Default for write, as we don't want to create so many threads (and we can only have one write transaction at a time) 2019-11-08 17:08:44 +01:00
Benoit Marty
ec4d7e29ec Ensure we use the correct viewModelProvider 2019-11-08 17:08:29 +01:00
Benoit Marty
a6df63f6d9 Fix crash 2019-11-08 16:56:45 +01:00
Benoit Marty
ea7213a5ae Split long lines 2019-11-08 16:16:42 +01:00
Benoit Marty
590a13334d ktlint 2019-11-08 16:04:41 +01:00
Benoit Marty
631448335d Rename stuff for code coherence 2019-11-08 15:36:12 +01:00
Benoit Marty
12376368c7 Rename class 2019-11-08 15:20:55 +01:00
Benoit Marty
f17564d743 Simple code 2019-11-08 15:08:50 +01:00
Benoit Marty
a6fcc7dca6 Move class to dedicated file 2019-11-08 15:07:01 +01:00
Benoit Marty
70bce9e7dd Ensure ViewModel follow the same pattern to handle actions 2019-11-08 15:05:11 +01:00
ganfra
17f3614288 Perf: try to optimize room summary updates 2019-11-08 13:48:35 +01:00
Benoit Marty
238d1d87c6 Rename class 2019-11-08 13:31:39 +01:00
Benoit Marty
82f639b91f Rename to Shared 2019-11-08 12:09:08 +01:00
Benoit Marty
c8bc553caa Move class to dedicated file 2019-11-08 12:01:36 +01:00
Benoit Marty
fa5d44af65 Create common parent for Action view model, to handle navigation, action, or other type of event 2019-11-08 11:54:17 +01:00
ganfra
cbdbe5033f Merge pull request #664 from vector-im/feature/room_list_actions
Feature/room list actions
2019-11-07 15:45:45 +01:00
ganfra
61ac250e2b Merge branch 'develop' into feature/room_list_actions 2019-11-07 15:43:21 +01:00
ganfra
04f72dfcb8 Clean code after Benoit's review 2019-11-07 15:19:12 +01:00
Benoit Marty
10ca5d94ea Fix issue after rebase 2019-11-07 14:29:08 +01:00
Benoit Marty
c5b8c69ae5 Merge pull request #668 from vector-im/feature/block_user
Block user and display ignored users list
2019-11-07 14:28:23 +01:00
Benoit Marty
d3d7f7cc61 Split long lines 2019-11-07 14:18:57 +01:00
Benoit Marty
b6bb714264 Display name and avatar of the user 2019-11-07 14:18:57 +01:00
Benoit Marty
a87310ac15 update comment 2019-11-07 14:18:57 +01:00
Benoit Marty
032e1b3d19 ktlint 2019-11-07 14:18:57 +01:00
Benoit Marty
d9f15c1d21 Block user possibility after reporting event content 2019-11-07 14:18:57 +01:00
Benoit Marty
99d09f71ad Changelog 2019-11-07 14:18:57 +01:00
Benoit Marty
9c952b6bc8 Display ignored users list 2019-11-07 14:18:57 +01:00
Benoit Marty
fbae3d27c2 Ignore/Unignore userIds 2019-11-07 14:13:29 +01:00
Benoit Marty
2f7d1f9f01 Ignored Users account data 2019-11-07 14:13:29 +01:00
Benoit Marty
114101699d Fix potential ignoring of account data (return@foreach) 2019-11-07 14:13:29 +01:00
Benoit Marty
f5c0dcb5ea Merge pull request #670 from vector-im/feature/fragment_factory
Feature/fragment factory
2019-11-07 13:59:46 +01:00
Benoit Marty
241220ce1f remove unused import 2019-11-07 13:59:21 +01:00
Benoit Marty
98d97e574c Fix regression when filtering emojis 2019-11-07 12:23:21 +01:00
Benoit Marty
96e610970a Finish the work 2019-11-07 12:08:17 +01:00
Benoit Marty
2027802f82 Add debug screen for all SAS emoji 2019-11-07 10:37:49 +01:00
ganfra
54f93db632 RoomDetail : enter/exit special mode without waiting for draft to update 2019-11-06 20:08:19 +01:00
ganfra
3af7ca9ab0 Retrofit: lazy init okhttp 2019-11-06 20:07:28 +01:00
ganfra
93ef3edab3 Remove some use of sync write in realm 2019-11-06 18:47:11 +01:00
Benoit Marty
c85852262e Remove bad value 2019-11-06 18:28:16 +01:00
Benoit Marty
d0c3271628 Import string from Riot-Android and fix #671 2019-11-06 18:19:37 +01:00
ganfra
ad9a48d5fa Clean code 2019-11-05 18:36:30 +01:00
ganfra
219d1383e5 Fragments: use FragmentContainerView 2019-11-05 18:13:55 +01:00
ganfra
8871280fab Fragments: use constructor injections in most of the Fragments 2019-11-05 18:12:04 +01:00
Benoit Marty
fb3e953e28 Merge pull request #667 from vector-im/feature/realm_cleanup
Import change form https://github.com/matrix-org/matrix-android-sdk/pull/505
2019-11-05 12:13:25 +01:00
Benoit Marty
10712fd6ab ktlint 2019-11-05 12:13:06 +01:00
Benoit Marty
9d478dbfe2 Import change form https://github.com/matrix-org/matrix-android-sdk/pull/505 2019-11-05 11:18:22 +01:00
ganfra
3013d67c16 Fragment factory: start including the new version with FragmentFactory [WIP] 2019-11-04 19:33:56 +01:00
Benoit Marty
bee8c2d159 Ensure color is retrieved from current theme, even when theme change 2019-11-04 18:12:24 +01:00
ganfra
945e5d5a74 Merge branch 'develop' into feature/room_list_actions 2019-11-04 17:17:43 +01:00
ganfra
93df8c56a8 Fix compilation error and use mockk instead of manual mocking (prone to error) 2019-11-04 17:09:03 +01:00
Benoit Marty
cd1a964067 Merge pull request #649 from vector-im/feature/spoiler_support
Support spoilers in messages
2019-11-04 16:54:43 +01:00
Benoit Marty
e4b829f0cf Lift of 'return' 2019-11-04 16:53:51 +01:00
Benoit Marty
7206d84a6b Add FIXME 2019-11-04 16:51:45 +01:00
Benoit Marty
b3233d3eb7 Change spoiler bg colors 2019-11-04 16:50:32 +01:00
Valere
3c4c0ed46a Add /spoiler command 2019-11-04 16:50:32 +01:00
Valere
24f1262005 Merge refactoring 2019-11-04 16:49:53 +01:00
Benoit Marty
86667a6d8a Passes text color instead of context 2019-11-04 16:49:53 +01:00
Benoit Marty
42e0d0f769 Improve code to check url validity 2019-11-04 16:49:53 +01:00
Valere
e976055253 Support spoilers in messages 2019-11-04 16:49:53 +01:00
Benoit Marty
84d6c8ec16 Merge pull request #646 from vector-im/feature/search_reaction
Search reaction by name/keywords
2019-11-04 15:51:24 +01:00
Benoit Marty
9fdfd091ac Merge branch 'develop' into feature/search_reaction 2019-11-04 15:51:16 +01:00
ganfra
e66766f41c Update CHANGES 2019-11-04 15:12:30 +01:00
ganfra
6177e69855 Merge branch 'develop' into feature/room_list_actions 2019-11-04 15:11:20 +01:00
ganfra
5c71cabb5f Clean code 2019-11-04 15:08:08 +01:00
ganfra
6ebe5532c5 Room list actions: use new strings 2019-11-04 14:59:12 +01:00
ganfra
8030c44f44 Room list actions: fix some UI issues and render selected notification state 2019-11-04 14:31:03 +01:00
Benoit Marty
a85b5af761 Merge pull request #641 from vector-im/feature/fix_crash
Fix crash
2019-11-04 14:28:31 +01:00
Benoit Marty
d780c74abf Merge pull request #657 from vector-im/feature/locales
Support Cyrillic script
2019-11-04 14:27:24 +01:00
Benoit Marty
5d7efa7f8f Merge pull request #660 from vector-im/feature/permission
Ask for permission to write external storage when uri comes from the keyboard (#658)
2019-11-04 14:26:12 +01:00
Benoit Marty
7e467443ed Merge pull request #651 from vector-im/feature/markdown_off
Markdown off
2019-11-04 10:23:21 +01:00
Benoit Marty
8439c337f7 Merge branch 'develop' into feature/markdown_off 2019-11-04 10:22:20 +01:00
Valere
151ad01038 Use RxBinding on searchView 2019-11-01 11:57:15 +01:00
Valere
73267442bb Fix / remove listener 2019-11-01 11:30:13 +01:00
Benoit Marty
43fd794c96 Ask for permission to write external storage when uri comes from the keyboard (#658) 2019-10-31 15:48:07 +01:00
Benoit Marty
36060fe332 Merge pull request #654 from vector-im/feature/timeline_message_code
Feature/timeline message code
2019-10-31 15:08:13 +01:00
Benoit Marty
3483debcc1 Little cleanup 2019-10-31 12:08:55 +01:00
Benoit Marty
4324f6abbd Add paragraph about a11y 2019-10-31 11:11:03 +01:00
Benoit Marty
43f8d8d8aa Merge pull request #656 from pvagner/a11y_file_type_selector
a11y: file type selector
2019-10-31 10:52:28 +01:00
Benoit Marty
fb1ff77ec4 Add string from Riot-Android 2019-10-31 10:09:27 +01:00
Peter Vágner
e355a7f6dd Changelog entry 2019-10-31 07:58:08 +01:00
Peter Vágner
33e35368fc a11y: better presentation for file type selector buttons to screen reader users
Signed-off-by: Peter Vágner <pvdeejay@gmail.com>
2019-10-31 06:36:28 +01:00
ganfra
0e49a11e5e Merge pull request #648 from vector-im/feature/fix_#498
Feature/fix #498
2019-10-30 19:28:35 +01:00
ganfra
d47cf7e932 Merge branch 'develop' into feature/fix_#498 2019-10-30 19:26:11 +01:00
ganfra
101057520b Fix disambiguated with empty senderName 2019-10-30 19:25:24 +01:00
ganfra
30b2e53002 Update CHANGES 2019-10-30 19:02:44 +01:00
ganfra
5ab31a0ef5 Fix klint 2019-10-30 19:00:56 +01:00
ganfra
b4ae331086 Timeline: render inline and block code 2019-10-30 19:00:00 +01:00
Benoit Marty
3f447df13c Support local script (imported from https://github.com/vector-im/riot-android/pull/3364) 2019-10-30 16:59:31 +01:00
ganfra
3517873156 Timeline: Start handling code blocks. [WIP] 2019-10-29 19:08:48 +01:00
Benoit Marty
118870bc41 ktlint cleanup 2019-10-29 17:02:55 +01:00
Benoit Marty
d001ab5bef Merge pull request #640 from Dominaezzz/kotlinify
The last of the clean up.
2019-10-29 17:01:38 +01:00
Benoit Marty
7496a88dcd Markdown set to off by default (Fixes #412) 2019-10-29 16:22:12 +01:00
Benoit Marty
6567c5e6c7 Small kotlin improvement 2019-10-29 16:20:22 +01:00
Benoit Marty
361427488f Passphrase does not match (Export room keys) (Fixes #644) 2019-10-29 14:38:04 +01:00
Benoit Marty
7272343e6d Update comment 2019-10-29 14:32:05 +01:00
Benoit Marty
f0b3151d71 Merge pull request #639 from vector-im/feature/compile_tests
Build and run test on CI
2019-10-29 14:27:47 +01:00
ganfra
035359cb35 Update CHANGES and clean code 2019-10-28 17:01:41 +01:00
ganfra
57b640622b Sender Name: we should use disambiguated display name over senderName. PrevContent fallback is now handled in SDK 2019-10-28 16:48:55 +01:00
Valere
de4c389c76 klint cleaning 2019-10-28 15:12:49 +01:00
Valere
199456487c Search reaction by name/keywords 2019-10-28 14:36:15 +01:00
ganfra
00ca5dc70a RoomListActions: handle room notification state. Still need to branch UI 2019-10-25 18:23:47 +02:00
Valere
a04802b238 CI / upgrade queue to xlarge 2019-10-25 11:14:17 +02:00
ganfra
cb275aee37 Room list actions: start showing items and refact a bit RxStore 2019-10-24 19:11:49 +02:00
Benoit Marty
fbf73c7c8e shorter code 2019-10-24 18:52:34 +02:00
Benoit Marty
0040f8e924 Fix crash reported by Rageshake, stateKey can be null 2019-10-24 18:51:47 +02:00
Benoit Marty
6cca242f77 Fix Android test compilation issue 2019-10-24 17:49:34 +02:00
Benoit Marty
2929b8f617 Ensure Android tests compile and fix warnings 2019-10-24 17:24:42 +02:00
Benoit Marty
8422c6de17 Remove test sample 2019-10-24 17:21:19 +02:00
Benoit Marty
7c567b04bb Make test compile and pass 2019-10-24 16:36:12 +02:00
Dominic Fischer
1ac99e92a6 Light refactoring.
Signed-off-by: Dominic Fischer <dominicfischer7@gmail.com>
2019-10-24 14:58:11 +01:00
Dominic Fischer
5ab975cc5c General kotlinification.
Signed-off-by: Dominic Fischer <dominicfischer7@gmail.com>
2019-10-24 14:53:44 +01:00
Dominic Fischer
2cf63ea92a Remove import java.util.* from kotlin files.
Signed-off-by: Dominic Fischer <dominicfischer7@gmail.com>
2019-10-24 14:53:10 +01:00
Benoit Marty
9e8d8ce878 Build and run test on CI 2019-10-24 15:52:40 +02:00
Benoit Marty
b766bce07d Version++ 2019-10-24 14:40:31 +02:00
Benoit Marty
0a0af221f0 Merge branch 'release/0.7.0' into develop 2019-10-24 14:37:51 +02:00
ganfra
9762d5be40 Room list actions: start creating all the components 2019-10-23 19:05:59 +02:00
588 changed files with 16944 additions and 5041 deletions

View File

@@ -3,14 +3,36 @@
# https://github.com/buildkite-plugins/docker-buildkite-plugin/releases
# We propagate the environment to the container (sse https://github.com/buildkite-plugins/docker-buildkite-plugin#propagate-environment-optional-boolean)
# Build debug version of the RiotX application, from the develop branch and the features branches
steps:
- label: "Assemble GPlay Debug version"
- label: "Compile and run Unit tests"
agents:
# We use a medium sized instance instead of the normal small ones because
# gradle build is long
# gradle build can be memory hungry
queue: "medium"
commands:
- "./gradlew clean test --stacktrace"
plugins:
- docker#v3.1.0:
image: "runmymind/docker-android-sdk"
propagate-environment: true
- label: "Compile Android tests"
agents:
# We use a medium sized instance instead of the normal small ones because
# gradle build can be memory hungry
queue: "medium"
commands:
- "./gradlew clean assembleAndroidTest --stacktrace"
plugins:
- docker#v3.1.0:
image: "runmymind/docker-android-sdk"
propagate-environment: true
- label: "Assemble GPlay Debug version"
agents:
# We use a xlarge sized instance instead of the normal small ones because
# gradle build can be memory hungry
queue: "xlarge"
commands:
- "./gradlew clean lintGplayRelease assembleGplayDebug --stacktrace"
artifact_paths:
@@ -23,9 +45,9 @@ steps:
- label: "Assemble FDroid Debug version"
agents:
# We use a medium sized instance instead of the normal small ones because
# gradle build is long
queue: "medium"
# We use a xlarge sized instance instead of the normal small ones because
# gradle build can be memory hungry
queue: "xlarge"
commands:
- "./gradlew clean lintFdroidRelease assembleFdroidDebug --stacktrace"
artifact_paths:
@@ -38,9 +60,9 @@ steps:
- label: "Build Google Play unsigned APK"
agents:
# We use a medium sized instance instead of the normal small ones because
# gradle build is long
queue: "medium"
# We use a xlarge sized instance instead of the normal small ones because
# gradle build can be memory hungry
queue: "xlarge"
commands:
- "./gradlew clean assembleGplayRelease --stacktrace"
artifact_paths:

View File

@@ -3,7 +3,9 @@
<words>
<w>backstack</w>
<w>bytearray</w>
<w>checkables</w>
<w>ciphertext</w>
<w>coroutine</w>
<w>decryptor</w>
<w>emoji</w>
<w>emojis</w>
@@ -12,8 +14,11 @@
<w>linkified</w>
<w>linkify</w>
<w>megolm</w>
<w>msisdn</w>
<w>pbkdf</w>
<w>pkcs</w>
<w>signin</w>
<w>signup</w>
</words>
</dictionary>
</component>

View File

@@ -1,3 +1,53 @@
Changes in RiotX 0.9.1 (2019-12-05)
===================================================
Bugfix 🐛:
- Fix an issue with DB transaction (#740)
Changes in RiotX 0.9.0 (2019-12-05)
===================================================
Features ✨:
- Account creation. It's now possible to create account on any homeserver with RiotX (#34)
- Iteration of the login flow (#613)
Improvements 🙌:
- Send mention Pills from composer
- Links in message preview in the bottom sheet are now active.
- Rework the read marker to make it more usable
Other changes:
- Fix a small grammatical error when an empty room list is shown.
Bugfix 🐛:
- Do not show long click help if only invitation are displayed
- Fix emoji filtering not working
- Fix issue of closing Realm in another thread (#725)
- Attempt to properly cancel the crypto module when user signs out (#724)
Changes in RiotX 0.8.0 (2019-11-19)
===================================================
Features ✨:
- Handle long click on room in the room list (#395)
- Ignore/UnIgnore users, and display list of ignored users (#542, #617)
Improvements 🙌:
- Search reaction by name or keyword in emoji picker
- Handle code tags (#567)
- Support spoiler messages
- Support m.sticker and m.room.join_rules events in timeline
Other changes:
- Markdown set to off by default (#412)
- Accessibility improvements to the attachment file type chooser
Bugfix 🐛:
- Fix issues with some member events rendering (#498)
- Passphrase does not match (Export room keys) (#644)
- Ask for permission to write external storage when uri comes from the keyboard (#658)
- Fix issue with english US/GB translation (#671)
Changes in RiotX 0.7.0 (2019-10-24)
===================================================
@@ -12,6 +62,7 @@ Improvements:
- Attachments: start using system pickers (#52)
- Mark all messages as read (#396)
Other changes:
- Accessibility improvements to read receipts in the room timeline and reactions emoji chooser

View File

@@ -86,6 +86,10 @@ Also, if possible, please test your change on a real device. Testing on Android
When adding new string resources, please only add new entries in file `value/strings.xml`. Translations will be added later by the community of translators with a specific tool named [Weblate](https://translate.riot.im/projects/riot-android/).
Do not hesitate to use plurals when appropriate.
### Accessibility
Please consider accessibility as an important point. As a minimum requirement, in layout XML files please use attributes such as `android:contentDescription` and `android:importantForAccessibility`, and test with a screen reader if it's working well. You can add new string resources, dedicated to accessibility, in this case, please prefix theirs id with `a11y_`.
### Layout
When adding or editing layouts, make sure the layout will render correctly if device uses a RTL (Right To Left) language.

260
docs/signin.md Normal file
View File

@@ -0,0 +1,260 @@
# Sign in to a homeserver
This document describes the flow of signin to a homeserver, and also the flow when user want to reset his password. Examples come from the `matrix.org` homeserver.
## Sign up flows
### Get the flow
Client request the sign-in flows, once the homeserver is chosen by the user and its url is known (in the example it's `https://matrix.org`)
> curl -X GET 'https://matrix.org/_matrix/client/r0/login'
200
```json
{
"flows": [
{
"type": "m.login.password"
}
]
}
```
### Login with username
The user is able to connect using `m.login.password`
> curl -X POST --data $'{"identifier":{"type":"m.id.user","user":"alice"},"password":"weak_password","type":"m.login.password","initial_device_display_name":"Portable"}' 'https://matrix.org/_matrix/client/r0/login'
```json
{
"identifier": {
"type": "m.id.user",
"user": "alice"
},
"password": "weak_password",
"type": "m.login.password",
"initial_device_display_name": "Portable"
}
```
#### Incorrect password
403
```json
{
"errcode": "M_FORBIDDEN",
"error": "Invalid password"
}
```
#### Correct password:
We get credential (200)
```json
{
"user_id": "@benoit0816:matrix.org",
"access_token": "MDAxOGxvY2F0aW9uIG1hdHREDACTEDb2l0MDgxNjptYXRyaXgub3JnCjAwMTZjaWQgdHlwZSA9IGFjY2VzcwowMDIxY2lkIG5vbmNlID0gfnYrSypfdTtkNXIuNWx1KgowMDJmc2lnbmF0dXJlIOsh1XqeAkXexh4qcofl_aR4kHJoSOWYGOhE7-ubX-DZCg",
"home_server": "matrix.org",
"device_id": "GTVREDALBF",
"well_known": {
"m.homeserver": {
"base_url": "https:\/\/matrix.org\/"
}
}
}
```
### Login with email
If the user has associated an email with its account, he can signin using the email.
> curl -X POST --data $'{"identifier":{"type":"m.id.thirdparty","medium":"email","address":"alice@yopmail.com"},"password":"weak_password","type":"m.login.password","initial_device_display_name":"Portable"}' 'https://matrix.org/_matrix/client/r0/login'
```json
{
"identifier": {
"type": "m.id.thirdparty",
"medium": "email",
"address": "alice@yopmail.com"
},
"password": "weak_password",
"type": "m.login.password",
"initial_device_display_name": "Portable"
}
```
#### Unknown email
403
```json
{
"errcode": "M_FORBIDDEN",
"error": ""
}
```
#### Known email, wrong password
403
```json
{
"errcode": "M_FORBIDDEN",
"error": "Invalid password"
}
```
##### Known email, correct password
We get the credentials (200)
```json
{
"user_id": "@alice:matrix.org",
"access_token": "MDAxOGxvY2F0aW9uIG1hdHJpeC5vcmREDACTEDZXJfaWQgPSBAYmVub2l0MDgxNjptYXRyaXgub3JnCjAwMTZjaWQgdHlwZSA9IGFjY2VzcwowMDIxY2lkIG5vbmNlID0gNjtDY0MwRlNPSFFoOC5wOgowMDJmc2lnbmF0dXJlIGiTRm1mYLLxQywxOh3qzQVT8HoEorSokEP2u-bAwtnYCg",
"home_server": "matrix.org",
"device_id": "WBSREDASND",
"well_known": {
"m.homeserver": {
"base_url": "https:\/\/matrix.org\/"
}
}
}
```
### Login with Msisdn
Not supported yet in RiotX
### Login with SSO
> curl -X GET 'https://homeserver.with.sso/_matrix/client/r0/login'
200
```json
{
"flows": [
{
"type": "m.login.sso"
}
]
}
```
In this case, the user can click on "Sign in with SSO" and the web screen will be displayed on the page `https://homeserver.with.sso/_matrix/static/client/login/` and the credentials will be passed back to the native code through the JS bridge
## Reset password
Ref: `https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-account-password-email-requesttoken`
When the user has forgotten his password, he can reset it by providing an email and a new password.
Here is the flow:
### Send email
User is asked to enter the email linked to his account and a new password.
We display a warning regarding e2e.
At the first step, we do not send the password, only the email and a client secret, generated by the application
> curl -X POST --data $'{"client_secret":"6c57f284-85e2-421b-8270-fb1795a120a7","send_attempt":0,"email":"user@domain.com"}' 'https://matrix.org/_matrix/client/r0/account/password/email/requestToken'
```json
{
"client_secret": "6c57f284-85e2-421b-8270-fb1795a120a7",
"send_attempt": 0,
"email": "user@domain.com"
}
```
#### When the email is not known
We get a 400
```json
{
"errcode": "M_THREEPID_NOT_FOUND",
"error": "Email not found"
}
```
#### When the email is known
We get a 200 with a `sid`
```json
{
"sid": "tQNbrREDACTEDldA"
}
```
Then the user is asked to click on the link in the email he just received, and to confirm when it's done.
During this step, the new password is sent to the homeserver.
If the user confirms before the link is clicked, we get an error:
> curl -X POST --data $'{"auth":{"type":"m.login.email.identity","threepid_creds":{"client_secret":"6c57f284-85e2-421b-8270-fb1795a120a7","sid":"tQNbrREDACTEDldA"}},"new_password":"weak_password"}' 'https://matrix.org/_matrix/client/r0/account/password'
```json
{
"auth": {
"type": "m.login.email.identity",
"threepid_creds": {
"client_secret": "6c57f284-85e2-421b-8270-fb1795a120a7",
"sid": "tQNbrREDACTEDldA"
}
},
"new_password": "weak_password"
}
```
401
```json
{
"errcode": "M_UNAUTHORIZED",
"error": ""
}
```
### User clicks on the link
The link has the form:
https://matrix.org/_matrix/client/unstable/password_reset/email/submit_token?token=fzZLBlcqhTKeaFQFSRbsQnQCkzbwtGAD&client_secret=6c57f284-85e2-421b-8270-fb1795a120a7&sid=tQNbrREDACTEDldA
It contains the client secret, a token and the sid
When the user click the link, if validate his ownership and the new password can now be ent by the application (on user demand):
> curl -X POST --data $'{"auth":{"type":"m.login.email.identity","threepid_creds":{"client_secret":"6c57f284-85e2-421b-8270-fb1795a120a7","sid":"tQNbrREDACTEDldA"}},"new_password":"weak_password"}' 'https://matrix.org/_matrix/client/r0/account/password'
```json
{
"auth": {
"type": "m.login.email.identity",
"threepid_creds": {
"client_secret": "6c57f284-85e2-421b-8270-fb1795a120a7",
"sid": "tQNbrREDACTEDldA"
}
},
"new_password": "weak_password"
}
```
200
```json
{}
```
The password has been changed, and all the existing token are invalidated. User can now login with the new password.

579
docs/signup.md Normal file
View File

@@ -0,0 +1,579 @@
# Sign up to a homeserver
This document describes the flow of registration to a homeserver. Examples come from the `matrix.org` homeserver.
*Ref*: https://matrix.org/docs/spec/client_server/latest#account-registration-and-management
## Sign up flows
### First step
Client request the sign-up flows, once the homeserver is chosen by the user and its url is known (in the example it's `https://matrix.org`)
> curl -X POST --data $'{}' 'https://matrix.org/_matrix/client/r0/register'
```json
{
}
```
We get the flows with a 401, which also means the the registration is possible on this homeserver.
```json
{
"session": "vwehdKMtkRedactedAMwgCACZ",
"flows": [
{
"stages": [
"m.login.recaptcha",
"m.login.terms",
"m.login.dummy"
]
},
{
"stages": [
"m.login.recaptcha",
"m.login.terms",
"m.login.email.identity"
]
}
],
"params": {
"m.login.recaptcha": {
"public_key": "6LcgI54UAAAAAoREDACTEDoDdOocFpYVdjYBRe4zb"
},
"m.login.terms": {
"policies": {
"privacy_policy": {
"version": "1.0",
"en": {
"name": "Terms and Conditions",
"url": "https:\/\/matrix.org\/_matrix\/consent?v=1.0"
}
}
}
}
}
}
```
If the registration is not possible, we get a 403
```json
{
"errcode": "M_FORBIDDEN",
"error": "Registration is disabled"
}
```
### Step 1: entering user name and password
The app is displaying a form to enter username and password.
> curl -X POST --data $'{"initial_device_display_name":"Mobile device","username":"alice","password": "weak_password"}' 'https://matrix.org/_matrix/client/r0/register'
```json
{
"initial_device_display_name": "Mobile device",
"username": "alice",
"password": "weak_password"
}
```
401. Note that the `session` value has changed (because we did not provide the previous value in the request body), but it's ok, we will use the new value for the next steps.
```json
{
"session": "xptUYoREDACTEDogOWAGVnbJQ",
"flows": [
{
"stages": [
"m.login.recaptcha",
"m.login.terms",
"m.login.dummy"
]
},
{
"stages": [
"m.login.recaptcha",
"m.login.terms",
"m.login.email.identity"
]
}
],
"params": {
"m.login.recaptcha": {
"public_key": "6LcgI54UAAAAAoREDACTEDoDdOocFpYVdjYBRe4zb"
},
"m.login.terms": {
"policies": {
"privacy_policy": {
"version": "1.0",
"en": {
"name": "Terms and Conditions",
"url": "https:\/\/matrix.org\/_matrix\/consent?v=1.0"
}
}
}
}
}
}
```
#### If username already exists
We get a 400:
```json
{
"errcode": "M_USER_IN_USE",
"error": "User ID already taken."
}
```
### Step 2: entering email
User is proposed to enter an email. We skip this step.
> curl -X POST --data $'{"auth":{"session":"xptUYoREDACTEDogOWAGVnbJQ","type":"m.login.dummy"}}' 'https://matrix.org/_matrix/client/r0/register'
```json
{
"auth": {
"session": "xptUYoREDACTEDogOWAGVnbJQ",
"type": "m.login.dummy"
}
}
```
401
```json
{
"session": "xptUYoREDACTEDogOWAGVnbJQ",
"flows": [
{
"stages": [
"m.login.recaptcha",
"m.login.terms",
"m.login.dummy"
]
},
{
"stages": [
"m.login.recaptcha",
"m.login.terms",
"m.login.email.identity"
]
}
],
"params": {
"m.login.recaptcha": {
"public_key": "6LcgI54UAAAAAoREDACTEDoDdOocFpYVdjYBRe4zb"
},
"m.login.terms": {
"policies": {
"privacy_policy": {
"version": "1.0",
"en": {
"name": "Terms and Conditions",
"url": "https:\/\/matrix.org\/_matrix\/consent?v=1.0"
}
}
}
}
},
"completed": [
"m.login.dummy"
]
}
```
### Step 2 bis: we enter an email
We request a token to the homeserver. The `client_secret` is generated by the application
> curl -X POST --data $'{"client_secret":"53e679ea-oRED-ACTED-92b8-3012c49c6cfa","email":"alice@yopmail.com","send_attempt":0}' 'https://matrix.org/_matrix/client/r0/register/email/requestToken'
```json
{
"client_secret": "53e679ea-oRED-ACTED-92b8-3012c49c6cfa",
"email": "alice@yopmail.com",
"send_attempt": 0
}
```
200
```json
{
"sid": "qlBCREDACTEDEtgxD"
}
```
And
> curl -X POST --data $'{"auth":{"threepid_creds":{"client_secret":"53e679ea-oRED-ACTED-92b8-3012c49c6cfa","sid":"qlBCREDACTEDEtgxD"},"session":"xptUYoREDACTEDogOWAGVnbJQ","type":"m.login.email.identity"}}' 'https://matrix.org/_matrix/client/r0/register'
```json
{
"auth": {
"threepid_creds": {
"client_secret": "53e679ea-oRED-ACTED-92b8-3012c49c6cfa",
"sid": "qlBCREDACTEDEtgxD"
},
"session": "xptUYoREDACTEDogOWAGVnbJQ",
"type": "m.login.email.identity"
}
}
```
We get 401 since the email is not validated yet:
```json
{
"errcode": "M_UNAUTHORIZED",
"error": ""
}
```
The app is now polling on
> curl -X POST --data $'{"auth":{"threepid_creds":{"client_secret":"53e679ea-oRED-ACTED-92b8-3012c49c6cfa","sid":"qlBCREDACTEDEtgxD"},"session":"xptUYoREDACTEDogOWAGVnbJQ","type":"m.login.email.identity"}}' 'https://matrix.org/_matrix/client/r0/register'
```json
{
"auth": {
"threepid_creds": {
"client_secret": "53e679ea-oRED-ACTED-92b8-3012c49c6cfa",
"sid": "qlBCREDACTEDEtgxD"
},
"session": "xptUYoREDACTEDogOWAGVnbJQ",
"type": "m.login.email.identity"
}
}
```
We click on the link received by email `https://matrix.org/_matrix/client/unstable/registration/email/submit_token?token=vtQjQIZfwdoREDACTEDozrmKYSWlCXsJ&client_secret=53e679ea-oRED-ACTED-92b8-3012c49c6cfa&sid=qlBCREDACTEDEtgxD` which contains:
- A `token` vtQjQIZfwdoREDACTEDozrmKYSWlCXsJ
- The `client_secret`: 53e679ea-oRED-ACTED-92b8-3012c49c6cfa
- A `sid`: qlBCREDACTEDEtgxD
Once the link is clicked, the registration request (polling) returns a 401 with the following content:
```json
{
"session": "xptUYoREDACTEDogOWAGVnbJQ",
"flows": [
{
"stages": [
"m.login.recaptcha",
"m.login.terms",
"m.login.dummy"
]
},
{
"stages": [
"m.login.recaptcha",
"m.login.terms",
"m.login.email.identity"
]
}
],
"params": {
"m.login.recaptcha": {
"public_key": "6LcgI54UAAAAAoREDACTEDoDdOocFpYVdjYBRe4zb"
},
"m.login.terms": {
"policies": {
"privacy_policy": {
"version": "1.0",
"en": {
"name": "Terms and Conditions",
"url": "https:\/\/matrix.org\/_matrix\/consent?v=1.0"
}
}
}
}
},
"completed": [
"m.login.email.identity"
]
}
```
### Step 3: Accepting T&C
User is proposed to accept T&C and he accepts them
> curl -X POST --data $'{"auth":{"session":"xptUYoREDACTEDogOWAGVnbJQ","type":"m.login.terms"}}' 'https://matrix.org/_matrix/client/r0/register'
```json
{
"auth": {
"session": "xptUYoREDACTEDogOWAGVnbJQ",
"type": "m.login.terms"
}
}
```
401
```json
{
"session": "xptUYoREDACTEDogOWAGVnbJQ",
"flows": [
{
"stages": [
"m.login.recaptcha",
"m.login.terms",
"m.login.dummy"
]
},
{
"stages": [
"m.login.recaptcha",
"m.login.terms",
"m.login.email.identity"
]
}
],
"params": {
"m.login.recaptcha": {
"public_key": "6LcgI54UAAAAAoREDACTEDoDdOocFpYVdjYBRe4zb"
},
"m.login.terms": {
"policies": {
"privacy_policy": {
"version": "1.0",
"en": {
"name": "Terms and Conditions",
"url": "https:\/\/matrix.org\/_matrix\/consent?v=1.0"
}
}
}
}
},
"completed": [
"m.login.dummy",
"m.login.terms"
]
}
```
### Step 4: Captcha
User is proposed to prove he is not a robot and he does it:
> curl -X POST --data $'{"auth":{"response":"03AOLTBLSiGS9GhFDpAMblJ2nlXOmHXqAYJ5OvHCPUjiVLBef3k9snOYI_BDC32-t4D2jv-tpvkaiEI_uloobFd9RUTPpJ7con2hMddbKjSCYqXqcUQFhzhbcX6kw8uBnh2sbwBe80_ihrHGXEoACXQkL0ki1Q0uEtOeW20YBRjbNABsZPpLNZhGIWC0QVXnQ4FouAtZrl3gOAiyM-oG3cgP6M9pcANIAC_7T2P2amAHbtsTlSR9CsazNyS-rtDR9b5MywdtnWN9Aw8fTJb8cXQk_j7nvugMxzofPjSOrPKcr8h5OqPlpUCyxxnFtag6cuaPSUwh43D2L0E-ZX7djzaY2Yh_U2n6HegFNPOQ22CJmfrKwDlodmAfMPvAXyq77n3HpoREDACTEDo3830RHF4BfkGXUaZjctgg-A1mvC17hmQmQpkG7IhDqyw0onU-0vF_-ehCjq_CcQEDpS_O3uiHJaG5xGf-0rhLm57v_wA3deugbsZuO4uTuxZZycN_mKxZ97jlDVBetl9hc_5REPbhcT1w3uzTCSx7Q","session":"xptUYoREDACTEDogOWAGVnbJQ","type":"m.login.recaptcha"}}' 'https://matrix.org/_matrix/client/r0/register'
```json
{
"auth": {
"response": "03AOLTBLSiGS9GhFDpAMblJ2nlXOmHXqAYJ5OvHCPUjiVLBef3k9snOYI_BDC32-t4D2jv-tpvkaiEI_uloobFd9RUTPpJ7con2hMddbKjSCYqXqcUQFhzhbcX6kw8uBnh2sbwBe80_ihrHGXEoACXQkL0ki1Q0uEtOeW20YBRjbNABsZPpLNZhGIWC0QVXnQ4FouAtZrl3gOAiyM-oG3cgP6M9pcANIAC_7T2P2amAHbtsTlSR9CsazNyS-rtDR9b5MywdtnWN9Aw8fTJb8cXQk_j7nvugMxzofPjSOrPKcr8h5OqPlpUCyxxnFtag6cuaPSUwh43D2L0E-ZX7djzaY2Yh_U2n6HegFNPOQ22CJmfrKwDlodmAfMPvAXyq77n3HpoREDACTEDo3830RHF4BfkGXUaZjctgg-A1mvC17hmQmQpkG7IhDqyw0onU-0vF_-ehCjq_CcQEDpS_O3uiHJaG5xGf-0rhLm57v_wA3deugbsZuO4uTuxZZycN_mKxZ97jlDVBetl9hc_5REPbhcT1w3uzTCSx7Q",
"session": "xptUYoREDACTEDogOWAGVnbJQ",
"type": "m.login.recaptcha"
}
}
```
200
```json
{
"user_id": "@alice:matrix.org",
"home_server": "matrix.org",
"access_token": "MDAxOGxvY2F0aW9uIG1hdHJpeC5vcmcKMoREDACTEDo50aWZpZXIga2V5CjAwMTBjaWQgZ2VuID0gMQowMDI5Y2lkIHVzZXJfaWQgPSBAYmVub2l0eHh4eDptYXRoREDACTEDoCjAwMTZjaWQgdHlwZSA9IGFjY2VzcwowMDIxY2lkIG5vbmNlID0gNHVSVm00aVFDaWlKdoREDACTEDoJmc2lnbmF0dXJlIOmHnTLRfxiPjhrWhS-dThUX-qAzZktfRThzH1YyAsxaCg",
"device_id": "FLBAREDAJZ"
}
```
The account is created!
### Step 5: MSISDN
Some homeservers may require the user to enter MSISDN.
On matrix.org, it's not required, and not even optional, but it's still possible for the app to add a MSISDN during the registration.
The user enter a phone number and select a country, the `client_secret` is generated by the application
> curl -X POST --data $'{"client_secret":"d3e285f6-972a-496c-9a22-7915a2db57c7","send_attempt":1,"country":"FR","phone_number":"+33611223344"}' 'https://matrix.org/_matrix/client/r0/register/msisdn/requestToken'
```json
{
"client_secret": "d3e285f6-972a-496c-9a22-7915a2db57c7",
"send_attempt": 1,
"country": "FR",
"phone_number": "+33611223344"
}
```
If the msisdn is already associated to another account, you will received an error:
```json
{
"errcode": "M_THREEPID_IN_USE",
"error": "Phone number is already in use"
}
```
If it is not the case, the homeserver send the SMS and returns some data, especially a `sid` and a `submit_url`:
```json
{
"msisdn": "33611223344",
"intl_fmt": "+336 11 22 33 44",
"success": true,
"sid": "1678881798",
"submit_url": "https:\/\/matrix.org\/_matrix\/client\/unstable\/add_threepid\/msisdn\/submit_token"
}
```
When you execute the register request, with the received `sid`, you get an error since the MSISDN is not validated yet:
> curl -X POST --data $'{"auth":{"type":"m.login.msisdn","session":"xptUYoREDACTEDogOWAGVnbJQ","threepid_creds":{"client_secret":"d3e285f6-972a-496c-9a22-7915a2db57c7","sid":"1678881798"}}}' 'https://matrix.org/_matrix/client/r0/register'
```json
"auth": {
"type": "m.login.msisdn",
"session": "xptUYoREDACTEDogOWAGVnbJQ",
"threepid_creds": {
"client_secret": "d3e285f6-972a-496c-9a22-7915a2db57c7",
"sid": "1678881798"
}
}
}
```
There is an issue on Synapse, which return a 401, it sends too much data along with the classical MatrixError fields:
```json
{
"session": "xptUYoREDACTEDogOWAGVnbJQ",
"flows": [
{
"stages": [
"m.login.recaptcha",
"m.login.terms",
"m.login.dummy"
]
},
{
"stages": [
"m.login.recaptcha",
"m.login.terms",
"m.login.email.identity"
]
}
],
"params": {
"m.login.recaptcha": {
"public_key": "6LcgI54UAAAAABGdGmruw6DdOocFpYVdjYBRe4zb"
},
"m.login.terms": {
"policies": {
"privacy_policy": {
"version": "1.0",
"en": {
"name": "Terms and Conditions",
"url": "https:\/\/matrix.org\/_matrix\/consent?v=1.0"
}
}
}
}
},
"completed": [],
"error": "",
"errcode": "M_UNAUTHORIZED"
}
```
The user receive the SMS, he can enter the SMS code in the app, which is sent using the "submit_url" received ie the response of the `requestToken` request:
> curl -X POST --data $'{"client_secret":"d3e285f6-972a-496c-9a22-7915a2db57c7","sid":"1678881798","token":"123456"}' 'https://matrix.org/_matrix/client/unstable/add_threepid/msisdn/submit_token'
```json
{
"client_secret": "d3e285f6-972a-496c-9a22-7915a2db57c7",
"sid": "1678881798",
"token": "123456"
}
```
If the code is not correct, we get a 200 with:
```json
{
"success": false
}
```
And if the code is correct we get a 200 with:
```json
{
"success": true
}
```
We can now execute the registration request, to the homeserver
> curl -X POST --data $'{"auth":{"type":"m.login.msisdn","session":"xptUYoREDACTEDogOWAGVnbJQ","threepid_creds":{"client_secret":"d3e285f6-972a-496c-9a22-7915a2db57c7","sid":"1678881798"}}}' 'https://matrix.org/_matrix/client/r0/register'
```json
{
"auth": {
"type": "m.login.msisdn",
"session": "xptUYoREDACTEDogOWAGVnbJQ",
"threepid_creds": {
"client_secret": "d3e285f6-972a-496c-9a22-7915a2db57c7",
"sid": "1678881798"
}
}
}
```
Now the homeserver consider that the `m.login.msisdn` step is completed (401):
```json
{
"session": "xptUYoREDACTEDogOWAGVnbJQ",
"flows": [
{
"stages": [
"m.login.recaptcha",
"m.login.terms",
"m.login.dummy"
]
},
{
"stages": [
"m.login.recaptcha",
"m.login.terms",
"m.login.email.identity"
]
}
],
"params": {
"m.login.recaptcha": {
"public_key": "6LcgI54UAAAAABGdGmruw6DdOocFpYVdjYBRe4zb"
},
"m.login.terms": {
"policies": {
"privacy_policy": {
"version": "1.0",
"en": {
"name": "Terms and Conditions",
"url": "https:\/\/matrix.org\/_matrix\/consent?v=1.0"
}
}
}
}
},
"completed": [
"m.login.msisdn"
]
}
```

View File

@@ -11,6 +11,8 @@ android {
versionCode 1
versionName "1.0"
// Multidex is useful for tests
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

View File

@@ -1,42 +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.rx;
import android.content.Context;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("im.vector.matrix.rx.test", appContext.getPackageName());
}
}

View File

@@ -20,6 +20,7 @@ import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
import im.vector.matrix.android.api.session.room.model.ReadReceipt
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
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
@@ -67,6 +68,10 @@ class RxRoom(private val room: Room) {
fun liveDrafts(): Observable<List<UserDraft>> {
return room.getDraftsLive().asObservable()
}
fun liveNotificationState(): Observable<RoomNotificationState> {
return room.getLiveRoomNotificationState().asObservable()
}
}
fun Room.rx(): RxRoom {

View File

@@ -54,6 +54,10 @@ class RxSession(private val session: Session) {
return session.liveUsers().asObservable()
}
fun liveIgnoredUsers(): Observable<List<User>> {
return session.liveIgnoredUsers().asObservable()
}
fun livePagedUsers(filter: String? = null): Observable<PagedList<User>> {
return session.livePagedUsers(filter).asObservable()
}

View File

@@ -155,7 +155,8 @@ dependencies {
testImplementation 'junit:junit:4.12'
testImplementation 'org.robolectric:robolectric:4.3'
//testImplementation 'org.robolectric:shadows-support-v4:3.0'
testImplementation 'io.mockk:mockk:1.9.3.kotlin12'
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
testImplementation 'io.mockk:mockk:1.9.2.kotlin12'
testImplementation 'org.amshove.kluent:kluent-android:1.44'
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
@@ -165,7 +166,8 @@ dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'org.amshove.kluent:kluent-android:1.44'
androidTestImplementation 'io.mockk:mockk-android:1.9.3.kotlin12'
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
androidTestImplementation 'io.mockk:mockk-android:1.9.2.kotlin12'
androidTestImplementation "androidx.arch.core:core-testing:$lifecycle_version"
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"

View File

@@ -17,12 +17,12 @@
package im.vector.matrix.android
import android.content.Context
import androidx.test.InstrumentationRegistry
import androidx.test.core.app.ApplicationProvider
import java.io.File
interface InstrumentedTest {
fun context(): Context {
return InstrumentationRegistry.getTargetContext()
return ApplicationProvider.getApplicationContext()
}
fun cacheDir(): File {

View File

@@ -17,11 +17,11 @@
package im.vector.matrix.android.auth
import androidx.test.annotation.UiThreadTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.rule.GrantPermissionRule
import androidx.test.runner.AndroidJUnit4
import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.OkReplayRuleChainNoActivity
import im.vector.matrix.android.api.auth.Authenticator
import im.vector.matrix.android.api.auth.AuthenticationService
import okreplay.*
import org.junit.ClassRule
import org.junit.Rule
@@ -29,9 +29,9 @@ import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
internal class AuthenticatorTest : InstrumentedTest {
internal class AuthenticationServiceTest : InstrumentedTest {
lateinit var authenticator: Authenticator
lateinit var authenticationService: AuthenticationService
lateinit var okReplayInterceptor: OkReplayInterceptor
private val okReplayConfig = OkReplayConfig.Builder()

View File

@@ -22,7 +22,7 @@ import androidx.work.Configuration
import androidx.work.WorkManager
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.BuildConfig
import im.vector.matrix.android.api.auth.Authenticator
import im.vector.matrix.android.api.auth.AuthenticationService
import im.vector.matrix.android.internal.SessionManager
import im.vector.matrix.android.internal.di.DaggerMatrixComponent
import im.vector.matrix.android.internal.network.UserAgentHolder
@@ -46,7 +46,7 @@ data class MatrixConfiguration(
*/
class Matrix private constructor(context: Context, matrixConfiguration: MatrixConfiguration) {
@Inject internal lateinit var authenticator: Authenticator
@Inject internal lateinit var authenticationService: AuthenticationService
@Inject internal lateinit var userAgentHolder: UserAgentHolder
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
@Inject internal lateinit var olmManager: OlmManager
@@ -64,8 +64,8 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
fun getUserAgent() = userAgentHolder.userAgent
fun authenticator(): Authenticator {
return authenticator
fun authenticationService(): AuthenticationService {
return authenticationService
}
companion object {

View File

@@ -19,29 +19,48 @@ package im.vector.matrix.android.api.auth
import im.vector.matrix.android.api.MatrixCallback
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.login.LoginWizard
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
/**
* This interface defines methods to authenticate to a matrix server.
* This interface defines methods to authenticate or to create an account to a matrix server.
*/
interface Authenticator {
interface AuthenticationService {
/**
* Request the supported login flows for this homeserver
* Request the supported login flows for this homeserver.
* This is the first method to call to be able to get a wizard to login or the create an account
*/
fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResponse>): Cancelable
fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResult>): Cancelable
/**
* @param homeServerConnectionConfig this param is used to configure the Homeserver
* @param login the login field
* @param password the password field
* @param callback the matrix callback on which you'll receive the result of authentication.
* @return return a [Cancelable]
* Return a LoginWizard, to login to the homeserver. The login flow has to be retrieved first.
*/
fun authenticate(homeServerConnectionConfig: HomeServerConnectionConfig, login: String, password: String, callback: MatrixCallback<Session>): Cancelable
fun getLoginWizard(): LoginWizard
/**
* Return a RegistrationWizard, to create an matrix account on the homeserver. The login flow has to be retrieved first.
*/
fun getRegistrationWizard(): RegistrationWizard
/**
* True when login and password has been sent with success to the homeserver
*/
val isRegistrationStarted: Boolean
/**
* Cancel pending login or pending registration
*/
fun cancelPendingLoginOrRegistration()
/**
* Reset all pending settings, including current HomeServerConnectionConfig
*/
fun reset()
/**
* Check if there is an authenticated [Session].
@@ -67,5 +86,7 @@ interface Authenticator {
/**
* Create a session after a SSO successful login
*/
fun createSessionFromSso(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session
fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
credentials: Credentials,
callback: MatrixCallback<Session>): Cancelable
}

View File

@@ -30,4 +30,7 @@ data class Credentials(
@Json(name = "home_server") val homeServer: String,
@Json(name = "access_token") val accessToken: String,
@Json(name = "refresh_token") val refreshToken: String?,
@Json(name = "device_id") val deviceId: String?)
@Json(name = "device_id") val deviceId: String?,
// Optional data that may contain info to override home server and/or identity server
@Json(name = "well_known") val wellKnown: WellKnown? = null
)

View File

@@ -25,7 +25,7 @@ import okhttp3.TlsVersion
/**
* This data class holds how to connect to a specific Homeserver.
* It's used with [im.vector.matrix.android.api.auth.Authenticator] class.
* It's used with [im.vector.matrix.android.api.auth.AuthenticationService] class.
* You should use the [Builder] to create one.
*/
@JsonClass(generateAdapter = true)

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.auth.data
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
// Either a LoginFlowResponse, or an error if the homeserver is outdated
sealed class LoginFlowResult {
data class Success(
val loginFlowResponse: LoginFlowResponse,
val isLoginAndRegistrationSupported: Boolean
) : LoginFlowResult()
object OutdatedHomeserver : LoginFlowResult()
}

View File

@@ -0,0 +1,111 @@
/*
* 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.api.auth.data
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* Model for https://matrix.org/docs/spec/client_server/latest#get-matrix-client-versions
*
* Ex:
* <pre>
* {
* "unstable_features": {
* "m.lazy_load_members": true
* },
* "versions": [
* "r0.0.1",
* "r0.1.0",
* "r0.2.0",
* "r0.3.0"
* ]
* }
* </pre>
*/
@JsonClass(generateAdapter = true)
data class Versions(
@Json(name = "versions")
val supportedVersions: List<String>? = null,
@Json(name = "unstable_features")
val unstableFeatures: Map<String, Boolean>? = null
)
// MatrixClientServerAPIVersion
private const val r0_0_1 = "r0.0.1"
private const val r0_1_0 = "r0.1.0"
private const val r0_2_0 = "r0.2.0"
private const val r0_3_0 = "r0.3.0"
private const val r0_4_0 = "r0.4.0"
private const val r0_5_0 = "r0.5.0"
private const val r0_6_0 = "r0.6.0"
// MatrixVersionsFeature
private const val FEATURE_LAZY_LOAD_MEMBERS = "m.lazy_load_members"
private const val FEATURE_REQUIRE_IDENTITY_SERVER = "m.require_identity_server"
private const val FEATURE_ID_ACCESS_TOKEN = "m.id_access_token"
private const val FEATURE_SEPARATE_ADD_AND_BIND = "m.separate_add_and_bind"
/**
* Return true if the SDK supports this homeserver version
*/
fun Versions.isSupportedBySdk(): Boolean {
return supportLazyLoadMembers()
}
/**
* Return true if the SDK supports this homeserver version for login and registration
*/
fun Versions.isLoginAndRegistrationSupportedBySdk(): Boolean {
return !doesServerRequireIdentityServerParam()
&& doesServerAcceptIdentityAccessToken()
&& doesServerSeparatesAddAndBind()
}
/**
* Return true if the server support the lazy loading of room members
*
* @return true if the server support the lazy loading of room members
*/
private fun Versions.supportLazyLoadMembers(): Boolean {
return supportedVersions?.contains(r0_5_0) == true
|| unstableFeatures?.get(FEATURE_LAZY_LOAD_MEMBERS) == true
}
/**
* Indicate if the `id_server` parameter is required when registering with an 3pid,
* adding a 3pid or resetting password.
*/
private fun Versions.doesServerRequireIdentityServerParam(): Boolean {
if (supportedVersions?.contains(r0_6_0) == true) return false
return unstableFeatures?.get(FEATURE_REQUIRE_IDENTITY_SERVER) ?: true
}
/**
* Indicate if the `id_access_token` parameter can be safely passed to the homeserver.
* Some homeservers may trigger errors if they are not prepared for the new parameter.
*/
private fun Versions.doesServerAcceptIdentityAccessToken(): Boolean {
return supportedVersions?.contains(r0_6_0) == true
|| unstableFeatures?.get(FEATURE_ID_ACCESS_TOKEN) ?: false
}
private fun Versions.doesServerSeparatesAddAndBind(): Boolean {
return supportedVersions?.contains(r0_6_0) == true
|| unstableFeatures?.get(FEATURE_SEPARATE_ADD_AND_BIND) ?: false
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.auth.data
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
* <pre>
* {
* "m.homeserver": {
* "base_url": "https://matrix.org"
* },
* "m.identity_server": {
* "base_url": "https://vector.im"
* }
* "m.integrations": {
* "managers": [
* {
* "api_url": "https://integrations.example.org",
* "ui_url": "https://integrations.example.org/ui"
* },
* {
* "api_url": "https://bots.example.org"
* }
* ]
* }
* }
* </pre>
*/
@JsonClass(generateAdapter = true)
data class WellKnown(
@Json(name = "m.homeserver")
var homeServer: WellKnownBaseConfig? = null,
@Json(name = "m.identity_server")
var identityServer: WellKnownBaseConfig? = null,
@Json(name = "m.integrations")
var integrations: Map<String, @JvmSuppressWildcards Any>? = null
) {
/**
* Returns the list of integration managers proposed
*/
fun getIntegrationManagers(): List<WellKnownManagerConfig> {
val managers = ArrayList<WellKnownManagerConfig>()
integrations?.get("managers")?.let {
(it as? ArrayList<*>)?.let { configs ->
configs.forEach { config ->
(config as? Map<*, *>)?.let { map ->
val apiUrl = map["api_url"] as? String
val uiUrl = map["ui_url"] as? String ?: apiUrl
if (apiUrl != null
&& apiUrl.startsWith("https://")
&& uiUrl!!.startsWith("https://")) {
managers.add(WellKnownManagerConfig(
apiUrl = apiUrl,
uiUrl = uiUrl
))
}
}
}
}
}
return managers
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.auth.data
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
* <pre>
* {
* "base_url": "https://vector.im"
* }
* </pre>
*/
@JsonClass(generateAdapter = true)
data class WellKnownBaseConfig(
@Json(name = "base_url")
val baseURL: String? = null
)

View File

@@ -0,0 +1,21 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.auth.data
data class WellKnownManagerConfig(
val apiUrl : String,
val uiUrl: String
)

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.auth.login
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.util.Cancelable
interface LoginWizard {
/**
* @param login the login field
* @param password the password field
* @param deviceName the initial device name
* @param callback the matrix callback on which you'll receive the result of authentication.
* @return return a [Cancelable]
*/
fun login(login: String,
password: String,
deviceName: String,
callback: MatrixCallback<Session>): Cancelable
/**
* Reset user password
*/
fun resetPassword(email: String,
newPassword: String,
callback: MatrixCallback<Unit>): Cancelable
/**
* Confirm the new password, once the user has checked his email
*/
fun resetPasswordMailConfirmed(callback: MatrixCallback<Unit>): Cancelable
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.auth.registration
sealed class RegisterThreePid {
data class Email(val email: String) : RegisterThreePid()
data class Msisdn(val msisdn: String, val countryCode: String) : RegisterThreePid()
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.auth.registration
import im.vector.matrix.android.api.session.Session
// Either a session or an object containing data about registration stages
sealed class RegistrationResult {
data class Success(val session: Session) : RegistrationResult()
data class FlowResponse(val flowResult: FlowResult) : RegistrationResult()
}
data class FlowResult(
val missingStages: List<Stage>,
val completedStages: List<Stage>
)

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.auth.registration
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable
interface RegistrationWizard {
fun getRegistrationFlow(callback: MatrixCallback<RegistrationResult>): Cancelable
fun createAccount(userName: String, password: String, initialDeviceDisplayName: String?, callback: MatrixCallback<RegistrationResult>): Cancelable
fun performReCaptcha(response: String, callback: MatrixCallback<RegistrationResult>): Cancelable
fun acceptTerms(callback: MatrixCallback<RegistrationResult>): Cancelable
fun dummy(callback: MatrixCallback<RegistrationResult>): Cancelable
fun addThreePid(threePid: RegisterThreePid, callback: MatrixCallback<RegistrationResult>): Cancelable
fun sendAgainThreePid(callback: MatrixCallback<RegistrationResult>): Cancelable
fun handleValidateThreePid(code: String, callback: MatrixCallback<RegistrationResult>): Cancelable
fun checkIfEmailHasBeenValidated(delayMillis: Long, callback: MatrixCallback<RegistrationResult>): Cancelable
val currentThreePid: String?
// True when login and password has been sent with success to the homeserver
val isRegistrationStarted: Boolean
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.auth.registration
sealed class Stage(open val mandatory: Boolean) {
// m.login.recaptcha
data class ReCaptcha(override val mandatory: Boolean, val publicKey: String) : Stage(mandatory)
// m.login.oauth2
// m.login.email.identity
data class Email(override val mandatory: Boolean) : Stage(mandatory)
// m.login.msisdn
data class Msisdn(override val mandatory: Boolean) : Stage(mandatory)
// m.login.token
// m.login.dummy, can be mandatory if there is no other stages. In this case the account cannot be created by just sending a username
// and a password, the dummy stage has to be done
data class Dummy(override val mandatory: Boolean) : Stage(mandatory)
// Undocumented yet: m.login.terms
data class Terms(override val mandatory: Boolean, val policies: TermPolicies) : Stage(mandatory)
// For unknown stages
data class Other(override val mandatory: Boolean, val type: String, val params: Map<*, *>?) : Stage(mandatory)
}
typealias TermPolicies = Map<*, *>

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.crypto
import im.vector.matrix.android.api.session.crypto.sas.EmojiRepresentation
import im.vector.matrix.android.internal.crypto.verification.getEmojiForCode
/**
* Provide all the emojis used for SAS verification (for debug purpose)
*/
fun getAllVerificationEmojis(): List<EmojiRepresentation> {
return (0..63).map { getEmojiForCode(it) }
}

View File

@@ -34,6 +34,7 @@ sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
data class Cancelled(val throwable: Throwable? = null) : Failure(throwable)
data class NetworkConnection(val ioException: IOException? = null) : Failure(ioException)
data class ServerError(val error: MatrixError, val httpCode: Int) : Failure(RuntimeException(error.toString()))
object SuccessError : Failure(RuntimeException(RuntimeException("SuccessResult is false")))
// When server send an error, but it cannot be interpreted as a MatrixError
data class OtherServerError(val errorBody: String, val httpCode: Int) : Failure(RuntimeException(errorBody))

View File

@@ -31,7 +31,9 @@ data class MatrixError(
@Json(name = "consent_uri") val consentUri: String? = null,
// RESOURCE_LIMIT_EXCEEDED data
@Json(name = "limit_type") val limitType: String? = null,
@Json(name = "admin_contact") val adminUri: String? = null) {
@Json(name = "admin_contact") val adminUri: String? = null,
// For LIMIT_EXCEEDED
@Json(name = "retry_after_ms") val retryAfterMillis: Long? = null) {
companion object {
const val FORBIDDEN = "M_FORBIDDEN"

View File

@@ -21,7 +21,7 @@ import timber.log.Timber
sealed class Action {
object Notify : Action()
object DoNotNotify : Action()
data class Sound(val sound: String) : Action()
data class Sound(val sound: String = ACTION_OBJECT_VALUE_VALUE_DEFAULT) : Action()
data class Highlight(val highlight: Boolean) : Action()
}
@@ -63,6 +63,29 @@ private const val ACTION_OBJECT_VALUE_VALUE_DEFAULT = "default"
*
* </pre>
*/
@Suppress("IMPLICIT_CAST_TO_ANY")
fun List<Action>.toJson(): List<Any> {
return map { action ->
when (action) {
is Action.Notify -> ACTION_NOTIFY
is Action.DoNotNotify -> ACTION_DONT_NOTIFY
is Action.Sound -> {
mapOf(
ACTION_OBJECT_SET_TWEAK_KEY to ACTION_OBJECT_SET_TWEAK_VALUE_SOUND,
ACTION_OBJECT_VALUE_KEY to action.sound
)
}
is Action.Highlight -> {
mapOf(
ACTION_OBJECT_SET_TWEAK_KEY to ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT,
ACTION_OBJECT_VALUE_KEY to action.highlight
)
}
}
}
}
fun PushRule.getActions(): List<Action> {
val result = ArrayList<Action>()

View File

@@ -34,6 +34,10 @@ interface PushRuleService {
fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable
fun addPushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable
fun removePushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable
fun addPushRuleListener(listener: PushRuleListener)
fun removePushRuleListener(listener: PushRuleListener)

View File

@@ -19,7 +19,7 @@ package im.vector.matrix.android.api.session.cache
import im.vector.matrix.android.api.MatrixCallback
/**
* This interface defines a method to sign out. It's implemented at the session level.
* This interface defines a method to clear the cache. It's implemented at the session level.
*/
interface CacheService {

View File

@@ -16,7 +16,7 @@
package im.vector.matrix.android.api.session.events.model
import java.util.*
import java.util.UUID
object LocalEcho {

View File

@@ -17,6 +17,7 @@
package im.vector.matrix.android.api.session.file
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
import java.io.File
@@ -47,5 +48,5 @@ interface FileService {
fileName: String,
url: String?,
elementToDecrypt: ElementToDecrypt?,
callback: MatrixCallback<File>)
callback: MatrixCallback<File>): Cancelable
}

View File

@@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.room.crypto.RoomCryptoService
import im.vector.matrix.android.api.session.room.members.MembershipService
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.relation.RelationService
import im.vector.matrix.android.api.session.room.notification.RoomPushRuleService
import im.vector.matrix.android.api.session.room.reporting.ReportingService
import im.vector.matrix.android.api.session.room.read.ReadService
import im.vector.matrix.android.api.session.room.send.DraftService
@@ -41,7 +42,8 @@ interface Room :
StateService,
ReportingService,
RelationService,
RoomCryptoService {
RoomCryptoService,
RoomPushRuleService {
/**
* The roomId of this room

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.matrix.android.api.session.room.model
import com.squareup.moshi.Json
/**
* Enum for [RoomJoinRulesContent] : https://matrix.org/docs/spec/client_server/r0.4.0#m-room-join-rules
*/
enum class RoomJoinRules(val value: String) {
@Json(name = "public")
PUBLIC("public"),
@Json(name = "invite")
INVITE("invite"),
@Json(name = "knock")
KNOCK("knock"),
@Json(name = "private")
PRIVATE("private")
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.matrix.android.api.session.room.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* Class representing the EventType.STATE_ROOM_JOIN_RULES state event content
*/
@JsonClass(generateAdapter = true)
data class RoomJoinRulesContent(
@Json(name = "join_rule") val joinRules: RoomJoinRules? = null
)

View File

@@ -38,7 +38,7 @@ data class MessageImageContent(
/**
* Metadata about the image referred to in url.
*/
@Json(name = "info") val info: ImageInfo? = null,
@Json(name = "info") override val info: ImageInfo? = null,
/**
* Required. Required if the file is unencrypted. The URL (typically MXC URI) to the image.
@@ -52,4 +52,4 @@ data class MessageImageContent(
* Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
*/
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageEncryptedContent
) : MessageImageInfoContent

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.matrix.android.api.session.room.model.message
/**
* A content with image information
*/
interface MessageImageInfoContent : MessageEncryptedContent {
val info: ImageInfo?
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.matrix.android.api.session.room.model.message
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
@JsonClass(generateAdapter = true)
data class MessageStickerContent(
/**
* Set in local, not from server
*/
override val type: String = MessageType.MSGTYPE_STICKER_LOCAL,
/**
* Required. A textual representation of the image. This could be the alt text of the image, the filename of the image,
* or some kind of content description for accessibility e.g. 'image attachment'.
*/
@Json(name = "body") override val body: String,
/**
* Metadata about the image referred to in url.
*/
@Json(name = "info") override val info: ImageInfo? = null,
/**
* Required. Required if the file is unencrypted. The URL (typically MXC URI) to the image.
*/
@Json(name = "url") override val url: String? = null,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null,
/**
* Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
*/
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageImageInfoContent

View File

@@ -72,7 +72,7 @@ interface RelationService {
*/
fun editTextMessage(targetEventId: String,
msgType: String,
newBodyText: String,
newBodyText: CharSequence,
newBodyAutoMarkdown: Boolean,
compatibilityBodyText: String = "* $newBodyText"): Cancelable
@@ -97,12 +97,14 @@ interface RelationService {
/**
* Reply to an event in the timeline (must be in same room)
* https://matrix.org/docs/spec/client_server/r0.4.0.html#id350
* The replyText can be a Spannable and contains special spans (UserMentionSpan) that will be translated
* by the sdk into pills.
* @param eventReplied the event referenced by the reply
* @param replyText the reply text
* @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present
*/
fun replyToMessage(eventReplied: TimelineEvent,
replyText: String,
replyText: CharSequence,
autoMarkdown: Boolean = false): Cancelable?
fun getEventSummaryLive(eventId: String): LiveData<Optional<EventAnnotationsSummary>>

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.notification
/**
* Defines the room notification state
*/
enum class RoomNotificationState {
/**
* All the messages will trigger a noisy notification
*/
ALL_MESSAGES_NOISY,
/**
* All the messages will trigger a notification
*/
ALL_MESSAGES,
/**
* Only the messages with user display name / user name will trigger notifications
*/
MENTIONS_ONLY,
/**
* No notifications
*/
MUTE
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.notification
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable
interface RoomPushRuleService {
fun getLiveRoomNotificationState(): LiveData<RoomNotificationState>
fun setRoomNotificationState(roomNotificationState: RoomNotificationState, matrixCallback: MatrixCallback<Unit>): Cancelable
}

View File

@@ -29,20 +29,23 @@ interface SendService {
/**
* Method to send a text message asynchronously.
* The text to send can be a Spannable and contains special spans (UserMentionSpan) that will be translated
* by the sdk into pills.
* @param text the text message to send
* @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE
* @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present
* @return a [Cancelable]
*/
fun sendTextMessage(text: String, msgType: String = MessageType.MSGTYPE_TEXT, autoMarkdown: Boolean = false): Cancelable
fun sendTextMessage(text: CharSequence, msgType: String = MessageType.MSGTYPE_TEXT, autoMarkdown: Boolean = false): Cancelable
/**
* Method to send a text message with a formatted body.
* @param text the text message to send
* @param formattedText The formatted body using MessageType#FORMAT_MATRIX_HTML
* @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE
* @return a [Cancelable]
*/
fun sendFormattedTextMessage(text: String, formattedText: String): Cancelable
fun sendFormattedTextMessage(text: String, formattedText: String, msgType: String = MessageType.MSGTYPE_TEXT): Cancelable
/**
* Method to send a media asynchronously.

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.send
/**
* Tag class for spans that should mention a user.
* These Spans will be transformed into pills when detected in message to send
*/
interface UserMentionSpan {
val displayName: String
val userId: String
}

View File

@@ -30,10 +30,16 @@ package im.vector.matrix.android.api.session.room.timeline
*/
interface Timeline {
var listener: Listener?
val timelineID: String
val isLive: Boolean
fun addListener(listener: Listener): Boolean
fun removeListener(listener: Listener): Boolean
fun removeAllListeners()
/**
* This should be called before any other method after creating the timeline. It ensures the underlying database is open
*/
@@ -98,7 +104,7 @@ interface Timeline {
interface Listener {
/**
* Call when the timeline has been updated through pagination or sync.
* @param snapshot the most uptodate snapshot
* @param snapshot the most up to date snapshot
*/
fun onUpdated(snapshot: List<TimelineEvent>)
}

View File

@@ -23,6 +23,7 @@ import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
import im.vector.matrix.android.api.session.room.model.ReadReceipt
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageStickerContent
import im.vector.matrix.android.api.session.room.model.message.isReply
import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
@@ -40,8 +41,7 @@ data class TimelineEvent(
val isUniqueDisplayName: Boolean,
val senderAvatar: String?,
val annotations: EventAnnotationsSummary? = null,
val readReceipts: List<ReadReceipt> = emptyList(),
val hasReadMarker: Boolean = false
val readReceipts: List<ReadReceipt> = emptyList()
) {
val metadata = HashMap<String, Any>()
@@ -62,15 +62,11 @@ data class TimelineEvent(
}
fun getDisambiguatedDisplayName(): String {
return if (isUniqueDisplayName) {
senderName
} else {
senderName?.let { name ->
"$name (${root.senderId})"
}
return when {
senderName.isNullOrBlank() -> root.senderId ?: ""
isUniqueDisplayName -> senderName
else -> "$senderName (${root.senderId})"
}
?: root.senderId
?: ""
}
/**
@@ -103,8 +99,14 @@ fun TimelineEvent.getEditedEventId(): String? {
/**
* Get last MessageContent, after a possible edition
*/
fun TimelineEvent.getLastMessageContent(): MessageContent? = annotations?.editSummary?.aggregatedContent?.toModel()
?: root.getClearContent().toModel()
fun TimelineEvent.getLastMessageContent(): MessageContent? {
return if (root.getClearType() == EventType.STICKER) {
root.getClearContent().toModel<MessageStickerContent>()
} else {
annotations?.editSummary?.aggregatedContent?.toModel()
?: root.getClearContent().toModel()
}
}
/**
* Get last Message body, after a possible edition
@@ -113,7 +115,8 @@ fun TimelineEvent.getLastMessageBody(): String? {
val lastMessageContent = getLastMessageContent()
if (lastMessageContent != null) {
return lastMessageContent.newContent?.toModel<MessageContent>()?.body ?: lastMessageContent.body
return lastMessageContent.newContent?.toModel<MessageContent>()?.body
?: lastMessageContent.body
}
return null

View File

@@ -64,4 +64,19 @@ interface UserService {
* @return a Livedata of users
*/
fun livePagedUsers(filter: String? = null): LiveData<PagedList<User>>
/**
* Get list of ignored users
*/
fun liveIgnoredUsers(): LiveData<List<User>>
/**
* Ignore users
*/
fun ignoreUserIds(userIds: List<String>, callback: MatrixCallback<Unit>): Cancelable
/**
* Un-ignore some users
*/
fun unIgnoreUserIds(userIds: List<String>, callback: MatrixCallback<Unit>): Cancelable
}

View File

@@ -29,3 +29,5 @@ interface Cancelable {
// no-op
}
}
object NoOpCancellable : Cancelable

View File

@@ -21,4 +21,6 @@ import java.lang.reflect.ParameterizedType
typealias JsonDict = Map<String, @JvmSuppressWildcards Any>
val emptyJsonDict = emptyMap<String, Any>()
internal val JSON_DICT_PARAMETERIZED_TYPE: ParameterizedType = Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java)

View File

@@ -17,20 +17,47 @@
package im.vector.matrix.android.internal.auth
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.Versions
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
import im.vector.matrix.android.internal.auth.login.ResetPasswordMailConfirmed
import im.vector.matrix.android.internal.auth.registration.*
import im.vector.matrix.android.internal.network.NetworkConstants
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.POST
import retrofit2.http.*
/**
* The login REST API.
*/
internal interface AuthAPI {
/**
* Get the version information of the homeserver
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_ + "versions")
fun versions(): Call<Versions>
/**
* Register to the homeserver
* Ref: https://matrix.org/docs/spec/client_server/latest#account-registration-and-management
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register")
fun register(@Body registrationParams: RegistrationParams): Call<Credentials>
/**
* Add 3Pid during registration
* Ref: https://gist.github.com/jryans/839a09bf0c5a70e2f36ed990d50ed928
* https://github.com/matrix-org/matrix-doc/pull/2290
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register/{threePid}/requestToken")
fun add3Pid(@Path("threePid") threePid: String, @Body params: AddThreePidRegistrationParams): Call<AddThreePidRegistrationResponse>
/**
* Validate 3pid
*/
@POST
fun validate3Pid(@Url url: String, @Body params: ValidationCodeBody): Call<SuccessResult>
/**
* Get the supported login flow
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-login
@@ -47,4 +74,16 @@ internal interface AuthAPI {
@Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000")
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
fun login(@Body loginParams: PasswordLoginParams): Call<Credentials>
/**
* Ask the homeserver to reset the password associated with the provided email.
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/password/email/requestToken")
fun resetPassword(@Body params: AddThreePidRegistrationParams): Call<AddThreePidRegistrationResponse>
/**
* Ask the homeserver to reset the password with the provided new password once the email is validated.
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/password")
fun resetPasswordMailConfirmed(@Body params: ResetPasswordMailConfirmed): Call<Unit>
}

View File

@@ -20,8 +20,10 @@ import android.content.Context
import dagger.Binds
import dagger.Module
import dagger.Provides
import im.vector.matrix.android.api.auth.Authenticator
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
@@ -50,7 +52,8 @@ internal abstract class AuthModule {
}
.name("matrix-sdk-auth.realm")
.modules(AuthRealmModule())
.deleteRealmIfMigrationNeeded()
.schemaVersion(AuthRealmMigration.SCHEMA_VERSION)
.migration(AuthRealmMigration())
.build()
}
}
@@ -59,5 +62,11 @@ internal abstract class AuthModule {
abstract fun bindSessionParamsStore(sessionParamsStore: RealmSessionParamsStore): SessionParamsStore
@Binds
abstract fun bindAuthenticator(authenticator: DefaultAuthenticator): Authenticator
abstract fun bindPendingSessionStore(pendingSessionStore: RealmPendingSessionStore): PendingSessionStore
@Binds
abstract fun bindAuthenticationService(authenticationService: DefaultAuthenticationService): AuthenticationService
@Binds
abstract fun bindSessionCreator(sessionCreator: DefaultSessionCreator): SessionCreator
}

View File

@@ -0,0 +1,205 @@
/*
* 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.auth
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.*
import im.vector.matrix.android.api.auth.login.LoginWizard
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
import im.vector.matrix.android.api.session.Session
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.db.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
import im.vector.matrix.android.internal.network.RetrofitFactory
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.launchToCallback
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.toCancelable
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import javax.inject.Inject
internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated
private val okHttpClient: Lazy<OkHttpClient>,
private val retrofitFactory: RetrofitFactory,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val sessionParamsStore: SessionParamsStore,
private val sessionManager: SessionManager,
private val sessionCreator: SessionCreator,
private val pendingSessionStore: PendingSessionStore
) : AuthenticationService {
private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData()
private var currentLoginWizard: LoginWizard? = null
private var currentRegistrationWizard: RegistrationWizard? = null
override fun hasAuthenticatedSessions(): Boolean {
return sessionParamsStore.getLast() != null
}
override fun getLastAuthenticatedSession(): Session? {
val sessionParams = sessionParamsStore.getLast()
return sessionParams?.let {
sessionManager.getOrCreateSession(it)
}
}
override fun getSession(sessionParams: SessionParams): Session? {
return sessionManager.getOrCreateSession(sessionParams)
}
override fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResult>): Cancelable {
pendingSessionData = null
return GlobalScope.launch(coroutineDispatchers.main) {
pendingSessionStore.delete()
val result = runCatching {
getLoginFlowInternal(homeServerConnectionConfig)
}
result.fold(
{
if (it is LoginFlowResult.Success) {
// The homeserver exists and up to date, keep the config
pendingSessionData = PendingSessionData(homeServerConnectionConfig)
.also { data -> pendingSessionStore.savePendingSessionData(data) }
}
callback.onSuccess(it)
},
{
callback.onFailure(it)
}
)
}
.toCancelable()
}
private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig) = withContext(coroutineDispatchers.io) {
val authAPI = buildAuthAPI(homeServerConnectionConfig)
// First check the homeserver version
val versions = executeRequest<Versions> {
apiCall = authAPI.versions()
}
if (versions.isSupportedBySdk()) {
// Get the login flow
val loginFlowResponse = executeRequest<LoginFlowResponse> {
apiCall = authAPI.getLoginFlows()
}
LoginFlowResult.Success(loginFlowResponse, versions.isLoginAndRegistrationSupportedBySdk())
} else {
// Not supported
LoginFlowResult.OutdatedHomeserver
}
}
override fun getRegistrationWizard(): RegistrationWizard {
return currentRegistrationWizard
?: let {
pendingSessionData?.homeServerConnectionConfig?.let {
DefaultRegistrationWizard(
okHttpClient,
retrofitFactory,
coroutineDispatchers,
sessionCreator,
pendingSessionStore
).also {
currentRegistrationWizard = it
}
} ?: error("Please call getLoginFlow() with success first")
}
}
override val isRegistrationStarted: Boolean
get() = currentRegistrationWizard?.isRegistrationStarted == true
override fun getLoginWizard(): LoginWizard {
return currentLoginWizard
?: let {
pendingSessionData?.homeServerConnectionConfig?.let {
DefaultLoginWizard(
okHttpClient,
retrofitFactory,
coroutineDispatchers,
sessionCreator,
pendingSessionStore
).also {
currentLoginWizard = it
}
} ?: error("Please call getLoginFlow() with success first")
}
}
override fun cancelPendingLoginOrRegistration() {
currentLoginWizard = null
currentRegistrationWizard = null
// Keep only the home sever config
// Update the local pendingSessionData synchronously
pendingSessionData = pendingSessionData?.homeServerConnectionConfig
?.let { PendingSessionData(it) }
.also {
GlobalScope.launch(coroutineDispatchers.main) {
if (it == null) {
// Should not happen
pendingSessionStore.delete()
} else {
pendingSessionStore.savePendingSessionData(it)
}
}
}
}
override fun reset() {
currentLoginWizard = null
currentRegistrationWizard = null
pendingSessionData = null
GlobalScope.launch(coroutineDispatchers.main) {
pendingSessionStore.delete()
}
}
override fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
credentials: Credentials,
callback: MatrixCallback<Session>): Cancelable {
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
createSessionFromSso(credentials, homeServerConnectionConfig)
}
}
private suspend fun createSessionFromSso(credentials: Credentials,
homeServerConnectionConfig: HomeServerConnectionConfig): Session = withContext(coroutineDispatchers.computation) {
sessionCreator.createSession(credentials, homeServerConnectionConfig)
}
private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {
val retrofit = retrofitFactory.create(okHttpClient, homeServerConnectionConfig.homeServerUri.toString())
return retrofit.create(AuthAPI::class.java)
}
}

View File

@@ -1,125 +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.auth
import android.util.Patterns
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.Authenticator
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.session.Session
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.PasswordLoginParams
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
import im.vector.matrix.android.internal.di.Unauthenticated
import im.vector.matrix.android.internal.extensions.foldToCallback
import im.vector.matrix.android.internal.network.RetrofitFactory
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.util.CancelableCoroutine
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import javax.inject.Inject
import javax.inject.Provider
internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
private val okHttpClient: Provider<OkHttpClient>,
private val retrofitFactory: RetrofitFactory,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val sessionParamsStore: SessionParamsStore,
private val sessionManager: SessionManager
) : Authenticator {
override fun hasAuthenticatedSessions(): Boolean {
return sessionParamsStore.getLast() != null
}
override fun getLastAuthenticatedSession(): Session? {
val sessionParams = sessionParamsStore.getLast()
return sessionParams?.let {
sessionManager.getOrCreateSession(it)
}
}
override fun getSession(sessionParams: SessionParams): Session? {
return sessionManager.getOrCreateSession(sessionParams)
}
override fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResponse>): Cancelable {
val job = GlobalScope.launch(coroutineDispatchers.main) {
val result = runCatching {
getLoginFlowInternal(homeServerConnectionConfig)
}
result.foldToCallback(callback)
}
return CancelableCoroutine(job)
}
override fun authenticate(homeServerConnectionConfig: HomeServerConnectionConfig,
login: String,
password: String,
callback: MatrixCallback<Session>): Cancelable {
val job = GlobalScope.launch(coroutineDispatchers.main) {
val sessionOrFailure = runCatching {
authenticate(homeServerConnectionConfig, login, password)
}
sessionOrFailure.foldToCallback(callback)
}
return CancelableCoroutine(job)
}
private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig) = withContext(coroutineDispatchers.io) {
val authAPI = buildAuthAPI(homeServerConnectionConfig)
executeRequest<LoginFlowResponse> {
apiCall = authAPI.getLoginFlows()
}
}
private suspend fun authenticate(homeServerConnectionConfig: HomeServerConnectionConfig,
login: String,
password: String) = withContext(coroutineDispatchers.io) {
val authAPI = buildAuthAPI(homeServerConnectionConfig)
val loginParams = if (Patterns.EMAIL_ADDRESS.matcher(login).matches()) {
PasswordLoginParams.thirdPartyIdentifier(ThreePidMedium.EMAIL, login, password, "Mobile")
} else {
PasswordLoginParams.userIdentifier(login, password, "Mobile")
}
val credentials = executeRequest<Credentials> {
apiCall = authAPI.login(loginParams)
}
val sessionParams = SessionParams(credentials, homeServerConnectionConfig)
sessionParamsStore.save(sessionParams)
sessionManager.getOrCreateSession(sessionParams)
}
override fun createSessionFromSso(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session {
val sessionParams = SessionParams(credentials, homeServerConnectionConfig)
sessionParamsStore.save(sessionParams)
return sessionManager.getOrCreateSession(sessionParams)
}
private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {
val retrofit = retrofitFactory.create(okHttpClient.get(), homeServerConnectionConfig.homeServerUri.toString())
return retrofit.create(AuthAPI::class.java)
}
}

View File

@@ -0,0 +1,31 @@
/*
* 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.auth
import im.vector.matrix.android.internal.auth.db.PendingSessionData
/**
* Store for elements when doing login or registration
*/
internal interface PendingSessionStore {
suspend fun savePendingSessionData(pendingSessionData: PendingSessionData)
fun getPendingSessionData(): PendingSessionData?
suspend fun delete()
}

View File

@@ -0,0 +1,68 @@
/*
* 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.auth
import android.net.Uri
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.session.Session
import im.vector.matrix.android.internal.SessionManager
import timber.log.Timber
import javax.inject.Inject
internal interface SessionCreator {
suspend fun createSession(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session
}
internal class DefaultSessionCreator @Inject constructor(
private val sessionParamsStore: SessionParamsStore,
private val sessionManager: SessionManager,
private val pendingSessionStore: PendingSessionStore
) : SessionCreator {
/**
* Credentials can affect the homeServerConnectionConfig, override home server url and/or
* identity server url if provided in the credentials
*/
override suspend fun createSession(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session {
// We can cleanup the pending session params
pendingSessionStore.delete()
val sessionParams = SessionParams(
credentials = credentials,
homeServerConnectionConfig = homeServerConnectionConfig.copy(
homeServerUri = credentials.wellKnown?.homeServer?.baseURL
// remove trailing "/"
?.trim { it == '/' }
?.takeIf { it.isNotBlank() }
?.also { Timber.d("Overriding homeserver url to $it") }
?.let { Uri.parse(it) }
?: homeServerConnectionConfig.homeServerUri,
identityServerUri = credentials.wellKnown?.identityServer?.baseURL
// remove trailing "/"
?.trim { it == '/' }
?.takeIf { it.isNotBlank() }
?.also { Timber.d("Overriding identity server url to $it") }
?.let { Uri.parse(it) }
?: homeServerConnectionConfig.identityServerUri
))
sessionParamsStore.save(sessionParams)
return sessionManager.getOrCreateSession(sessionParams)
}
}

View File

@@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.auth
import arrow.core.Try
import im.vector.matrix.android.api.auth.data.SessionParams
internal interface SessionParamsStore {
@@ -27,9 +26,9 @@ internal interface SessionParamsStore {
fun getAll(): List<SessionParams>
fun save(sessionParams: SessionParams): Try<Unit>
suspend fun save(sessionParams: SessionParams)
fun delete(userId: String): Try<Unit>
suspend fun delete(userId: String)
fun deleteAll(): Try<Unit>
suspend fun deleteAll()
}

View File

@@ -30,12 +30,4 @@ data class InteractiveAuthenticationFlow(
@Json(name = "stages")
val stages: List<String>? = null
) {
companion object {
// Possible values for type
const val TYPE_LOGIN_SSO = "m.login.sso"
const val TYPE_LOGIN_TOKEN = "m.login.token"
const val TYPE_LOGIN_PASSWORD = "m.login.password"
}
}
)

View File

@@ -25,4 +25,7 @@ object LoginFlowTypes {
const val MSISDN = "m.login.msisdn"
const val RECAPTCHA = "m.login.recaptcha"
const val DUMMY = "m.login.dummy"
const val TERMS = "m.login.terms"
const val TOKEN = "m.login.token"
const val SSO = "m.login.sso"
}

View File

@@ -19,34 +19,46 @@ package im.vector.matrix.android.internal.auth.data
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* Ref:
* - https://matrix.org/docs/spec/client_server/r0.5.0#password-based
* - https://matrix.org/docs/spec/client_server/r0.5.0#identifier-types
*/
@JsonClass(generateAdapter = true)
internal data class PasswordLoginParams(@Json(name = "identifier") val identifier: Map<String, String>,
@Json(name = "password") val password: String,
@Json(name = "type") override val type: String,
@Json(name = "initial_device_display_name") val deviceDisplayName: String?,
@Json(name = "device_id") val deviceId: String?) : LoginParams {
internal data class PasswordLoginParams(
@Json(name = "identifier") val identifier: Map<String, String>,
@Json(name = "password") val password: String,
@Json(name = "type") override val type: String,
@Json(name = "initial_device_display_name") val deviceDisplayName: String?,
@Json(name = "device_id") val deviceId: String?) : LoginParams {
companion object {
private const val IDENTIFIER_KEY_TYPE = "type"
val IDENTIFIER_KEY_TYPE_USER = "m.id.user"
val IDENTIFIER_KEY_TYPE_THIRD_PARTY = "m.id.thirdparty"
val IDENTIFIER_KEY_TYPE_PHONE = "m.id.phone"
private const val IDENTIFIER_KEY_TYPE_USER = "m.id.user"
private const val IDENTIFIER_KEY_USER = "user"
val IDENTIFIER_KEY_TYPE = "type"
val IDENTIFIER_KEY_MEDIUM = "medium"
val IDENTIFIER_KEY_ADDRESS = "address"
val IDENTIFIER_KEY_USER = "user"
val IDENTIFIER_KEY_COUNTRY = "country"
val IDENTIFIER_KEY_NUMBER = "number"
private const val IDENTIFIER_KEY_TYPE_THIRD_PARTY = "m.id.thirdparty"
private const val IDENTIFIER_KEY_MEDIUM = "medium"
private const val IDENTIFIER_KEY_ADDRESS = "address"
private const val IDENTIFIER_KEY_TYPE_PHONE = "m.id.phone"
private const val IDENTIFIER_KEY_COUNTRY = "country"
private const val IDENTIFIER_KEY_PHONE = "phone"
fun userIdentifier(user: String,
password: String,
deviceDisplayName: String? = null,
deviceId: String? = null): PasswordLoginParams {
val identifier = HashMap<String, String>()
identifier[IDENTIFIER_KEY_TYPE] = IDENTIFIER_KEY_TYPE_USER
identifier[IDENTIFIER_KEY_USER] = user
return PasswordLoginParams(identifier, password, LoginFlowTypes.PASSWORD, deviceDisplayName, deviceId)
return PasswordLoginParams(
mapOf(
IDENTIFIER_KEY_TYPE to IDENTIFIER_KEY_TYPE_USER,
IDENTIFIER_KEY_USER to user
),
password,
LoginFlowTypes.PASSWORD,
deviceDisplayName,
deviceId)
}
fun thirdPartyIdentifier(medium: String,
@@ -54,11 +66,33 @@ internal data class PasswordLoginParams(@Json(name = "identifier") val identifie
password: String,
deviceDisplayName: String? = null,
deviceId: String? = null): PasswordLoginParams {
val identifier = HashMap<String, String>()
identifier[IDENTIFIER_KEY_TYPE] = IDENTIFIER_KEY_TYPE_THIRD_PARTY
identifier[IDENTIFIER_KEY_MEDIUM] = medium
identifier[IDENTIFIER_KEY_ADDRESS] = address
return PasswordLoginParams(identifier, password, LoginFlowTypes.PASSWORD, deviceDisplayName, deviceId)
return PasswordLoginParams(
mapOf(
IDENTIFIER_KEY_TYPE to IDENTIFIER_KEY_TYPE_THIRD_PARTY,
IDENTIFIER_KEY_MEDIUM to medium,
IDENTIFIER_KEY_ADDRESS to address
),
password,
LoginFlowTypes.PASSWORD,
deviceDisplayName,
deviceId)
}
fun phoneIdentifier(country: String,
phone: String,
password: String,
deviceDisplayName: String? = null,
deviceId: String? = null): PasswordLoginParams {
return PasswordLoginParams(
mapOf(
IDENTIFIER_KEY_TYPE to IDENTIFIER_KEY_TYPE_PHONE,
IDENTIFIER_KEY_COUNTRY to country,
IDENTIFIER_KEY_PHONE to phone
),
password,
LoginFlowTypes.PASSWORD,
deviceDisplayName,
deviceId)
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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.auth.db
import io.realm.DynamicRealm
import io.realm.RealmMigration
import timber.log.Timber
internal class AuthRealmMigration : RealmMigration {
companion object {
// Current schema version
const val SCHEMA_VERSION = 1L
}
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
Timber.d("Migrating Auth Realm from $oldVersion to $newVersion")
if (oldVersion <= 0) {
Timber.d("Step 0 -> 1")
Timber.d("Create PendingSessionEntity")
realm.schema.create("PendingSessionEntity")
.addField(PendingSessionEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, String::class.java)
.setRequired(PendingSessionEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, true)
.addField(PendingSessionEntityFields.CLIENT_SECRET, String::class.java)
.setRequired(PendingSessionEntityFields.CLIENT_SECRET, true)
.addField(PendingSessionEntityFields.SEND_ATTEMPT, Integer::class.java)
.setRequired(PendingSessionEntityFields.SEND_ATTEMPT, true)
.addField(PendingSessionEntityFields.RESET_PASSWORD_DATA_JSON, String::class.java)
.addField(PendingSessionEntityFields.CURRENT_SESSION, String::class.java)
.addField(PendingSessionEntityFields.IS_REGISTRATION_STARTED, Boolean::class.java)
.addField(PendingSessionEntityFields.CURRENT_THREE_PID_DATA_JSON, String::class.java)
}
}
}

View File

@@ -23,6 +23,7 @@ import io.realm.annotations.RealmModule
*/
@RealmModule(library = true,
classes = [
SessionParamsEntity::class
SessionParamsEntity::class,
PendingSessionEntity::class
])
internal class AuthRealmModule

View File

@@ -0,0 +1,50 @@
/*
* 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.auth.db
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.*
/**
* This class holds all pending data when creating a session, either by login or by register
*/
internal data class PendingSessionData(
val homeServerConnectionConfig: HomeServerConnectionConfig,
/* ==========================================================================================
* Common
* ========================================================================================== */
val clientSecret: String = UUID.randomUUID().toString(),
val sendAttempt: Int = 0,
/* ==========================================================================================
* For login
* ========================================================================================== */
val resetPasswordData: ResetPasswordData? = null,
/* ==========================================================================================
* For register
* ========================================================================================== */
val currentSession: String? = null,
val isRegistrationStarted: Boolean = false,
val currentThreePidData: ThreePidData? = null
)

View File

@@ -0,0 +1,29 @@
/*
* 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.auth.db
import io.realm.RealmObject
internal open class PendingSessionEntity(
var homeServerConnectionConfigJson: String = "",
var clientSecret: String = "",
var sendAttempt: Int = 0,
var resetPasswordDataJson: String? = null,
var currentSession: String? = null,
var isRegistrationStarted: Boolean = false,
var currentThreePidDataJson: String? = null
) : RealmObject()

View File

@@ -0,0 +1,69 @@
/*
* 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.auth.db
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.ThreePidData
import javax.inject.Inject
internal class PendingSessionMapper @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.homeServerConnectionConfigJson)!!
val resetPasswordData = entity.resetPasswordDataJson?.let { resetPasswordDataAdapter.fromJson(it) }
val threePidData = entity.currentThreePidDataJson?.let { threePidDataAdapter.fromJson(it) }
return PendingSessionData(
homeServerConnectionConfig = homeServerConnectionConfig,
clientSecret = entity.clientSecret,
sendAttempt = entity.sendAttempt,
resetPasswordData = resetPasswordData,
currentSession = entity.currentSession,
isRegistrationStarted = entity.isRegistrationStarted,
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(
homeServerConnectionConfigJson = homeServerConnectionConfigJson,
clientSecret = sessionData.clientSecret,
sendAttempt = sessionData.sendAttempt,
resetPasswordDataJson = resetPasswordDataJson,
currentSession = sessionData.currentSession,
isRegistrationStarted = sessionData.isRegistrationStarted,
currentThreePidDataJson = currentThreePidDataJson
)
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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.auth.db
import im.vector.matrix.android.internal.auth.PendingSessionStore
import im.vector.matrix.android.internal.database.awaitTransaction
import im.vector.matrix.android.internal.di.AuthDatabase
import io.realm.Realm
import io.realm.RealmConfiguration
import javax.inject.Inject
internal class RealmPendingSessionStore @Inject constructor(private val mapper: PendingSessionMapper,
@AuthDatabase
private val realmConfiguration: RealmConfiguration
) : PendingSessionStore {
override suspend fun savePendingSessionData(pendingSessionData: PendingSessionData) {
awaitTransaction(realmConfiguration) { realm ->
val entity = mapper.map(pendingSessionData)
if (entity != null) {
realm.where(PendingSessionEntity::class.java)
.findAll()
.deleteAllFromRealm()
realm.insert(entity)
}
}
}
override fun getPendingSessionData(): PendingSessionData? {
return Realm.getInstance(realmConfiguration).use { realm ->
realm
.where(PendingSessionEntity::class.java)
.findAll()
.map { mapper.map(it) }
.firstOrNull()
}
}
override suspend fun delete() {
awaitTransaction(realmConfiguration) {
it.where(PendingSessionEntity::class.java)
.findAll()
.deleteAllFromRealm()
}
}
}

View File

@@ -16,12 +16,14 @@
package im.vector.matrix.android.internal.auth.db
import arrow.core.Try
import im.vector.matrix.android.api.auth.data.SessionParams
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 io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.exceptions.RealmPrimaryKeyConstraintException
import timber.log.Timber
import javax.inject.Inject
internal class RealmSessionParamsStore @Inject constructor(private val mapper: SessionParamsMapper,
@@ -30,73 +32,63 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S
) : SessionParamsStore {
override fun getLast(): SessionParams? {
val realm = Realm.getInstance(realmConfiguration)
val sessionParams = realm
.where(SessionParamsEntity::class.java)
.findAll()
.map { mapper.map(it) }
.lastOrNull()
realm.close()
return sessionParams
return Realm.getInstance(realmConfiguration).use { realm ->
realm
.where(SessionParamsEntity::class.java)
.findAll()
.map { mapper.map(it) }
.lastOrNull()
}
}
override fun get(userId: String): SessionParams? {
val realm = Realm.getInstance(realmConfiguration)
val sessionParams = realm
.where(SessionParamsEntity::class.java)
.equalTo(SessionParamsEntityFields.USER_ID, userId)
.findAll()
.map { mapper.map(it) }
.firstOrNull()
realm.close()
return sessionParams
return Realm.getInstance(realmConfiguration).use { realm ->
realm
.where(SessionParamsEntity::class.java)
.equalTo(SessionParamsEntityFields.USER_ID, userId)
.findAll()
.map { mapper.map(it) }
.firstOrNull()
}
}
override fun getAll(): List<SessionParams> {
val realm = Realm.getInstance(realmConfiguration)
val sessionParams = realm
.where(SessionParamsEntity::class.java)
.findAll()
.mapNotNull { mapper.map(it) }
realm.close()
return sessionParams
return Realm.getInstance(realmConfiguration).use { realm ->
realm
.where(SessionParamsEntity::class.java)
.findAll()
.mapNotNull { mapper.map(it) }
}
}
override fun save(sessionParams: SessionParams): Try<Unit> {
return Try {
override suspend fun save(sessionParams: SessionParams) {
awaitTransaction(realmConfiguration) {
val entity = mapper.map(sessionParams)
if (entity != null) {
val realm = Realm.getInstance(realmConfiguration)
realm.executeTransaction {
try {
it.insert(entity)
} catch (e: RealmPrimaryKeyConstraintException) {
Timber.e(e, "Something wrong happened during previous session creation. Override with new credentials")
it.insertOrUpdate(entity)
}
realm.close()
}
}
}
override fun delete(userId: String): Try<Unit> {
return Try {
val realm = Realm.getInstance(realmConfiguration)
realm.executeTransaction {
it.where(SessionParamsEntity::class.java)
.equalTo(SessionParamsEntityFields.USER_ID, userId)
.findAll()
.deleteAllFromRealm()
}
realm.close()
override suspend fun delete(userId: String) {
awaitTransaction(realmConfiguration) {
it.where(SessionParamsEntity::class.java)
.equalTo(SessionParamsEntityFields.USER_ID, userId)
.findAll()
.deleteAllFromRealm()
}
}
override fun deleteAll(): Try<Unit> {
return Try {
val realm = Realm.getInstance(realmConfiguration)
realm.executeTransaction {
it.where(SessionParamsEntity::class.java)
.findAll()
.deleteAllFromRealm()
}
realm.close()
override suspend fun deleteAll() {
awaitTransaction(realmConfiguration) {
it.where(SessionParamsEntity::class.java)
.findAll()
.deleteAllFromRealm()
}
}
}

View File

@@ -0,0 +1,130 @@
/*
* 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.auth.login
import android.util.Patterns
import dagger.Lazy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.login.LoginWizard
import im.vector.matrix.android.api.auth.registration.RegisterThreePid
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.NoOpCancellable
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.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.AddThreePidRegistrationParams
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationResponse
import im.vector.matrix.android.internal.auth.registration.RegisterAddThreePidTask
import im.vector.matrix.android.internal.network.RetrofitFactory
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.launchToCallback
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
internal class DefaultLoginWizard(
okHttpClient: Lazy<OkHttpClient>,
retrofitFactory: RetrofitFactory,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val sessionCreator: SessionCreator,
private val pendingSessionStore: PendingSessionStore
) : LoginWizard {
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
private val authAPI = retrofitFactory.create(okHttpClient, pendingSessionData.homeServerConnectionConfig.homeServerUri.toString())
.create(AuthAPI::class.java)
override fun login(login: String,
password: String,
deviceName: String,
callback: MatrixCallback<Session>): Cancelable {
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
loginInternal(login, password, deviceName)
}
}
private suspend fun loginInternal(login: String,
password: String,
deviceName: String) = withContext(coroutineDispatchers.computation) {
val loginParams = if (Patterns.EMAIL_ADDRESS.matcher(login).matches()) {
PasswordLoginParams.thirdPartyIdentifier(ThreePidMedium.EMAIL, login, password, deviceName)
} else {
PasswordLoginParams.userIdentifier(login, password, deviceName)
}
val credentials = executeRequest<Credentials> {
apiCall = authAPI.login(loginParams)
}
sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
}
override fun resetPassword(email: String, newPassword: String, callback: MatrixCallback<Unit>): Cancelable {
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
resetPasswordInternal(email, newPassword)
}
}
private suspend fun resetPasswordInternal(email: String, newPassword: String) {
val param = RegisterAddThreePidTask.Params(
RegisterThreePid.Email(email),
pendingSessionData.clientSecret,
pendingSessionData.sendAttempt
)
pendingSessionData = pendingSessionData.copy(sendAttempt = pendingSessionData.sendAttempt + 1)
.also { pendingSessionStore.savePendingSessionData(it) }
val result = executeRequest<AddThreePidRegistrationResponse> {
apiCall = authAPI.resetPassword(AddThreePidRegistrationParams.from(param))
}
pendingSessionData = pendingSessionData.copy(resetPasswordData = ResetPasswordData(newPassword, result))
.also { pendingSessionStore.savePendingSessionData(it) }
}
override fun resetPasswordMailConfirmed(callback: MatrixCallback<Unit>): Cancelable {
val safeResetPasswordData = pendingSessionData.resetPasswordData ?: run {
callback.onFailure(IllegalStateException("developer error, no reset password in progress"))
return NoOpCancellable
}
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
resetPasswordMailConfirmedInternal(safeResetPasswordData)
}
}
private suspend fun resetPasswordMailConfirmedInternal(resetPasswordData: ResetPasswordData) {
val param = ResetPasswordMailConfirmed.create(
pendingSessionData.clientSecret,
resetPasswordData.addThreePidRegistrationResponse.sid,
resetPasswordData.newPassword
)
executeRequest<Unit> {
apiCall = authAPI.resetPasswordMailConfirmed(param)
}
// Set to null?
// resetPasswordData = null
}
}

View File

@@ -0,0 +1,29 @@
/*
* 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.auth.login
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationResponse
/**
* Container to store the data when a reset password is in the email validation step
*/
@JsonClass(generateAdapter = true)
internal data class ResetPasswordData(
val newPassword: String,
val addThreePidRegistrationResponse: AddThreePidRegistrationResponse
)

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2014 OpenMarket Ltd
* Copyright 2017 Vector Creations 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.auth.login
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.internal.auth.registration.AuthParams
/**
* Class to pass parameters to reset the password once a email has been validated.
*/
@JsonClass(generateAdapter = true)
internal data class ResetPasswordMailConfirmed(
// authentication parameters
@Json(name = "auth")
val auth: AuthParams? = null,
// the new password
@Json(name = "new_password")
val newPassword: String? = null
) {
companion object {
fun create(clientSecret: String, sid: String, newPassword: String): ResetPasswordMailConfirmed {
return ResetPasswordMailConfirmed(
auth = AuthParams.createForResetPassword(clientSecret, sid),
newPassword = newPassword
)
}
}
}

View File

@@ -0,0 +1,101 @@
/*
* 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.auth.registration
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.auth.registration.RegisterThreePid
/**
* Add a three Pid during authentication
*/
@JsonClass(generateAdapter = true)
internal data class AddThreePidRegistrationParams(
/**
* Required. A unique string generated by the client, and used to identify the validation attempt.
* It must be a string consisting of the characters [0-9a-zA-Z.=_-]. Its length must not exceed 255 characters and it must not be empty.
*/
@Json(name = "client_secret")
val clientSecret: String,
/**
* Required. The server will only send an email if the send_attempt is a number greater than the most recent one which it has seen,
* scoped to that email + client_secret pair. This is to avoid repeatedly sending the same email in the case of request retries between
* the POSTing user and the identity server. The client should increment this value if they desire a new email (e.g. a reminder) to be sent.
* If they do not, the server should respond with success but not resend the email.
*/
@Json(name = "send_attempt")
val sendAttempt: Int,
/**
* Optional. When the validation is completed, the identity server will redirect the user to this URL. This option is ignored when
* submitting 3PID validation information through a POST request.
*/
@Json(name = "next_link")
val nextLink: String? = null,
/**
* Required. The hostname of the identity server to communicate with. May optionally include a port.
* This parameter is ignored when the homeserver handles 3PID verification.
*/
@Json(name = "id_server")
val id_server: String? = null,
/* ==========================================================================================
* For emails
* ========================================================================================== */
/**
* Required. The email address to validate.
*/
@Json(name = "email")
val email: String? = null,
/* ==========================================================================================
* For Msisdn
* ========================================================================================== */
/**
* Required. The two-letter uppercase ISO country code that the number in phone_number should be parsed as if it were dialled from.
*/
@Json(name = "country")
val countryCode: String? = null,
/**
* Required. The phone number to validate.
*/
@Json(name = "phone_number")
val msisdn: String? = null
) {
companion object {
fun from(params: RegisterAddThreePidTask.Params): AddThreePidRegistrationParams {
return when (params.threePid) {
is RegisterThreePid.Email -> AddThreePidRegistrationParams(
email = params.threePid.email,
clientSecret = params.clientSecret,
sendAttempt = params.sendAttempt
)
is RegisterThreePid.Msisdn -> AddThreePidRegistrationParams(
msisdn = params.threePid.msisdn,
countryCode = params.threePid.countryCode,
clientSecret = params.clientSecret,
sendAttempt = params.sendAttempt
)
}
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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.auth.registration
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class AddThreePidRegistrationResponse(
/**
* Required. The session ID. Session IDs are opaque strings that must consist entirely of the characters [0-9a-zA-Z.=_-].
* Their length must not exceed 255 characters and they must not be empty.
*/
@Json(name = "sid")
val sid: String,
/**
* An optional field containing a URL where the client must submit the validation token to, with identical parameters to the Identity
* Service API's POST /validate/email/submitToken endpoint. The homeserver must send this token to the user (if applicable),
* who should then be prompted to provide it to the client.
*
* If this field is not present, the client can assume that verification will happen without the client's involvement provided
* the homeserver advertises this specification version in the /versions response (ie: r0.5.0).
*/
@Json(name = "submit_url")
val submitUrl: String? = null,
/* ==========================================================================================
* It seems that the homeserver is sending more data, we may need it
* ========================================================================================== */
@Json(name = "msisdn")
val msisdn: String? = null,
@Json(name = "intl_fmt")
val formattedMsisdn: String? = null,
@Json(name = "success")
val success: Boolean? = null
)

View File

@@ -0,0 +1,102 @@
/*
* 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.auth.registration
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
/**
* Open class, parent to all possible authentication parameters
*/
@JsonClass(generateAdapter = true)
internal data class AuthParams(
@Json(name = "type")
val type: String,
/**
* Note: session can be null for reset password request
*/
@Json(name = "session")
val session: String?,
/**
* parameter for "m.login.recaptcha" type
*/
@Json(name = "response")
val captchaResponse: String? = null,
/**
* parameter for "m.login.email.identity" type
*/
@Json(name = "threepid_creds")
val threePidCredentials: ThreePidCredentials? = null
) {
companion object {
fun createForCaptcha(session: String, captchaResponse: String): AuthParams {
return AuthParams(
type = LoginFlowTypes.RECAPTCHA,
session = session,
captchaResponse = captchaResponse
)
}
fun createForEmailIdentity(session: String, threePidCredentials: ThreePidCredentials): AuthParams {
return AuthParams(
type = LoginFlowTypes.EMAIL_IDENTITY,
session = session,
threePidCredentials = threePidCredentials
)
}
/**
* Note that there is a bug in Synapse (I have to investigate where), but if we pass LoginFlowTypes.MSISDN,
* the homeserver answer with the login flow with MatrixError fields and not with a simple MatrixError 401.
*/
fun createForMsisdnIdentity(session: String, threePidCredentials: ThreePidCredentials): AuthParams {
return AuthParams(
type = LoginFlowTypes.MSISDN,
session = session,
threePidCredentials = threePidCredentials
)
}
fun createForResetPassword(clientSecret: String, sid: String): AuthParams {
return AuthParams(
type = LoginFlowTypes.EMAIL_IDENTITY,
session = null,
threePidCredentials = ThreePidCredentials(
clientSecret = clientSecret,
sid = sid
)
)
}
}
}
@JsonClass(generateAdapter = true)
data class ThreePidCredentials(
@Json(name = "client_secret")
val clientSecret: String? = null,
@Json(name = "id_server")
val idServer: String? = null,
@Json(name = "sid")
val sid: String? = null
)

View File

@@ -0,0 +1,246 @@
/*
* 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.auth.registration
import dagger.Lazy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.registration.RegisterThreePid
import im.vector.matrix.android.api.auth.registration.RegistrationResult
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.failure.Failure.RegistrationFlowError
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.NoOpCancellable
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
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import okhttp3.OkHttpClient
/**
* This class execute the registration request and is responsible to keep the session of interactive authentication
*/
internal class DefaultRegistrationWizard(
private val okHttpClient: Lazy<OkHttpClient>,
private val retrofitFactory: RetrofitFactory,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val sessionCreator: SessionCreator,
private val pendingSessionStore: PendingSessionStore
) : RegistrationWizard {
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
private val authAPI = buildAuthAPI()
private val registerTask = DefaultRegisterTask(authAPI)
private val registerAddThreePidTask = DefaultRegisterAddThreePidTask(authAPI)
private val validateCodeTask = DefaultValidateCodeTask(authAPI)
override val currentThreePid: String?
get() {
return when (val threePid = pendingSessionData.currentThreePidData?.threePid) {
is RegisterThreePid.Email -> threePid.email
is RegisterThreePid.Msisdn -> {
// Take formatted msisdn if provided by the server
pendingSessionData.currentThreePidData?.addThreePidRegistrationResponse?.formattedMsisdn?.takeIf { it.isNotBlank() } ?: threePid.msisdn
}
null -> null
}
}
override val isRegistrationStarted: Boolean
get() = pendingSessionData.isRegistrationStarted
override fun getRegistrationFlow(callback: MatrixCallback<RegistrationResult>): Cancelable {
val params = RegistrationParams()
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
performRegistrationRequest(params)
}
}
override fun createAccount(userName: String,
password: String,
initialDeviceDisplayName: String?,
callback: MatrixCallback<RegistrationResult>): Cancelable {
val params = RegistrationParams(
username = userName,
password = password,
initialDeviceDisplayName = initialDeviceDisplayName
)
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
performRegistrationRequest(params)
.also {
pendingSessionData = pendingSessionData.copy(isRegistrationStarted = true)
.also { pendingSessionStore.savePendingSessionData(it) }
}
}
}
override fun performReCaptcha(response: String, callback: MatrixCallback<RegistrationResult>): Cancelable {
val safeSession = pendingSessionData.currentSession ?: run {
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
return NoOpCancellable
}
val params = RegistrationParams(auth = AuthParams.createForCaptcha(safeSession, response))
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
performRegistrationRequest(params)
}
}
override fun acceptTerms(callback: MatrixCallback<RegistrationResult>): Cancelable {
val safeSession = pendingSessionData.currentSession ?: run {
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
return NoOpCancellable
}
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.TERMS, session = safeSession))
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
performRegistrationRequest(params)
}
}
override fun addThreePid(threePid: RegisterThreePid, callback: MatrixCallback<RegistrationResult>): Cancelable {
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
pendingSessionData = pendingSessionData.copy(currentThreePidData = null)
.also { pendingSessionStore.savePendingSessionData(it) }
sendThreePid(threePid)
}
}
override fun sendAgainThreePid(callback: MatrixCallback<RegistrationResult>): Cancelable {
val safeCurrentThreePid = pendingSessionData.currentThreePidData?.threePid ?: run {
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
return NoOpCancellable
}
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
sendThreePid(safeCurrentThreePid)
}
}
private suspend fun sendThreePid(threePid: RegisterThreePid): RegistrationResult {
val safeSession = pendingSessionData.currentSession ?: throw IllegalStateException("developer error, call createAccount() method first")
val response = registerAddThreePidTask.execute(
RegisterAddThreePidTask.Params(
threePid,
pendingSessionData.clientSecret,
pendingSessionData.sendAttempt))
pendingSessionData = pendingSessionData.copy(sendAttempt = pendingSessionData.sendAttempt + 1)
.also { pendingSessionStore.savePendingSessionData(it) }
val params = RegistrationParams(
auth = if (threePid is RegisterThreePid.Email) {
AuthParams.createForEmailIdentity(safeSession,
ThreePidCredentials(
clientSecret = pendingSessionData.clientSecret,
sid = response.sid
)
)
} else {
AuthParams.createForMsisdnIdentity(safeSession,
ThreePidCredentials(
clientSecret = pendingSessionData.clientSecret,
sid = response.sid
)
)
}
)
// Store data
pendingSessionData = pendingSessionData.copy(currentThreePidData = ThreePidData.from(threePid, response, params))
.also { pendingSessionStore.savePendingSessionData(it) }
// and send the sid a first time
return performRegistrationRequest(params)
}
override fun checkIfEmailHasBeenValidated(delayMillis: Long, callback: MatrixCallback<RegistrationResult>): Cancelable {
val safeParam = pendingSessionData.currentThreePidData?.registrationParams ?: run {
callback.onFailure(IllegalStateException("developer error, no pending three pid"))
return NoOpCancellable
}
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
performRegistrationRequest(safeParam, delayMillis)
}
}
override fun handleValidateThreePid(code: String, callback: MatrixCallback<RegistrationResult>): Cancelable {
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
validateThreePid(code)
}
}
private suspend fun validateThreePid(code: String): RegistrationResult {
val registrationParams = pendingSessionData.currentThreePidData?.registrationParams
?: throw IllegalStateException("developer error, no pending three pid")
val safeCurrentData = pendingSessionData.currentThreePidData ?: throw IllegalStateException("developer error, call createAccount() method first")
val url = safeCurrentData.addThreePidRegistrationResponse.submitUrl ?: throw IllegalStateException("Missing url the send the code")
val validationBody = ValidationCodeBody(
clientSecret = pendingSessionData.clientSecret,
sid = safeCurrentData.addThreePidRegistrationResponse.sid,
code = code
)
val validationResponse = validateCodeTask.execute(ValidateCodeTask.Params(url, validationBody))
if (validationResponse.success == true) {
// The entered code is correct
// Same than validate email
return performRegistrationRequest(registrationParams, 3_000)
} else {
// The code is not correct
throw Failure.SuccessError
}
}
override fun dummy(callback: MatrixCallback<RegistrationResult>): Cancelable {
val safeSession = pendingSessionData.currentSession ?: run {
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
return NoOpCancellable
}
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.DUMMY, session = safeSession))
performRegistrationRequest(params)
}
}
private suspend fun performRegistrationRequest(registrationParams: RegistrationParams,
delayMillis: Long = 0): RegistrationResult {
delay(delayMillis)
val credentials = try {
registerTask.execute(RegisterTask.Params(registrationParams))
} catch (exception: Throwable) {
if (exception is RegistrationFlowError) {
pendingSessionData = pendingSessionData.copy(currentSession = exception.registrationFlowResponse.session)
.also { pendingSessionStore.savePendingSessionData(it) }
return RegistrationResult.FlowResponse(exception.registrationFlowResponse.toFlowResult())
} else {
throw exception
}
}
val session = sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
return RegistrationResult.Success(session)
}
private fun buildAuthAPI(): AuthAPI {
val retrofit = retrofitFactory.create(okHttpClient, pendingSessionData.homeServerConnectionConfig.homeServerUri.toString())
return retrofit.create(AuthAPI::class.java)
}
}

View File

@@ -0,0 +1,31 @@
/*
* 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 org.matrix.androidsdk.rest.model.login
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
/**
* This class represent a localized privacy policy for registration Flow.
*/
@Parcelize
data class LocalizedFlowDataLoginTerms(
var policyName: String? = null,
var version: String? = null,
var localizedUrl: String? = null,
var localizedName: String? = null
) : Parcelable

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.auth.registration
import im.vector.matrix.android.api.auth.registration.RegisterThreePid
import im.vector.matrix.android.internal.auth.AuthAPI
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
internal interface RegisterAddThreePidTask : Task<RegisterAddThreePidTask.Params, AddThreePidRegistrationResponse> {
data class Params(
val threePid: RegisterThreePid,
val clientSecret: String,
val sendAttempt: Int
)
}
internal class DefaultRegisterAddThreePidTask(private val authAPI: AuthAPI)
: RegisterAddThreePidTask {
override suspend fun execute(params: RegisterAddThreePidTask.Params): AddThreePidRegistrationResponse {
return executeRequest {
apiCall = authAPI.add3Pid(params.threePid.toPath(), AddThreePidRegistrationParams.from(params))
}
}
private fun RegisterThreePid.toPath(): String {
return when (this) {
is RegisterThreePid.Email -> "email"
is RegisterThreePid.Msisdn -> "msisdn"
}
}
}

View File

@@ -0,0 +1,62 @@
/*
* 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.auth.registration
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.internal.auth.AuthAPI
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
internal interface RegisterTask : Task<RegisterTask.Params, Credentials> {
data class Params(
val registrationParams: RegistrationParams
)
}
internal class DefaultRegisterTask(private val authAPI: AuthAPI)
: RegisterTask {
override suspend fun execute(params: RegisterTask.Params): Credentials {
try {
return executeRequest {
apiCall = authAPI.register(params.registrationParams)
}
} catch (throwable: Throwable) {
if (throwable is Failure.OtherServerError && throwable.httpCode == 401) {
// Parse to get a RegistrationFlowResponse
val registrationFlowResponse = try {
MoshiProvider.providesMoshi()
.adapter(RegistrationFlowResponse::class.java)
.fromJson(throwable.errorBody)
} catch (e: Exception) {
null
}
// check if the server response can be cast
if (registrationFlowResponse != null) {
throw Failure.RegistrationFlowError(registrationFlowResponse)
} else {
throw throwable
}
} else {
// Other error
throw throwable
}
}
}
}

View File

@@ -18,8 +18,12 @@ package im.vector.matrix.android.internal.auth.registration
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.auth.registration.FlowResult
import im.vector.matrix.android.api.auth.registration.Stage
import im.vector.matrix.android.api.auth.registration.TermPolicies
import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.internal.auth.data.InteractiveAuthenticationFlow
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
@JsonClass(generateAdapter = true)
data class RegistrationFlowResponse(
@@ -50,4 +54,46 @@ data class RegistrationFlowResponse(
*/
@Json(name = "params")
var params: JsonDict? = null
/**
* WARNING,
* The two MatrixError fields "errcode" and "error" can also be present here in case of error when validating a stage,
* But in this case Moshi will be able to parse the result as a MatrixError, see [RetrofitExtensions.toFailure]
* Ex: when polling for "m.login.msisdn" validation
*/
)
/**
* Convert to something easier to handle on client side
*/
fun RegistrationFlowResponse.toFlowResult(): FlowResult {
// Get all the returned stages
val allFlowTypes = mutableSetOf<String>()
val missingStage = mutableListOf<Stage>()
val completedStage = mutableListOf<Stage>()
this.flows?.forEach { it.stages?.mapTo(allFlowTypes) { type -> type } }
allFlowTypes.forEach { type ->
val isMandatory = flows?.all { type in it.stages ?: emptyList() } == true
val stage = when (type) {
LoginFlowTypes.RECAPTCHA -> Stage.ReCaptcha(isMandatory, ((params?.get(type) as? Map<*, *>)?.get("public_key") as? String)
?: "")
LoginFlowTypes.DUMMY -> Stage.Dummy(isMandatory)
LoginFlowTypes.TERMS -> Stage.Terms(isMandatory, params?.get(type) as? TermPolicies ?: emptyMap<String, String>())
LoginFlowTypes.EMAIL_IDENTITY -> Stage.Email(isMandatory)
LoginFlowTypes.MSISDN -> Stage.Msisdn(isMandatory)
else -> Stage.Other(isMandatory, type, (params?.get(type) as? Map<*, *>))
}
if (type in completedStages ?: emptyList()) {
completedStage.add(stage)
} else {
missingStage.add(stage)
}
}
return FlowResult(missingStage, completedStage)
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2014 OpenMarket Ltd
* Copyright 2017 Vector Creations 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.auth.registration
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* Class to pass parameters to the different registration types for /register.
*/
@JsonClass(generateAdapter = true)
internal data class RegistrationParams(
// authentication parameters
@Json(name = "auth")
val auth: AuthParams? = null,
// the account username
@Json(name = "username")
val username: String? = null,
// the account password
@Json(name = "password")
val password: String? = null,
// device name
@Json(name = "initial_device_display_name")
val initialDeviceDisplayName: String? = null,
// Temporary flag to notify the server that we support msisdn flow. Used to prevent old app
// versions to end up in fallback because the HS returns the msisdn flow which they don't support
val x_show_msisdn: Boolean? = null
)

View File

@@ -0,0 +1,26 @@
/*
* 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.auth.registration
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class SuccessResult(
@Json(name = "success")
val success: Boolean?
)

View File

@@ -0,0 +1,54 @@
/*
* 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.auth.registration
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.auth.registration.RegisterThreePid
/**
* Container to store the data when a three pid is in validation step
*/
@JsonClass(generateAdapter = true)
internal data class ThreePidData(
val email: String,
val msisdn: String,
val country: String,
val addThreePidRegistrationResponse: AddThreePidRegistrationResponse,
val registrationParams: RegistrationParams
) {
val threePid: RegisterThreePid
get() {
return if (email.isNotBlank()) {
RegisterThreePid.Email(email)
} else {
RegisterThreePid.Msisdn(msisdn, country)
}
}
companion object {
fun from(threePid: RegisterThreePid,
addThreePidRegistrationResponse: AddThreePidRegistrationResponse,
registrationParams: RegistrationParams): ThreePidData {
return when (threePid) {
is RegisterThreePid.Email ->
ThreePidData(threePid.email, "", "", addThreePidRegistrationResponse, registrationParams)
is RegisterThreePid.Msisdn ->
ThreePidData("", threePid.msisdn, threePid.countryCode, addThreePidRegistrationResponse, registrationParams)
}
}
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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.auth.registration
import im.vector.matrix.android.internal.auth.AuthAPI
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
internal interface ValidateCodeTask : Task<ValidateCodeTask.Params, SuccessResult> {
data class Params(
val url: String,
val body: ValidationCodeBody
)
}
internal class DefaultValidateCodeTask(private val authAPI: AuthAPI)
: ValidateCodeTask {
override suspend fun execute(params: ValidateCodeTask.Params): SuccessResult {
return executeRequest {
apiCall = authAPI.validate3Pid(params.url, params.body)
}
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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.auth.registration
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* This object is used to send a code received by SMS to validate Msisdn ownership
*/
@JsonClass(generateAdapter = true)
data class ValidationCodeBody(
@Json(name = "client_secret")
val clientSecret: String,
@Json(name = "sid")
val sid: String,
@Json(name = "token")
val code: String
)

View File

@@ -37,6 +37,8 @@ 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 io.realm.RealmConfiguration
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import retrofit2.Retrofit
import java.io.File
@@ -66,6 +68,13 @@ internal abstract class CryptoModule {
.build()
}
@JvmStatic
@Provides
@SessionScope
fun providesCryptoCoroutineScope(): CoroutineScope {
return CoroutineScope(SupervisorJob())
}
@JvmStatic
@Provides
@CryptoDatabase

View File

@@ -132,7 +132,8 @@ internal class DefaultCryptoService @Inject constructor(
private val loadRoomMembersTask: LoadRoomMembersTask,
private val monarchy: Monarchy,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val taskExecutor: TaskExecutor
private val taskExecutor: TaskExecutor,
private val cryptoCoroutineScope: CoroutineScope
) : CryptoService {
private val uiHandler = Handler(Looper.getMainLooper())
@@ -243,7 +244,8 @@ internal class DefaultCryptoService @Inject constructor(
return
}
isStarting.set(true)
GlobalScope.launch(coroutineDispatchers.crypto) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
internalStart(isInitialSync)
}
}
@@ -269,10 +271,9 @@ internal class DefaultCryptoService @Inject constructor(
isStarted.set(true)
},
{
Timber.e("Start failed: $it")
delay(1000)
isStarting.set(false)
internalStart(isInitialSync)
isStarted.set(false)
Timber.e(it, "Start failed")
}
)
}
@@ -281,9 +282,12 @@ internal class DefaultCryptoService @Inject constructor(
* Close the crypto
*/
fun close() = runBlocking(coroutineDispatchers.crypto) {
cryptoCoroutineScope.coroutineContext.cancelChildren(CancellationException("Closing crypto module"))
outgoingRoomKeyRequestManager.stop()
olmDevice.release()
cryptoStore.close()
outgoingRoomKeyRequestManager.stop()
}
// Aways enabled on RiotX
@@ -305,19 +309,21 @@ internal class DefaultCryptoService @Inject constructor(
* @param syncResponse the syncResponse
*/
fun onSyncCompleted(syncResponse: SyncResponse) {
GlobalScope.launch(coroutineDispatchers.crypto) {
if (syncResponse.deviceLists != null) {
deviceListManager.handleDeviceListsChanges(syncResponse.deviceLists.changed, syncResponse.deviceLists.left)
}
if (syncResponse.deviceOneTimeKeysCount != null) {
val currentCount = syncResponse.deviceOneTimeKeysCount.signedCurve25519 ?: 0
oneTimeKeysUploader.updateOneTimeKeyCount(currentCount)
}
if (isStarted()) {
// Make sure we process to-device messages before generating new one-time-keys #2782
deviceListManager.refreshOutdatedDeviceLists()
oneTimeKeysUploader.maybeUploadOneTimeKeys()
incomingRoomKeyRequestManager.processReceivedRoomKeyRequests()
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
runCatching {
if (syncResponse.deviceLists != null) {
deviceListManager.handleDeviceListsChanges(syncResponse.deviceLists.changed, syncResponse.deviceLists.left)
}
if (syncResponse.deviceOneTimeKeysCount != null) {
val currentCount = syncResponse.deviceOneTimeKeysCount.signedCurve25519 ?: 0
oneTimeKeysUploader.updateOneTimeKeyCount(currentCount)
}
if (isStarted()) {
// Make sure we process to-device messages before generating new one-time-keys #2782
deviceListManager.refreshOutdatedDeviceLists()
oneTimeKeysUploader.maybeUploadOneTimeKeys()
incomingRoomKeyRequestManager.processReceivedRoomKeyRequests()
}
}
}
}
@@ -511,7 +517,7 @@ internal class DefaultCryptoService @Inject constructor(
eventType: String,
roomId: String,
callback: MatrixCallback<MXEncryptEventContentResult>) {
GlobalScope.launch(coroutineDispatchers.crypto) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
if (!isStarted()) {
Timber.v("## encryptEventContent() : wait after e2e init")
internalStart(false)
@@ -571,7 +577,7 @@ internal class DefaultCryptoService @Inject constructor(
* @param callback the callback to return data or null
*/
override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>) {
GlobalScope.launch {
cryptoCoroutineScope.launch {
val result = runCatching {
withContext(coroutineDispatchers.crypto) {
internalDecryptEvent(event, timeline)
@@ -621,7 +627,7 @@ internal class DefaultCryptoService @Inject constructor(
* @param event the event
*/
fun onToDeviceEvent(event: Event) {
GlobalScope.launch(coroutineDispatchers.crypto) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
when (event.getClearType()) {
EventType.ROOM_KEY, EventType.FORWARDED_ROOM_KEY -> {
onRoomKeyEvent(event)
@@ -661,7 +667,7 @@ internal class DefaultCryptoService @Inject constructor(
* @param event the encryption event.
*/
private fun onRoomEncryptionEvent(roomId: String, event: Event) {
GlobalScope.launch(coroutineDispatchers.crypto) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
val params = LoadRoomMembersTask.Params(roomId)
try {
loadRoomMembersTask.execute(params)
@@ -753,7 +759,7 @@ internal class DefaultCryptoService @Inject constructor(
* @param callback the exported keys
*/
override fun exportRoomKeys(password: String, callback: MatrixCallback<ByteArray>) {
GlobalScope.launch(coroutineDispatchers.main) {
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
runCatching {
exportRoomKeys(password, MXMegolmExportEncryption.DEFAULT_ITERATION_COUNT)
}.foldToCallback(callback)
@@ -791,7 +797,7 @@ internal class DefaultCryptoService @Inject constructor(
password: String,
progressListener: ProgressListener?,
callback: MatrixCallback<ImportRoomKeysResult>) {
GlobalScope.launch(coroutineDispatchers.main) {
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
runCatching {
withContext(coroutineDispatchers.crypto) {
Timber.v("## importRoomKeys starts")
@@ -839,7 +845,7 @@ internal class DefaultCryptoService @Inject constructor(
*/
fun checkUnknownDevices(userIds: List<String>, callback: MatrixCallback<Unit>) {
// force the refresh to ensure that the devices list is up-to-date
GlobalScope.launch(coroutineDispatchers.crypto) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
runCatching {
val keys = deviceListManager.downloadKeys(userIds, true)
val unknownDevices = getUnknownDevices(keys)
@@ -999,7 +1005,7 @@ internal class DefaultCryptoService @Inject constructor(
}
override fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>) {
GlobalScope.launch(coroutineDispatchers.crypto) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
runCatching {
deviceListManager.downloadKeys(userIds, forceDownload)
}.foldToCallback(callback)

View File

@@ -66,7 +66,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
if (':' in userId) {
try {
synchronized(notReadyToRetryHS) {
res = !notReadyToRetryHS.contains(userId.substring(userId.lastIndexOf(":") + 1))
res = !notReadyToRetryHS.contains(userId.substringAfterLast(':'))
}
} catch (e: Exception) {
Timber.e(e, "## canRetryKeysDownload() failed")

View File

@@ -25,7 +25,6 @@ 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
import kotlin.collections.ArrayList
@SessionScope
internal class IncomingRoomKeyRequestManager @Inject constructor(
@@ -51,7 +50,7 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
*
* @param event the announcement event.
*/
suspend fun onRoomKeyRequestEvent(event: Event) {
fun onRoomKeyRequestEvent(event: Event) {
val roomKeyShare = event.getClearContent().toModel<RoomKeyShare>()
when (roomKeyShare?.action) {
RoomKeyShare.ACTION_SHARE_REQUEST -> receivedRoomKeyRequests.add(IncomingRoomKeyRequest(event))
@@ -78,7 +77,7 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
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.e("## processReceivedRoomKeyRequests() : Ignoring room key request from other user for now")
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?
@@ -86,11 +85,11 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
// the keys for the requested events, and can drop the requests.
val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, alg)
if (null == decryptor) {
Timber.e("## processReceivedRoomKeyRequests() : room key request for unknown $alg in room $roomId")
Timber.w("## processReceivedRoomKeyRequests() : room key request for unknown $alg in room $roomId")
continue
}
if (!decryptor.hasKeysForKeyRequest(request)) {
Timber.e("## processReceivedRoomKeyRequests() : room key request for unknown session ${body.sessionId!!}")
Timber.w("## processReceivedRoomKeyRequests() : room key request for unknown session ${body.sessionId!!}")
cryptoStore.deleteIncomingRoomKeyRequest(request)
continue
}
@@ -139,7 +138,7 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
if (null != receivedRoomKeyRequestCancellations) {
for (request in receivedRoomKeyRequestCancellations!!) {
Timber.v("## ## processReceivedRoomKeyRequests() : m.room_key_request cancellation for " + request.userId
+ ":" + request.deviceId + " id " + request.requestId)
+ ":" + 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

View File

@@ -764,7 +764,7 @@ internal class MXOlmDevice @Inject constructor(
return session
}
} else {
Timber.e("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId")
Timber.w("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId")
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON)
}
}

View File

@@ -42,8 +42,6 @@ internal class OneTimeKeysUploader @Inject constructor(
private var lastOneTimeKeyCheck: Long = 0
private var oneTimeKeyCount: Int? = null
private var lastPublishedOneTimeKeys: Map<String, Map<String, String>>? = null
/**
* Stores the current one_time_key count which will be handled later (in a call of
* _onSyncCompleted). The count is e.g. coming from a /sync response.
@@ -59,10 +57,12 @@ internal class OneTimeKeysUploader @Inject constructor(
*/
suspend fun maybeUploadOneTimeKeys() {
if (oneTimeKeyCheckInProgress) {
Timber.v("maybeUploadOneTimeKeys: already in progress")
return
}
if (System.currentTimeMillis() - lastOneTimeKeyCheck < ONE_TIME_KEY_UPLOAD_PERIOD) {
// we've done a key upload recently.
Timber.v("maybeUploadOneTimeKeys: executed too recently")
return
}
@@ -79,12 +79,8 @@ internal class OneTimeKeysUploader @Inject constructor(
// discard the oldest private keys first. This will eventually clean
// out stale private keys that won't receive a message.
val keyLimit = floor(maxOneTimeKeys / 2.0).toInt()
if (oneTimeKeyCount != null) {
uploadOTK(oneTimeKeyCount!!, keyLimit)
} else {
// ask the server how many keys we have
val uploadKeysParams = UploadKeysTask.Params(null, null, credentials.deviceId!!)
val response = uploadKeysTask.execute(uploadKeysParams)
val oneTimeKeyCountFromSync = oneTimeKeyCount
if (oneTimeKeyCountFromSync != null) {
// We need to keep a pool of one time public keys on the server so that
// other devices can start conversations with us. But we can only store
// a finite number of private keys in the olm Account object.
@@ -96,14 +92,17 @@ internal class OneTimeKeysUploader @Inject constructor(
// private keys clogging up our local storage.
// So we need some kind of engineering compromise to balance all of
// these factors.
// TODO Why we do not set oneTimeKeyCount here?
// TODO This is not needed anymore, see https://github.com/matrix-org/matrix-js-sdk/pull/493 (TODO on iOS also)
val keyCount = response.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)
uploadOTK(keyCount, keyLimit)
try {
val uploadedKeys = uploadOTK(oneTimeKeyCountFromSync, keyLimit)
Timber.v("## uploadKeys() : success, $uploadedKeys key(s) sent")
} finally {
oneTimeKeyCheckInProgress = false
}
} else {
Timber.w("maybeUploadOneTimeKeys: waiting to know the number of OTK from the sync")
oneTimeKeyCheckInProgress = false
lastOneTimeKeyCheck = 0
}
Timber.v("## uploadKeys() : success")
oneTimeKeyCount = null
oneTimeKeyCheckInProgress = false
}
/**
@@ -111,53 +110,51 @@ internal class OneTimeKeysUploader @Inject constructor(
*
* @param keyCount the key count
* @param keyLimit the limit
* @return the number of uploaded keys
*/
private suspend fun uploadOTK(keyCount: Int, keyLimit: Int) {
private suspend fun uploadOTK(keyCount: Int, keyLimit: Int): Int {
if (keyLimit <= keyCount) {
// If we don't need to generate any more keys then we are done.
return
return 0
}
val keysThisLoop = min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER)
olmDevice.generateOneTimeKeys(keysThisLoop)
val response = uploadOneTimeKeys()
val response = uploadOneTimeKeys(olmDevice.getOneTimeKeys())
olmDevice.markKeysAsPublished()
if (response.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) {
uploadOTK(response.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit)
// Maybe upload other keys
return keysThisLoop + uploadOTK(response.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit)
} else {
Timber.e("## uploadLoop() : response for uploading keys does not contain one_time_key_counts.signed_curve25519")
Timber.e("## uploadOTK() : response for uploading keys does not contain one_time_key_counts.signed_curve25519")
throw Exception("response for uploading keys does not contain one_time_key_counts.signed_curve25519")
}
}
/**
* Upload my user's one time keys.
* Upload curve25519 one time keys.
*/
private suspend fun uploadOneTimeKeys(): KeysUploadResponse {
val oneTimeKeys = olmDevice.getOneTimeKeys()
private suspend fun uploadOneTimeKeys(oneTimeKeys: Map<String, Map<String, String>>?): KeysUploadResponse {
val oneTimeJson = mutableMapOf<String, Any>()
val curve25519Map = oneTimeKeys?.get(OlmAccount.JSON_KEY_ONE_TIME_KEY)
val curve25519Map = oneTimeKeys?.get(OlmAccount.JSON_KEY_ONE_TIME_KEY) ?: emptyMap()
if (null != curve25519Map) {
for ((key_id, value) in curve25519Map) {
val k = mutableMapOf<String, Any>()
k["key"] = value
curve25519Map.forEach { (key_id, value) ->
val k = mutableMapOf<String, Any>()
k["key"] = value
// the key is also signed
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, k)
// the key is also signed
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, k)
k["signatures"] = objectSigner.signObject(canonicalJson)
k["signatures"] = objectSigner.signObject(canonicalJson)
oneTimeJson["signed_curve25519:$key_id"] = k
}
oneTimeJson["signed_curve25519:$key_id"] = k
}
// For now, we set the device id explicitly, as we may not be using the
// same one as used in login.
val uploadParams = UploadKeysTask.Params(null, oneTimeJson, credentials.deviceId!!)
val response = uploadKeysTask.execute(uploadParams)
lastPublishedOneTimeKeys = oneTimeKeys
olmDevice.markKeysAsPublished()
return response
return uploadKeysTask.execute(uploadParams)
}
companion object {

View File

@@ -63,6 +63,7 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
*/
fun stop() {
isClientRunning = false
stopTimer()
}
/**
@@ -171,6 +172,10 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
}, 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).
@@ -187,7 +192,7 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND))
if (null == outgoingRoomKeyRequest) {
Timber.e("## sendOutgoingRoomKeyRequests() : No more outgoing room key requests")
Timber.v("## sendOutgoingRoomKeyRequests() : No more outgoing room key requests")
sendOutgoingRoomKeyRequestsRunning.set(false)
return
}
@@ -216,7 +221,7 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
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)
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)

View File

@@ -34,7 +34,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber
@@ -46,8 +46,9 @@ internal class MXMegolmDecryption(private val userId: String,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
private val cryptoStore: IMXCryptoStore,
private val sendToDeviceTask: SendToDeviceTask,
private val coroutineDispatchers: MatrixCoroutineDispatchers)
: IMXDecrypting {
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val cryptoCoroutineScope: CoroutineScope
) : IMXDecrypting {
var newSessionListener: NewSessionListener? = null
@@ -61,7 +62,7 @@ internal class MXMegolmDecryption(private val userId: String,
return decryptEvent(event, timeline, true)
}
private suspend fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): MXEventDecryptionResult {
private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): MXEventDecryptionResult {
if (event.roomId.isNullOrBlank()) {
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
}
@@ -292,7 +293,7 @@ internal class MXMegolmDecryption(private val userId: String,
return
}
val userId = request.userId ?: return
GlobalScope.launch(coroutineDispatchers.crypto) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
runCatching { deviceListManager.downloadKeys(listOf(userId), false) }
.mapCatching {
val deviceId = request.deviceId

View File

@@ -25,17 +25,21 @@ import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.CoroutineScope
import javax.inject.Inject
internal class MXMegolmDecryptionFactory @Inject constructor(@UserId private val userId: String,
private val olmDevice: MXOlmDevice,
private val deviceListManager: DeviceListManager,
private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager,
private val messageEncrypter: MessageEncrypter,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
private val cryptoStore: IMXCryptoStore,
private val sendToDeviceTask: SendToDeviceTask,
private val coroutineDispatchers: MatrixCoroutineDispatchers) {
internal class MXMegolmDecryptionFactory @Inject constructor(
@UserId private val userId: String,
private val olmDevice: MXOlmDevice,
private val deviceListManager: DeviceListManager,
private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager,
private val messageEncrypter: MessageEncrypter,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
private val cryptoStore: IMXCryptoStore,
private val sendToDeviceTask: SendToDeviceTask,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val cryptoCoroutineScope: CoroutineScope
) {
fun create(): MXMegolmDecryption {
return MXMegolmDecryption(
@@ -47,6 +51,7 @@ internal class MXMegolmDecryptionFactory @Inject constructor(@UserId private val
ensureOlmSessionsForDevicesAction,
cryptoStore,
sendToDeviceTask,
coroutineDispatchers)
coroutineDispatchers,
cryptoCoroutineScope)
}
}

View File

@@ -59,7 +59,7 @@ 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.awaitCallback
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.olm.OlmException
@@ -102,7 +102,8 @@ internal class KeysBackup @Inject constructor(
private val updateKeysBackupVersionTask: UpdateKeysBackupVersionTask,
// Task executor
private val taskExecutor: TaskExecutor,
private val coroutineDispatchers: MatrixCoroutineDispatchers
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val cryptoCoroutineScope: CoroutineScope
) : KeysBackupService {
private val uiHandler = Handler(Looper.getMainLooper())
@@ -143,7 +144,7 @@ internal class KeysBackup @Inject constructor(
override fun prepareKeysBackupVersion(password: String?,
progressListener: ProgressListener?,
callback: MatrixCallback<MegolmBackupCreationInfo>) {
GlobalScope.launch(coroutineDispatchers.main) {
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
runCatching {
withContext(coroutineDispatchers.crypto) {
val olmPkDecryption = OlmPkDecryption()
@@ -233,7 +234,7 @@ internal class KeysBackup @Inject constructor(
}
override fun deleteBackup(version: String, callback: MatrixCallback<Unit>?) {
GlobalScope.launch(coroutineDispatchers.main) {
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
withContext(coroutineDispatchers.crypto) {
// If we're currently backing up to this backup... stop.
// (We start using it automatically in createKeysBackupVersion so this is symmetrical).
@@ -344,9 +345,7 @@ internal class KeysBackup @Inject constructor(
}
})
}
}
keysBackupStateManager.addListener(keysBackupStateListener!!)
}.also { keysBackupStateManager.addListener(it) }
backupKeys()
}
@@ -448,7 +447,7 @@ internal class KeysBackup @Inject constructor(
callback.onFailure(IllegalArgumentException("Missing element"))
} else {
GlobalScope.launch(coroutineDispatchers.main) {
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
val updateKeysBackupVersionBody = withContext(coroutineDispatchers.crypto) {
// Get current signatures, or create an empty set
val myUserSignatures = authData.signatures?.get(userId)?.toMutableMap()
@@ -523,7 +522,7 @@ internal class KeysBackup @Inject constructor(
callback: MatrixCallback<Unit>) {
Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}")
GlobalScope.launch(coroutineDispatchers.main) {
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
val isValid = withContext(coroutineDispatchers.crypto) {
isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion)
}
@@ -543,7 +542,7 @@ internal class KeysBackup @Inject constructor(
callback: MatrixCallback<Unit>) {
Timber.v("trustKeysBackupVersionWithPassphrase: version ${keysBackupVersion.version}")
GlobalScope.launch(coroutineDispatchers.main) {
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
val recoveryKey = withContext(coroutineDispatchers.crypto) {
recoveryKeyFromPassword(password, keysBackupVersion, null)
}
@@ -614,7 +613,7 @@ internal class KeysBackup @Inject constructor(
callback: MatrixCallback<ImportRoomKeysResult>) {
Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}")
GlobalScope.launch(coroutineDispatchers.main) {
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
runCatching {
val decryption = withContext(coroutineDispatchers.crypto) {
// Check if the recovery is valid before going any further
@@ -695,7 +694,7 @@ internal class KeysBackup @Inject constructor(
callback: MatrixCallback<ImportRoomKeysResult>) {
Timber.v("[MXKeyBackup] restoreKeyBackup with password: From backup version: ${keysBackupVersion.version}")
GlobalScope.launch(coroutineDispatchers.main) {
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
runCatching {
val progressListener = if (stepProgressListener != null) {
object : ProgressListener {
@@ -729,8 +728,8 @@ internal class KeysBackup @Inject constructor(
* parameters and always returns a KeysBackupData object through the Callback
*/
private suspend fun getKeys(sessionId: String?,
roomId: String?,
version: String): KeysBackupData {
roomId: String?,
version: String): KeysBackupData {
return if (roomId != null && sessionId != null) {
// Get key for the room and for the session
val data = getRoomSessionDataTask.execute(GetRoomSessionDataTask.Params(roomId, sessionId, version))
@@ -1154,7 +1153,7 @@ internal class KeysBackup @Inject constructor(
keysBackupStateManager.state = KeysBackupState.BackingUp
GlobalScope.launch(coroutineDispatchers.main) {
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
withContext(coroutineDispatchers.crypto) {
Timber.v("backupKeys: 2 - Encrypting keys")

View File

@@ -41,8 +41,7 @@ fun <T> doWithRealm(realmConfiguration: RealmConfiguration, action: (Realm) -> T
*/
fun <T : RealmObject> doRealmQueryAndCopy(realmConfiguration: RealmConfiguration, action: (Realm) -> T?): T? {
return Realm.getInstance(realmConfiguration).use { realm ->
val result = action.invoke(realm)
result?.let { realm.copyFromRealm(it) }
action.invoke(realm)?.let { realm.copyFromRealm(it) }
}
}
@@ -51,8 +50,7 @@ fun <T : RealmObject> doRealmQueryAndCopy(realmConfiguration: RealmConfiguration
*/
fun <T : RealmObject> doRealmQueryAndCopyList(realmConfiguration: RealmConfiguration, action: (Realm) -> Iterable<T>): Iterable<T> {
return Realm.getInstance(realmConfiguration).use { realm ->
val result = action.invoke(realm)
realm.copyFromRealm(result)
action.invoke(realm).let { realm.copyFromRealm(it) }
}
}

View File

@@ -91,7 +91,7 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
realmLocker = Realm.getInstance(realmConfiguration)
// Ensure CryptoMetadataEntity is inserted in DB
doWithRealm(realmConfiguration) { realm ->
doRealmTransaction(realmConfiguration) { realm ->
var currentMetadata = realm.where<CryptoMetadataEntity>().findFirst()
var deleteAll = false
@@ -109,15 +109,13 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
}
if (currentMetadata == null) {
realm.executeTransaction {
if (deleteAll) {
it.deleteAll()
}
if (deleteAll) {
realm.deleteAll()
}
// Metadata not found, or database cleaned, create it
it.createObject(CryptoMetadataEntity::class.java, credentials.userId).apply {
deviceId = credentials.deviceId
}
// Metadata not found, or database cleaned, create it
realm.createObject(CryptoMetadataEntity::class.java, credentials.userId).apply {
deviceId = credentials.deviceId
}
}
}
@@ -645,9 +643,10 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
}
override fun getOutgoingRoomKeyRequestByState(states: Set<OutgoingRoomKeyRequest.RequestState>): OutgoingRoomKeyRequest? {
val statesIndex = states.map { it.ordinal }.toTypedArray()
return doRealmQueryAndCopy(realmConfiguration) {
it.where<OutgoingRoomKeyRequestEntity>()
.`in`(OutgoingRoomKeyRequestEntityFields.STATE, states.map { it.ordinal }.toTypedArray())
.`in`(OutgoingRoomKeyRequestEntityFields.STATE, statesIndex)
.findFirst()
}
?.toOutgoingRoomKeyRequest()

View File

@@ -41,6 +41,7 @@ internal open class OutgoingRoomKeyRequestEntity(
* Convert to OutgoingRoomKeyRequest
*/
fun toOutgoingRoomKeyRequest(): OutgoingRoomKeyRequest {
val cancellationTxnId = this.cancellationTxnId
return OutgoingRoomKeyRequest(
RoomKeyRequestBody().apply {
algorithm = requestBodyAlgorithm

View File

@@ -53,10 +53,10 @@ internal class DefaultUploadKeysTask @Inject constructor(private val cryptoApi:
}
return executeRequest {
if (encodedDeviceId.isNullOrBlank()) {
apiCall = cryptoApi.uploadKeys(body)
apiCall = if (encodedDeviceId.isBlank()) {
cryptoApi.uploadKeys(body)
} else {
apiCall = cryptoApi.uploadKeys(encodedDeviceId, body)
cryptoApi.uploadKeys(encodedDeviceId, body)
}
}
}

View File

@@ -43,6 +43,7 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import java.lang.Exception
import java.util.UUID
import javax.inject.Inject
import kotlin.collections.HashMap
@@ -166,72 +167,59 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
return
}
// Download device keys prior to everything
checkKeysAreDownloaded(
otherUserId!!,
startReq,
success = {
Timber.v("## SAS onStartRequestReceived ${startReq.transactionID!!}")
val tid = startReq.transactionID!!
val existing = getExistingTransaction(otherUserId, tid)
val existingTxs = getExistingTransactionsForUser(otherUserId)
if (existing != null) {
// should cancel both!
Timber.v("## SAS onStartRequestReceived - Request exist with same if ${startReq.transactionID!!}")
existing.cancel(CancelCode.UnexpectedMessage)
cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
} else if (existingTxs?.isEmpty() == false) {
Timber.v("## SAS onStartRequestReceived - There is already a transaction with this user ${startReq.transactionID!!}")
// Multiple keyshares between two devices: any two devices may only have at most one key verification in flight at a time.
existingTxs.forEach {
it.cancel(CancelCode.UnexpectedMessage)
}
cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
} else {
// Ok we can create
if (KeyVerificationStart.VERIF_METHOD_SAS == startReq.method) {
Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionID!!}")
val tx = IncomingSASVerificationTransaction(
this,
setDeviceVerificationAction,
credentials,
cryptoStore,
sendToDeviceTask,
taskExecutor,
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
startReq.transactionID!!,
otherUserId)
addTransaction(tx)
tx.acceptToDeviceEvent(otherUserId, startReq)
} else {
Timber.e("## SAS onStartRequestReceived - unknown method ${startReq.method}")
cancelTransaction(tid, otherUserId, startReq.fromDevice
?: event.getSenderKey()!!, CancelCode.UnknownMethod)
}
}
},
error = {
cancelTransaction(startReq.transactionID!!, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
})
if (checkKeysAreDownloaded(otherUserId!!, startReq) != null) {
Timber.v("## SAS onStartRequestReceived ${startReq.transactionID!!}")
val tid = startReq.transactionID!!
val existing = getExistingTransaction(otherUserId, tid)
val existingTxs = getExistingTransactionsForUser(otherUserId)
if (existing != null) {
// should cancel both!
Timber.v("## SAS onStartRequestReceived - Request exist with same if ${startReq.transactionID!!}")
existing.cancel(CancelCode.UnexpectedMessage)
cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
} else if (existingTxs?.isEmpty() == false) {
Timber.v("## SAS onStartRequestReceived - There is already a transaction with this user ${startReq.transactionID!!}")
// Multiple keyshares between two devices: any two devices may only have at most one key verification in flight at a time.
existingTxs.forEach {
it.cancel(CancelCode.UnexpectedMessage)
}
cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
} else {
// Ok we can create
if (KeyVerificationStart.VERIF_METHOD_SAS == startReq.method) {
Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionID!!}")
val tx = IncomingSASVerificationTransaction(
this,
setDeviceVerificationAction,
credentials,
cryptoStore,
sendToDeviceTask,
taskExecutor,
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
startReq.transactionID!!,
otherUserId)
addTransaction(tx)
tx.acceptToDeviceEvent(otherUserId, startReq)
} else {
Timber.e("## SAS onStartRequestReceived - unknown method ${startReq.method}")
cancelTransaction(tid, otherUserId, startReq.fromDevice
?: event.getSenderKey()!!, CancelCode.UnknownMethod)
}
}
} else {
cancelTransaction(startReq.transactionID!!, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
}
}
private suspend fun checkKeysAreDownloaded(otherUserId: String,
startReq: KeyVerificationStart,
success: (MXUsersDevicesMap<MXDeviceInfo>) -> Unit,
error: () -> Unit) {
runCatching {
deviceListManager.downloadKeys(listOf(otherUserId), true)
}.fold(
{
if (it.getUserDeviceIds(otherUserId)?.contains(startReq.fromDevice) == true) {
success(it)
} else {
error()
}
},
{
error()
}
)
startReq: KeyVerificationStart): MXUsersDevicesMap<MXDeviceInfo>? {
return try {
val keys = deviceListManager.downloadKeys(listOf(otherUserId), true)
val deviceIds = keys.getUserDeviceIds(otherUserId) ?: return null
keys.takeIf { deviceIds.contains(startReq.fromDevice) }
} catch (e: Exception) {
null
}
}
private suspend fun onCancelReceived(event: Event) {
@@ -342,10 +330,8 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
private fun addTransaction(tx: VerificationTransaction) {
tx.otherUserId.let { otherUserId ->
synchronized(txMap) {
if (txMap[otherUserId] == null) {
txMap[otherUserId] = HashMap()
}
txMap[otherUserId]?.set(tx.transactionId, tx)
val txInnerMap = txMap.getOrPut(otherUserId) { HashMap() }
txInnerMap[tx.transactionId] = tx
dispatchTxAdded(tx)
tx.addListener(this)
}

View File

@@ -20,19 +20,26 @@ import io.realm.RealmConfiguration
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
import timber.log.Timber
suspend fun awaitTransaction(config: RealmConfiguration, transaction: suspend (realm: Realm) -> Unit) = withContext(Dispatchers.IO) {
suspend fun <T> awaitTransaction(config: RealmConfiguration, transaction: suspend (realm: Realm) -> T) = withContext(Dispatchers.Default) {
Realm.getInstance(config).use { bgRealm ->
bgRealm.beginTransaction()
val result: T
try {
transaction(bgRealm)
val start = System.currentTimeMillis()
result = transaction(bgRealm)
if (isActive) {
bgRealm.commitTransaction()
val end = System.currentTimeMillis()
val time = end - start
Timber.v("Execute transaction in $time millis")
}
} finally {
if (bgRealm.isInTransaction) {
bgRealm.cancelTransaction()
}
}
result
}
}

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