mirror of
https://github.com/vector-im/riotX-android
synced 2025-10-08 17:22:47 +02:00
Compare commits
358 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
f9c0256afd | ||
|
6d8850b3d6 | ||
|
d88edd578f | ||
|
eb9775e307 | ||
|
aa9d66b991 | ||
|
fb8ba32fb4 | ||
|
8e9ac8198d | ||
|
7a05207ae4 | ||
|
6b39cf3b70 | ||
|
994759e11a | ||
|
f31c1b69cb | ||
|
bdb9d2fbb8 | ||
|
9fb50dde32 | ||
|
a145aae0aa | ||
|
3623072f08 | ||
|
2717ad475a | ||
|
a6f8fe9317 | ||
|
f9eb80b4ec | ||
|
9510d71cd3 | ||
|
e7a47ae32a | ||
|
7890b929a7 | ||
|
0376de08f4 | ||
|
90c472fef9 | ||
|
8e873672a9 | ||
|
bba52e77d1 | ||
|
64d73ae8e6 | ||
|
d9982076f9 | ||
|
ab489df83d | ||
|
5e07e96bdb | ||
|
c495aa4914 | ||
|
ff267ba9bc | ||
|
69f923383c | ||
|
c69852c849 | ||
|
6d7f2670df | ||
|
71de8fdad3 | ||
|
998d9f2c59 | ||
|
4f3da353e4 | ||
|
4154cb2b85 | ||
|
3c6eb4bccf | ||
|
7b4398404b | ||
|
9b882978ed | ||
|
49178dc633 | ||
|
490ce4b51d | ||
|
5b63856d96 | ||
|
538c4d1a64 | ||
|
1cadbb8eed | ||
|
3f4f7457c7 | ||
|
ebf21fe9d8 | ||
|
a343da594f | ||
|
938289e8eb | ||
|
e23763e6db | ||
|
c06b8486ea | ||
|
67fe776d91 | ||
|
10cc270273 | ||
|
46d96429e0 | ||
|
9f9c418085 | ||
|
c412006f0e | ||
|
5d3c376267 | ||
|
a3f8f138a6 | ||
|
4b273e8746 | ||
|
f11cd47df3 | ||
|
f984758d37 | ||
|
97766404d6 | ||
|
38b93c527b | ||
|
62bae67080 | ||
|
2a4cdec020 | ||
|
6bd7257cf2 | ||
|
500eb113b6 | ||
|
1bec8c29b8 | ||
|
0ecb23199c | ||
|
33925fcf57 | ||
|
bf9ce4f690 | ||
|
d2a4163dff | ||
|
a0d7aef92e | ||
|
29f32cf8eb | ||
|
bb1c988a49 | ||
|
f063abe068 | ||
|
db87d8f644 | ||
|
efa858a337 | ||
|
fd90f3b9fc | ||
|
aa51764068 | ||
|
0a19ded167 | ||
|
2e3763e8b4 | ||
|
0c4e0890b1 | ||
|
e532d97ec1 | ||
|
fbde8d7d18 | ||
|
f96bea742e | ||
|
86bfdd011e | ||
|
d5c2c1938c | ||
|
7ce8a13ddf | ||
|
9bd4dbb65f | ||
|
ee875b359b | ||
|
3eb2e1655f | ||
|
9b207dd5dc | ||
|
3f1540b54e | ||
|
2b30925163 | ||
|
4690754f5f | ||
|
a9526cdd92 | ||
|
ab4d42fb20 | ||
|
0014e8ef06 | ||
|
311d8ddf7b | ||
|
6cb3c222a9 | ||
|
f84ec08847 | ||
|
9d0188cbf1 | ||
|
73462a3045 | ||
|
3eebf965e5 | ||
|
bba58d25e1 | ||
|
fedb45b019 | ||
|
9b83f08654 | ||
|
91fcf428dd | ||
|
7e1a279fd9 | ||
|
8de1fa835b | ||
|
af45c554fd | ||
|
11bc7051fd | ||
|
489a594027 | ||
|
3f83c161e4 | ||
|
e0a36b794f | ||
|
d2b516bdc2 | ||
|
37166caea2 | ||
|
9fa131c297 | ||
|
71ae3c4a8c | ||
|
f87526e615 | ||
|
51f53e2ae9 | ||
|
ef35f0a044 | ||
|
5db3f51ddb | ||
|
49f7ce3554 | ||
|
a3111dc2d8 | ||
|
be95542110 | ||
|
6723a566c2 | ||
|
90027cc4d5 | ||
|
810b226f21 | ||
|
42c5adf08d | ||
|
5edfb78721 | ||
|
491a38a79f | ||
|
051f77087e | ||
|
1a603742d0 | ||
|
edb65f1787 | ||
|
9af8355c07 | ||
|
dd44078297 | ||
|
a1f96a5b5a | ||
|
5770023593 | ||
|
2789268c23 | ||
|
eb4355890e | ||
|
4e41156db3 | ||
|
1a0b8b35f8 | ||
|
5f9cdcb4b4 | ||
|
2e4c3f850a | ||
|
127916a8d9 | ||
|
248a584e1a | ||
|
b8a3ad0c43 | ||
|
1f161b7e23 | ||
|
23315ede92 | ||
|
20ad3abb60 | ||
|
ac377fceba | ||
|
abbe56acfa | ||
|
f74cabd145 | ||
|
0e2237226f | ||
|
62d5aba796 | ||
|
f12e6c941d | ||
|
7caa8ce3bc | ||
|
20f969d563 | ||
|
a8f24e5c39 | ||
|
8ae9544b48 | ||
|
3758334824 | ||
|
6d8e5b892e | ||
|
c18c140ec9 | ||
|
1dc7dfc896 | ||
|
1c03163a33 | ||
|
9aa270c7ad | ||
|
3f80076fb1 | ||
|
dfbf448bb7 | ||
|
95fc20dca0 | ||
|
381084b2ab | ||
|
41ac2c6d70 | ||
|
08ea3d049e | ||
|
f24889230c | ||
|
b5f9549a8b | ||
|
e3e38d4c8a | ||
|
416bef7903 | ||
|
823acebf78 | ||
|
3e91125872 | ||
|
9a628c7b5d | ||
|
fb46a14172 | ||
|
ca4e75a1a0 | ||
|
2871e4f5b1 | ||
|
b7bfb20a2e | ||
|
a1aa16715d | ||
|
55add4734d | ||
|
2849e1f846 | ||
|
5b9876a20c | ||
|
adf299081d | ||
|
d50b690523 | ||
|
c6b0ae63ea | ||
|
3c93807fe6 | ||
|
7f1f98c2e5 | ||
|
6525314af8 | ||
|
da8d6fb4f4 | ||
|
fa6a9cab7e | ||
|
bdfc4ad8a7 | ||
|
6ab7209e4d | ||
|
4485d1c685 | ||
|
8b63f78d76 | ||
|
2e87e0b4c1 | ||
|
507134407b | ||
|
7663cd4e23 | ||
|
ec2954200e | ||
|
eb32c5455f | ||
|
fc367b3c3e | ||
|
57dcd569f3 | ||
|
3673520ef6 | ||
|
fe17050580 | ||
|
ec40a8c969 | ||
|
6b1b3bec85 | ||
|
6bd6ececb7 | ||
|
c7db695e67 | ||
|
4cefdfedce | ||
|
6ce241163e | ||
|
79350899c5 | ||
|
f265724a3c | ||
|
2e50d2a36e | ||
|
643c062858 | ||
|
0e0db67aef | ||
|
6dc5b126d6 | ||
|
d2acabddd9 | ||
|
ec71b53c1e | ||
|
fc3d4187d1 | ||
|
a25f309990 | ||
|
5449592422 | ||
|
19b415871d | ||
|
6463f3439f | ||
|
f2320f9571 | ||
|
fc91694bdd | ||
|
dbb41108ef | ||
|
08c864bad7 | ||
|
9c5c65a243 | ||
|
b6199b1f27 | ||
|
38da54119a | ||
|
65b09ad4f0 | ||
|
603b8fae45 | ||
|
50e2e6a823 | ||
|
bb237e3bbb | ||
|
1bd2c0d220 | ||
|
bcb811a7e8 | ||
|
ec4d7e29ec | ||
|
a6df63f6d9 | ||
|
ea7213a5ae | ||
|
590a13334d | ||
|
631448335d | ||
|
12376368c7 | ||
|
f17564d743 | ||
|
a6fcc7dca6 | ||
|
70bce9e7dd | ||
|
17f3614288 | ||
|
238d1d87c6 | ||
|
82f639b91f | ||
|
c8bc553caa | ||
|
fa5d44af65 | ||
|
cbdbe5033f | ||
|
61ac250e2b | ||
|
04f72dfcb8 | ||
|
10ca5d94ea | ||
|
c5b8c69ae5 | ||
|
d3d7f7cc61 | ||
|
b6bb714264 | ||
|
a87310ac15 | ||
|
032e1b3d19 | ||
|
d9f15c1d21 | ||
|
99d09f71ad | ||
|
9c952b6bc8 | ||
|
fbae3d27c2 | ||
|
2f7d1f9f01 | ||
|
114101699d | ||
|
f5c0dcb5ea | ||
|
241220ce1f | ||
|
98d97e574c | ||
|
96e610970a | ||
|
2027802f82 | ||
|
54f93db632 | ||
|
3af7ca9ab0 | ||
|
93ef3edab3 | ||
|
c85852262e | ||
|
d0c3271628 | ||
|
ad9a48d5fa | ||
|
219d1383e5 | ||
|
8871280fab | ||
|
fb3e953e28 | ||
|
10712fd6ab | ||
|
9d478dbfe2 | ||
|
3013d67c16 | ||
|
bee8c2d159 | ||
|
945e5d5a74 | ||
|
93df8c56a8 | ||
|
cd1a964067 | ||
|
e4b829f0cf | ||
|
7206d84a6b | ||
|
b3233d3eb7 | ||
|
3c4c0ed46a | ||
|
24f1262005 | ||
|
86667a6d8a | ||
|
42e0d0f769 | ||
|
e976055253 | ||
|
84d6c8ec16 | ||
|
9fdfd091ac | ||
|
e66766f41c | ||
|
6177e69855 | ||
|
5c71cabb5f | ||
|
6ebe5532c5 | ||
|
8030c44f44 | ||
|
a85b5af761 | ||
|
d780c74abf | ||
|
5d7efa7f8f | ||
|
7e467443ed | ||
|
8439c337f7 | ||
|
151ad01038 | ||
|
73267442bb | ||
|
43fd794c96 | ||
|
36060fe332 | ||
|
3483debcc1 | ||
|
4324f6abbd | ||
|
43f8d8d8aa | ||
|
fb1ff77ec4 | ||
|
e355a7f6dd | ||
|
33e35368fc | ||
|
0e49a11e5e | ||
|
d47cf7e932 | ||
|
101057520b | ||
|
30b2e53002 | ||
|
5ab31a0ef5 | ||
|
b4ae331086 | ||
|
3f447df13c | ||
|
3517873156 | ||
|
118870bc41 | ||
|
d001ab5bef | ||
|
7496a88dcd | ||
|
6567c5e6c7 | ||
|
361427488f | ||
|
7272343e6d | ||
|
f0b3151d71 | ||
|
035359cb35 | ||
|
57b640622b | ||
|
de4c389c76 | ||
|
199456487c | ||
|
00ca5dc70a | ||
|
a04802b238 | ||
|
cb275aee37 | ||
|
fbf73c7c8e | ||
|
0040f8e924 | ||
|
6cca242f77 | ||
|
2929b8f617 | ||
|
8422c6de17 | ||
|
7c567b04bb | ||
|
1ac99e92a6 | ||
|
5ab975cc5c | ||
|
2cf63ea92a | ||
|
9e8d8ce878 | ||
|
b766bce07d | ||
|
0a0af221f0 | ||
|
9762d5be40 |
@@ -3,14 +3,36 @@
|
|||||||
# https://github.com/buildkite-plugins/docker-buildkite-plugin/releases
|
# 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)
|
# 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:
|
steps:
|
||||||
- label: "Assemble GPlay Debug version"
|
- label: "Compile and run Unit tests"
|
||||||
agents:
|
agents:
|
||||||
# We use a medium sized instance instead of the normal small ones because
|
# 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"
|
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:
|
commands:
|
||||||
- "./gradlew clean lintGplayRelease assembleGplayDebug --stacktrace"
|
- "./gradlew clean lintGplayRelease assembleGplayDebug --stacktrace"
|
||||||
artifact_paths:
|
artifact_paths:
|
||||||
@@ -23,9 +45,9 @@ steps:
|
|||||||
|
|
||||||
- label: "Assemble FDroid Debug version"
|
- label: "Assemble FDroid Debug version"
|
||||||
agents:
|
agents:
|
||||||
# We use a medium sized instance instead of the normal small ones because
|
# We use a xlarge sized instance instead of the normal small ones because
|
||||||
# gradle build is long
|
# gradle build can be memory hungry
|
||||||
queue: "medium"
|
queue: "xlarge"
|
||||||
commands:
|
commands:
|
||||||
- "./gradlew clean lintFdroidRelease assembleFdroidDebug --stacktrace"
|
- "./gradlew clean lintFdroidRelease assembleFdroidDebug --stacktrace"
|
||||||
artifact_paths:
|
artifact_paths:
|
||||||
@@ -38,9 +60,9 @@ steps:
|
|||||||
|
|
||||||
- label: "Build Google Play unsigned APK"
|
- label: "Build Google Play unsigned APK"
|
||||||
agents:
|
agents:
|
||||||
# We use a medium sized instance instead of the normal small ones because
|
# We use a xlarge sized instance instead of the normal small ones because
|
||||||
# gradle build is long
|
# gradle build can be memory hungry
|
||||||
queue: "medium"
|
queue: "xlarge"
|
||||||
commands:
|
commands:
|
||||||
- "./gradlew clean assembleGplayRelease --stacktrace"
|
- "./gradlew clean assembleGplayRelease --stacktrace"
|
||||||
artifact_paths:
|
artifact_paths:
|
||||||
|
5
.idea/dictionaries/bmarty.xml
generated
5
.idea/dictionaries/bmarty.xml
generated
@@ -3,7 +3,9 @@
|
|||||||
<words>
|
<words>
|
||||||
<w>backstack</w>
|
<w>backstack</w>
|
||||||
<w>bytearray</w>
|
<w>bytearray</w>
|
||||||
|
<w>checkables</w>
|
||||||
<w>ciphertext</w>
|
<w>ciphertext</w>
|
||||||
|
<w>coroutine</w>
|
||||||
<w>decryptor</w>
|
<w>decryptor</w>
|
||||||
<w>emoji</w>
|
<w>emoji</w>
|
||||||
<w>emojis</w>
|
<w>emojis</w>
|
||||||
@@ -12,8 +14,11 @@
|
|||||||
<w>linkified</w>
|
<w>linkified</w>
|
||||||
<w>linkify</w>
|
<w>linkify</w>
|
||||||
<w>megolm</w>
|
<w>megolm</w>
|
||||||
|
<w>msisdn</w>
|
||||||
<w>pbkdf</w>
|
<w>pbkdf</w>
|
||||||
<w>pkcs</w>
|
<w>pkcs</w>
|
||||||
|
<w>signin</w>
|
||||||
|
<w>signup</w>
|
||||||
</words>
|
</words>
|
||||||
</dictionary>
|
</dictionary>
|
||||||
</component>
|
</component>
|
51
CHANGES.md
51
CHANGES.md
@@ -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)
|
Changes in RiotX 0.7.0 (2019-10-24)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
@@ -12,6 +62,7 @@ Improvements:
|
|||||||
- Attachments: start using system pickers (#52)
|
- Attachments: start using system pickers (#52)
|
||||||
- Mark all messages as read (#396)
|
- Mark all messages as read (#396)
|
||||||
|
|
||||||
|
|
||||||
Other changes:
|
Other changes:
|
||||||
- Accessibility improvements to read receipts in the room timeline and reactions emoji chooser
|
- Accessibility improvements to read receipts in the room timeline and reactions emoji chooser
|
||||||
|
|
||||||
|
@@ -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/).
|
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.
|
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
|
### Layout
|
||||||
|
|
||||||
When adding or editing layouts, make sure the layout will render correctly if device uses a RTL (Right To Left) language.
|
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
260
docs/signin.md
Normal 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
579
docs/signup.md
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
@@ -11,6 +11,8 @@ android {
|
|||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
|
|
||||||
|
// Multidex is useful for tests
|
||||||
|
multiDexEnabled true
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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.EventAnnotationsSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
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.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.send.UserDraft
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.android.api.util.Optional
|
||||||
@@ -67,6 +68,10 @@ class RxRoom(private val room: Room) {
|
|||||||
fun liveDrafts(): Observable<List<UserDraft>> {
|
fun liveDrafts(): Observable<List<UserDraft>> {
|
||||||
return room.getDraftsLive().asObservable()
|
return room.getDraftsLive().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun liveNotificationState(): Observable<RoomNotificationState> {
|
||||||
|
return room.getLiveRoomNotificationState().asObservable()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Room.rx(): RxRoom {
|
fun Room.rx(): RxRoom {
|
||||||
|
@@ -54,6 +54,10 @@ class RxSession(private val session: Session) {
|
|||||||
return session.liveUsers().asObservable()
|
return session.liveUsers().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun liveIgnoredUsers(): Observable<List<User>> {
|
||||||
|
return session.liveIgnoredUsers().asObservable()
|
||||||
|
}
|
||||||
|
|
||||||
fun livePagedUsers(filter: String? = null): Observable<PagedList<User>> {
|
fun livePagedUsers(filter: String? = null): Observable<PagedList<User>> {
|
||||||
return session.livePagedUsers(filter).asObservable()
|
return session.livePagedUsers(filter).asObservable()
|
||||||
}
|
}
|
||||||
|
@@ -155,7 +155,8 @@ dependencies {
|
|||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
testImplementation 'org.robolectric:robolectric:4.3'
|
testImplementation 'org.robolectric:robolectric:4.3'
|
||||||
//testImplementation 'org.robolectric:shadows-support-v4:3.0'
|
//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.amshove.kluent:kluent-android:1.44'
|
||||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
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.ext:junit:1.1.1'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||||
androidTestImplementation 'org.amshove.kluent:kluent-android:1.44'
|
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 "androidx.arch.core:core-testing:$lifecycle_version"
|
||||||
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||||
|
|
||||||
|
@@ -17,12 +17,12 @@
|
|||||||
package im.vector.matrix.android
|
package im.vector.matrix.android
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.test.InstrumentationRegistry
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
interface InstrumentedTest {
|
interface InstrumentedTest {
|
||||||
fun context(): Context {
|
fun context(): Context {
|
||||||
return InstrumentationRegistry.getTargetContext()
|
return ApplicationProvider.getApplicationContext()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cacheDir(): File {
|
fun cacheDir(): File {
|
||||||
|
@@ -17,11 +17,11 @@
|
|||||||
package im.vector.matrix.android.auth
|
package im.vector.matrix.android.auth
|
||||||
|
|
||||||
import androidx.test.annotation.UiThreadTest
|
import androidx.test.annotation.UiThreadTest
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.rule.GrantPermissionRule
|
import androidx.test.rule.GrantPermissionRule
|
||||||
import androidx.test.runner.AndroidJUnit4
|
|
||||||
import im.vector.matrix.android.InstrumentedTest
|
import im.vector.matrix.android.InstrumentedTest
|
||||||
import im.vector.matrix.android.OkReplayRuleChainNoActivity
|
import im.vector.matrix.android.OkReplayRuleChainNoActivity
|
||||||
import im.vector.matrix.android.api.auth.Authenticator
|
import im.vector.matrix.android.api.auth.AuthenticationService
|
||||||
import okreplay.*
|
import okreplay.*
|
||||||
import org.junit.ClassRule
|
import org.junit.ClassRule
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
@@ -29,9 +29,9 @@ import org.junit.Test
|
|||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
internal class AuthenticatorTest : InstrumentedTest {
|
internal class AuthenticationServiceTest : InstrumentedTest {
|
||||||
|
|
||||||
lateinit var authenticator: Authenticator
|
lateinit var authenticationService: AuthenticationService
|
||||||
lateinit var okReplayInterceptor: OkReplayInterceptor
|
lateinit var okReplayInterceptor: OkReplayInterceptor
|
||||||
|
|
||||||
private val okReplayConfig = OkReplayConfig.Builder()
|
private val okReplayConfig = OkReplayConfig.Builder()
|
@@ -22,7 +22,7 @@ import androidx.work.Configuration
|
|||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.BuildConfig
|
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.SessionManager
|
||||||
import im.vector.matrix.android.internal.di.DaggerMatrixComponent
|
import im.vector.matrix.android.internal.di.DaggerMatrixComponent
|
||||||
import im.vector.matrix.android.internal.network.UserAgentHolder
|
import im.vector.matrix.android.internal.network.UserAgentHolder
|
||||||
@@ -46,7 +46,7 @@ data class MatrixConfiguration(
|
|||||||
*/
|
*/
|
||||||
class Matrix private constructor(context: Context, matrixConfiguration: 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 userAgentHolder: UserAgentHolder
|
||||||
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
|
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
|
||||||
@Inject internal lateinit var olmManager: OlmManager
|
@Inject internal lateinit var olmManager: OlmManager
|
||||||
@@ -64,8 +64,8 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
|||||||
|
|
||||||
fun getUserAgent() = userAgentHolder.userAgent
|
fun getUserAgent() = userAgentHolder.userAgent
|
||||||
|
|
||||||
fun authenticator(): Authenticator {
|
fun authenticationService(): AuthenticationService {
|
||||||
return authenticator
|
return authenticationService
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@@ -19,29 +19,48 @@ package im.vector.matrix.android.api.auth
|
|||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||||
|
import im.vector.matrix.android.api.auth.data.LoginFlowResult
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
import im.vector.matrix.android.api.auth.data.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.session.Session
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
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
|
* Return a LoginWizard, to login to the homeserver. The login flow has to be retrieved first.
|
||||||
* @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]
|
|
||||||
*/
|
*/
|
||||||
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].
|
* Check if there is an authenticated [Session].
|
||||||
@@ -67,5 +86,7 @@ interface Authenticator {
|
|||||||
/**
|
/**
|
||||||
* Create a session after a SSO successful login
|
* 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
|
||||||
}
|
}
|
@@ -30,4 +30,7 @@ data class Credentials(
|
|||||||
@Json(name = "home_server") val homeServer: String,
|
@Json(name = "home_server") val homeServer: String,
|
||||||
@Json(name = "access_token") val accessToken: String,
|
@Json(name = "access_token") val accessToken: String,
|
||||||
@Json(name = "refresh_token") val refreshToken: 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
|
||||||
|
)
|
||||||
|
@@ -25,7 +25,7 @@ import okhttp3.TlsVersion
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This data class holds how to connect to a specific Homeserver.
|
* 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.
|
* You should use the [Builder] to create one.
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
|
@@ -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()
|
||||||
|
}
|
@@ -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
|
||||||
|
}
|
@@ -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
|
||||||
|
}
|
||||||
|
}
|
@@ -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
|
||||||
|
)
|
@@ -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
|
||||||
|
)
|
@@ -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
|
||||||
|
}
|
@@ -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()
|
||||||
|
}
|
@@ -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>
|
||||||
|
)
|
@@ -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
|
||||||
|
}
|
@@ -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<*, *>
|
@@ -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) }
|
||||||
|
}
|
@@ -34,6 +34,7 @@ sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
|
|||||||
data class Cancelled(val throwable: Throwable? = null) : Failure(throwable)
|
data class Cancelled(val throwable: Throwable? = null) : Failure(throwable)
|
||||||
data class NetworkConnection(val ioException: IOException? = null) : Failure(ioException)
|
data class NetworkConnection(val ioException: IOException? = null) : Failure(ioException)
|
||||||
data class ServerError(val error: MatrixError, val httpCode: Int) : Failure(RuntimeException(error.toString()))
|
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
|
// 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))
|
data class OtherServerError(val errorBody: String, val httpCode: Int) : Failure(RuntimeException(errorBody))
|
||||||
|
|
||||||
|
@@ -31,7 +31,9 @@ data class MatrixError(
|
|||||||
@Json(name = "consent_uri") val consentUri: String? = null,
|
@Json(name = "consent_uri") val consentUri: String? = null,
|
||||||
// RESOURCE_LIMIT_EXCEEDED data
|
// RESOURCE_LIMIT_EXCEEDED data
|
||||||
@Json(name = "limit_type") val limitType: String? = null,
|
@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 {
|
companion object {
|
||||||
const val FORBIDDEN = "M_FORBIDDEN"
|
const val FORBIDDEN = "M_FORBIDDEN"
|
||||||
|
@@ -21,7 +21,7 @@ import timber.log.Timber
|
|||||||
sealed class Action {
|
sealed class Action {
|
||||||
object Notify : Action()
|
object Notify : Action()
|
||||||
object DoNotNotify : 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()
|
data class Highlight(val highlight: Boolean) : Action()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,6 +63,29 @@ private const val ACTION_OBJECT_VALUE_VALUE_DEFAULT = "default"
|
|||||||
*
|
*
|
||||||
* </pre>
|
* </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> {
|
fun PushRule.getActions(): List<Action> {
|
||||||
val result = ArrayList<Action>()
|
val result = ArrayList<Action>()
|
||||||
|
|
||||||
|
@@ -34,6 +34,10 @@ interface PushRuleService {
|
|||||||
|
|
||||||
fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable
|
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 addPushRuleListener(listener: PushRuleListener)
|
||||||
|
|
||||||
fun removePushRuleListener(listener: PushRuleListener)
|
fun removePushRuleListener(listener: PushRuleListener)
|
||||||
|
@@ -19,7 +19,7 @@ package im.vector.matrix.android.api.session.cache
|
|||||||
import im.vector.matrix.android.api.MatrixCallback
|
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 {
|
interface CacheService {
|
||||||
|
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.api.session.events.model
|
package im.vector.matrix.android.api.session.events.model
|
||||||
|
|
||||||
import java.util.*
|
import java.util.UUID
|
||||||
|
|
||||||
object LocalEcho {
|
object LocalEcho {
|
||||||
|
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
package im.vector.matrix.android.api.session.file
|
package im.vector.matrix.android.api.session.file
|
||||||
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
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 im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@@ -47,5 +48,5 @@ interface FileService {
|
|||||||
fileName: String,
|
fileName: String,
|
||||||
url: String?,
|
url: String?,
|
||||||
elementToDecrypt: ElementToDecrypt?,
|
elementToDecrypt: ElementToDecrypt?,
|
||||||
callback: MatrixCallback<File>)
|
callback: MatrixCallback<File>): Cancelable
|
||||||
}
|
}
|
||||||
|
@@ -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.members.MembershipService
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.relation.RelationService
|
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.reporting.ReportingService
|
||||||
import im.vector.matrix.android.api.session.room.read.ReadService
|
import im.vector.matrix.android.api.session.room.read.ReadService
|
||||||
import im.vector.matrix.android.api.session.room.send.DraftService
|
import im.vector.matrix.android.api.session.room.send.DraftService
|
||||||
@@ -41,7 +42,8 @@ interface Room :
|
|||||||
StateService,
|
StateService,
|
||||||
ReportingService,
|
ReportingService,
|
||||||
RelationService,
|
RelationService,
|
||||||
RoomCryptoService {
|
RoomCryptoService,
|
||||||
|
RoomPushRuleService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The roomId of this room
|
* The roomId of this room
|
||||||
|
@@ -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")
|
||||||
|
}
|
@@ -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
|
||||||
|
)
|
@@ -38,7 +38,7 @@ data class MessageImageContent(
|
|||||||
/**
|
/**
|
||||||
* Metadata about the image referred to in url.
|
* 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.
|
* 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.
|
* Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
|
||||||
*/
|
*/
|
||||||
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
|
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
|
||||||
) : MessageEncryptedContent
|
) : MessageImageInfoContent
|
||||||
|
@@ -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?
|
||||||
|
}
|
@@ -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
|
@@ -72,7 +72,7 @@ interface RelationService {
|
|||||||
*/
|
*/
|
||||||
fun editTextMessage(targetEventId: String,
|
fun editTextMessage(targetEventId: String,
|
||||||
msgType: String,
|
msgType: String,
|
||||||
newBodyText: String,
|
newBodyText: CharSequence,
|
||||||
newBodyAutoMarkdown: Boolean,
|
newBodyAutoMarkdown: Boolean,
|
||||||
compatibilityBodyText: String = "* $newBodyText"): Cancelable
|
compatibilityBodyText: String = "* $newBodyText"): Cancelable
|
||||||
|
|
||||||
@@ -97,12 +97,14 @@ interface RelationService {
|
|||||||
/**
|
/**
|
||||||
* Reply to an event in the timeline (must be in same room)
|
* Reply to an event in the timeline (must be in same room)
|
||||||
* https://matrix.org/docs/spec/client_server/r0.4.0.html#id350
|
* 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 eventReplied the event referenced by the reply
|
||||||
* @param replyText the reply text
|
* @param replyText the reply text
|
||||||
* @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present
|
* @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present
|
||||||
*/
|
*/
|
||||||
fun replyToMessage(eventReplied: TimelineEvent,
|
fun replyToMessage(eventReplied: TimelineEvent,
|
||||||
replyText: String,
|
replyText: CharSequence,
|
||||||
autoMarkdown: Boolean = false): Cancelable?
|
autoMarkdown: Boolean = false): Cancelable?
|
||||||
|
|
||||||
fun getEventSummaryLive(eventId: String): LiveData<Optional<EventAnnotationsSummary>>
|
fun getEventSummaryLive(eventId: String): LiveData<Optional<EventAnnotationsSummary>>
|
||||||
|
@@ -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
|
||||||
|
}
|
@@ -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
|
||||||
|
}
|
@@ -29,20 +29,23 @@ interface SendService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to send a text message asynchronously.
|
* 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 text the text message to send
|
||||||
* @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE
|
* @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
|
* @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present
|
||||||
* @return a [Cancelable]
|
* @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.
|
* Method to send a text message with a formatted body.
|
||||||
* @param text the text message to send
|
* @param text the text message to send
|
||||||
* @param formattedText The formatted body using MessageType#FORMAT_MATRIX_HTML
|
* @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]
|
* @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.
|
* Method to send a media asynchronously.
|
||||||
|
@@ -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
|
||||||
|
}
|
@@ -30,10 +30,16 @@ package im.vector.matrix.android.api.session.room.timeline
|
|||||||
*/
|
*/
|
||||||
interface Timeline {
|
interface Timeline {
|
||||||
|
|
||||||
var listener: Listener?
|
val timelineID: String
|
||||||
|
|
||||||
val isLive: Boolean
|
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
|
* 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 {
|
interface Listener {
|
||||||
/**
|
/**
|
||||||
* Call when the timeline has been updated through pagination or sync.
|
* 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>)
|
fun onUpdated(snapshot: List<TimelineEvent>)
|
||||||
}
|
}
|
||||||
|
@@ -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.EventAnnotationsSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
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.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.session.room.model.message.isReply
|
||||||
import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply
|
import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply
|
||||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||||
@@ -40,8 +41,7 @@ data class TimelineEvent(
|
|||||||
val isUniqueDisplayName: Boolean,
|
val isUniqueDisplayName: Boolean,
|
||||||
val senderAvatar: String?,
|
val senderAvatar: String?,
|
||||||
val annotations: EventAnnotationsSummary? = null,
|
val annotations: EventAnnotationsSummary? = null,
|
||||||
val readReceipts: List<ReadReceipt> = emptyList(),
|
val readReceipts: List<ReadReceipt> = emptyList()
|
||||||
val hasReadMarker: Boolean = false
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val metadata = HashMap<String, Any>()
|
val metadata = HashMap<String, Any>()
|
||||||
@@ -62,15 +62,11 @@ data class TimelineEvent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getDisambiguatedDisplayName(): String {
|
fun getDisambiguatedDisplayName(): String {
|
||||||
return if (isUniqueDisplayName) {
|
return when {
|
||||||
senderName
|
senderName.isNullOrBlank() -> root.senderId ?: ""
|
||||||
} else {
|
isUniqueDisplayName -> senderName
|
||||||
senderName?.let { name ->
|
else -> "$senderName (${root.senderId})"
|
||||||
"$name (${root.senderId})"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
?: root.senderId
|
|
||||||
?: ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -103,8 +99,14 @@ fun TimelineEvent.getEditedEventId(): String? {
|
|||||||
/**
|
/**
|
||||||
* Get last MessageContent, after a possible edition
|
* Get last MessageContent, after a possible edition
|
||||||
*/
|
*/
|
||||||
fun TimelineEvent.getLastMessageContent(): MessageContent? = annotations?.editSummary?.aggregatedContent?.toModel()
|
fun TimelineEvent.getLastMessageContent(): MessageContent? {
|
||||||
?: root.getClearContent().toModel()
|
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
|
* Get last Message body, after a possible edition
|
||||||
@@ -113,7 +115,8 @@ fun TimelineEvent.getLastMessageBody(): String? {
|
|||||||
val lastMessageContent = getLastMessageContent()
|
val lastMessageContent = getLastMessageContent()
|
||||||
|
|
||||||
if (lastMessageContent != null) {
|
if (lastMessageContent != null) {
|
||||||
return lastMessageContent.newContent?.toModel<MessageContent>()?.body ?: lastMessageContent.body
|
return lastMessageContent.newContent?.toModel<MessageContent>()?.body
|
||||||
|
?: lastMessageContent.body
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
|
@@ -64,4 +64,19 @@ interface UserService {
|
|||||||
* @return a Livedata of users
|
* @return a Livedata of users
|
||||||
*/
|
*/
|
||||||
fun livePagedUsers(filter: String? = null): LiveData<PagedList<User>>
|
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
|
||||||
}
|
}
|
||||||
|
@@ -29,3 +29,5 @@ interface Cancelable {
|
|||||||
// no-op
|
// no-op
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object NoOpCancellable : Cancelable
|
||||||
|
@@ -21,4 +21,6 @@ import java.lang.reflect.ParameterizedType
|
|||||||
|
|
||||||
typealias JsonDict = Map<String, @JvmSuppressWildcards Any>
|
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)
|
internal val JSON_DICT_PARAMETERIZED_TYPE: ParameterizedType = Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java)
|
||||||
|
@@ -17,20 +17,47 @@
|
|||||||
package im.vector.matrix.android.internal.auth
|
package im.vector.matrix.android.internal.auth
|
||||||
|
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
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.LoginFlowResponse
|
||||||
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
|
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 im.vector.matrix.android.internal.network.NetworkConstants
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.*
|
||||||
import retrofit2.http.GET
|
|
||||||
import retrofit2.http.Headers
|
|
||||||
import retrofit2.http.POST
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The login REST API.
|
* The login REST API.
|
||||||
*/
|
*/
|
||||||
internal interface AuthAPI {
|
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
|
* Get the supported login flow
|
||||||
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-login
|
* 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")
|
@Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000")
|
||||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
|
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
|
||||||
fun login(@Body loginParams: PasswordLoginParams): Call<Credentials>
|
fun login(@Body loginParams: PasswordLoginParams): Call<Credentials>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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>
|
||||||
}
|
}
|
||||||
|
@@ -20,8 +20,10 @@ import android.content.Context
|
|||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
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.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.auth.db.RealmSessionParamsStore
|
||||||
import im.vector.matrix.android.internal.database.RealmKeysUtils
|
import im.vector.matrix.android.internal.database.RealmKeysUtils
|
||||||
import im.vector.matrix.android.internal.di.AuthDatabase
|
import im.vector.matrix.android.internal.di.AuthDatabase
|
||||||
@@ -50,7 +52,8 @@ internal abstract class AuthModule {
|
|||||||
}
|
}
|
||||||
.name("matrix-sdk-auth.realm")
|
.name("matrix-sdk-auth.realm")
|
||||||
.modules(AuthRealmModule())
|
.modules(AuthRealmModule())
|
||||||
.deleteRealmIfMigrationNeeded()
|
.schemaVersion(AuthRealmMigration.SCHEMA_VERSION)
|
||||||
|
.migration(AuthRealmMigration())
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,5 +62,11 @@ internal abstract class AuthModule {
|
|||||||
abstract fun bindSessionParamsStore(sessionParamsStore: RealmSessionParamsStore): SessionParamsStore
|
abstract fun bindSessionParamsStore(sessionParamsStore: RealmSessionParamsStore): SessionParamsStore
|
||||||
|
|
||||||
@Binds
|
@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
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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()
|
||||||
|
}
|
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.auth
|
package im.vector.matrix.android.internal.auth
|
||||||
|
|
||||||
import arrow.core.Try
|
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
|
|
||||||
internal interface SessionParamsStore {
|
internal interface SessionParamsStore {
|
||||||
@@ -27,9 +26,9 @@ internal interface SessionParamsStore {
|
|||||||
|
|
||||||
fun getAll(): List<SessionParams>
|
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()
|
||||||
}
|
}
|
||||||
|
@@ -30,12 +30,4 @@ data class InteractiveAuthenticationFlow(
|
|||||||
|
|
||||||
@Json(name = "stages")
|
@Json(name = "stages")
|
||||||
val stages: List<String>? = null
|
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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -25,4 +25,7 @@ object LoginFlowTypes {
|
|||||||
const val MSISDN = "m.login.msisdn"
|
const val MSISDN = "m.login.msisdn"
|
||||||
const val RECAPTCHA = "m.login.recaptcha"
|
const val RECAPTCHA = "m.login.recaptcha"
|
||||||
const val DUMMY = "m.login.dummy"
|
const val DUMMY = "m.login.dummy"
|
||||||
|
const val TERMS = "m.login.terms"
|
||||||
|
const val TOKEN = "m.login.token"
|
||||||
|
const val SSO = "m.login.sso"
|
||||||
}
|
}
|
||||||
|
@@ -19,34 +19,46 @@ package im.vector.matrix.android.internal.auth.data
|
|||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
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)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class PasswordLoginParams(@Json(name = "identifier") val identifier: Map<String, String>,
|
internal data class PasswordLoginParams(
|
||||||
@Json(name = "password") val password: String,
|
@Json(name = "identifier") val identifier: Map<String, String>,
|
||||||
@Json(name = "type") override val type: String,
|
@Json(name = "password") val password: String,
|
||||||
@Json(name = "initial_device_display_name") val deviceDisplayName: String?,
|
@Json(name = "type") override val type: String,
|
||||||
@Json(name = "device_id") val deviceId: String?) : LoginParams {
|
@Json(name = "initial_device_display_name") val deviceDisplayName: String?,
|
||||||
|
@Json(name = "device_id") val deviceId: String?) : LoginParams {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private const val IDENTIFIER_KEY_TYPE = "type"
|
||||||
|
|
||||||
val IDENTIFIER_KEY_TYPE_USER = "m.id.user"
|
private const val IDENTIFIER_KEY_TYPE_USER = "m.id.user"
|
||||||
val IDENTIFIER_KEY_TYPE_THIRD_PARTY = "m.id.thirdparty"
|
private const val IDENTIFIER_KEY_USER = "user"
|
||||||
val IDENTIFIER_KEY_TYPE_PHONE = "m.id.phone"
|
|
||||||
|
|
||||||
val IDENTIFIER_KEY_TYPE = "type"
|
private const val IDENTIFIER_KEY_TYPE_THIRD_PARTY = "m.id.thirdparty"
|
||||||
val IDENTIFIER_KEY_MEDIUM = "medium"
|
private const val IDENTIFIER_KEY_MEDIUM = "medium"
|
||||||
val IDENTIFIER_KEY_ADDRESS = "address"
|
private const val IDENTIFIER_KEY_ADDRESS = "address"
|
||||||
val IDENTIFIER_KEY_USER = "user"
|
|
||||||
val IDENTIFIER_KEY_COUNTRY = "country"
|
private const val IDENTIFIER_KEY_TYPE_PHONE = "m.id.phone"
|
||||||
val IDENTIFIER_KEY_NUMBER = "number"
|
private const val IDENTIFIER_KEY_COUNTRY = "country"
|
||||||
|
private const val IDENTIFIER_KEY_PHONE = "phone"
|
||||||
|
|
||||||
fun userIdentifier(user: String,
|
fun userIdentifier(user: String,
|
||||||
password: String,
|
password: String,
|
||||||
deviceDisplayName: String? = null,
|
deviceDisplayName: String? = null,
|
||||||
deviceId: String? = null): PasswordLoginParams {
|
deviceId: String? = null): PasswordLoginParams {
|
||||||
val identifier = HashMap<String, String>()
|
return PasswordLoginParams(
|
||||||
identifier[IDENTIFIER_KEY_TYPE] = IDENTIFIER_KEY_TYPE_USER
|
mapOf(
|
||||||
identifier[IDENTIFIER_KEY_USER] = user
|
IDENTIFIER_KEY_TYPE to IDENTIFIER_KEY_TYPE_USER,
|
||||||
return PasswordLoginParams(identifier, password, LoginFlowTypes.PASSWORD, deviceDisplayName, deviceId)
|
IDENTIFIER_KEY_USER to user
|
||||||
|
),
|
||||||
|
password,
|
||||||
|
LoginFlowTypes.PASSWORD,
|
||||||
|
deviceDisplayName,
|
||||||
|
deviceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun thirdPartyIdentifier(medium: String,
|
fun thirdPartyIdentifier(medium: String,
|
||||||
@@ -54,11 +66,33 @@ internal data class PasswordLoginParams(@Json(name = "identifier") val identifie
|
|||||||
password: String,
|
password: String,
|
||||||
deviceDisplayName: String? = null,
|
deviceDisplayName: String? = null,
|
||||||
deviceId: String? = null): PasswordLoginParams {
|
deviceId: String? = null): PasswordLoginParams {
|
||||||
val identifier = HashMap<String, String>()
|
return PasswordLoginParams(
|
||||||
identifier[IDENTIFIER_KEY_TYPE] = IDENTIFIER_KEY_TYPE_THIRD_PARTY
|
mapOf(
|
||||||
identifier[IDENTIFIER_KEY_MEDIUM] = medium
|
IDENTIFIER_KEY_TYPE to IDENTIFIER_KEY_TYPE_THIRD_PARTY,
|
||||||
identifier[IDENTIFIER_KEY_ADDRESS] = address
|
IDENTIFIER_KEY_MEDIUM to medium,
|
||||||
return PasswordLoginParams(identifier, password, LoginFlowTypes.PASSWORD, deviceDisplayName, deviceId)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -23,6 +23,7 @@ import io.realm.annotations.RealmModule
|
|||||||
*/
|
*/
|
||||||
@RealmModule(library = true,
|
@RealmModule(library = true,
|
||||||
classes = [
|
classes = [
|
||||||
SessionParamsEntity::class
|
SessionParamsEntity::class,
|
||||||
|
PendingSessionEntity::class
|
||||||
])
|
])
|
||||||
internal class AuthRealmModule
|
internal class AuthRealmModule
|
||||||
|
@@ -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
|
||||||
|
)
|
@@ -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()
|
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -16,12 +16,14 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.auth.db
|
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.api.auth.data.SessionParams
|
||||||
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||||
|
import im.vector.matrix.android.internal.database.awaitTransaction
|
||||||
import im.vector.matrix.android.internal.di.AuthDatabase
|
import im.vector.matrix.android.internal.di.AuthDatabase
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
|
import io.realm.exceptions.RealmPrimaryKeyConstraintException
|
||||||
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class RealmSessionParamsStore @Inject constructor(private val mapper: SessionParamsMapper,
|
internal class RealmSessionParamsStore @Inject constructor(private val mapper: SessionParamsMapper,
|
||||||
@@ -30,73 +32,63 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S
|
|||||||
) : SessionParamsStore {
|
) : SessionParamsStore {
|
||||||
|
|
||||||
override fun getLast(): SessionParams? {
|
override fun getLast(): SessionParams? {
|
||||||
val realm = Realm.getInstance(realmConfiguration)
|
return Realm.getInstance(realmConfiguration).use { realm ->
|
||||||
val sessionParams = realm
|
realm
|
||||||
.where(SessionParamsEntity::class.java)
|
.where(SessionParamsEntity::class.java)
|
||||||
.findAll()
|
.findAll()
|
||||||
.map { mapper.map(it) }
|
.map { mapper.map(it) }
|
||||||
.lastOrNull()
|
.lastOrNull()
|
||||||
realm.close()
|
}
|
||||||
return sessionParams
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun get(userId: String): SessionParams? {
|
override fun get(userId: String): SessionParams? {
|
||||||
val realm = Realm.getInstance(realmConfiguration)
|
return Realm.getInstance(realmConfiguration).use { realm ->
|
||||||
val sessionParams = realm
|
realm
|
||||||
.where(SessionParamsEntity::class.java)
|
.where(SessionParamsEntity::class.java)
|
||||||
.equalTo(SessionParamsEntityFields.USER_ID, userId)
|
.equalTo(SessionParamsEntityFields.USER_ID, userId)
|
||||||
.findAll()
|
.findAll()
|
||||||
.map { mapper.map(it) }
|
.map { mapper.map(it) }
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
realm.close()
|
}
|
||||||
return sessionParams
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getAll(): List<SessionParams> {
|
override fun getAll(): List<SessionParams> {
|
||||||
val realm = Realm.getInstance(realmConfiguration)
|
return Realm.getInstance(realmConfiguration).use { realm ->
|
||||||
val sessionParams = realm
|
realm
|
||||||
.where(SessionParamsEntity::class.java)
|
.where(SessionParamsEntity::class.java)
|
||||||
.findAll()
|
.findAll()
|
||||||
.mapNotNull { mapper.map(it) }
|
.mapNotNull { mapper.map(it) }
|
||||||
realm.close()
|
}
|
||||||
return sessionParams
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun save(sessionParams: SessionParams): Try<Unit> {
|
override suspend fun save(sessionParams: SessionParams) {
|
||||||
return Try {
|
awaitTransaction(realmConfiguration) {
|
||||||
val entity = mapper.map(sessionParams)
|
val entity = mapper.map(sessionParams)
|
||||||
if (entity != null) {
|
if (entity != null) {
|
||||||
val realm = Realm.getInstance(realmConfiguration)
|
try {
|
||||||
realm.executeTransaction {
|
|
||||||
it.insert(entity)
|
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> {
|
override suspend fun delete(userId: String) {
|
||||||
return Try {
|
awaitTransaction(realmConfiguration) {
|
||||||
val realm = Realm.getInstance(realmConfiguration)
|
it.where(SessionParamsEntity::class.java)
|
||||||
realm.executeTransaction {
|
.equalTo(SessionParamsEntityFields.USER_ID, userId)
|
||||||
it.where(SessionParamsEntity::class.java)
|
.findAll()
|
||||||
.equalTo(SessionParamsEntityFields.USER_ID, userId)
|
.deleteAllFromRealm()
|
||||||
.findAll()
|
|
||||||
.deleteAllFromRealm()
|
|
||||||
}
|
|
||||||
realm.close()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun deleteAll(): Try<Unit> {
|
override suspend fun deleteAll() {
|
||||||
return Try {
|
awaitTransaction(realmConfiguration) {
|
||||||
val realm = Realm.getInstance(realmConfiguration)
|
it.where(SessionParamsEntity::class.java)
|
||||||
realm.executeTransaction {
|
.findAll()
|
||||||
it.where(SessionParamsEntity::class.java)
|
.deleteAllFromRealm()
|
||||||
.findAll()
|
|
||||||
.deleteAllFromRealm()
|
|
||||||
}
|
|
||||||
realm.close()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
|
}
|
||||||
|
}
|
@@ -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
|
||||||
|
)
|
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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
|
||||||
|
)
|
@@ -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
|
||||||
|
)
|
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
@@ -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
|
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -18,8 +18,12 @@ package im.vector.matrix.android.internal.auth.registration
|
|||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.api.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.api.util.JsonDict
|
||||||
import im.vector.matrix.android.internal.auth.data.InteractiveAuthenticationFlow
|
import im.vector.matrix.android.internal.auth.data.InteractiveAuthenticationFlow
|
||||||
|
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class RegistrationFlowResponse(
|
data class RegistrationFlowResponse(
|
||||||
@@ -50,4 +54,46 @@ data class RegistrationFlowResponse(
|
|||||||
*/
|
*/
|
||||||
@Json(name = "params")
|
@Json(name = "params")
|
||||||
var params: JsonDict? = null
|
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)
|
||||||
|
}
|
||||||
|
@@ -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
|
||||||
|
)
|
@@ -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?
|
||||||
|
)
|
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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
|
||||||
|
)
|
@@ -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.ClearCacheTask
|
||||||
import im.vector.matrix.android.internal.session.cache.RealmClearCacheTask
|
import im.vector.matrix.android.internal.session.cache.RealmClearCacheTask
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@@ -66,6 +68,13 @@ internal abstract class CryptoModule {
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@Provides
|
||||||
|
@SessionScope
|
||||||
|
fun providesCryptoCoroutineScope(): CoroutineScope {
|
||||||
|
return CoroutineScope(SupervisorJob())
|
||||||
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Provides
|
@Provides
|
||||||
@CryptoDatabase
|
@CryptoDatabase
|
||||||
|
@@ -132,7 +132,8 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
private val loadRoomMembersTask: LoadRoomMembersTask,
|
private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||||
private val monarchy: Monarchy,
|
private val monarchy: Monarchy,
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
private val taskExecutor: TaskExecutor
|
private val taskExecutor: TaskExecutor,
|
||||||
|
private val cryptoCoroutineScope: CoroutineScope
|
||||||
) : CryptoService {
|
) : CryptoService {
|
||||||
|
|
||||||
private val uiHandler = Handler(Looper.getMainLooper())
|
private val uiHandler = Handler(Looper.getMainLooper())
|
||||||
@@ -243,7 +244,8 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
isStarting.set(true)
|
isStarting.set(true)
|
||||||
GlobalScope.launch(coroutineDispatchers.crypto) {
|
|
||||||
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
internalStart(isInitialSync)
|
internalStart(isInitialSync)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -269,10 +271,9 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
isStarted.set(true)
|
isStarted.set(true)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Timber.e("Start failed: $it")
|
|
||||||
delay(1000)
|
|
||||||
isStarting.set(false)
|
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
|
* Close the crypto
|
||||||
*/
|
*/
|
||||||
fun close() = runBlocking(coroutineDispatchers.crypto) {
|
fun close() = runBlocking(coroutineDispatchers.crypto) {
|
||||||
|
cryptoCoroutineScope.coroutineContext.cancelChildren(CancellationException("Closing crypto module"))
|
||||||
|
|
||||||
|
outgoingRoomKeyRequestManager.stop()
|
||||||
|
|
||||||
olmDevice.release()
|
olmDevice.release()
|
||||||
cryptoStore.close()
|
cryptoStore.close()
|
||||||
outgoingRoomKeyRequestManager.stop()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aways enabled on RiotX
|
// Aways enabled on RiotX
|
||||||
@@ -305,19 +309,21 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
* @param syncResponse the syncResponse
|
* @param syncResponse the syncResponse
|
||||||
*/
|
*/
|
||||||
fun onSyncCompleted(syncResponse: SyncResponse) {
|
fun onSyncCompleted(syncResponse: SyncResponse) {
|
||||||
GlobalScope.launch(coroutineDispatchers.crypto) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
if (syncResponse.deviceLists != null) {
|
runCatching {
|
||||||
deviceListManager.handleDeviceListsChanges(syncResponse.deviceLists.changed, syncResponse.deviceLists.left)
|
if (syncResponse.deviceLists != null) {
|
||||||
}
|
deviceListManager.handleDeviceListsChanges(syncResponse.deviceLists.changed, syncResponse.deviceLists.left)
|
||||||
if (syncResponse.deviceOneTimeKeysCount != null) {
|
}
|
||||||
val currentCount = syncResponse.deviceOneTimeKeysCount.signedCurve25519 ?: 0
|
if (syncResponse.deviceOneTimeKeysCount != null) {
|
||||||
oneTimeKeysUploader.updateOneTimeKeyCount(currentCount)
|
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
|
if (isStarted()) {
|
||||||
deviceListManager.refreshOutdatedDeviceLists()
|
// Make sure we process to-device messages before generating new one-time-keys #2782
|
||||||
oneTimeKeysUploader.maybeUploadOneTimeKeys()
|
deviceListManager.refreshOutdatedDeviceLists()
|
||||||
incomingRoomKeyRequestManager.processReceivedRoomKeyRequests()
|
oneTimeKeysUploader.maybeUploadOneTimeKeys()
|
||||||
|
incomingRoomKeyRequestManager.processReceivedRoomKeyRequests()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -511,7 +517,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
eventType: String,
|
eventType: String,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
callback: MatrixCallback<MXEncryptEventContentResult>) {
|
callback: MatrixCallback<MXEncryptEventContentResult>) {
|
||||||
GlobalScope.launch(coroutineDispatchers.crypto) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
if (!isStarted()) {
|
if (!isStarted()) {
|
||||||
Timber.v("## encryptEventContent() : wait after e2e init")
|
Timber.v("## encryptEventContent() : wait after e2e init")
|
||||||
internalStart(false)
|
internalStart(false)
|
||||||
@@ -571,7 +577,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
* @param callback the callback to return data or null
|
* @param callback the callback to return data or null
|
||||||
*/
|
*/
|
||||||
override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>) {
|
override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>) {
|
||||||
GlobalScope.launch {
|
cryptoCoroutineScope.launch {
|
||||||
val result = runCatching {
|
val result = runCatching {
|
||||||
withContext(coroutineDispatchers.crypto) {
|
withContext(coroutineDispatchers.crypto) {
|
||||||
internalDecryptEvent(event, timeline)
|
internalDecryptEvent(event, timeline)
|
||||||
@@ -621,7 +627,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
* @param event the event
|
* @param event the event
|
||||||
*/
|
*/
|
||||||
fun onToDeviceEvent(event: Event) {
|
fun onToDeviceEvent(event: Event) {
|
||||||
GlobalScope.launch(coroutineDispatchers.crypto) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
when (event.getClearType()) {
|
when (event.getClearType()) {
|
||||||
EventType.ROOM_KEY, EventType.FORWARDED_ROOM_KEY -> {
|
EventType.ROOM_KEY, EventType.FORWARDED_ROOM_KEY -> {
|
||||||
onRoomKeyEvent(event)
|
onRoomKeyEvent(event)
|
||||||
@@ -661,7 +667,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
* @param event the encryption event.
|
* @param event the encryption event.
|
||||||
*/
|
*/
|
||||||
private fun onRoomEncryptionEvent(roomId: String, event: Event) {
|
private fun onRoomEncryptionEvent(roomId: String, event: Event) {
|
||||||
GlobalScope.launch(coroutineDispatchers.crypto) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
val params = LoadRoomMembersTask.Params(roomId)
|
val params = LoadRoomMembersTask.Params(roomId)
|
||||||
try {
|
try {
|
||||||
loadRoomMembersTask.execute(params)
|
loadRoomMembersTask.execute(params)
|
||||||
@@ -753,7 +759,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
* @param callback the exported keys
|
* @param callback the exported keys
|
||||||
*/
|
*/
|
||||||
override fun exportRoomKeys(password: String, callback: MatrixCallback<ByteArray>) {
|
override fun exportRoomKeys(password: String, callback: MatrixCallback<ByteArray>) {
|
||||||
GlobalScope.launch(coroutineDispatchers.main) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
||||||
runCatching {
|
runCatching {
|
||||||
exportRoomKeys(password, MXMegolmExportEncryption.DEFAULT_ITERATION_COUNT)
|
exportRoomKeys(password, MXMegolmExportEncryption.DEFAULT_ITERATION_COUNT)
|
||||||
}.foldToCallback(callback)
|
}.foldToCallback(callback)
|
||||||
@@ -791,7 +797,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
password: String,
|
password: String,
|
||||||
progressListener: ProgressListener?,
|
progressListener: ProgressListener?,
|
||||||
callback: MatrixCallback<ImportRoomKeysResult>) {
|
callback: MatrixCallback<ImportRoomKeysResult>) {
|
||||||
GlobalScope.launch(coroutineDispatchers.main) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
||||||
runCatching {
|
runCatching {
|
||||||
withContext(coroutineDispatchers.crypto) {
|
withContext(coroutineDispatchers.crypto) {
|
||||||
Timber.v("## importRoomKeys starts")
|
Timber.v("## importRoomKeys starts")
|
||||||
@@ -839,7 +845,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
fun checkUnknownDevices(userIds: List<String>, callback: MatrixCallback<Unit>) {
|
fun checkUnknownDevices(userIds: List<String>, callback: MatrixCallback<Unit>) {
|
||||||
// force the refresh to ensure that the devices list is up-to-date
|
// force the refresh to ensure that the devices list is up-to-date
|
||||||
GlobalScope.launch(coroutineDispatchers.crypto) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
runCatching {
|
runCatching {
|
||||||
val keys = deviceListManager.downloadKeys(userIds, true)
|
val keys = deviceListManager.downloadKeys(userIds, true)
|
||||||
val unknownDevices = getUnknownDevices(keys)
|
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>>) {
|
override fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>) {
|
||||||
GlobalScope.launch(coroutineDispatchers.crypto) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
runCatching {
|
runCatching {
|
||||||
deviceListManager.downloadKeys(userIds, forceDownload)
|
deviceListManager.downloadKeys(userIds, forceDownload)
|
||||||
}.foldToCallback(callback)
|
}.foldToCallback(callback)
|
||||||
|
@@ -66,7 +66,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
|||||||
if (':' in userId) {
|
if (':' in userId) {
|
||||||
try {
|
try {
|
||||||
synchronized(notReadyToRetryHS) {
|
synchronized(notReadyToRetryHS) {
|
||||||
res = !notReadyToRetryHS.contains(userId.substring(userId.lastIndexOf(":") + 1))
|
res = !notReadyToRetryHS.contains(userId.substringAfterLast(':'))
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e, "## canRetryKeysDownload() failed")
|
Timber.e(e, "## canRetryKeysDownload() failed")
|
||||||
|
@@ -25,7 +25,6 @@ import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
|||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
|
|
||||||
@SessionScope
|
@SessionScope
|
||||||
internal class IncomingRoomKeyRequestManager @Inject constructor(
|
internal class IncomingRoomKeyRequestManager @Inject constructor(
|
||||||
@@ -51,7 +50,7 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
|
|||||||
*
|
*
|
||||||
* @param event the announcement event.
|
* @param event the announcement event.
|
||||||
*/
|
*/
|
||||||
suspend fun onRoomKeyRequestEvent(event: Event) {
|
fun onRoomKeyRequestEvent(event: Event) {
|
||||||
val roomKeyShare = event.getClearContent().toModel<RoomKeyShare>()
|
val roomKeyShare = event.getClearContent().toModel<RoomKeyShare>()
|
||||||
when (roomKeyShare?.action) {
|
when (roomKeyShare?.action) {
|
||||||
RoomKeyShare.ACTION_SHARE_REQUEST -> receivedRoomKeyRequests.add(IncomingRoomKeyRequest(event))
|
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}")
|
Timber.v("m.room_key_request from $userId:$deviceId for $roomId / ${body.sessionId} id ${request.requestId}")
|
||||||
if (userId == null || credentials.userId != userId) {
|
if (userId == null || credentials.userId != userId) {
|
||||||
// TODO: determine if we sent this device the keys already: in
|
// 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
|
return
|
||||||
}
|
}
|
||||||
// TODO: should we queue up requests we don't yet have keys for, in case they turn up later?
|
// TODO: should we queue up requests we don't yet have keys for, in case they turn up later?
|
||||||
@@ -86,11 +85,11 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
|
|||||||
// the keys for the requested events, and can drop the requests.
|
// the keys for the requested events, and can drop the requests.
|
||||||
val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, alg)
|
val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, alg)
|
||||||
if (null == decryptor) {
|
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
|
continue
|
||||||
}
|
}
|
||||||
if (!decryptor.hasKeysForKeyRequest(request)) {
|
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)
|
cryptoStore.deleteIncomingRoomKeyRequest(request)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -139,7 +138,7 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
|
|||||||
if (null != receivedRoomKeyRequestCancellations) {
|
if (null != receivedRoomKeyRequestCancellations) {
|
||||||
for (request in receivedRoomKeyRequestCancellations!!) {
|
for (request in receivedRoomKeyRequestCancellations!!) {
|
||||||
Timber.v("## ## processReceivedRoomKeyRequests() : m.room_key_request cancellation for " + request.userId
|
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
|
// 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
|
// about, but we don't currently have a record of that, so we just pass
|
||||||
|
@@ -764,7 +764,7 @@ internal class MXOlmDevice @Inject constructor(
|
|||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
} else {
|
} 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)
|
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -42,8 +42,6 @@ internal class OneTimeKeysUploader @Inject constructor(
|
|||||||
private var lastOneTimeKeyCheck: Long = 0
|
private var lastOneTimeKeyCheck: Long = 0
|
||||||
private var oneTimeKeyCount: Int? = null
|
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
|
* 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.
|
* _onSyncCompleted). The count is e.g. coming from a /sync response.
|
||||||
@@ -59,10 +57,12 @@ internal class OneTimeKeysUploader @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
suspend fun maybeUploadOneTimeKeys() {
|
suspend fun maybeUploadOneTimeKeys() {
|
||||||
if (oneTimeKeyCheckInProgress) {
|
if (oneTimeKeyCheckInProgress) {
|
||||||
|
Timber.v("maybeUploadOneTimeKeys: already in progress")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (System.currentTimeMillis() - lastOneTimeKeyCheck < ONE_TIME_KEY_UPLOAD_PERIOD) {
|
if (System.currentTimeMillis() - lastOneTimeKeyCheck < ONE_TIME_KEY_UPLOAD_PERIOD) {
|
||||||
// we've done a key upload recently.
|
// we've done a key upload recently.
|
||||||
|
Timber.v("maybeUploadOneTimeKeys: executed too recently")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,12 +79,8 @@ internal class OneTimeKeysUploader @Inject constructor(
|
|||||||
// discard the oldest private keys first. This will eventually clean
|
// discard the oldest private keys first. This will eventually clean
|
||||||
// out stale private keys that won't receive a message.
|
// out stale private keys that won't receive a message.
|
||||||
val keyLimit = floor(maxOneTimeKeys / 2.0).toInt()
|
val keyLimit = floor(maxOneTimeKeys / 2.0).toInt()
|
||||||
if (oneTimeKeyCount != null) {
|
val oneTimeKeyCountFromSync = oneTimeKeyCount
|
||||||
uploadOTK(oneTimeKeyCount!!, keyLimit)
|
if (oneTimeKeyCountFromSync != null) {
|
||||||
} else {
|
|
||||||
// ask the server how many keys we have
|
|
||||||
val uploadKeysParams = UploadKeysTask.Params(null, null, credentials.deviceId!!)
|
|
||||||
val response = uploadKeysTask.execute(uploadKeysParams)
|
|
||||||
// We need to keep a pool of one time public keys on the server so that
|
// 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
|
// other devices can start conversations with us. But we can only store
|
||||||
// a finite number of private keys in the olm Account object.
|
// 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.
|
// private keys clogging up our local storage.
|
||||||
// So we need some kind of engineering compromise to balance all of
|
// So we need some kind of engineering compromise to balance all of
|
||||||
// these factors.
|
// these factors.
|
||||||
// TODO Why we do not set oneTimeKeyCount here?
|
try {
|
||||||
// TODO This is not needed anymore, see https://github.com/matrix-org/matrix-js-sdk/pull/493 (TODO on iOS also)
|
val uploadedKeys = uploadOTK(oneTimeKeyCountFromSync, keyLimit)
|
||||||
val keyCount = response.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)
|
Timber.v("## uploadKeys() : success, $uploadedKeys key(s) sent")
|
||||||
uploadOTK(keyCount, keyLimit)
|
} 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 keyCount the key count
|
||||||
* @param keyLimit the limit
|
* @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 (keyLimit <= keyCount) {
|
||||||
// If we don't need to generate any more keys then we are done.
|
// 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)
|
val keysThisLoop = min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER)
|
||||||
olmDevice.generateOneTimeKeys(keysThisLoop)
|
olmDevice.generateOneTimeKeys(keysThisLoop)
|
||||||
val response = uploadOneTimeKeys()
|
val response = uploadOneTimeKeys(olmDevice.getOneTimeKeys())
|
||||||
|
olmDevice.markKeysAsPublished()
|
||||||
|
|
||||||
if (response.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) {
|
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 {
|
} 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")
|
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 {
|
private suspend fun uploadOneTimeKeys(oneTimeKeys: Map<String, Map<String, String>>?): KeysUploadResponse {
|
||||||
val oneTimeKeys = olmDevice.getOneTimeKeys()
|
|
||||||
val oneTimeJson = mutableMapOf<String, Any>()
|
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) {
|
curve25519Map.forEach { (key_id, value) ->
|
||||||
for ((key_id, value) in curve25519Map) {
|
val k = mutableMapOf<String, Any>()
|
||||||
val k = mutableMapOf<String, Any>()
|
k["key"] = value
|
||||||
k["key"] = value
|
|
||||||
|
|
||||||
// the key is also signed
|
// the key is also signed
|
||||||
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, k)
|
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
|
// For now, we set the device id explicitly, as we may not be using the
|
||||||
// same one as used in login.
|
// same one as used in login.
|
||||||
val uploadParams = UploadKeysTask.Params(null, oneTimeJson, credentials.deviceId!!)
|
val uploadParams = UploadKeysTask.Params(null, oneTimeJson, credentials.deviceId!!)
|
||||||
val response = uploadKeysTask.execute(uploadParams)
|
return uploadKeysTask.execute(uploadParams)
|
||||||
lastPublishedOneTimeKeys = oneTimeKeys
|
|
||||||
olmDevice.markKeysAsPublished()
|
|
||||||
return response
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@@ -63,6 +63,7 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
fun stop() {
|
fun stop() {
|
||||||
isClientRunning = false
|
isClientRunning = false
|
||||||
|
stopTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -171,6 +172,10 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
|
|||||||
}, SEND_KEY_REQUESTS_DELAY_MS.toLong())
|
}, SEND_KEY_REQUESTS_DELAY_MS.toLong())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun stopTimer() {
|
||||||
|
BACKGROUND_HANDLER.removeCallbacksAndMessages(null)
|
||||||
|
}
|
||||||
|
|
||||||
// look for and send any queued requests. Runs itself recursively until
|
// 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
|
// there are no more requests, or there is an error (in which case, the
|
||||||
// timer will be restarted before the promise resolves).
|
// timer will be restarted before the promise resolves).
|
||||||
@@ -187,7 +192,7 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
|
|||||||
OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND))
|
OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND))
|
||||||
|
|
||||||
if (null == outgoingRoomKeyRequest) {
|
if (null == outgoingRoomKeyRequest) {
|
||||||
Timber.e("## sendOutgoingRoomKeyRequests() : No more outgoing room key requests")
|
Timber.v("## sendOutgoingRoomKeyRequests() : No more outgoing room key requests")
|
||||||
sendOutgoingRoomKeyRequestsRunning.set(false)
|
sendOutgoingRoomKeyRequestsRunning.set(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -216,7 +221,7 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
|
|||||||
sendMessageToDevices(requestMessage, request.recipients, request.requestId, object : MatrixCallback<Unit> {
|
sendMessageToDevices(requestMessage, request.recipients, request.requestId, object : MatrixCallback<Unit> {
|
||||||
private fun onDone(state: OutgoingRoomKeyRequest.RequestState) {
|
private fun onDone(state: OutgoingRoomKeyRequest.RequestState) {
|
||||||
if (request.state !== OutgoingRoomKeyRequest.RequestState.UNSENT) {
|
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 {
|
} else {
|
||||||
request.state = state
|
request.state = state
|
||||||
cryptoStore.updateOutgoingRoomKeyRequest(request)
|
cryptoStore.updateOutgoingRoomKeyRequest(request)
|
||||||
|
@@ -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.store.IMXCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
|
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@@ -46,8 +46,9 @@ internal class MXMegolmDecryption(private val userId: String,
|
|||||||
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
|
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
|
||||||
private val cryptoStore: IMXCryptoStore,
|
private val cryptoStore: IMXCryptoStore,
|
||||||
private val sendToDeviceTask: SendToDeviceTask,
|
private val sendToDeviceTask: SendToDeviceTask,
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers)
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
: IMXDecrypting {
|
private val cryptoCoroutineScope: CoroutineScope
|
||||||
|
) : IMXDecrypting {
|
||||||
|
|
||||||
var newSessionListener: NewSessionListener? = null
|
var newSessionListener: NewSessionListener? = null
|
||||||
|
|
||||||
@@ -61,7 +62,7 @@ internal class MXMegolmDecryption(private val userId: String,
|
|||||||
return decryptEvent(event, timeline, true)
|
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()) {
|
if (event.roomId.isNullOrBlank()) {
|
||||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
||||||
}
|
}
|
||||||
@@ -292,7 +293,7 @@ internal class MXMegolmDecryption(private val userId: String,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
val userId = request.userId ?: return
|
val userId = request.userId ?: return
|
||||||
GlobalScope.launch(coroutineDispatchers.crypto) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
runCatching { deviceListManager.downloadKeys(listOf(userId), false) }
|
runCatching { deviceListManager.downloadKeys(listOf(userId), false) }
|
||||||
.mapCatching {
|
.mapCatching {
|
||||||
val deviceId = request.deviceId
|
val deviceId = request.deviceId
|
||||||
|
@@ -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.crypto.tasks.SendToDeviceTask
|
||||||
import im.vector.matrix.android.internal.di.UserId
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class MXMegolmDecryptionFactory @Inject constructor(@UserId private val userId: String,
|
internal class MXMegolmDecryptionFactory @Inject constructor(
|
||||||
private val olmDevice: MXOlmDevice,
|
@UserId private val userId: String,
|
||||||
private val deviceListManager: DeviceListManager,
|
private val olmDevice: MXOlmDevice,
|
||||||
private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager,
|
private val deviceListManager: DeviceListManager,
|
||||||
private val messageEncrypter: MessageEncrypter,
|
private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager,
|
||||||
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
|
private val messageEncrypter: MessageEncrypter,
|
||||||
private val cryptoStore: IMXCryptoStore,
|
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
|
||||||
private val sendToDeviceTask: SendToDeviceTask,
|
private val cryptoStore: IMXCryptoStore,
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers) {
|
private val sendToDeviceTask: SendToDeviceTask,
|
||||||
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
|
private val cryptoCoroutineScope: CoroutineScope
|
||||||
|
) {
|
||||||
|
|
||||||
fun create(): MXMegolmDecryption {
|
fun create(): MXMegolmDecryption {
|
||||||
return MXMegolmDecryption(
|
return MXMegolmDecryption(
|
||||||
@@ -47,6 +51,7 @@ internal class MXMegolmDecryptionFactory @Inject constructor(@UserId private val
|
|||||||
ensureOlmSessionsForDevicesAction,
|
ensureOlmSessionsForDevicesAction,
|
||||||
cryptoStore,
|
cryptoStore,
|
||||||
sendToDeviceTask,
|
sendToDeviceTask,
|
||||||
coroutineDispatchers)
|
coroutineDispatchers,
|
||||||
|
cryptoCoroutineScope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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.JsonCanonicalizer
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import im.vector.matrix.android.internal.util.awaitCallback
|
import im.vector.matrix.android.internal.util.awaitCallback
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.matrix.olm.OlmException
|
import org.matrix.olm.OlmException
|
||||||
@@ -102,7 +102,8 @@ internal class KeysBackup @Inject constructor(
|
|||||||
private val updateKeysBackupVersionTask: UpdateKeysBackupVersionTask,
|
private val updateKeysBackupVersionTask: UpdateKeysBackupVersionTask,
|
||||||
// Task executor
|
// Task executor
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
|
private val cryptoCoroutineScope: CoroutineScope
|
||||||
) : KeysBackupService {
|
) : KeysBackupService {
|
||||||
|
|
||||||
private val uiHandler = Handler(Looper.getMainLooper())
|
private val uiHandler = Handler(Looper.getMainLooper())
|
||||||
@@ -143,7 +144,7 @@ internal class KeysBackup @Inject constructor(
|
|||||||
override fun prepareKeysBackupVersion(password: String?,
|
override fun prepareKeysBackupVersion(password: String?,
|
||||||
progressListener: ProgressListener?,
|
progressListener: ProgressListener?,
|
||||||
callback: MatrixCallback<MegolmBackupCreationInfo>) {
|
callback: MatrixCallback<MegolmBackupCreationInfo>) {
|
||||||
GlobalScope.launch(coroutineDispatchers.main) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
||||||
runCatching {
|
runCatching {
|
||||||
withContext(coroutineDispatchers.crypto) {
|
withContext(coroutineDispatchers.crypto) {
|
||||||
val olmPkDecryption = OlmPkDecryption()
|
val olmPkDecryption = OlmPkDecryption()
|
||||||
@@ -233,7 +234,7 @@ internal class KeysBackup @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun deleteBackup(version: String, callback: MatrixCallback<Unit>?) {
|
override fun deleteBackup(version: String, callback: MatrixCallback<Unit>?) {
|
||||||
GlobalScope.launch(coroutineDispatchers.main) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
||||||
withContext(coroutineDispatchers.crypto) {
|
withContext(coroutineDispatchers.crypto) {
|
||||||
// If we're currently backing up to this backup... stop.
|
// If we're currently backing up to this backup... stop.
|
||||||
// (We start using it automatically in createKeysBackupVersion so this is symmetrical).
|
// (We start using it automatically in createKeysBackupVersion so this is symmetrical).
|
||||||
@@ -344,9 +345,7 @@ internal class KeysBackup @Inject constructor(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}.also { keysBackupStateManager.addListener(it) }
|
||||||
|
|
||||||
keysBackupStateManager.addListener(keysBackupStateListener!!)
|
|
||||||
|
|
||||||
backupKeys()
|
backupKeys()
|
||||||
}
|
}
|
||||||
@@ -448,7 +447,7 @@ internal class KeysBackup @Inject constructor(
|
|||||||
|
|
||||||
callback.onFailure(IllegalArgumentException("Missing element"))
|
callback.onFailure(IllegalArgumentException("Missing element"))
|
||||||
} else {
|
} else {
|
||||||
GlobalScope.launch(coroutineDispatchers.main) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
||||||
val updateKeysBackupVersionBody = withContext(coroutineDispatchers.crypto) {
|
val updateKeysBackupVersionBody = withContext(coroutineDispatchers.crypto) {
|
||||||
// Get current signatures, or create an empty set
|
// Get current signatures, or create an empty set
|
||||||
val myUserSignatures = authData.signatures?.get(userId)?.toMutableMap()
|
val myUserSignatures = authData.signatures?.get(userId)?.toMutableMap()
|
||||||
@@ -523,7 +522,7 @@ internal class KeysBackup @Inject constructor(
|
|||||||
callback: MatrixCallback<Unit>) {
|
callback: MatrixCallback<Unit>) {
|
||||||
Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}")
|
Timber.v("trustKeysBackupVersionWithRecoveryKey: version ${keysBackupVersion.version}")
|
||||||
|
|
||||||
GlobalScope.launch(coroutineDispatchers.main) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
||||||
val isValid = withContext(coroutineDispatchers.crypto) {
|
val isValid = withContext(coroutineDispatchers.crypto) {
|
||||||
isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion)
|
isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion)
|
||||||
}
|
}
|
||||||
@@ -543,7 +542,7 @@ internal class KeysBackup @Inject constructor(
|
|||||||
callback: MatrixCallback<Unit>) {
|
callback: MatrixCallback<Unit>) {
|
||||||
Timber.v("trustKeysBackupVersionWithPassphrase: version ${keysBackupVersion.version}")
|
Timber.v("trustKeysBackupVersionWithPassphrase: version ${keysBackupVersion.version}")
|
||||||
|
|
||||||
GlobalScope.launch(coroutineDispatchers.main) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
||||||
val recoveryKey = withContext(coroutineDispatchers.crypto) {
|
val recoveryKey = withContext(coroutineDispatchers.crypto) {
|
||||||
recoveryKeyFromPassword(password, keysBackupVersion, null)
|
recoveryKeyFromPassword(password, keysBackupVersion, null)
|
||||||
}
|
}
|
||||||
@@ -614,7 +613,7 @@ internal class KeysBackup @Inject constructor(
|
|||||||
callback: MatrixCallback<ImportRoomKeysResult>) {
|
callback: MatrixCallback<ImportRoomKeysResult>) {
|
||||||
Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}")
|
Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}")
|
||||||
|
|
||||||
GlobalScope.launch(coroutineDispatchers.main) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
||||||
runCatching {
|
runCatching {
|
||||||
val decryption = withContext(coroutineDispatchers.crypto) {
|
val decryption = withContext(coroutineDispatchers.crypto) {
|
||||||
// Check if the recovery is valid before going any further
|
// Check if the recovery is valid before going any further
|
||||||
@@ -695,7 +694,7 @@ internal class KeysBackup @Inject constructor(
|
|||||||
callback: MatrixCallback<ImportRoomKeysResult>) {
|
callback: MatrixCallback<ImportRoomKeysResult>) {
|
||||||
Timber.v("[MXKeyBackup] restoreKeyBackup with password: From backup version: ${keysBackupVersion.version}")
|
Timber.v("[MXKeyBackup] restoreKeyBackup with password: From backup version: ${keysBackupVersion.version}")
|
||||||
|
|
||||||
GlobalScope.launch(coroutineDispatchers.main) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
||||||
runCatching {
|
runCatching {
|
||||||
val progressListener = if (stepProgressListener != null) {
|
val progressListener = if (stepProgressListener != null) {
|
||||||
object : ProgressListener {
|
object : ProgressListener {
|
||||||
@@ -729,8 +728,8 @@ internal class KeysBackup @Inject constructor(
|
|||||||
* parameters and always returns a KeysBackupData object through the Callback
|
* parameters and always returns a KeysBackupData object through the Callback
|
||||||
*/
|
*/
|
||||||
private suspend fun getKeys(sessionId: String?,
|
private suspend fun getKeys(sessionId: String?,
|
||||||
roomId: String?,
|
roomId: String?,
|
||||||
version: String): KeysBackupData {
|
version: String): KeysBackupData {
|
||||||
return if (roomId != null && sessionId != null) {
|
return if (roomId != null && sessionId != null) {
|
||||||
// Get key for the room and for the session
|
// Get key for the room and for the session
|
||||||
val data = getRoomSessionDataTask.execute(GetRoomSessionDataTask.Params(roomId, sessionId, version))
|
val data = getRoomSessionDataTask.execute(GetRoomSessionDataTask.Params(roomId, sessionId, version))
|
||||||
@@ -1154,7 +1153,7 @@ internal class KeysBackup @Inject constructor(
|
|||||||
|
|
||||||
keysBackupStateManager.state = KeysBackupState.BackingUp
|
keysBackupStateManager.state = KeysBackupState.BackingUp
|
||||||
|
|
||||||
GlobalScope.launch(coroutineDispatchers.main) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
|
||||||
withContext(coroutineDispatchers.crypto) {
|
withContext(coroutineDispatchers.crypto) {
|
||||||
Timber.v("backupKeys: 2 - Encrypting keys")
|
Timber.v("backupKeys: 2 - Encrypting keys")
|
||||||
|
|
||||||
|
@@ -41,8 +41,7 @@ fun <T> doWithRealm(realmConfiguration: RealmConfiguration, action: (Realm) -> T
|
|||||||
*/
|
*/
|
||||||
fun <T : RealmObject> doRealmQueryAndCopy(realmConfiguration: RealmConfiguration, action: (Realm) -> T?): T? {
|
fun <T : RealmObject> doRealmQueryAndCopy(realmConfiguration: RealmConfiguration, action: (Realm) -> T?): T? {
|
||||||
return Realm.getInstance(realmConfiguration).use { realm ->
|
return Realm.getInstance(realmConfiguration).use { realm ->
|
||||||
val result = action.invoke(realm)
|
action.invoke(realm)?.let { realm.copyFromRealm(it) }
|
||||||
result?.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> {
|
fun <T : RealmObject> doRealmQueryAndCopyList(realmConfiguration: RealmConfiguration, action: (Realm) -> Iterable<T>): Iterable<T> {
|
||||||
return Realm.getInstance(realmConfiguration).use { realm ->
|
return Realm.getInstance(realmConfiguration).use { realm ->
|
||||||
val result = action.invoke(realm)
|
action.invoke(realm).let { realm.copyFromRealm(it) }
|
||||||
realm.copyFromRealm(result)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -91,7 +91,7 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
|
|||||||
realmLocker = Realm.getInstance(realmConfiguration)
|
realmLocker = Realm.getInstance(realmConfiguration)
|
||||||
|
|
||||||
// Ensure CryptoMetadataEntity is inserted in DB
|
// Ensure CryptoMetadataEntity is inserted in DB
|
||||||
doWithRealm(realmConfiguration) { realm ->
|
doRealmTransaction(realmConfiguration) { realm ->
|
||||||
var currentMetadata = realm.where<CryptoMetadataEntity>().findFirst()
|
var currentMetadata = realm.where<CryptoMetadataEntity>().findFirst()
|
||||||
|
|
||||||
var deleteAll = false
|
var deleteAll = false
|
||||||
@@ -109,15 +109,13 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (currentMetadata == null) {
|
if (currentMetadata == null) {
|
||||||
realm.executeTransaction {
|
if (deleteAll) {
|
||||||
if (deleteAll) {
|
realm.deleteAll()
|
||||||
it.deleteAll()
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Metadata not found, or database cleaned, create it
|
// Metadata not found, or database cleaned, create it
|
||||||
it.createObject(CryptoMetadataEntity::class.java, credentials.userId).apply {
|
realm.createObject(CryptoMetadataEntity::class.java, credentials.userId).apply {
|
||||||
deviceId = credentials.deviceId
|
deviceId = credentials.deviceId
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -645,9 +643,10 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getOutgoingRoomKeyRequestByState(states: Set<OutgoingRoomKeyRequest.RequestState>): OutgoingRoomKeyRequest? {
|
override fun getOutgoingRoomKeyRequestByState(states: Set<OutgoingRoomKeyRequest.RequestState>): OutgoingRoomKeyRequest? {
|
||||||
|
val statesIndex = states.map { it.ordinal }.toTypedArray()
|
||||||
return doRealmQueryAndCopy(realmConfiguration) {
|
return doRealmQueryAndCopy(realmConfiguration) {
|
||||||
it.where<OutgoingRoomKeyRequestEntity>()
|
it.where<OutgoingRoomKeyRequestEntity>()
|
||||||
.`in`(OutgoingRoomKeyRequestEntityFields.STATE, states.map { it.ordinal }.toTypedArray())
|
.`in`(OutgoingRoomKeyRequestEntityFields.STATE, statesIndex)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
}
|
}
|
||||||
?.toOutgoingRoomKeyRequest()
|
?.toOutgoingRoomKeyRequest()
|
||||||
|
@@ -41,6 +41,7 @@ internal open class OutgoingRoomKeyRequestEntity(
|
|||||||
* Convert to OutgoingRoomKeyRequest
|
* Convert to OutgoingRoomKeyRequest
|
||||||
*/
|
*/
|
||||||
fun toOutgoingRoomKeyRequest(): OutgoingRoomKeyRequest {
|
fun toOutgoingRoomKeyRequest(): OutgoingRoomKeyRequest {
|
||||||
|
val cancellationTxnId = this.cancellationTxnId
|
||||||
return OutgoingRoomKeyRequest(
|
return OutgoingRoomKeyRequest(
|
||||||
RoomKeyRequestBody().apply {
|
RoomKeyRequestBody().apply {
|
||||||
algorithm = requestBodyAlgorithm
|
algorithm = requestBodyAlgorithm
|
||||||
|
@@ -53,10 +53,10 @@ internal class DefaultUploadKeysTask @Inject constructor(private val cryptoApi:
|
|||||||
}
|
}
|
||||||
|
|
||||||
return executeRequest {
|
return executeRequest {
|
||||||
if (encodedDeviceId.isNullOrBlank()) {
|
apiCall = if (encodedDeviceId.isBlank()) {
|
||||||
apiCall = cryptoApi.uploadKeys(body)
|
cryptoApi.uploadKeys(body)
|
||||||
} else {
|
} else {
|
||||||
apiCall = cryptoApi.uploadKeys(encodedDeviceId, body)
|
cryptoApi.uploadKeys(encodedDeviceId, body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -43,6 +43,7 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
|||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.lang.Exception
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.collections.HashMap
|
import kotlin.collections.HashMap
|
||||||
@@ -166,72 +167,59 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Download device keys prior to everything
|
// Download device keys prior to everything
|
||||||
checkKeysAreDownloaded(
|
if (checkKeysAreDownloaded(otherUserId!!, startReq) != null) {
|
||||||
otherUserId!!,
|
Timber.v("## SAS onStartRequestReceived ${startReq.transactionID!!}")
|
||||||
startReq,
|
val tid = startReq.transactionID!!
|
||||||
success = {
|
val existing = getExistingTransaction(otherUserId, tid)
|
||||||
Timber.v("## SAS onStartRequestReceived ${startReq.transactionID!!}")
|
val existingTxs = getExistingTransactionsForUser(otherUserId)
|
||||||
val tid = startReq.transactionID!!
|
if (existing != null) {
|
||||||
val existing = getExistingTransaction(otherUserId, tid)
|
// should cancel both!
|
||||||
val existingTxs = getExistingTransactionsForUser(otherUserId)
|
Timber.v("## SAS onStartRequestReceived - Request exist with same if ${startReq.transactionID!!}")
|
||||||
if (existing != null) {
|
existing.cancel(CancelCode.UnexpectedMessage)
|
||||||
// should cancel both!
|
cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
|
||||||
Timber.v("## SAS onStartRequestReceived - Request exist with same if ${startReq.transactionID!!}")
|
} else if (existingTxs?.isEmpty() == false) {
|
||||||
existing.cancel(CancelCode.UnexpectedMessage)
|
Timber.v("## SAS onStartRequestReceived - There is already a transaction with this user ${startReq.transactionID!!}")
|
||||||
cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
|
// Multiple keyshares between two devices: any two devices may only have at most one key verification in flight at a time.
|
||||||
} else if (existingTxs?.isEmpty() == false) {
|
existingTxs.forEach {
|
||||||
Timber.v("## SAS onStartRequestReceived - There is already a transaction with this user ${startReq.transactionID!!}")
|
it.cancel(CancelCode.UnexpectedMessage)
|
||||||
// Multiple keyshares between two devices: any two devices may only have at most one key verification in flight at a time.
|
}
|
||||||
existingTxs.forEach {
|
cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
|
||||||
it.cancel(CancelCode.UnexpectedMessage)
|
} else {
|
||||||
}
|
// Ok we can create
|
||||||
cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
|
if (KeyVerificationStart.VERIF_METHOD_SAS == startReq.method) {
|
||||||
} else {
|
Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionID!!}")
|
||||||
// Ok we can create
|
val tx = IncomingSASVerificationTransaction(
|
||||||
if (KeyVerificationStart.VERIF_METHOD_SAS == startReq.method) {
|
this,
|
||||||
Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionID!!}")
|
setDeviceVerificationAction,
|
||||||
val tx = IncomingSASVerificationTransaction(
|
credentials,
|
||||||
this,
|
cryptoStore,
|
||||||
setDeviceVerificationAction,
|
sendToDeviceTask,
|
||||||
credentials,
|
taskExecutor,
|
||||||
cryptoStore,
|
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
|
||||||
sendToDeviceTask,
|
startReq.transactionID!!,
|
||||||
taskExecutor,
|
otherUserId)
|
||||||
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
|
addTransaction(tx)
|
||||||
startReq.transactionID!!,
|
tx.acceptToDeviceEvent(otherUserId, startReq)
|
||||||
otherUserId)
|
} else {
|
||||||
addTransaction(tx)
|
Timber.e("## SAS onStartRequestReceived - unknown method ${startReq.method}")
|
||||||
tx.acceptToDeviceEvent(otherUserId, startReq)
|
cancelTransaction(tid, otherUserId, startReq.fromDevice
|
||||||
} else {
|
?: event.getSenderKey()!!, CancelCode.UnknownMethod)
|
||||||
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)
|
||||||
}
|
}
|
||||||
},
|
|
||||||
error = {
|
|
||||||
cancelTransaction(startReq.transactionID!!, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun checkKeysAreDownloaded(otherUserId: String,
|
private suspend fun checkKeysAreDownloaded(otherUserId: String,
|
||||||
startReq: KeyVerificationStart,
|
startReq: KeyVerificationStart): MXUsersDevicesMap<MXDeviceInfo>? {
|
||||||
success: (MXUsersDevicesMap<MXDeviceInfo>) -> Unit,
|
return try {
|
||||||
error: () -> Unit) {
|
val keys = deviceListManager.downloadKeys(listOf(otherUserId), true)
|
||||||
runCatching {
|
val deviceIds = keys.getUserDeviceIds(otherUserId) ?: return null
|
||||||
deviceListManager.downloadKeys(listOf(otherUserId), true)
|
keys.takeIf { deviceIds.contains(startReq.fromDevice) }
|
||||||
}.fold(
|
} catch (e: Exception) {
|
||||||
{
|
null
|
||||||
if (it.getUserDeviceIds(otherUserId)?.contains(startReq.fromDevice) == true) {
|
}
|
||||||
success(it)
|
|
||||||
} else {
|
|
||||||
error()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
error()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun onCancelReceived(event: Event) {
|
private suspend fun onCancelReceived(event: Event) {
|
||||||
@@ -342,10 +330,8 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
|
|||||||
private fun addTransaction(tx: VerificationTransaction) {
|
private fun addTransaction(tx: VerificationTransaction) {
|
||||||
tx.otherUserId.let { otherUserId ->
|
tx.otherUserId.let { otherUserId ->
|
||||||
synchronized(txMap) {
|
synchronized(txMap) {
|
||||||
if (txMap[otherUserId] == null) {
|
val txInnerMap = txMap.getOrPut(otherUserId) { HashMap() }
|
||||||
txMap[otherUserId] = HashMap()
|
txInnerMap[tx.transactionId] = tx
|
||||||
}
|
|
||||||
txMap[otherUserId]?.set(tx.transactionId, tx)
|
|
||||||
dispatchTxAdded(tx)
|
dispatchTxAdded(tx)
|
||||||
tx.addListener(this)
|
tx.addListener(this)
|
||||||
}
|
}
|
||||||
|
@@ -20,19 +20,26 @@ import io.realm.RealmConfiguration
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.withContext
|
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 ->
|
Realm.getInstance(config).use { bgRealm ->
|
||||||
bgRealm.beginTransaction()
|
bgRealm.beginTransaction()
|
||||||
|
val result: T
|
||||||
try {
|
try {
|
||||||
transaction(bgRealm)
|
val start = System.currentTimeMillis()
|
||||||
|
result = transaction(bgRealm)
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
bgRealm.commitTransaction()
|
bgRealm.commitTransaction()
|
||||||
|
val end = System.currentTimeMillis()
|
||||||
|
val time = end - start
|
||||||
|
Timber.v("Execute transaction in $time millis")
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (bgRealm.isInTransaction) {
|
if (bgRealm.isInTransaction) {
|
||||||
bgRealm.cancelTransaction()
|
bgRealm.cancelTransaction()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user