mirror of
https://github.com/vector-im/riotX-android
synced 2025-10-06 08:12:46 +02:00
Compare commits
851 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
997101a44b | ||
|
ec1422b0f0 | ||
|
d1c4d4b099 | ||
|
9a972b2f73 | ||
|
116bab5bc8 | ||
|
c76eb3bc98 | ||
|
81c1717384 | ||
|
0fd0500d30 | ||
|
34d638da4f | ||
|
e39b177b5b | ||
|
07aa3ee64c | ||
|
c94856cdf8 | ||
|
4dd0c04537 | ||
|
99c409b6d2 | ||
|
8cb2c2532f | ||
|
6f804cab4d | ||
|
cf3dbb378e | ||
|
0eb36a607b | ||
|
0509e76f18 | ||
|
e379ccf086 | ||
|
7ae52d676d | ||
|
3d33018ffa | ||
|
860595520b | ||
|
ae318a835e | ||
|
7a3dbecc08 | ||
|
c52aae7c29 | ||
|
f0f3e8ddb9 | ||
|
a95102a78f | ||
|
2adafbeb03 | ||
|
f3a5fb7fe3 | ||
|
907a786b1a | ||
|
e3ed3e5b05 | ||
|
a2b366ebfe | ||
|
f7de2f0f13 | ||
|
919225bdfd | ||
|
88cba74cac | ||
|
e9ca876444 | ||
|
0992e76800 | ||
|
8a9498bae4 | ||
|
d2598480c8 | ||
|
6e57b06673 | ||
|
a036be6436 | ||
|
53c7ea2831 | ||
|
e117fec74f | ||
|
6e01b75b2f | ||
|
691e7fe616 | ||
|
e793a46576 | ||
|
fa48e8cffa | ||
|
69cf437a39 | ||
|
ebbc432570 | ||
|
2532724e92 | ||
|
e31693b4b7 | ||
|
3ed6452232 | ||
|
166aaa62f0 | ||
|
45e5fff622 | ||
|
8f2dba09ee | ||
|
2ea46c5e54 | ||
|
fc5f0f7673 | ||
|
188f4a2e72 | ||
|
cf97fc3b01 | ||
|
5267ba240a | ||
|
f185dcacd7 | ||
|
330a33a0e8 | ||
|
b75b299847 | ||
|
7c59bcc928 | ||
|
0e110b0794 | ||
|
e156a62e19 | ||
|
628439aa65 | ||
|
d49fcb80fc | ||
|
ca37895619 | ||
|
43497e0da9 | ||
|
efa510c0a8 | ||
|
0828159ee4 | ||
|
90f21198c3 | ||
|
a0b0778fce | ||
|
d937fa67ad | ||
|
91d396fbca | ||
|
afba5b2b6c | ||
|
b579a1bc83 | ||
|
db49673fc7 | ||
|
bb0511e659 | ||
|
0e0763ec2d | ||
|
e1c6542e03 | ||
|
01484978bd | ||
|
cad14c93d0 | ||
|
ee52b9185b | ||
|
861a7f791f | ||
|
f2fa57224b | ||
|
e0977dd97b | ||
|
f47bef71a4 | ||
|
92985fc8e7 | ||
|
243b0a7d82 | ||
|
92c719a803 | ||
|
f4108ae0eb | ||
|
ecf3fee709 | ||
|
e67e472025 | ||
|
a6541481bf | ||
|
85a4f83662 | ||
|
22955e6b34 | ||
|
9520aff848 | ||
|
6b09a78ece | ||
|
789bcc8d77 | ||
|
2914117a8e | ||
|
8049962a99 | ||
|
225b1c380e | ||
|
60d80ea0ba | ||
|
c8211098f3 | ||
|
e78fde4eca | ||
|
59d60813fb | ||
|
4c31e52892 | ||
|
c646fd2b36 | ||
|
6432859732 | ||
|
2beef7d816 | ||
|
623056455b | ||
|
7a4d9370e3 | ||
|
fe3138492e | ||
|
05a52164f3 | ||
|
d14f1dd1ab | ||
|
88e8c11ee5 | ||
|
7afc7bdb31 | ||
|
84a3754c9f | ||
|
4b2f8e9174 | ||
|
a17932e17e | ||
|
084c27a2bb | ||
|
ed2f62cbe7 | ||
|
38fb7185b6 | ||
|
ce42d2fb8a | ||
|
af3fc22e2d | ||
|
b659cb60a2 | ||
|
34cf9903dc | ||
|
062a21e39a | ||
|
4510aff00a | ||
|
d0953b8406 | ||
|
7822660ce7 | ||
|
bdfcf5c67c | ||
|
ae0d09a049 | ||
|
69759b7415 | ||
|
7e8e1ab9b7 | ||
|
b44f5d3b4a | ||
|
03f8b66993 | ||
|
e411f139c8 | ||
|
e962d1dadf | ||
|
756b0febe6 | ||
|
1535f3e2e5 | ||
|
3e808dec90 | ||
|
637f4a8350 | ||
|
d3bc9f52fd | ||
|
ffd8ac859d | ||
|
6e43e9b51c | ||
|
426171508e | ||
|
e86460b578 | ||
|
8dd5f88dba | ||
|
3aa6de7cf5 | ||
|
a75242c79d | ||
|
784918350b | ||
|
0199cf9a03 | ||
|
ab6e7a3b8a | ||
|
f489265ce7 | ||
|
6c9c3e5cb3 | ||
|
9b7c2599a7 | ||
|
25bbd7c526 | ||
|
e0c3f3638d | ||
|
d45653dbb3 | ||
|
f70623beea | ||
|
e542e4ba22 | ||
|
05d1e64cb5 | ||
|
adac80062c | ||
|
28f8d9500e | ||
|
538bda329e | ||
|
a00ddca188 | ||
|
8ac2cb0530 | ||
|
19d655ec41 | ||
|
4961bb0e08 | ||
|
f45040c405 | ||
|
9fed62e4a7 | ||
|
43b9e2cec9 | ||
|
3185b88fe5 | ||
|
c2906d9b06 | ||
|
cfb615f972 | ||
|
9c22c0952c | ||
|
5a834619c0 | ||
|
3b62f50f7b | ||
|
18e804d174 | ||
|
c105d82027 | ||
|
860921217d | ||
|
3fe2f2876a | ||
|
e84fd408be | ||
|
f361fd7355 | ||
|
458e3ee5e8 | ||
|
5fa247a0c5 | ||
|
48e58967b2 | ||
|
d1d79c0191 | ||
|
0a41a9f773 | ||
|
03fa0e6ad6 | ||
|
8eebcef4e9 | ||
|
ea1c75c16a | ||
|
7a2aefd8fb | ||
|
33ec1bbfb3 | ||
|
8883832b86 | ||
|
308828ef50 | ||
|
885dac4ad1 | ||
|
c03d61e09f | ||
|
b2bacdfa4e | ||
|
06defaf14e | ||
|
4698cf7a9b | ||
|
5004fba986 | ||
|
8cc82fe5ba | ||
|
c9fb231714 | ||
|
0f22b55786 | ||
|
535148e68a | ||
|
878e093b6b | ||
|
0e5f741b6b | ||
|
36b1717fc1 | ||
|
37392b5495 | ||
|
84f2fc41b3 | ||
|
ebdf75091a | ||
|
ce304ace2b | ||
|
0d2acec73e | ||
|
0144764f69 | ||
|
aad4b3dc39 | ||
|
040deea655 | ||
|
1e2b5dd428 | ||
|
8d32c27ce0 | ||
|
074a9e9f29 | ||
|
650b6bd9ea | ||
|
3dd74d6828 | ||
|
f717a37a4a | ||
|
d8b1372a0f | ||
|
678cf50dbd | ||
|
57fca80cbb | ||
|
cf7de8bb8b | ||
|
a70fdedce5 | ||
|
c173235ee3 | ||
|
f74b1e6c2e | ||
|
c9bc6f4a9e | ||
|
63c18e82c8 | ||
|
037b2e1d60 | ||
|
aea9c958bf | ||
|
7f185a729e | ||
|
d08b4e1ea0 | ||
|
4eaed945e2 | ||
|
14b1b10556 | ||
|
df762e40bb | ||
|
684972185f | ||
|
04dd13d03b | ||
|
700fd47f22 | ||
|
b36759deb4 | ||
|
25d224be6b | ||
|
fe013f803e | ||
|
6abc51d05d | ||
|
a98916c985 | ||
|
9e29533aad | ||
|
7119403cde | ||
|
7f55e4fb1e | ||
|
82df62a600 | ||
|
d0c722eae1 | ||
|
40649e9c3c | ||
|
431b285806 | ||
|
75f84fe1f4 | ||
|
98bf02efa9 | ||
|
0eb68b531c | ||
|
9124844e3e | ||
|
f568553d21 | ||
|
92c9d4fc22 | ||
|
957d51cf3f | ||
|
54ecc25831 | ||
|
738a368a6f | ||
|
969f070175 | ||
|
d8d78b124d | ||
|
9a9f0c200e | ||
|
247ffc1270 | ||
|
690d05aeca | ||
|
c33f3b76fa | ||
|
8616c454e1 | ||
|
17db994d35 | ||
|
6c1c1ca8b0 | ||
|
f1613eacbb | ||
|
67d1c2dc80 | ||
|
c2b2b856a1 | ||
|
44f946513f | ||
|
1cafca6de6 | ||
|
35ee7f0b40 | ||
|
6e8e7164c6 | ||
|
6bbded1e65 | ||
|
0aa90c3eea | ||
|
b44b0ec998 | ||
|
07c6259734 | ||
|
1785d4d0b4 | ||
|
22d06928c8 | ||
|
d6fe6e44bd | ||
|
d51ee19f3f | ||
|
717e5161a6 | ||
|
be9fa268b1 | ||
|
14e8bbcec6 | ||
|
26105dc25f | ||
|
53ba1c2068 | ||
|
fa004c9d93 | ||
|
be94921918 | ||
|
2f5fe59aa6 | ||
|
89fb2cf391 | ||
|
f1d2abc9b1 | ||
|
2aa8512f6f | ||
|
2d31402cf0 | ||
|
6d61848ed6 | ||
|
0e0b724535 | ||
|
e13915b0c7 | ||
|
361f0415bb | ||
|
c3b662fa1f | ||
|
2d3e23ee11 | ||
|
fc8ab0d462 | ||
|
89629ffe93 | ||
|
e97d565809 | ||
|
7e3413eda7 | ||
|
7966f6e308 | ||
|
e1286a0ed4 | ||
|
1e2d267fec | ||
|
8b3403c115 | ||
|
da4b029093 | ||
|
b48113a353 | ||
|
e1884e7c73 | ||
|
daae030134 | ||
|
19e1da1216 | ||
|
e9bb95b3b3 | ||
|
a43ca5925c | ||
|
dd46798bda | ||
|
d70a09ded8 | ||
|
439aa7854c | ||
|
750550ad3e | ||
|
b6af2269d2 | ||
|
afbda4ac28 | ||
|
f5fd0ac323 | ||
|
18de0ca951 | ||
|
b53c073b90 | ||
|
13ebef334f | ||
|
b1ba4e393e | ||
|
d898bc71f7 | ||
|
0afa7a706a | ||
|
da68212255 | ||
|
583139d51e | ||
|
cee8ae3af4 | ||
|
c7c6cf70e4 | ||
|
1491bddb3b | ||
|
ac5db83880 | ||
|
b2f3ba220e | ||
|
deb783f797 | ||
|
5fcf54cd57 | ||
|
4306cb7812 | ||
|
a4b8dc9400 | ||
|
be9393fabe | ||
|
c7a7ad7b57 | ||
|
78b7f03138 | ||
|
ffeae7ec83 | ||
|
db77e7b817 | ||
|
fcee85a682 | ||
|
17ddb5ce43 | ||
|
53583c691f | ||
|
2b9d3960b3 | ||
|
92befcde5d | ||
|
697eaec197 | ||
|
86fba28313 | ||
|
f3c3c07d46 | ||
|
8966e24925 | ||
|
becc5a7b54 | ||
|
a61434ae08 | ||
|
20b726819f | ||
|
bfd847179f | ||
|
7e955ef0e4 | ||
|
2697800deb | ||
|
2c47fe9f0d | ||
|
423f21b02e | ||
|
eb6546d81c | ||
|
4e2878300f | ||
|
4578b9df7f | ||
|
d679c9d5d8 | ||
|
dc7b3dfc9d | ||
|
6843ea113b | ||
|
0a8c954397 | ||
|
358e10a093 | ||
|
c0b7ea6dd1 | ||
|
0b5e618c1c | ||
|
1f528ee428 | ||
|
bbd8b89589 | ||
|
a5d2d65131 | ||
|
b45504d97a | ||
|
0598ecaca3 | ||
|
0d9749a515 | ||
|
836766f978 | ||
|
93851d0ab2 | ||
|
5fff637bee | ||
|
eae015caa1 | ||
|
80a356c7e2 | ||
|
3a0eed795a | ||
|
c1c0c6f2c6 | ||
|
f04868ba19 | ||
|
b052884912 | ||
|
7fb7729af6 | ||
|
2f5d824c65 | ||
|
fbc46b3c8b | ||
|
e986c9d343 | ||
|
3100473305 | ||
|
5eb9f32acb | ||
|
0d12a80832 | ||
|
077c166c09 | ||
|
5d26b6a7cb | ||
|
68c1e8fc6d | ||
|
1ffd7dbb9f | ||
|
0cc48a190f | ||
|
8206a78156 | ||
|
6a1e38ca04 | ||
|
779f380d2f | ||
|
55f7461747 | ||
|
7665aba22c | ||
|
e96c5f7305 | ||
|
c5ba34d619 | ||
|
c13439eeb9 | ||
|
d27b73f6be | ||
|
bb427700d2 | ||
|
4589aaa11c | ||
|
b3dbcd7936 | ||
|
cac246aa15 | ||
|
d2f0957eba | ||
|
db18272ef2 | ||
|
cf5d89ea9b | ||
|
0aeb327062 | ||
|
5dc50195b3 | ||
|
83db9b34d4 | ||
|
a43df43642 | ||
|
57a87ba620 | ||
|
f6cbc15cf7 | ||
|
7322144dc8 | ||
|
8e357c6b7f | ||
|
7b20db64a5 | ||
|
429c634ed9 | ||
|
05230a6afa | ||
|
43eb804b23 | ||
|
5840248ffa | ||
|
6ea38c7eb0 | ||
|
9586fa9f90 | ||
|
0d0af6906e | ||
|
93070f3524 | ||
|
7cf7b7e10e | ||
|
1de4869cde | ||
|
a4eba653a3 | ||
|
21d0db8382 | ||
|
269d6e4d08 | ||
|
3cf341c3bf | ||
|
f0a9be2ec7 | ||
|
8955e5461c | ||
|
087ff1c041 | ||
|
1a307a0c4d | ||
|
071a43c8d4 | ||
|
7b46c49ded | ||
|
da5672d229 | ||
|
dcfd9ee7a7 | ||
|
35a6f90ed6 | ||
|
d463e5e500 | ||
|
0f00597444 | ||
|
a806f70b35 | ||
|
67f07bd1bb | ||
|
39e18446ae | ||
|
4dc0b00569 | ||
|
87979ccadd | ||
|
7c2a5af8f2 | ||
|
dc6d4c6789 | ||
|
db3d5e2677 | ||
|
6dc8bdde04 | ||
|
c02cfb2f4f | ||
|
947c46d7b5 | ||
|
8942ce964a | ||
|
a05c401892 | ||
|
f25c981173 | ||
|
43055964ba | ||
|
a4192a0761 | ||
|
9c8ff7de7f | ||
|
b4247c89e4 | ||
|
cdabca6def | ||
|
2d6f0205a4 | ||
|
4e8177f738 | ||
|
798e9e4fde | ||
|
8871390167 | ||
|
fc86e7e1f6 | ||
|
21912c290a | ||
|
df335c7aa3 | ||
|
8bd4cc8f54 | ||
|
4e3df99e42 | ||
|
b1e1b4a7dc | ||
|
a233e9b0a0 | ||
|
ebecb9bb9a | ||
|
35962c3cb5 | ||
|
0ac6a26b6e | ||
|
0a887c0926 | ||
|
54c0239969 | ||
|
8559254593 | ||
|
626eb4d06b | ||
|
aa16ba88ae | ||
|
a633c11c1d | ||
|
a4931e21ae | ||
|
996fabb327 | ||
|
6c4e71d7d4 | ||
|
ad0ad502aa | ||
|
42b47c25aa | ||
|
7ef1970a0b | ||
|
409d751612 | ||
|
bdce71abfd | ||
|
114bce5f64 | ||
|
20e5ebc88b | ||
|
52aa57ac7c | ||
|
8daf72a4b0 | ||
|
51eb2cda95 | ||
|
57779c99c2 | ||
|
02e02ed691 | ||
|
af0b798ef1 | ||
|
51be8d5ed5 | ||
|
270bed5013 | ||
|
20b3c33fb0 | ||
|
b2aaf1cca1 | ||
|
5f6969e2cc | ||
|
f0648ee52a | ||
|
88c70a2c10 | ||
|
22c3ed6bb9 | ||
|
b0d25fa84f | ||
|
57636207d2 | ||
|
eac9133bb1 | ||
|
f7e7659750 | ||
|
e719541b5e | ||
|
bd7acfbb1a | ||
|
25b42cb4f3 | ||
|
928149fe35 | ||
|
a80181da9e | ||
|
72de5d6adc | ||
|
ed4154d763 | ||
|
4ee13b6fa1 | ||
|
33fb1dd147 | ||
|
736905edf8 | ||
|
e8a91eab88 | ||
|
b951af0116 | ||
|
c3299845c1 | ||
|
54644db587 | ||
|
cb0e93c43e | ||
|
4c4ec6cfe8 | ||
|
449be02f53 | ||
|
25d2c2e2c6 | ||
|
ec2ba7c0b2 | ||
|
06a13d5c20 | ||
|
7e0591ffee | ||
|
1363100f94 | ||
|
06cf59bca7 | ||
|
e37dd547b8 | ||
|
671c1259af | ||
|
3d07ccd98e | ||
|
03b9774c56 | ||
|
0f1ddee71c | ||
|
855efa93cc | ||
|
d0f776a9cf | ||
|
da66e38c68 | ||
|
a4ba8c152d | ||
|
9b320ed3c7 | ||
|
c854491248 | ||
|
5755d5bfaa | ||
|
ff320fec55 | ||
|
8c8a84b039 | ||
|
045e3d7bae | ||
|
491f0e6032 | ||
|
3163bc8b80 | ||
|
eca3bf0817 | ||
|
63355ca256 | ||
|
c39a0e4fd5 | ||
|
59280ed18e | ||
|
c1acb1af66 | ||
|
8a4f0a0c00 | ||
|
a6368c473e | ||
|
3615ca6b95 | ||
|
ddb00ba23a | ||
|
91cf4b647d | ||
|
f989eed8b0 | ||
|
4d296ddc09 | ||
|
6186c22e02 | ||
|
13cd13a42f | ||
|
a42eb42178 | ||
|
7924ef207c | ||
|
5900245018 | ||
|
00c239bc42 | ||
|
0cb43eef51 | ||
|
41a8f40241 | ||
|
a8641ef879 | ||
|
4d207e6acd | ||
|
1227de3f9c | ||
|
6cad129625 | ||
|
9ccf51fbc0 | ||
|
990867204e | ||
|
5795b7e063 | ||
|
b612a7e63c | ||
|
50c73b68aa | ||
|
c7ac5e2293 | ||
|
43cb1fe68b | ||
|
f807de9a83 | ||
|
754f220596 | ||
|
3f7ca8669a | ||
|
28c6921a0a | ||
|
26bb8ce2be | ||
|
8434f9326e | ||
|
68f93c6c31 | ||
|
7961423556 | ||
|
ac07fb47d7 | ||
|
fbcbd6def5 | ||
|
3fe15f2d45 | ||
|
968377a5be | ||
|
5652140f5d | ||
|
e97c95f40a | ||
|
7ac5f58f32 | ||
|
ce2f4e163d | ||
|
cc94b6cf7d | ||
|
ab3cc90ed5 | ||
|
614127e46b | ||
|
66fc38ad4b | ||
|
f68e84d9da | ||
|
2b2e6dd6f8 | ||
|
621e78a864 | ||
|
828e972c74 | ||
|
15bd7d1c5b | ||
|
79e81dbdde | ||
|
f93f50b582 | ||
|
d934f92ebd | ||
|
d20cf484ff | ||
|
ec4458e84a | ||
|
6c1719e365 | ||
|
467f48f1a6 | ||
|
a44cb876c9 | ||
|
e79c824913 | ||
|
b8e9cc70f2 | ||
|
a2f32307f0 | ||
|
c1d39cefd5 | ||
|
0edc562120 | ||
|
8ae2f06044 | ||
|
aa496e6efb | ||
|
1372192031 | ||
|
ea03f76847 | ||
|
d74a5f9979 | ||
|
febadcc4f6 | ||
|
17ece54cb0 | ||
|
da04a74350 | ||
|
634c8947bd | ||
|
f6f6fa99fb | ||
|
dcfbfc4981 | ||
|
6201a9b8ef | ||
|
1981d2e9ac | ||
|
66f6b1ecac | ||
|
affe2b59da | ||
|
757f8ec96a | ||
|
bf5e2b96df | ||
|
5666965321 | ||
|
7bf1f916c4 | ||
|
d44e43d94b | ||
|
9fe32fe915 | ||
|
e21cb3082b | ||
|
c50bc10f92 | ||
|
1b416028b4 | ||
|
85493b7532 | ||
|
dbabe0232f | ||
|
dfc8e8ec4c | ||
|
f00db49bda | ||
|
b4a3eb2cb3 | ||
|
81012746c4 | ||
|
1deacfbb34 | ||
|
c35d854776 | ||
|
c0fa259b40 | ||
|
391d3cb6b5 | ||
|
6d56220d98 | ||
|
2e4d30ef29 | ||
|
c57fa3f0d0 | ||
|
f2bca51046 | ||
|
6751d88ade | ||
|
e26a0bc9ae | ||
|
6dc517584f | ||
|
24d0cdef1f | ||
|
6639f89a68 | ||
|
0c0e9521f5 | ||
|
f2b684aa9e | ||
|
68ca0e9d4b | ||
|
e54077e020 | ||
|
9111800e7a | ||
|
ef6847671a | ||
|
b8cb0588fe | ||
|
667b371653 | ||
|
190fbb95ec | ||
|
f97e08b4e5 | ||
|
7242cbda40 | ||
|
9e3011d4c8 | ||
|
eb50256af7 | ||
|
ccacd20428 | ||
|
ac46fe9e16 | ||
|
9cfb83f0d2 | ||
|
943ba3bebd | ||
|
fccfd00949 | ||
|
68323057aa | ||
|
5081361c2d | ||
|
3ba619d45c | ||
|
f5dc0b38ff | ||
|
8357abd455 | ||
|
ede899d78e | ||
|
a703574004 | ||
|
ef2abbfbd4 | ||
|
7c0137e2dc | ||
|
34dec64d9c | ||
|
3968bb3488 | ||
|
6f2d7aebba | ||
|
366a35913b | ||
|
aec49fe542 | ||
|
f04d8b0e03 | ||
|
08af61b778 | ||
|
277f35a352 | ||
|
68512e475f | ||
|
0eff00ebee | ||
|
8a4a288074 | ||
|
5b1f887760 | ||
|
dcb6af6c45 | ||
|
3ff5952417 | ||
|
b480eb3688 | ||
|
12abca1b80 | ||
|
1d8ed387bc | ||
|
5521c094f7 | ||
|
222b72a014 | ||
|
8904ca27f2 | ||
|
6c5da97c16 | ||
|
d4d73db5ae | ||
|
1a436f962f | ||
|
dc61ee61f5 | ||
|
5b4b5e7a57 | ||
|
0164f94047 | ||
|
153587bd82 | ||
|
326f2e99fb | ||
|
1dfd6f232a | ||
|
42d61944b5 | ||
|
50a8ffeca1 | ||
|
f605bb8270 | ||
|
7ffb6113a4 | ||
|
156e6114c1 | ||
|
c91bc82cd9 | ||
|
8b481e2294 | ||
|
92bf3f1349 | ||
|
6474735662 | ||
|
45c5626267 | ||
|
c27264761d | ||
|
c6abfa14ea | ||
|
2f237cf17b | ||
|
bf5ba99653 | ||
|
8ecdac7c31 | ||
|
a40dd31543 | ||
|
dff89cb2e1 | ||
|
cdbb657961 | ||
|
443d45db6a | ||
|
a995615f87 | ||
|
024c62515c | ||
|
75e66a6550 | ||
|
e2f7890bb8 | ||
|
0a77d5014e | ||
|
91464a071e | ||
|
5244612ef6 | ||
|
e51439ade0 | ||
|
acd90657c7 | ||
|
e5482d48c0 | ||
|
5816a04a37 | ||
|
4b7da9ae6b | ||
|
f7cbc01023 | ||
|
1de57bbf3b | ||
|
42a8c561db | ||
|
5bef9aef6a | ||
|
f8c1ec985f | ||
|
12429d8091 | ||
|
3bb5e127d6 | ||
|
1d46b523b9 | ||
|
6721f337bd | ||
|
535cdf0ef5 | ||
|
19990b27bb | ||
|
4b3c5d5135 | ||
|
b6fe80faf4 | ||
|
638970fa77 | ||
|
c63f3edb06 | ||
|
9a6fe1af4e | ||
|
a01482dca4 | ||
|
5db1010e47 | ||
|
6130a0a654 | ||
|
3c1e1090e7 | ||
|
5cb47dae35 | ||
|
f68e98b2c7 | ||
|
420a55da76 | ||
|
f9aed28732 | ||
|
e30c17eab7 | ||
|
2c9a8865bf | ||
|
bddd70afdb | ||
|
c4388348f7 | ||
|
ee7828a445 | ||
|
37ac45c90a | ||
|
63d3bf93f2 | ||
|
2de8865730 | ||
|
fcffe1f3c3 | ||
|
cfcec04029 | ||
|
b56a41bec7 | ||
|
6bf89aeac9 | ||
|
e583c03751 | ||
|
d20b1cb64a | ||
|
22642e71a3 | ||
|
6e85b20b0e | ||
|
fcd290410e | ||
|
727d86236b | ||
|
2651f82337 | ||
|
3b62402cfe | ||
|
6cc8d1b205 | ||
|
49e5fafb2d | ||
|
e36367c040 | ||
|
f7fd23b153 | ||
|
4f70c40b1a | ||
|
5b875e0571 | ||
|
6db0de321c | ||
|
0c1f30208d | ||
|
08cfe79625 | ||
|
22a599633d | ||
|
14acbb2b4d | ||
|
6f5bebedf8 | ||
|
6fe77eba72 | ||
|
286a5081ff | ||
|
56c241c9bd | ||
|
68151d838f | ||
|
572b174cfe | ||
|
b71d8185a2 | ||
|
8051d9e3be | ||
|
1bf8fef292 | ||
|
b8a9397e73 | ||
|
009d691d5b | ||
|
6933159245 | ||
|
75549c41e0 | ||
|
5e2f888eaf | ||
|
d3d6d44665 | ||
|
fc6225a7ac | ||
|
3639007985 | ||
|
d5137897c1 | ||
|
b67735c31a | ||
|
8ff31ac49d | ||
|
5e0235e48d | ||
|
9e63a3219c | ||
|
757e90986e | ||
|
06fc5c2dd9 | ||
|
20dbe2dd0d | ||
|
6f56c74e9d | ||
|
a2367ef14f | ||
|
687884431e | ||
|
a8e19f3cc9 | ||
|
cb4752812a | ||
|
ccd9d2961d | ||
|
d1db17f244 | ||
|
aa4327c4da |
4
.idea/dictionaries/bmarty.xml
generated
4
.idea/dictionaries/bmarty.xml
generated
@@ -12,12 +12,15 @@
|
||||
<w>fdroid</w>
|
||||
<w>gplay</w>
|
||||
<w>hmac</w>
|
||||
<w>homeserver</w>
|
||||
<w>ktlint</w>
|
||||
<w>linkified</w>
|
||||
<w>linkify</w>
|
||||
<w>megolm</w>
|
||||
<w>msisdn</w>
|
||||
<w>msisdns</w>
|
||||
<w>pbkdf</w>
|
||||
<w>pids</w>
|
||||
<w>pkcs</w>
|
||||
<w>riotx</w>
|
||||
<w>signin</w>
|
||||
@@ -25,6 +28,7 @@
|
||||
<w>signup</w>
|
||||
<w>ssss</w>
|
||||
<w>threepid</w>
|
||||
<w>unwedging</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
@@ -23,10 +23,10 @@ android:
|
||||
- platform-tools
|
||||
|
||||
# The BuildTools version used by your project
|
||||
- build-tools-28.0.3
|
||||
- build-tools-29.0.3
|
||||
|
||||
# The SDK version used to compile your project
|
||||
- android-28
|
||||
- android-29
|
||||
|
||||
before_cache:
|
||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||
|
113
CHANGES.md
113
CHANGES.md
@@ -1,3 +1,115 @@
|
||||
Changes in RiotX 0.21.0 (2020-05-28)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
- Identity server support (#607)
|
||||
- Switch language support (#41)
|
||||
- Display list of attachments of a room (#860)
|
||||
|
||||
Improvements 🙌:
|
||||
- Better connectivity lost indicator when airplane mode is on
|
||||
- Add a setting to hide redacted events (#951)
|
||||
- Render formatted_body for m.notice and m.emote (#1196)
|
||||
- Change icon to magnifying-glass to filter room (#1384)
|
||||
|
||||
Bugfix 🐛:
|
||||
- After jump to unread, newer messages are never loaded (#1008)
|
||||
- Fix issues with FontScale switch (#69, #645)
|
||||
- "Seen by" uses 12h time (#1378)
|
||||
- Enable markdown (if active) when sending emote (#734)
|
||||
- Screenshots for Rageshake now includes Dialogs such as BottomSheet (#1349)
|
||||
|
||||
SDK API changes ⚠️:
|
||||
- initialize with proxy configuration
|
||||
|
||||
Other changes:
|
||||
- support new key agreement method for SAS (#1374)
|
||||
|
||||
Changes in RiotX 0.20.0 (2020-05-15)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
- Add Direct Shortcuts (#652)
|
||||
|
||||
Improvements 🙌:
|
||||
- Invite member(s) to an existing room (#1276)
|
||||
- Improve notification accessibility with ticker text (#1226)
|
||||
- Support homeserver discovery from MXID (DISABLED: waiting for design) (#476)
|
||||
|
||||
Bugfix 🐛:
|
||||
- Fix | Verify Manually by Text crashes if private SSK not known (#1337)
|
||||
- Sometimes the same device appears twice in the list of devices of a user (#1329)
|
||||
- Random Crashes while doing sth with cross signing keys (#1364)
|
||||
- Crash | crash while restoring key backup (#1366)
|
||||
|
||||
SDK API changes ⚠️:
|
||||
- excludedUserIds parameter added to the UserService.getPagedUsersLive() function
|
||||
|
||||
Changes in RiotX 0.19.0 (2020-05-04)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
- Change password (#528)
|
||||
- Cross-Signing | Support SSSS secret sharing (#944)
|
||||
- Cross-Signing | Verify new session from existing session (#1134)
|
||||
- Cross-Signing | Bootstraping cross signing with 4S from mobile (#985)
|
||||
- Save media files to Gallery (#973)
|
||||
- Account deactivation (with password only) (#35)
|
||||
|
||||
Improvements 🙌:
|
||||
- Verification DM / Handle concurrent .start after .ready (#794)
|
||||
- Reimplementation of multiple attachment picker
|
||||
- Cross-Signing | Update Shield Logic for DM (#963)
|
||||
- Cross-Signing | Complete security new session design update (#1135)
|
||||
- Cross-Signing | Setup key backup as part of SSSS bootstrapping (#1201)
|
||||
- Cross-Signing | Gossip key backup recovery key (#1200)
|
||||
- Show room encryption status as a bubble tile (#1078)
|
||||
- UX/UI | Add indicator to home tab on invite (#957)
|
||||
- Cross-Signing | Restore history after recover from passphrase (#1214)
|
||||
- Cross-Sign | QR code scan confirmation screens design update (#1187)
|
||||
- Emoji Verification | It's not the same butterfly! (#1220)
|
||||
- Cross-Signing | Composer decoration: shields (#1077)
|
||||
- Cross-Signing | Migrate existing keybackup to cross signing with 4S from mobile (#1197)
|
||||
- Show a warning dialog if the text of the clicked link does not match the link target (#922)
|
||||
- Cross-Signing | Consider not using a spinner on the 'complete security' prompt (#1271)
|
||||
- Restart broken Olm sessions ([MSC1719](https://github.com/matrix-org/matrix-doc/pull/1719))
|
||||
- Cross-Signing | Hide Use recovery key when 4S is not setup (#1007)
|
||||
- Cross-Signing | Trust account xSigning keys by entering Recovery Key (select file or copy) #1199
|
||||
- E2E timeline decoration (#1279)
|
||||
- Manage Session Settings / Cross Signing update (#1295)
|
||||
- Cross-Signing | Review sessions toast update old vs new (#1293, #1306)
|
||||
|
||||
Bugfix 🐛:
|
||||
- Fix summary notification staying after "mark as read"
|
||||
- Missing avatar/displayname after verification request message (#841)
|
||||
- Crypto | RiotX sometimes rotate the current device keys (#1170)
|
||||
- RiotX can't restore cross signing keys saved by web in SSSS (#1174)
|
||||
- Cross- Signing | After signin in new session, verification paper trail in DM is off (#1191)
|
||||
- Failed to encrypt message in room (message stays in red), [thanks to pwr22] (#925)
|
||||
- Cross-Signing | web <-> riotX After QR code scan, gossiping fails (#1210)
|
||||
- Fix crash when trying to download file without internet connection (#1229)
|
||||
- Local echo are not updated in timeline (for failed & encrypted states)
|
||||
- Render image event even if thumbnail_info does not have mimetype defined (#1209)
|
||||
- RiotX now uses as many threads as it needs to do work and send messages (#1221)
|
||||
- Fix issue with media path (#1227)
|
||||
- Add user to direct chat by user id (#1065)
|
||||
- Use correct URL for SSO connection (#1178)
|
||||
- Emoji completion :tada: does not completes to 🎉 like on web (#1285)
|
||||
- Fix bad Shield Logic for DM (#963)
|
||||
|
||||
Translations 🗣:
|
||||
- Weblate now create PR directly to RiotX GitHub project
|
||||
|
||||
SDK API changes ⚠️:
|
||||
- Increase targetSdkVersion to 29
|
||||
|
||||
Build 🧱:
|
||||
- Compile with Android SDK 29 (Android Q)
|
||||
|
||||
Other changes:
|
||||
- Add a setting to prevent screenshots of the application, disabled by default (#1027)
|
||||
- Increase File Logger capacities ( + use dev log preferences)
|
||||
|
||||
Changes in RiotX 0.18.1 (2020-03-17)
|
||||
===================================================
|
||||
|
||||
@@ -404,6 +516,7 @@ Bugfix:
|
||||
- Fix messages with empty `in_reply_to` not rendering (#447)
|
||||
- Fix clear cache (#408) and Logout (#205)
|
||||
- Fix `(edited)` link can be copied to clipboard (#402)
|
||||
- KeyBackup / SSSS | Should get the key from SSSS instead of asking recovery Key (#1163)
|
||||
|
||||
Build:
|
||||
- Split APK: generate one APK per arch, to reduce APK size of about 30%
|
||||
|
@@ -13,6 +13,24 @@ Dedicated room for RiotX: [![RiotX Android Matrix room #riot-android:matrix.org]
|
||||
Please set the "hard wrap" setting of Android Studio to 160 chars, this is the setting we use internally to format the source code (Menu `Settings/Editor/Code Style` then `Hard wrap at`).
|
||||
Please ensure that your using the project formatting rules (which are in the project at .idea/codeStyles/), and format the file before committing them.
|
||||
|
||||
### Template
|
||||
|
||||
An Android Studio template has been added to the project to help creating all files needed when adding a new screen to the application. Fragment, ViewModel, Activity, etc.
|
||||
|
||||
To install the template (to be done only once):
|
||||
- Go to folder `./tools/template`.
|
||||
- Run the script `./configure.sh`.
|
||||
- Restart Android Studio.
|
||||
|
||||
To create a new screen:
|
||||
- First create a new package in your code.
|
||||
- Then right click on the package, and select `New/New Vector/RiotX Feature`.
|
||||
- Follow the Wizard, especially replace `Main` by something more relevant to your feature.
|
||||
- Click on `Finish`.
|
||||
- Remainning steps are described as TODO in the generated files, or will be pointed out by the compilator, or at runtime :)
|
||||
|
||||
Note that if the templates are modified, the only things to do is to restart Android Studio for the change to take effect.
|
||||
|
||||
## Compilation
|
||||
|
||||
For now, the Matrix SDK and the RiotX application are in the same project. So there is no specific thing to do, this project should compile without any special action.
|
||||
|
92
docs/identity_server.md
Normal file
92
docs/identity_server.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# Identity server
|
||||
|
||||
Issue: #607
|
||||
PR: #1354
|
||||
|
||||
## Introduction
|
||||
Identity Servers support contact discovery on Matrix by letting people look up Third Party Identifiers to see if the owner has publicly linked them with their Matrix ID.
|
||||
|
||||
## Implementation
|
||||
|
||||
The current implementation was Inspired by the code from Riot-Android.
|
||||
|
||||
Difference though (list not exhaustive):
|
||||
- Only API v2 is supported (see https://matrix.org/docs/spec/identity_service/latest)
|
||||
- Homeserver has to be up to date to support binding (Versions.isLoginAndRegistrationSupportedBySdk() has to return true)
|
||||
- The SDK managed the session and client secret when binding ThreePid. Those data are not exposed to the client.
|
||||
- The SDK supports incremental sendAttempt (this is not used by RiotX)
|
||||
- The "Continue" button is now under the information, and not as the same place that the checkbox
|
||||
- The app can cancel a binding. Current data are erased from DB.
|
||||
- The API (IdentityService) is improved.
|
||||
- A new DB to store data related to the identity server management.
|
||||
|
||||
Missing features (list not exhaustive):
|
||||
- Invite by 3Pid (will be in a dedicated PR)
|
||||
- Add email or phone to account (not P1, can be done on Riot-Web)
|
||||
- List email and phone of the account (could be done in a dedicated PR)
|
||||
- Search contact (not P1)
|
||||
- Logout from identity server when user sign out or deactivate his account.
|
||||
|
||||
## Related MSCs
|
||||
The list can be found here: https://matrix.org/blog/2019/09/27/privacy-improvements-in-synapse-1-4-and-riot-1-4
|
||||
|
||||
## Steps and requirements
|
||||
|
||||
- Only one identity server by account can be set. The user's choice is stored in account data with key `m.identity_server`. But every clients will managed its own token to log in to the identity server
|
||||
```json
|
||||
{
|
||||
"type": "m.identity_server",
|
||||
"content": {
|
||||
"base_url": "https://matrix.org"
|
||||
}
|
||||
}
|
||||
```
|
||||
- The accepted terms are stored in the account data:
|
||||
```json
|
||||
{
|
||||
"type": "m.accepted_terms",
|
||||
"content": {
|
||||
"accepted": [
|
||||
"https://vector.im/identity-server-privacy-notice-1"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Default identity server URL, from Wellknown data is proposed to the user.
|
||||
- Identity server can be set
|
||||
- Identity server can be changed on another user's device, so when the change is detected (thanks to account data sync) RiotX should properly disconnect from a previous identity server (I think it was not the case in Riot-Android, where we keep the token forever)
|
||||
- Registration to the identity server is managed with an openId token
|
||||
- Terms of service can be accepted when configuring the identity server.
|
||||
- Terms of service can be accepted after, if they change.
|
||||
- Identity server can be modified
|
||||
- Identity server can be disconnected with a warning dialog, with special content if there are current bound 3pid on this identity server.
|
||||
- Email can be bound
|
||||
- Email can be unbound
|
||||
- Phone can be bound
|
||||
- Phone can be unbound
|
||||
- Look up can be performed, to get matrixIds from local contact book (phone and email): Android permission correctly handled (not done yet)
|
||||
- Look up pepper can be updated if it is rotated on the identity server
|
||||
- Invitation using 3PID can be done (See #548) (not done yet)
|
||||
- Homeserver access-token will never be sent to an identity server
|
||||
- When user sign-out: logout from the identity server if any.
|
||||
- When user deactivate account: logout from the identity server if any.
|
||||
|
||||
## Screens
|
||||
|
||||
### Settings
|
||||
|
||||
Identity server settings can be accessed from the internal setting of the application, both from "Discovery" section and from identity detail section.
|
||||
|
||||
### Discovery screen
|
||||
|
||||
This screen displays the identity server configuration and the binding of the user's ThreePid (email and msisdn). This is the main screen of the feature.
|
||||
|
||||
### Set identity server screen
|
||||
|
||||
This screen is a form to set a new identity server URL
|
||||
|
||||
## Ref:
|
||||
- https://matrix.org/blog/2019/09/27/privacy-improvements-in-synapse-1-4-and-riot-1-4 is a good summary of the role of an Identity server and the proper way to configure and use it in respect to the privacy and the consent of the user.
|
||||
- API documentation: https://matrix.org/docs/spec/identity_service/latest
|
||||
- vector.im TOS: https://vector.im/identity-server-privacy-notice
|
@@ -38,10 +38,10 @@ When the client receives the new information, it immediately sends another reque
|
||||
This effectively emulates a server push feature.
|
||||
|
||||
The HTTP long Polling can be fine tuned in the **SDK** using two parameters:
|
||||
* timout (Sync request timeout)
|
||||
* timeout (Sync request timeout)
|
||||
* delay (Delay between each sync)
|
||||
|
||||
**timeout** is a server paramter, defined by:
|
||||
**timeout** is a server parameter, defined by:
|
||||
```
|
||||
The maximum time to wait, in milliseconds, before returning this request.`
|
||||
If no events (or other data) become available before this time elapses, the server will return a response with empty fields.
|
||||
|
@@ -57,7 +57,7 @@ We get credential (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"user_id": "@benoit0816:matrix.org",
|
||||
"user_id": "@alice:matrix.org",
|
||||
"access_token": "MDAxOGxvY2F0aW9uIG1hdHREDACTEDb2l0MDgxNjptYXRyaXgub3JnCjAwMTZjaWQgdHlwZSA9IGFjY2VzcwowMDIxY2lkIG5vbmNlID0gfnYrSypfdTtkNXIuNWx1KgowMDJmc2lnbmF0dXJlIOsh1XqeAkXexh4qcofl_aR4kHJoSOWYGOhE7-ubX-DZCg",
|
||||
"home_server": "matrix.org",
|
||||
"device_id": "GTVREDALBF",
|
||||
@@ -128,6 +128,8 @@ We get the credentials (200)
|
||||
}
|
||||
```
|
||||
|
||||
It's worth noting that the response from the homeserver contains the userId of Alice.
|
||||
|
||||
### Login with Msisdn
|
||||
|
||||
Not supported yet in RiotX
|
||||
|
@@ -8,7 +8,7 @@
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
org.gradle.jvmargs=-Xmx8192m
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
|
@@ -3,11 +3,11 @@ apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
compileSdkVersion 29
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 28
|
||||
targetSdkVersion 29
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
|
@@ -28,6 +28,7 @@ import im.vector.matrix.android.api.session.room.send.UserDraft
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.api.util.toOptional
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
|
||||
@@ -95,6 +96,10 @@ class RxRoom(private val room: Room) {
|
||||
fun liveNotificationState(): Observable<RoomNotificationState> {
|
||||
return room.getLiveRoomNotificationState().asObservable()
|
||||
}
|
||||
|
||||
fun invite(userId: String, reason: String? = null): Completable = completableBuilder<Unit> {
|
||||
room.invite(userId, reason, it)
|
||||
}
|
||||
}
|
||||
|
||||
fun Room.rx(): RxRoom {
|
||||
|
@@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||
import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams
|
||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||
import im.vector.matrix.android.api.session.pushers.Pusher
|
||||
import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
@@ -31,6 +32,8 @@ import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.api.util.toOptional
|
||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.store.PrivateKeysInfo
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
@@ -58,6 +61,13 @@ class RxSession(private val session: Session) {
|
||||
}
|
||||
}
|
||||
|
||||
fun liveMyDeviceInfo(): Observable<List<DeviceInfo>> {
|
||||
return session.cryptoService().getLiveMyDevicesInfo().asObservable()
|
||||
.startWithCallable {
|
||||
session.cryptoService().getMyDevicesInfo()
|
||||
}
|
||||
}
|
||||
|
||||
fun liveSyncState(): Observable<SyncState> {
|
||||
return session.getSyncStateLive().asObservable()
|
||||
}
|
||||
@@ -81,8 +91,13 @@ class RxSession(private val session: Session) {
|
||||
return session.getIgnoredUsersLive().asObservable()
|
||||
}
|
||||
|
||||
fun livePagedUsers(filter: String? = null): Observable<PagedList<User>> {
|
||||
return session.getPagedUsersLive(filter).asObservable()
|
||||
fun livePagedUsers(filter: String? = null, excludedUserIds: Set<String>? = null): Observable<PagedList<User>> {
|
||||
return session.getPagedUsersLive(filter, excludedUserIds).asObservable()
|
||||
}
|
||||
|
||||
fun liveThreePIds(refreshData: Boolean): Observable<List<ThreePid>> {
|
||||
return session.getThreePidsLive(refreshData).asObservable()
|
||||
.startWithCallable { session.getThreePids() }
|
||||
}
|
||||
|
||||
fun createRoom(roomParams: CreateRoomParams): Single<String> = singleBuilder {
|
||||
@@ -123,6 +138,13 @@ class RxSession(private val session: Session) {
|
||||
}
|
||||
}
|
||||
|
||||
fun liveCrossSigningPrivateKeys(): Observable<Optional<PrivateKeysInfo>> {
|
||||
return session.cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys().asObservable()
|
||||
.startWithCallable {
|
||||
session.cryptoService().crossSigningService().getCrossSigningPrivateKeys().toOptional()
|
||||
}
|
||||
}
|
||||
|
||||
fun liveAccountData(types: Set<String>): Observable<List<UserAccountDataEvent>> {
|
||||
return session.getLiveAccountDataEvents(types).asObservable()
|
||||
.startWithCallable {
|
||||
|
@@ -19,12 +19,12 @@ androidExtensions {
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
compileSdkVersion 29
|
||||
testOptions.unitTests.includeAndroidResources = true
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 28
|
||||
targetSdkVersion 29
|
||||
versionCode 1
|
||||
versionName "0.0.1"
|
||||
// Multidex is useful for tests
|
||||
@@ -71,6 +71,15 @@ android {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
androidTest {
|
||||
java.srcDirs += "src/sharedTest/java"
|
||||
}
|
||||
test {
|
||||
java.srcDirs += "src/sharedTest/java"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static def gitRevision() {
|
||||
@@ -97,6 +106,7 @@ dependencies {
|
||||
def coroutines_version = "1.3.2"
|
||||
def markwon_version = '3.1.0'
|
||||
def daggerVersion = '2.25.4'
|
||||
def work_version = '2.3.3'
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
||||
@@ -118,7 +128,7 @@ dependencies {
|
||||
implementation "ru.noties.markwon:core:$markwon_version"
|
||||
|
||||
// Image
|
||||
implementation 'androidx.exifinterface:exifinterface:1.1.0'
|
||||
implementation 'androidx.exifinterface:exifinterface:1.3.0-alpha01'
|
||||
implementation 'id.zelory:compressor:3.0.0'
|
||||
|
||||
// Database
|
||||
@@ -126,7 +136,7 @@ dependencies {
|
||||
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
|
||||
|
||||
// Work
|
||||
implementation "androidx.work:work-runtime-ktx:2.3.3"
|
||||
implementation "androidx.work:work-runtime-ktx:$work_version"
|
||||
|
||||
// FP
|
||||
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
||||
@@ -148,6 +158,9 @@ dependencies {
|
||||
// Bus
|
||||
implementation 'org.greenrobot:eventbus:3.1.1'
|
||||
|
||||
// Phone number https://github.com/google/libphonenumber
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.10.23'
|
||||
|
||||
debugImplementation 'com.airbnb.okreplay:okreplay:1.5.0'
|
||||
releaseImplementation 'com.airbnb.okreplay:noop:1.5.0'
|
||||
androidTestImplementation 'com.airbnb.okreplay:espresso:1.5.0'
|
||||
@@ -159,6 +172,8 @@ dependencies {
|
||||
testImplementation 'io.mockk:mockk:1.9.2.kotlin12'
|
||||
testImplementation 'org.amshove.kluent:kluent-android:1.44'
|
||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||
// Plant Timber tree for test
|
||||
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
|
||||
|
||||
androidTestImplementation 'androidx.test:core:1.2.0'
|
||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||
@@ -170,5 +185,6 @@ dependencies {
|
||||
androidTestImplementation 'io.mockk:mockk-android:1.9.2.kotlin12'
|
||||
androidTestImplementation "androidx.arch.core:core-testing:$arch_version"
|
||||
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||
|
||||
// Plant Timber tree for test
|
||||
androidTestImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
|
||||
}
|
||||
|
@@ -18,10 +18,15 @@ package im.vector.matrix.android
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import im.vector.matrix.android.test.shared.createTimberTestRule
|
||||
import org.junit.Rule
|
||||
import java.io.File
|
||||
|
||||
interface InstrumentedTest {
|
||||
|
||||
@Rule
|
||||
fun timberTestRule() = createTimberTestRule()
|
||||
|
||||
fun context(): Context {
|
||||
return ApplicationProvider.getApplicationContext()
|
||||
}
|
||||
|
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.account
|
||||
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.api.failure.isInvalidPassword
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.SessionTestParams
|
||||
import im.vector.matrix.android.common.TestConstants
|
||||
import org.amshove.kluent.shouldBeTrue
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.junit.runners.MethodSorters
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class ChangePasswordTest : InstrumentedTest {
|
||||
|
||||
private val commonTestHelper = CommonTestHelper(context())
|
||||
|
||||
companion object {
|
||||
private const val NEW_PASSWORD = "this is a new password"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun changePasswordTest() {
|
||||
val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = false))
|
||||
|
||||
// Change password
|
||||
commonTestHelper.doSync<Unit> {
|
||||
session.changePassword(TestConstants.PASSWORD, NEW_PASSWORD, it)
|
||||
}
|
||||
|
||||
// Try to login with the previous password, it will fail
|
||||
val throwable = commonTestHelper.logAccountWithError(session.myUserId, TestConstants.PASSWORD)
|
||||
throwable.isInvalidPassword().shouldBeTrue()
|
||||
|
||||
// Try to login with the new password, should work
|
||||
val session2 = commonTestHelper.logIntoAccount(session.myUserId, NEW_PASSWORD, SessionTestParams(withInitialSync = false))
|
||||
|
||||
commonTestHelper.signOutAndClose(session)
|
||||
commonTestHelper.signOutAndClose(session2)
|
||||
}
|
||||
}
|
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.account
|
||||
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.api.auth.data.LoginFlowResult
|
||||
import im.vector.matrix.android.api.auth.registration.RegistrationResult
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
import im.vector.matrix.android.api.failure.MatrixError
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.SessionTestParams
|
||||
import im.vector.matrix.android.common.TestConstants
|
||||
import im.vector.matrix.android.common.TestMatrixCallback
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.junit.runners.MethodSorters
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class DeactivateAccountTest : InstrumentedTest {
|
||||
|
||||
private val commonTestHelper = CommonTestHelper(context())
|
||||
|
||||
@Test
|
||||
fun deactivateAccountTest() {
|
||||
val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = false))
|
||||
|
||||
// Deactivate the account
|
||||
commonTestHelper.doSync<Unit> {
|
||||
session.deactivateAccount(TestConstants.PASSWORD, false, it)
|
||||
}
|
||||
|
||||
// Try to login on the previous account, it will fail (M_USER_DEACTIVATED)
|
||||
val throwable = commonTestHelper.logAccountWithError(session.myUserId, TestConstants.PASSWORD)
|
||||
|
||||
// Test the error
|
||||
assertTrue(throwable is Failure.ServerError
|
||||
&& throwable.error.code == MatrixError.M_USER_DEACTIVATED
|
||||
&& throwable.error.message == "This account has been deactivated")
|
||||
|
||||
// Try to create an account with the deactivate account user id, it will fail (M_USER_IN_USE)
|
||||
val hs = commonTestHelper.createHomeServerConfig()
|
||||
|
||||
commonTestHelper.doSync<LoginFlowResult> {
|
||||
commonTestHelper.matrix.authenticationService.getLoginFlow(hs, it)
|
||||
}
|
||||
|
||||
var accountCreationError: Throwable? = null
|
||||
commonTestHelper.waitWithLatch {
|
||||
commonTestHelper.matrix.authenticationService
|
||||
.getRegistrationWizard()
|
||||
.createAccount(session.myUserId.substringAfter("@").substringBefore(":"),
|
||||
TestConstants.PASSWORD,
|
||||
null,
|
||||
object : TestMatrixCallback<RegistrationResult>(it, false) {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
accountCreationError = failure
|
||||
super.onFailure(failure)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Test the error
|
||||
accountCreationError.let {
|
||||
assertTrue(it is Failure.ServerError
|
||||
&& it.error.code == MatrixError.M_USER_IN_USE)
|
||||
}
|
||||
|
||||
// No need to close the session, it has been deactivated
|
||||
}
|
||||
}
|
@@ -28,16 +28,17 @@ import im.vector.matrix.android.api.auth.data.LoginFlowResult
|
||||
import im.vector.matrix.android.api.auth.registration.RegistrationResult
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.LocalEcho
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.Room
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import im.vector.matrix.android.api.session.room.send.SendState
|
||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
||||
import im.vector.matrix.android.api.session.sync.SyncState
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
@@ -87,7 +88,8 @@ class CommonTestHelper(context: Context) {
|
||||
fun syncSession(session: Session) {
|
||||
val lock = CountDownLatch(1)
|
||||
|
||||
session.open()
|
||||
GlobalScope.launch(Dispatchers.Main) { session.open() }
|
||||
|
||||
session.startSync(true)
|
||||
|
||||
val syncLiveData = runBlocking(Dispatchers.Main) {
|
||||
@@ -115,7 +117,7 @@ class CommonTestHelper(context: Context) {
|
||||
*/
|
||||
fun sendTextMessage(room: Room, message: String, nbOfMessages: Int): List<TimelineEvent> {
|
||||
val sentEvents = ArrayList<TimelineEvent>(nbOfMessages)
|
||||
val latch = CountDownLatch(nbOfMessages)
|
||||
val latch = CountDownLatch(1)
|
||||
val timelineListener = object : Timeline.Listener {
|
||||
override fun onTimelineFailure(throwable: Throwable) {
|
||||
}
|
||||
@@ -126,7 +128,7 @@ class CommonTestHelper(context: Context) {
|
||||
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
val newMessages = snapshot
|
||||
.filter { LocalEcho.isLocalEchoId(it.eventId).not() }
|
||||
.filter { it.root.sendState == SendState.SYNCED }
|
||||
.filter { it.root.getClearType() == EventType.MESSAGE }
|
||||
.filter { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(message) == true }
|
||||
|
||||
@@ -142,7 +144,8 @@ class CommonTestHelper(context: Context) {
|
||||
for (i in 0 until nbOfMessages) {
|
||||
room.sendTextMessage(message + " #" + (i + 1))
|
||||
}
|
||||
await(latch)
|
||||
// Wait 3 second more per message
|
||||
await(latch, timeout = TestConstants.timeOutMillis + 3_000L * nbOfMessages)
|
||||
timeline.removeListener(timelineListener)
|
||||
timeline.dispose()
|
||||
|
||||
@@ -182,9 +185,9 @@ class CommonTestHelper(context: Context) {
|
||||
* @param testParams test params about the session
|
||||
* @return the session associated with the existing account
|
||||
*/
|
||||
private fun logIntoAccount(userId: String,
|
||||
password: String,
|
||||
testParams: SessionTestParams): Session {
|
||||
fun logIntoAccount(userId: String,
|
||||
password: String,
|
||||
testParams: SessionTestParams): Session {
|
||||
val session = logAccountAndSync(userId, password, testParams)
|
||||
assertNotNull(session)
|
||||
return session
|
||||
@@ -259,14 +262,81 @@ class CommonTestHelper(context: Context) {
|
||||
return session
|
||||
}
|
||||
|
||||
/**
|
||||
* Log into the account and expect an error
|
||||
*
|
||||
* @param userName the account username
|
||||
* @param password the password
|
||||
*/
|
||||
fun logAccountWithError(userName: String,
|
||||
password: String): Throwable {
|
||||
val hs = createHomeServerConfig()
|
||||
|
||||
doSync<LoginFlowResult> {
|
||||
matrix.authenticationService
|
||||
.getLoginFlow(hs, it)
|
||||
}
|
||||
|
||||
var requestFailure: Throwable? = null
|
||||
waitWithLatch { latch ->
|
||||
matrix.authenticationService
|
||||
.getLoginWizard()
|
||||
.login(userName, password, "myDevice", object : TestMatrixCallback<Session>(latch, onlySuccessful = false) {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
requestFailure = failure
|
||||
super.onFailure(failure)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
assertNotNull(requestFailure)
|
||||
return requestFailure!!
|
||||
}
|
||||
|
||||
fun createEventListener(latch: CountDownLatch, predicate: (List<TimelineEvent>) -> Boolean): Timeline.Listener {
|
||||
return object : Timeline.Listener {
|
||||
override fun onTimelineFailure(throwable: Throwable) {
|
||||
// noop
|
||||
}
|
||||
|
||||
override fun onNewTimelineEvents(eventIds: List<String>) {
|
||||
// noop
|
||||
}
|
||||
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
if (predicate(snapshot)) {
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Await for a latch and ensure the result is true
|
||||
*
|
||||
* @param latch
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
fun await(latch: CountDownLatch) {
|
||||
assertTrue(latch.await(TestConstants.timeOutMillis, TimeUnit.MILLISECONDS))
|
||||
fun await(latch: CountDownLatch, timeout: Long? = TestConstants.timeOutMillis) {
|
||||
assertTrue(latch.await(timeout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS))
|
||||
}
|
||||
|
||||
fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) {
|
||||
GlobalScope.launch {
|
||||
while (true) {
|
||||
delay(1000)
|
||||
if (condition()) {
|
||||
latch.countDown()
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun waitWithLatch(timeout: Long? = TestConstants.timeOutMillis, block: (CountDownLatch) -> Unit) {
|
||||
val latch = CountDownLatch(1)
|
||||
block(latch)
|
||||
await(latch, timeout)
|
||||
}
|
||||
|
||||
// Transform a method with a MatrixCallback to a synchronous method
|
||||
@@ -299,3 +369,13 @@ class CommonTestHelper(context: Context) {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
|
||||
fun List<TimelineEvent>.checkSendOrder(baseTextMessage: String, numberOfMessages: Int, startIndex: Int): Boolean {
|
||||
return drop(startIndex)
|
||||
.take(numberOfMessages)
|
||||
.foldRightIndexed(true) { index, timelineEvent, acc ->
|
||||
val body = timelineEvent.root.content.toModel<MessageContent>()?.body
|
||||
val currentMessageSuffix = numberOfMessages - index
|
||||
acc && (body == null || body.startsWith(baseTextMessage) && body.endsWith("#$currentMessageSuffix"))
|
||||
}
|
||||
}
|
||||
|
@@ -22,6 +22,7 @@ import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toContent
|
||||
import im.vector.matrix.android.api.session.room.Room
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||
@@ -40,8 +41,6 @@ import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import java.util.HashMap
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||
@@ -54,17 +53,19 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||
/**
|
||||
* @return alice session
|
||||
*/
|
||||
fun doE2ETestWithAliceInARoom(): CryptoTestData {
|
||||
fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true): CryptoTestData {
|
||||
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
|
||||
|
||||
val roomId = mTestHelper.doSync<String> {
|
||||
aliceSession.createRoom(CreateRoomParams(name = "MyRoom"), it)
|
||||
}
|
||||
|
||||
val room = aliceSession.getRoom(roomId)!!
|
||||
if (encryptedRoom) {
|
||||
val room = aliceSession.getRoom(roomId)!!
|
||||
|
||||
mTestHelper.doSync<Unit> {
|
||||
room.enableEncryption(callback = it)
|
||||
mTestHelper.doSync<Unit> {
|
||||
room.enableEncryption(callback = it)
|
||||
}
|
||||
}
|
||||
|
||||
return CryptoTestData(aliceSession, roomId)
|
||||
@@ -73,8 +74,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||
/**
|
||||
* @return alice and bob sessions
|
||||
*/
|
||||
fun doE2ETestWithAliceAndBobInARoom(): CryptoTestData {
|
||||
val cryptoTestData = doE2ETestWithAliceInARoom()
|
||||
fun doE2ETestWithAliceAndBobInARoom(encryptedRoom: Boolean = true): CryptoTestData {
|
||||
val cryptoTestData = doE2ETestWithAliceInARoom(encryptedRoom)
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceRoomId = cryptoTestData.roomId
|
||||
|
||||
@@ -140,64 +141,38 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||
* @return Alice, Bob and Sam session
|
||||
*/
|
||||
fun doE2ETestWithAliceAndBobAndSamInARoom(): CryptoTestData {
|
||||
val statuses = HashMap<String, String>()
|
||||
|
||||
val cryptoTestData = doE2ETestWithAliceAndBobInARoom()
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceRoomId = cryptoTestData.roomId
|
||||
|
||||
val room = aliceSession.getRoom(aliceRoomId)!!
|
||||
|
||||
val samSession = mTestHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams)
|
||||
|
||||
val lock1 = CountDownLatch(2)
|
||||
|
||||
// val samEventListener = object : MXEventListener() {
|
||||
// override fun onNewRoom(roomId: String) {
|
||||
// if (TextUtils.equals(roomId, aliceRoomId)) {
|
||||
// if (!statuses.containsKey("onNewRoom")) {
|
||||
// statuses["onNewRoom"] = "onNewRoom"
|
||||
// lock1.countDown()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// samSession.dataHandler.addListener(samEventListener)
|
||||
|
||||
room.invite(samSession.myUserId, null, object : TestMatrixCallback<Unit>(lock1) {
|
||||
override fun onSuccess(data: Unit) {
|
||||
statuses["invite"] = "invite"
|
||||
super.onSuccess(data)
|
||||
}
|
||||
})
|
||||
|
||||
mTestHelper.await(lock1)
|
||||
|
||||
assertTrue(statuses.containsKey("invite") && statuses.containsKey("onNewRoom"))
|
||||
|
||||
// samSession.dataHandler.removeListener(samEventListener)
|
||||
|
||||
val lock2 = CountDownLatch(1)
|
||||
|
||||
samSession.joinRoom(aliceRoomId, null, object : TestMatrixCallback<Unit>(lock2) {
|
||||
override fun onSuccess(data: Unit) {
|
||||
statuses["joinRoom"] = "joinRoom"
|
||||
super.onSuccess(data)
|
||||
}
|
||||
})
|
||||
|
||||
mTestHelper.await(lock2)
|
||||
assertTrue(statuses.containsKey("joinRoom"))
|
||||
val samSession = createSamAccountAndInviteToTheRoom(room)
|
||||
|
||||
// wait the initial sync
|
||||
SystemClock.sleep(1000)
|
||||
|
||||
// samSession.dataHandler.removeListener(samEventListener)
|
||||
|
||||
return CryptoTestData(aliceSession, aliceRoomId, cryptoTestData.secondSession, samSession)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Sam account and invite him in the room. He will accept the invitation
|
||||
* @Return Sam session
|
||||
*/
|
||||
fun createSamAccountAndInviteToTheRoom(room: Room): Session {
|
||||
val samSession = mTestHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams)
|
||||
|
||||
mTestHelper.doSync<Unit> {
|
||||
room.invite(samSession.myUserId, null, it)
|
||||
}
|
||||
|
||||
mTestHelper.doSync<Unit> {
|
||||
samSession.joinRoom(room.roomId, null, it)
|
||||
}
|
||||
|
||||
return samSession
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Alice and Bob sessions
|
||||
*/
|
||||
@@ -273,7 +248,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||
assertNotNull(eventWireContent.get("session_id"))
|
||||
assertNotNull(eventWireContent.get("sender_key"))
|
||||
|
||||
assertEquals(senderSession.sessionParams.credentials.deviceId, eventWireContent.get("device_id"))
|
||||
assertEquals(senderSession.sessionParams.deviceId, eventWireContent.get("device_id"))
|
||||
|
||||
assertNotNull(event.eventId)
|
||||
assertEquals(roomId, event.roomId)
|
||||
|
@@ -22,8 +22,8 @@ object TestConstants {
|
||||
|
||||
const val TESTS_HOME_SERVER_URL = "http://10.0.2.2:8080"
|
||||
|
||||
// Time out to use when waiting for server response. 10s
|
||||
private const val AWAIT_TIME_OUT_MILLIS = 10_000
|
||||
// Time out to use when waiting for server response. 20s
|
||||
private const val AWAIT_TIME_OUT_MILLIS = 20_000
|
||||
|
||||
// Time out to use when waiting for server response, when the debugger is connected. 10 minutes
|
||||
private const val AWAIT_TIME_OUT_WITH_DEBUGGER_MILLIS = 10 * 60_000
|
||||
|
@@ -20,6 +20,8 @@ import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore
|
||||
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
|
||||
import im.vector.matrix.android.internal.crypto.store.db.mapper.CrossSigningKeysMapper
|
||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
import io.realm.RealmConfiguration
|
||||
import kotlin.random.Random
|
||||
|
||||
@@ -31,6 +33,7 @@ internal class CryptoStoreHelper {
|
||||
.name("test.realm")
|
||||
.modules(RealmCryptoStoreModule())
|
||||
.build(),
|
||||
crossSigningKeysMapper = CrossSigningKeysMapper(MoshiProvider.providesMoshi()),
|
||||
credentials = createCredential())
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.crypto
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.api.extensions.tryThis
|
||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.CryptoTestHelper
|
||||
import im.vector.matrix.android.common.TestConstants
|
||||
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
|
||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
||||
import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm
|
||||
import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm
|
||||
import org.amshove.kluent.shouldBe
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
import org.matrix.olm.OlmSession
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
/**
|
||||
* Ref:
|
||||
* - https://github.com/matrix-org/matrix-doc/pull/1719
|
||||
* - https://matrix.org/docs/spec/client_server/latest#recovering-from-undecryptable-messages
|
||||
* - https://github.com/matrix-org/matrix-js-sdk/pull/780
|
||||
* - https://github.com/matrix-org/matrix-ios-sdk/pull/778
|
||||
* - https://github.com/matrix-org/matrix-ios-sdk/pull/784
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class UnwedgingTest : InstrumentedTest {
|
||||
|
||||
private lateinit var messagesReceivedByBob: List<TimelineEvent>
|
||||
private val mTestHelper = CommonTestHelper(context())
|
||||
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
|
||||
|
||||
@Before
|
||||
fun init() {
|
||||
messagesReceivedByBob = emptyList()
|
||||
}
|
||||
|
||||
/**
|
||||
* - Alice & Bob in a e2e room
|
||||
* - Alice sends a 1st message with a 1st megolm session
|
||||
* - Store the olm session between A&B devices
|
||||
* - Alice sends a 2nd message with a 2nd megolm session
|
||||
* - Simulate Alice using a backup of her OS and make her crypto state like after the first message
|
||||
* - Alice sends a 3rd message with a 3rd megolm session but a wedged olm session
|
||||
*
|
||||
* What Bob must see:
|
||||
* -> No issue with the 2 first messages
|
||||
* -> The third event must fail to decrypt at first because Bob the olm session is wedged
|
||||
* -> This is automatically fixed after SDKs restarted the olm session
|
||||
*/
|
||||
@Test
|
||||
fun testUnwedging() {
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceRoomId = cryptoTestData.roomId
|
||||
val bobSession = cryptoTestData.secondSession!!
|
||||
|
||||
val aliceCryptoStore = (aliceSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting
|
||||
|
||||
// bobSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
// aliceSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
|
||||
val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
|
||||
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
|
||||
|
||||
val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(20))
|
||||
bobTimeline.start()
|
||||
|
||||
val bobFinalLatch = CountDownLatch(1)
|
||||
val bobHasThreeDecryptedEventsListener = object : Timeline.Listener {
|
||||
override fun onTimelineFailure(throwable: Throwable) {
|
||||
// noop
|
||||
}
|
||||
|
||||
override fun onNewTimelineEvents(eventIds: List<String>) {
|
||||
// noop
|
||||
}
|
||||
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
val decryptedEventReceivedByBob = snapshot.filter { it.root.type == EventType.ENCRYPTED }
|
||||
Timber.d("Bob can now decrypt ${decryptedEventReceivedByBob.size} messages")
|
||||
if (decryptedEventReceivedByBob.size == 3) {
|
||||
if (decryptedEventReceivedByBob[0].root.mCryptoError == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
|
||||
bobFinalLatch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bobTimeline.addListener(bobHasThreeDecryptedEventsListener)
|
||||
|
||||
var latch = CountDownLatch(1)
|
||||
var bobEventsListener = createEventListener(latch, 1)
|
||||
bobTimeline.addListener(bobEventsListener)
|
||||
messagesReceivedByBob = emptyList()
|
||||
|
||||
// - Alice sends a 1st message with a 1st megolm session
|
||||
roomFromAlicePOV.sendTextMessage("First message")
|
||||
|
||||
// Wait for the message to be received by Bob
|
||||
mTestHelper.await(latch)
|
||||
bobTimeline.removeListener(bobEventsListener)
|
||||
|
||||
messagesReceivedByBob.size shouldBe 1
|
||||
val firstMessageSession = messagesReceivedByBob[0].root.content.toModel<EncryptedEventContent>()!!.sessionId!!
|
||||
|
||||
// - Store the olm session between A&B devices
|
||||
// Let us pickle our session with bob here so we can later unpickle it
|
||||
// and wedge our session.
|
||||
val sessionIdsForBob = aliceCryptoStore.getDeviceSessionIds(bobSession.cryptoService().getMyDevice().identityKey()!!)
|
||||
sessionIdsForBob!!.size shouldBe 1
|
||||
val olmSession = aliceCryptoStore.getDeviceSession(sessionIdsForBob.first(), bobSession.cryptoService().getMyDevice().identityKey()!!)!!
|
||||
|
||||
val oldSession = serializeForRealm(olmSession.olmSession)
|
||||
|
||||
aliceSession.cryptoService().discardOutboundSession(roomFromAlicePOV.roomId)
|
||||
Thread.sleep(6_000)
|
||||
|
||||
latch = CountDownLatch(1)
|
||||
bobEventsListener = createEventListener(latch, 2)
|
||||
bobTimeline.addListener(bobEventsListener)
|
||||
messagesReceivedByBob = emptyList()
|
||||
|
||||
Timber.i("## CRYPTO | testUnwedging: Alice sends a 2nd message with a 2nd megolm session")
|
||||
// - Alice sends a 2nd message with a 2nd megolm session
|
||||
roomFromAlicePOV.sendTextMessage("Second message")
|
||||
|
||||
// Wait for the message to be received by Bob
|
||||
mTestHelper.await(latch)
|
||||
bobTimeline.removeListener(bobEventsListener)
|
||||
|
||||
messagesReceivedByBob.size shouldBe 2
|
||||
// Session should have changed
|
||||
val secondMessageSession = messagesReceivedByBob[0].root.content.toModel<EncryptedEventContent>()!!.sessionId!!
|
||||
Assert.assertNotEquals(firstMessageSession, secondMessageSession)
|
||||
|
||||
// Let us wedge the session now. Set crypto state like after the first message
|
||||
Timber.i("## CRYPTO | testUnwedging: wedge the session now. Set crypto state like after the first message")
|
||||
|
||||
aliceCryptoStore.storeSession(OlmSessionWrapper(deserializeFromRealm<OlmSession>(oldSession)!!), bobSession.cryptoService().getMyDevice().identityKey()!!)
|
||||
Thread.sleep(6_000)
|
||||
|
||||
// Force new session, and key share
|
||||
aliceSession.cryptoService().discardOutboundSession(roomFromAlicePOV.roomId)
|
||||
|
||||
// Wait for the message to be received by Bob
|
||||
mTestHelper.waitWithLatch {
|
||||
bobEventsListener = createEventListener(it, 3)
|
||||
bobTimeline.addListener(bobEventsListener)
|
||||
messagesReceivedByBob = emptyList()
|
||||
|
||||
Timber.i("## CRYPTO | testUnwedging: Alice sends a 3rd message with a 3rd megolm session but a wedged olm session")
|
||||
// - Alice sends a 3rd message with a 3rd megolm session but a wedged olm session
|
||||
roomFromAlicePOV.sendTextMessage("Third message")
|
||||
// Bob should not be able to decrypt, because the session key could not be sent
|
||||
}
|
||||
bobTimeline.removeListener(bobEventsListener)
|
||||
|
||||
messagesReceivedByBob.size shouldBe 3
|
||||
|
||||
val thirdMessageSession = messagesReceivedByBob[0].root.content.toModel<EncryptedEventContent>()!!.sessionId!!
|
||||
Timber.i("## CRYPTO | testUnwedging: third message session ID $thirdMessageSession")
|
||||
Assert.assertNotEquals(secondMessageSession, thirdMessageSession)
|
||||
|
||||
Assert.assertEquals(EventType.ENCRYPTED, messagesReceivedByBob[0].root.getClearType())
|
||||
Assert.assertEquals(EventType.MESSAGE, messagesReceivedByBob[1].root.getClearType())
|
||||
Assert.assertEquals(EventType.MESSAGE, messagesReceivedByBob[2].root.getClearType())
|
||||
// Bob Should not be able to decrypt last message, because session could not be sent as the olm channel was wedged
|
||||
mTestHelper.await(bobFinalLatch)
|
||||
bobTimeline.removeListener(bobHasThreeDecryptedEventsListener)
|
||||
|
||||
// It's a trick to force key request on fail to decrypt
|
||||
mTestHelper.doSync<Unit> {
|
||||
bobSession.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(UserPasswordAuth(
|
||||
user = bobSession.myUserId,
|
||||
password = TestConstants.PASSWORD
|
||||
), it)
|
||||
}
|
||||
|
||||
// Wait until we received back the key
|
||||
mTestHelper.waitWithLatch {
|
||||
mTestHelper.retryPeriodicallyWithLatch(it) {
|
||||
// we should get back the key and be able to decrypt
|
||||
val result = tryThis {
|
||||
bobSession.cryptoService().decryptEvent(messagesReceivedByBob[0].root, "")
|
||||
}
|
||||
Timber.i("## CRYPTO | testUnwedging: decrypt result ${result?.clearEvent}")
|
||||
result != null
|
||||
}
|
||||
}
|
||||
|
||||
bobTimeline.dispose()
|
||||
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
}
|
||||
|
||||
private fun createEventListener(latch: CountDownLatch, expectedNumberOfMessages: Int): Timeline.Listener {
|
||||
return object : Timeline.Listener {
|
||||
override fun onTimelineFailure(throwable: Throwable) {
|
||||
// noop
|
||||
}
|
||||
|
||||
override fun onNewTimelineEvents(eventIds: List<String>) {
|
||||
// noop
|
||||
}
|
||||
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
messagesReceivedByBob = snapshot.filter { it.root.type == EventType.ENCRYPTED }
|
||||
|
||||
if (messagesReceivedByBob.size == expectedNumberOfMessages) {
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -122,7 +122,7 @@ class XSigningTest : InstrumentedTest {
|
||||
// We will want to test that in alice POV, this new device would be trusted by cross signing
|
||||
|
||||
val bobSession2 = mTestHelper.logIntoAccount(bobUserId, SessionTestParams(true))
|
||||
val bobSecondDeviceId = bobSession2.sessionParams.credentials.deviceId!!
|
||||
val bobSecondDeviceId = bobSession2.sessionParams.deviceId!!
|
||||
|
||||
// Check that bob first session sees the new login
|
||||
val data = mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||
|
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.gossiping
|
||||
|
||||
import android.util.Log
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.verification.SasVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationService
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility
|
||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.SessionTestParams
|
||||
import im.vector.matrix.android.common.TestConstants
|
||||
import im.vector.matrix.android.internal.crypto.GossipingRequestState
|
||||
import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestState
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion
|
||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertNotNull
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import junit.framework.TestCase.fail
|
||||
import org.junit.Assert
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class KeyShareTests : InstrumentedTest {
|
||||
|
||||
private val mTestHelper = CommonTestHelper(context())
|
||||
|
||||
@Test
|
||||
fun test_DoNotSelfShareIfNotTrusted() {
|
||||
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
|
||||
// Create an encrypted room and add a message
|
||||
val roomId = mTestHelper.doSync<String> {
|
||||
aliceSession.createRoom(
|
||||
CreateRoomParams(RoomDirectoryVisibility.PRIVATE).enableEncryptionWithAlgorithm(true),
|
||||
it
|
||||
)
|
||||
}
|
||||
val room = aliceSession.getRoom(roomId)
|
||||
assertNotNull(room)
|
||||
Thread.sleep(4_000)
|
||||
assertTrue(room?.isEncrypted() == true)
|
||||
val sentEventId = mTestHelper.sendTextMessage(room!!, "My Message", 1).first().eventId
|
||||
|
||||
// Open a new sessionx
|
||||
|
||||
val aliceSession2 = mTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
|
||||
|
||||
val roomSecondSessionPOV = aliceSession2.getRoom(roomId)
|
||||
|
||||
val receivedEvent = roomSecondSessionPOV?.getTimeLineEvent(sentEventId)
|
||||
assertNotNull(receivedEvent)
|
||||
assert(receivedEvent!!.isEncrypted())
|
||||
|
||||
try {
|
||||
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
||||
fail("should fail")
|
||||
} catch (failure: Throwable) {
|
||||
}
|
||||
|
||||
val outgoingRequestBefore = aliceSession2.cryptoService().getOutgoingRoomKeyRequest()
|
||||
// Try to request
|
||||
aliceSession2.cryptoService().requestRoomKeyForEvent(receivedEvent.root)
|
||||
|
||||
val waitLatch = CountDownLatch(1)
|
||||
val eventMegolmSessionId = receivedEvent.root.content.toModel<EncryptedEventContent>()?.sessionId
|
||||
|
||||
var outGoingRequestId: String? = null
|
||||
|
||||
mTestHelper.retryPeriodicallyWithLatch(waitLatch) {
|
||||
aliceSession2.cryptoService().getOutgoingRoomKeyRequest()
|
||||
.filter { req ->
|
||||
// filter out request that was known before
|
||||
!outgoingRequestBefore.any { req.requestId == it.requestId }
|
||||
}
|
||||
.let {
|
||||
val outgoing = it.firstOrNull { it.sessionId == eventMegolmSessionId }
|
||||
outGoingRequestId = outgoing?.requestId
|
||||
outgoing != null
|
||||
}
|
||||
}
|
||||
mTestHelper.await(waitLatch)
|
||||
|
||||
Log.v("TEST", "=======> Outgoing requet Id is $outGoingRequestId")
|
||||
|
||||
val outgoingRequestAfter = aliceSession2.cryptoService().getOutgoingRoomKeyRequest()
|
||||
|
||||
// We should have a new request
|
||||
Assert.assertTrue(outgoingRequestAfter.size > outgoingRequestBefore.size)
|
||||
Assert.assertNotNull(outgoingRequestAfter.first { it.sessionId == eventMegolmSessionId })
|
||||
|
||||
// The first session should see an incoming request
|
||||
// the request should be refused, because the device is not trusted
|
||||
mTestHelper.waitWithLatch { latch ->
|
||||
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
// DEBUG LOGS
|
||||
aliceSession.cryptoService().getIncomingRoomKeyRequest().let {
|
||||
Log.v("TEST", "Incoming request Session 1 (looking for $outGoingRequestId)")
|
||||
Log.v("TEST", "=========================")
|
||||
it.forEach { keyRequest ->
|
||||
Log.v("TEST", "[ts${keyRequest.localCreationTimestamp}] requestId ${keyRequest.requestId}, for sessionId ${keyRequest.requestBody?.sessionId} is ${keyRequest.state}")
|
||||
}
|
||||
Log.v("TEST", "=========================")
|
||||
}
|
||||
|
||||
val incoming = aliceSession.cryptoService().getIncomingRoomKeyRequest().firstOrNull { it.requestId == outGoingRequestId }
|
||||
incoming?.state == GossipingRequestState.REJECTED
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
||||
fail("should fail")
|
||||
} catch (failure: Throwable) {
|
||||
}
|
||||
|
||||
// Mark the device as trusted
|
||||
aliceSession.cryptoService().setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId,
|
||||
aliceSession2.sessionParams.deviceId ?: "")
|
||||
|
||||
// Re request
|
||||
aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
|
||||
|
||||
mTestHelper.waitWithLatch { latch ->
|
||||
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
aliceSession.cryptoService().getIncomingRoomKeyRequest().let {
|
||||
Log.v("TEST", "Incoming request Session 1")
|
||||
Log.v("TEST", "=========================")
|
||||
it.forEach {
|
||||
Log.v("TEST", "requestId ${it.requestId}, for sessionId ${it.requestBody?.sessionId} is ${it.state}")
|
||||
}
|
||||
Log.v("TEST", "=========================")
|
||||
|
||||
it.any { it.requestBody?.sessionId == eventMegolmSessionId && it.state == GossipingRequestState.ACCEPTED }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Thread.sleep(6_000)
|
||||
mTestHelper.waitWithLatch { latch ->
|
||||
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
aliceSession2.cryptoService().getOutgoingRoomKeyRequest().let {
|
||||
it.any { it.requestBody?.sessionId == eventMegolmSessionId && it.state == OutgoingGossipingRequestState.CANCELLED }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
||||
} catch (failure: Throwable) {
|
||||
fail("should have been able to decrypt")
|
||||
}
|
||||
|
||||
mTestHelper.signOutAndClose(aliceSession)
|
||||
mTestHelper.signOutAndClose(aliceSession2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_ShareSSSSSecret() {
|
||||
val aliceSession1 = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
|
||||
mTestHelper.doSync<Unit> {
|
||||
aliceSession1.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(UserPasswordAuth(
|
||||
user = aliceSession1.myUserId,
|
||||
password = TestConstants.PASSWORD
|
||||
), it)
|
||||
}
|
||||
|
||||
// Also bootstrap keybackup on first session
|
||||
val creationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> {
|
||||
aliceSession1.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it)
|
||||
}
|
||||
val version = mTestHelper.doSync<KeysVersion> {
|
||||
aliceSession1.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it)
|
||||
}
|
||||
// Save it for gossiping
|
||||
aliceSession1.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version)
|
||||
|
||||
val aliceSession2 = mTestHelper.logIntoAccount(aliceSession1.myUserId, SessionTestParams(true))
|
||||
|
||||
val aliceVerificationService1 = aliceSession1.cryptoService().verificationService()
|
||||
val aliceVerificationService2 = aliceSession2.cryptoService().verificationService()
|
||||
|
||||
// force keys download
|
||||
mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||
aliceSession1.cryptoService().downloadKeys(listOf(aliceSession1.myUserId), true, it)
|
||||
}
|
||||
mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||
aliceSession2.cryptoService().downloadKeys(listOf(aliceSession2.myUserId), true, it)
|
||||
}
|
||||
|
||||
var session1ShortCode: String? = null
|
||||
var session2ShortCode: String? = null
|
||||
|
||||
aliceVerificationService1.addListener(object : VerificationService.Listener {
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
Log.d("#TEST", "AA: tx incoming?:${tx.isIncoming} state ${tx.state}")
|
||||
if (tx is SasVerificationTransaction) {
|
||||
if (tx.state == VerificationTxState.OnStarted) {
|
||||
(tx as IncomingSasVerificationTransaction).performAccept()
|
||||
}
|
||||
if (tx.state == VerificationTxState.ShortCodeReady) {
|
||||
session1ShortCode = tx.getDecimalCodeRepresentation()
|
||||
tx.userHasVerifiedShortCode()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
aliceVerificationService2.addListener(object : VerificationService.Listener {
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
Log.d("#TEST", "BB: tx incoming?:${tx.isIncoming} state ${tx.state}")
|
||||
if (tx is SasVerificationTransaction) {
|
||||
if (tx.state == VerificationTxState.ShortCodeReady) {
|
||||
session2ShortCode = tx.getDecimalCodeRepresentation()
|
||||
tx.userHasVerifiedShortCode()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
val txId: String = "m.testVerif12"
|
||||
aliceVerificationService2.beginKeyVerification(VerificationMethod.SAS, aliceSession1.myUserId, aliceSession1.sessionParams.deviceId
|
||||
?: "", txId)
|
||||
|
||||
mTestHelper.waitWithLatch { latch ->
|
||||
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
aliceSession1.cryptoService().getDeviceInfo(aliceSession1.myUserId, aliceSession2.sessionParams.deviceId ?: "")?.isVerified == true
|
||||
}
|
||||
}
|
||||
|
||||
assertNotNull(session1ShortCode)
|
||||
Log.d("#TEST", "session1ShortCode: $session1ShortCode")
|
||||
assertNotNull(session2ShortCode)
|
||||
Log.d("#TEST", "session2ShortCode: $session2ShortCode")
|
||||
assertEquals(session1ShortCode, session2ShortCode)
|
||||
|
||||
// SSK and USK private keys should have been shared
|
||||
|
||||
mTestHelper.waitWithLatch(60_000) { latch ->
|
||||
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
Log.d("#TEST", "CAN XS :${aliceSession2.cryptoService().crossSigningService().getMyCrossSigningKeys()}")
|
||||
aliceSession2.cryptoService().crossSigningService().canCrossSign()
|
||||
}
|
||||
}
|
||||
|
||||
// Test that key backup key has been shared to
|
||||
mTestHelper.waitWithLatch(60_000) { latch ->
|
||||
val keysBackupService = aliceSession2.cryptoService().keysBackupService()
|
||||
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
Log.d("#TEST", "Recovery :${ keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey}")
|
||||
keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey == creationInfo.recoveryKey
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.keysbackup
|
||||
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.CryptoTestData
|
||||
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper2
|
||||
|
||||
/**
|
||||
* Data class to store result of [KeysBackupTestHelper.createKeysBackupScenarioWithPassword]
|
||||
*/
|
||||
data class KeysBackupScenarioData(val cryptoTestData: CryptoTestData,
|
||||
val aliceKeys: List<OlmInboundGroupSessionWrapper2>,
|
||||
val prepareKeysBackupDataResult: PrepareKeysBackupDataResult,
|
||||
val aliceSession2: Session) {
|
||||
fun cleanUp(testHelper: CommonTestHelper) {
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
testHelper.signOutAndClose(aliceSession2)
|
||||
}
|
||||
}
|
@@ -20,28 +20,19 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||
import im.vector.matrix.android.api.listeners.StepProgressListener
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.CryptoTestData
|
||||
import im.vector.matrix.android.common.CryptoTestHelper
|
||||
import im.vector.matrix.android.common.SessionTestParams
|
||||
import im.vector.matrix.android.common.TestConstants
|
||||
import im.vector.matrix.android.common.TestMatrixCallback
|
||||
import im.vector.matrix.android.common.assertDictEquals
|
||||
import im.vector.matrix.android.common.assertListEquals
|
||||
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
||||
import im.vector.matrix.android.internal.crypto.MegolmSessionData
|
||||
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
|
||||
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
||||
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotNull
|
||||
@@ -62,9 +53,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
|
||||
private val mTestHelper = CommonTestHelper(context())
|
||||
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
|
||||
|
||||
private val defaultSessionParams = SessionTestParams(withInitialSync = false)
|
||||
private val defaultSessionParamsWithInitialSync = SessionTestParams(withInitialSync = true)
|
||||
private val mKeysBackupTestHelper = KeysBackupTestHelper(mTestHelper, mCryptoTestHelper)
|
||||
|
||||
/**
|
||||
* - From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys
|
||||
@@ -111,7 +100,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
*/
|
||||
@Test
|
||||
fun prepareKeysBackupVersionTest() {
|
||||
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams)
|
||||
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams)
|
||||
|
||||
assertNotNull(bobSession.cryptoService().keysBackupService())
|
||||
|
||||
@@ -140,7 +129,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
*/
|
||||
@Test
|
||||
fun createKeysBackupVersionTest() {
|
||||
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams)
|
||||
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams)
|
||||
|
||||
val keysBackup = bobSession.cryptoService().keysBackupService()
|
||||
|
||||
@@ -183,7 +172,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
|
||||
val stateObserver = StateObserver(keysBackup, latch, 5)
|
||||
|
||||
prepareAndCreateKeysBackupData(keysBackup)
|
||||
mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||
|
||||
mTestHelper.await(latch)
|
||||
|
||||
@@ -217,7 +206,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
|
||||
val stateObserver = StateObserver(keysBackup)
|
||||
|
||||
prepareAndCreateKeysBackupData(keysBackup)
|
||||
mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||
|
||||
// Check that backupAllGroupSessions returns valid data
|
||||
val nbOfKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)
|
||||
@@ -264,7 +253,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
// - Pick a megolm key
|
||||
val session = keysBackup.store.inboundGroupSessionsToBackup(1)[0]
|
||||
|
||||
val keyBackupCreationInfo = prepareAndCreateKeysBackupData(keysBackup).megolmBackupCreationInfo
|
||||
val keyBackupCreationInfo = mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup).megolmBackupCreationInfo
|
||||
|
||||
// - Check encryptGroupSession() returns stg
|
||||
val keyBackupData = keysBackup.encryptGroupSession(session)
|
||||
@@ -282,7 +271,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
decryption!!)
|
||||
assertNotNull(sessionData)
|
||||
// - Compare the decrypted megolm key with the original one
|
||||
assertKeysEquals(session.exportKeys(), sessionData)
|
||||
mKeysBackupTestHelper.assertKeysEquals(session.exportKeys(), sessionData)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
@@ -296,7 +285,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
*/
|
||||
@Test
|
||||
fun restoreKeysBackupTest() {
|
||||
val testData = createKeysBackupScenarioWithPassword(null)
|
||||
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||
|
||||
// - Restore the e2e backup from the homeserver
|
||||
val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
|
||||
@@ -309,7 +298,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
)
|
||||
}
|
||||
|
||||
checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
||||
mKeysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
||||
|
||||
testData.cleanUp(mTestHelper)
|
||||
}
|
||||
@@ -326,46 +315,46 @@ class KeysBackupTest : InstrumentedTest {
|
||||
* - Restore must be successful
|
||||
* - *** There must be no more pending key share requests
|
||||
*/
|
||||
@Test
|
||||
fun restoreKeysBackupAndKeyShareRequestTest() {
|
||||
fail("Check with Valere for this test. I think we do not send key share request")
|
||||
|
||||
val testData = createKeysBackupScenarioWithPassword(null)
|
||||
|
||||
// - Check the SDK sent key share requests
|
||||
val cryptoStore2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store
|
||||
val unsentRequest = cryptoStore2
|
||||
.getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.UNSENT))
|
||||
val sentRequest = cryptoStore2
|
||||
.getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.SENT))
|
||||
|
||||
// Request is either sent or unsent
|
||||
assertTrue(unsentRequest != null || sentRequest != null)
|
||||
|
||||
// - Restore the e2e backup from the homeserver
|
||||
val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
||||
|
||||
// - There must be no more pending key share requests
|
||||
val unsentRequestAfterRestoration = cryptoStore2
|
||||
.getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.UNSENT))
|
||||
val sentRequestAfterRestoration = cryptoStore2
|
||||
.getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.SENT))
|
||||
|
||||
// Request is either sent or unsent
|
||||
assertTrue(unsentRequestAfterRestoration == null && sentRequestAfterRestoration == null)
|
||||
|
||||
testData.cleanUp(mTestHelper)
|
||||
}
|
||||
// @Test
|
||||
// fun restoreKeysBackupAndKeyShareRequestTest() {
|
||||
// fail("Check with Valere for this test. I think we do not send key share request")
|
||||
//
|
||||
// val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||
//
|
||||
// // - Check the SDK sent key share requests
|
||||
// val cryptoStore2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store
|
||||
// val unsentRequest = cryptoStore2
|
||||
// .getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.UNSENT))
|
||||
// val sentRequest = cryptoStore2
|
||||
// .getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.SENT))
|
||||
//
|
||||
// // Request is either sent or unsent
|
||||
// assertTrue(unsentRequest != null || sentRequest != null)
|
||||
//
|
||||
// // - Restore the e2e backup from the homeserver
|
||||
// val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
|
||||
// testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
// testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
||||
// null,
|
||||
// null,
|
||||
// null,
|
||||
// it
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// mKeysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
||||
//
|
||||
// // - There must be no more pending key share requests
|
||||
// val unsentRequestAfterRestoration = cryptoStore2
|
||||
// .getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.UNSENT))
|
||||
// val sentRequestAfterRestoration = cryptoStore2
|
||||
// .getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.SENT))
|
||||
//
|
||||
// // Request is either sent or unsent
|
||||
// assertTrue(unsentRequestAfterRestoration == null && sentRequestAfterRestoration == null)
|
||||
//
|
||||
// testData.cleanUp(mTestHelper)
|
||||
// }
|
||||
|
||||
/**
|
||||
* - Do an e2e backup to the homeserver with a recovery key
|
||||
@@ -381,7 +370,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
fun trustKeyBackupVersionTest() {
|
||||
// - Do an e2e backup to the homeserver with a recovery key
|
||||
// - And log Alice on a new device
|
||||
val testData = createKeysBackupScenarioWithPassword(null)
|
||||
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||
|
||||
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
|
||||
|
||||
@@ -400,7 +389,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
}
|
||||
|
||||
// Wait for backup state to be ReadyToBackUp
|
||||
waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
|
||||
mKeysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
|
||||
|
||||
// - Backup must be enabled on the new device, on the same version
|
||||
assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version)
|
||||
@@ -440,7 +429,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
fun trustKeyBackupVersionWithRecoveryKeyTest() {
|
||||
// - Do an e2e backup to the homeserver with a recovery key
|
||||
// - And log Alice on a new device
|
||||
val testData = createKeysBackupScenarioWithPassword(null)
|
||||
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||
|
||||
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
|
||||
|
||||
@@ -459,7 +448,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
}
|
||||
|
||||
// Wait for backup state to be ReadyToBackUp
|
||||
waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
|
||||
mKeysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
|
||||
|
||||
// - Backup must be enabled on the new device, on the same version
|
||||
assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version)
|
||||
@@ -497,7 +486,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
fun trustKeyBackupVersionWithWrongRecoveryKeyTest() {
|
||||
// - Do an e2e backup to the homeserver with a recovery key
|
||||
// - And log Alice on a new device
|
||||
val testData = createKeysBackupScenarioWithPassword(null)
|
||||
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||
|
||||
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
|
||||
|
||||
@@ -540,7 +529,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
|
||||
// - Do an e2e backup to the homeserver with a password
|
||||
// - And log Alice on a new device
|
||||
val testData = createKeysBackupScenarioWithPassword(password)
|
||||
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
|
||||
|
||||
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
|
||||
|
||||
@@ -559,7 +548,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
}
|
||||
|
||||
// Wait for backup state to be ReadyToBackUp
|
||||
waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
|
||||
mKeysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
|
||||
|
||||
// - Backup must be enabled on the new device, on the same version
|
||||
assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version)
|
||||
@@ -600,7 +589,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
|
||||
// - Do an e2e backup to the homeserver with a password
|
||||
// - And log Alice on a new device
|
||||
val testData = createKeysBackupScenarioWithPassword(password)
|
||||
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
|
||||
|
||||
val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
|
||||
|
||||
@@ -635,7 +624,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
*/
|
||||
@Test
|
||||
fun restoreKeysBackupWithAWrongRecoveryKeyTest() {
|
||||
val testData = createKeysBackupScenarioWithPassword(null)
|
||||
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||
|
||||
// - Try to restore the e2e backup with a wrong recovery key
|
||||
val latch2 = CountDownLatch(1)
|
||||
@@ -670,7 +659,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
fun testBackupWithPassword() {
|
||||
val password = "password"
|
||||
|
||||
val testData = createKeysBackupScenarioWithPassword(password)
|
||||
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
|
||||
|
||||
// - Restore the e2e backup with the password
|
||||
val steps = ArrayList<StepProgressListener.Step>()
|
||||
@@ -710,7 +699,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
assertEquals(50, (steps[103] as StepProgressListener.Step.ImportingKey).progress)
|
||||
assertEquals(100, (steps[104] as StepProgressListener.Step.ImportingKey).progress)
|
||||
|
||||
checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
||||
mKeysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
||||
|
||||
testData.cleanUp(mTestHelper)
|
||||
}
|
||||
@@ -726,7 +715,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
val password = "password"
|
||||
val wrongPassword = "passw0rd"
|
||||
|
||||
val testData = createKeysBackupScenarioWithPassword(password)
|
||||
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
|
||||
|
||||
// - Try to restore the e2e backup with a wrong password
|
||||
val latch2 = CountDownLatch(1)
|
||||
@@ -761,7 +750,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
fun testUseRecoveryKeyToRestoreAPasswordBasedKeysBackup() {
|
||||
val password = "password"
|
||||
|
||||
val testData = createKeysBackupScenarioWithPassword(password)
|
||||
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
|
||||
|
||||
// - Restore the e2e backup with the recovery key.
|
||||
val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
|
||||
@@ -774,7 +763,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
)
|
||||
}
|
||||
|
||||
checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
||||
mKeysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
||||
|
||||
testData.cleanUp(mTestHelper)
|
||||
}
|
||||
@@ -787,7 +776,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
*/
|
||||
@Test
|
||||
fun testUsePasswordToRestoreARecoveryKeyBasedKeysBackup() {
|
||||
val testData = createKeysBackupScenarioWithPassword(null)
|
||||
val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||
|
||||
// - Try to restore the e2e backup with a password
|
||||
val latch2 = CountDownLatch(1)
|
||||
@@ -826,7 +815,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
val stateObserver = StateObserver(keysBackup)
|
||||
|
||||
// - Do an e2e backup to the homeserver
|
||||
prepareAndCreateKeysBackupData(keysBackup)
|
||||
mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||
|
||||
// Get key backup version from the home server
|
||||
val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> {
|
||||
@@ -846,7 +835,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
assertTrue(signature.valid)
|
||||
assertNotNull(signature.device)
|
||||
assertEquals(cryptoTestData.firstSession.cryptoService().getMyDevice().deviceId, signature.deviceId)
|
||||
assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.credentials.deviceId)
|
||||
assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.deviceId)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
@@ -871,13 +860,13 @@ class KeysBackupTest : InstrumentedTest {
|
||||
|
||||
assertFalse(keysBackup.isEnabled)
|
||||
|
||||
val keyBackupCreationInfo = prepareAndCreateKeysBackupData(keysBackup)
|
||||
val keyBackupCreationInfo = mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||
|
||||
assertTrue(keysBackup.isEnabled)
|
||||
|
||||
// - Restart alice session
|
||||
// - Log Alice on a new device
|
||||
val aliceSession2 = mTestHelper.logIntoAccount(cryptoTestData.firstSession.myUserId, defaultSessionParamsWithInitialSync)
|
||||
val aliceSession2 = mTestHelper.logIntoAccount(cryptoTestData.firstSession.myUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
|
||||
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
|
||||
@@ -951,7 +940,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
})
|
||||
|
||||
// - Make alice back up her keys to her homeserver
|
||||
prepareAndCreateKeysBackupData(keysBackup)
|
||||
mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||
|
||||
assertTrue(keysBackup.isEnabled)
|
||||
|
||||
@@ -1001,19 +990,19 @@ class KeysBackupTest : InstrumentedTest {
|
||||
val stateObserver = StateObserver(keysBackup)
|
||||
|
||||
// - Make alice back up her keys to her homeserver
|
||||
prepareAndCreateKeysBackupData(keysBackup)
|
||||
mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||
|
||||
// Wait for keys backup to finish by asking again to backup keys.
|
||||
mTestHelper.doSync<Unit> {
|
||||
keysBackup.backupAllGroupSessions(null, it)
|
||||
}
|
||||
|
||||
val oldDeviceId = cryptoTestData.firstSession.sessionParams.credentials.deviceId!!
|
||||
val oldDeviceId = cryptoTestData.firstSession.sessionParams.deviceId!!
|
||||
val oldKeyBackupVersion = keysBackup.currentBackupVersion
|
||||
val aliceUserId = cryptoTestData.firstSession.myUserId
|
||||
|
||||
// - Log Alice on a new device
|
||||
val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, defaultSessionParamsWithInitialSync)
|
||||
val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
|
||||
|
||||
// - Post a message to have a new megolm session
|
||||
aliceSession2.cryptoService().setWarnOnUnknownDevices(false)
|
||||
@@ -1094,7 +1083,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
|
||||
assertFalse(keysBackup.isEnabled)
|
||||
|
||||
val keyBackupCreationInfo = prepareAndCreateKeysBackupData(keysBackup)
|
||||
val keyBackupCreationInfo = mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||
|
||||
assertTrue(keysBackup.isEnabled)
|
||||
|
||||
@@ -1107,169 +1096,4 @@ class KeysBackupTest : InstrumentedTest {
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* Private
|
||||
* ========================================================================================== */
|
||||
|
||||
/**
|
||||
* As KeysBackup is doing asynchronous call to update its internal state, this method help to wait for the
|
||||
* KeysBackup object to be in the specified state
|
||||
*/
|
||||
private fun waitForKeysBackupToBeInState(session: Session, state: KeysBackupState) {
|
||||
// If already in the wanted state, return
|
||||
if (session.cryptoService().keysBackupService().state == state) {
|
||||
return
|
||||
}
|
||||
|
||||
// Else observe state changes
|
||||
val latch = CountDownLatch(1)
|
||||
|
||||
session.cryptoService().keysBackupService().addListener(object : KeysBackupStateListener {
|
||||
override fun onStateChange(newState: KeysBackupState) {
|
||||
if (newState == state) {
|
||||
session.cryptoService().keysBackupService().removeListener(this)
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
mTestHelper.await(latch)
|
||||
}
|
||||
|
||||
private data class PrepareKeysBackupDataResult(val megolmBackupCreationInfo: MegolmBackupCreationInfo,
|
||||
val version: String)
|
||||
|
||||
private fun prepareAndCreateKeysBackupData(keysBackup: KeysBackupService,
|
||||
password: String? = null): PrepareKeysBackupDataResult {
|
||||
val stateObserver = StateObserver(keysBackup)
|
||||
|
||||
val megolmBackupCreationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> {
|
||||
keysBackup.prepareKeysBackupVersion(password, null, it)
|
||||
}
|
||||
|
||||
assertNotNull(megolmBackupCreationInfo)
|
||||
|
||||
assertFalse(keysBackup.isEnabled)
|
||||
|
||||
// Create the version
|
||||
val keysVersion = mTestHelper.doSync<KeysVersion> {
|
||||
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it)
|
||||
}
|
||||
|
||||
assertNotNull(keysVersion.version)
|
||||
|
||||
// Backup must be enable now
|
||||
assertTrue(keysBackup.isEnabled)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
return PrepareKeysBackupDataResult(megolmBackupCreationInfo, keysVersion.version!!)
|
||||
}
|
||||
|
||||
private fun assertKeysEquals(keys1: MegolmSessionData?, keys2: MegolmSessionData?) {
|
||||
assertNotNull(keys1)
|
||||
assertNotNull(keys2)
|
||||
|
||||
assertEquals(keys1?.algorithm, keys2?.algorithm)
|
||||
assertEquals(keys1?.roomId, keys2?.roomId)
|
||||
// No need to compare the shortcut
|
||||
// assertEquals(keys1?.sender_claimed_ed25519_key, keys2?.sender_claimed_ed25519_key)
|
||||
assertEquals(keys1?.senderKey, keys2?.senderKey)
|
||||
assertEquals(keys1?.sessionId, keys2?.sessionId)
|
||||
assertEquals(keys1?.sessionKey, keys2?.sessionKey)
|
||||
|
||||
assertListEquals(keys1?.forwardingCurve25519KeyChain, keys2?.forwardingCurve25519KeyChain)
|
||||
assertDictEquals(keys1?.senderClaimedKeys, keys2?.senderClaimedKeys)
|
||||
}
|
||||
|
||||
/**
|
||||
* Data class to store result of [createKeysBackupScenarioWithPassword]
|
||||
*/
|
||||
private data class KeysBackupScenarioData(val cryptoTestData: CryptoTestData,
|
||||
val aliceKeys: List<OlmInboundGroupSessionWrapper>,
|
||||
val prepareKeysBackupDataResult: PrepareKeysBackupDataResult,
|
||||
val aliceSession2: Session) {
|
||||
fun cleanUp(testHelper: CommonTestHelper) {
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
testHelper.signOutAndClose(aliceSession2)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common initial condition
|
||||
* - Do an e2e backup to the homeserver
|
||||
* - Log Alice on a new device, and wait for its keysBackup object to be ready (in state NotTrusted)
|
||||
*
|
||||
* @param password optional password
|
||||
*/
|
||||
private fun createKeysBackupScenarioWithPassword(password: String?): KeysBackupScenarioData {
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
|
||||
|
||||
val cryptoStore = (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store
|
||||
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
|
||||
|
||||
val stateObserver = StateObserver(keysBackup)
|
||||
|
||||
val aliceKeys = cryptoStore.inboundGroupSessionsToBackup(100)
|
||||
|
||||
// - Do an e2e backup to the homeserver
|
||||
val prepareKeysBackupDataResult = prepareAndCreateKeysBackupData(keysBackup, password)
|
||||
|
||||
var lastProgress = 0
|
||||
var lastTotal = 0
|
||||
mTestHelper.doSync<Unit> {
|
||||
keysBackup.backupAllGroupSessions(object : ProgressListener {
|
||||
override fun onProgress(progress: Int, total: Int) {
|
||||
lastProgress = progress
|
||||
lastTotal = total
|
||||
}
|
||||
}, it)
|
||||
}
|
||||
|
||||
assertEquals(2, lastProgress)
|
||||
assertEquals(2, lastTotal)
|
||||
|
||||
val aliceUserId = cryptoTestData.firstSession.myUserId
|
||||
|
||||
// - Log Alice on a new device
|
||||
val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, defaultSessionParamsWithInitialSync)
|
||||
|
||||
// Test check: aliceSession2 has no keys at login
|
||||
assertEquals(0, aliceSession2.cryptoService().inboundGroupSessionsCount(false))
|
||||
|
||||
// Wait for backup state to be NotTrusted
|
||||
waitForKeysBackupToBeInState(aliceSession2, KeysBackupState.NotTrusted)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
|
||||
return KeysBackupScenarioData(cryptoTestData,
|
||||
aliceKeys,
|
||||
prepareKeysBackupDataResult,
|
||||
aliceSession2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Common restore success check after [createKeysBackupScenarioWithPassword]:
|
||||
* - Imported keys number must be correct
|
||||
* - The new device must have the same count of megolm keys
|
||||
* - Alice must have the same keys on both devices
|
||||
*/
|
||||
private fun checkRestoreSuccess(testData: KeysBackupScenarioData,
|
||||
total: Int,
|
||||
imported: Int) {
|
||||
// - Imported keys number must be correct
|
||||
assertEquals(testData.aliceKeys.size, total)
|
||||
assertEquals(total, imported)
|
||||
|
||||
// - The new device must have the same count of megolm keys
|
||||
assertEquals(testData.aliceKeys.size, testData.aliceSession2.cryptoService().inboundGroupSessionsCount(false))
|
||||
|
||||
// - Alice must have the same keys on both devices
|
||||
for (aliceKey1 in testData.aliceKeys) {
|
||||
val aliceKey2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store
|
||||
.getInboundGroupSession(aliceKey1.olmInboundGroupSession!!.sessionIdentifier(), aliceKey1.senderKey!!)
|
||||
assertNotNull(aliceKey2)
|
||||
assertKeysEquals(aliceKey1.exportKeys(), aliceKey2!!.exportKeys())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.keysbackup
|
||||
|
||||
import im.vector.matrix.android.common.SessionTestParams
|
||||
|
||||
object KeysBackupTestConstants {
|
||||
val defaultSessionParams = SessionTestParams(withInitialSync = false)
|
||||
val defaultSessionParamsWithInitialSync = SessionTestParams(withInitialSync = true)
|
||||
}
|
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.keysbackup
|
||||
|
||||
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.CryptoTestHelper
|
||||
import im.vector.matrix.android.common.assertDictEquals
|
||||
import im.vector.matrix.android.common.assertListEquals
|
||||
import im.vector.matrix.android.internal.crypto.MegolmSessionData
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion
|
||||
import org.junit.Assert
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
class KeysBackupTestHelper(
|
||||
private val mTestHelper: CommonTestHelper,
|
||||
private val mCryptoTestHelper: CryptoTestHelper) {
|
||||
|
||||
/**
|
||||
* Common initial condition
|
||||
* - Do an e2e backup to the homeserver
|
||||
* - Log Alice on a new device, and wait for its keysBackup object to be ready (in state NotTrusted)
|
||||
*
|
||||
* @param password optional password
|
||||
*/
|
||||
fun createKeysBackupScenarioWithPassword(password: String?): KeysBackupScenarioData {
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
|
||||
|
||||
val cryptoStore = (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store
|
||||
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
|
||||
|
||||
val stateObserver = StateObserver(keysBackup)
|
||||
|
||||
val aliceKeys = cryptoStore.inboundGroupSessionsToBackup(100)
|
||||
|
||||
// - Do an e2e backup to the homeserver
|
||||
val prepareKeysBackupDataResult = prepareAndCreateKeysBackupData(keysBackup, password)
|
||||
|
||||
var lastProgress = 0
|
||||
var lastTotal = 0
|
||||
mTestHelper.doSync<Unit> {
|
||||
keysBackup.backupAllGroupSessions(object : ProgressListener {
|
||||
override fun onProgress(progress: Int, total: Int) {
|
||||
lastProgress = progress
|
||||
lastTotal = total
|
||||
}
|
||||
}, it)
|
||||
}
|
||||
|
||||
Assert.assertEquals(2, lastProgress)
|
||||
Assert.assertEquals(2, lastTotal)
|
||||
|
||||
val aliceUserId = cryptoTestData.firstSession.myUserId
|
||||
|
||||
// - Log Alice on a new device
|
||||
val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
|
||||
|
||||
// Test check: aliceSession2 has no keys at login
|
||||
Assert.assertEquals(0, aliceSession2.cryptoService().inboundGroupSessionsCount(false))
|
||||
|
||||
// Wait for backup state to be NotTrusted
|
||||
waitForKeysBackupToBeInState(aliceSession2, KeysBackupState.NotTrusted)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
|
||||
return KeysBackupScenarioData(cryptoTestData,
|
||||
aliceKeys,
|
||||
prepareKeysBackupDataResult,
|
||||
aliceSession2)
|
||||
}
|
||||
|
||||
fun prepareAndCreateKeysBackupData(keysBackup: KeysBackupService,
|
||||
password: String? = null): PrepareKeysBackupDataResult {
|
||||
val stateObserver = StateObserver(keysBackup)
|
||||
|
||||
val megolmBackupCreationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> {
|
||||
keysBackup.prepareKeysBackupVersion(password, null, it)
|
||||
}
|
||||
|
||||
Assert.assertNotNull(megolmBackupCreationInfo)
|
||||
|
||||
Assert.assertFalse(keysBackup.isEnabled)
|
||||
|
||||
// Create the version
|
||||
val keysVersion = mTestHelper.doSync<KeysVersion> {
|
||||
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it)
|
||||
}
|
||||
|
||||
Assert.assertNotNull(keysVersion.version)
|
||||
|
||||
// Backup must be enable now
|
||||
Assert.assertTrue(keysBackup.isEnabled)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
return PrepareKeysBackupDataResult(megolmBackupCreationInfo, keysVersion.version!!)
|
||||
}
|
||||
|
||||
/**
|
||||
* As KeysBackup is doing asynchronous call to update its internal state, this method help to wait for the
|
||||
* KeysBackup object to be in the specified state
|
||||
*/
|
||||
fun waitForKeysBackupToBeInState(session: Session, state: KeysBackupState) {
|
||||
// If already in the wanted state, return
|
||||
if (session.cryptoService().keysBackupService().state == state) {
|
||||
return
|
||||
}
|
||||
|
||||
// Else observe state changes
|
||||
val latch = CountDownLatch(1)
|
||||
|
||||
session.cryptoService().keysBackupService().addListener(object : KeysBackupStateListener {
|
||||
override fun onStateChange(newState: KeysBackupState) {
|
||||
if (newState == state) {
|
||||
session.cryptoService().keysBackupService().removeListener(this)
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
mTestHelper.await(latch)
|
||||
}
|
||||
|
||||
fun assertKeysEquals(keys1: MegolmSessionData?, keys2: MegolmSessionData?) {
|
||||
Assert.assertNotNull(keys1)
|
||||
Assert.assertNotNull(keys2)
|
||||
|
||||
Assert.assertEquals(keys1?.algorithm, keys2?.algorithm)
|
||||
Assert.assertEquals(keys1?.roomId, keys2?.roomId)
|
||||
// No need to compare the shortcut
|
||||
// assertEquals(keys1?.sender_claimed_ed25519_key, keys2?.sender_claimed_ed25519_key)
|
||||
Assert.assertEquals(keys1?.senderKey, keys2?.senderKey)
|
||||
Assert.assertEquals(keys1?.sessionId, keys2?.sessionId)
|
||||
Assert.assertEquals(keys1?.sessionKey, keys2?.sessionKey)
|
||||
|
||||
assertListEquals(keys1?.forwardingCurve25519KeyChain, keys2?.forwardingCurve25519KeyChain)
|
||||
assertDictEquals(keys1?.senderClaimedKeys, keys2?.senderClaimedKeys)
|
||||
}
|
||||
|
||||
/**
|
||||
* Common restore success check after [KeysBackupTestHelper.createKeysBackupScenarioWithPassword]:
|
||||
* - Imported keys number must be correct
|
||||
* - The new device must have the same count of megolm keys
|
||||
* - Alice must have the same keys on both devices
|
||||
*/
|
||||
fun checkRestoreSuccess(testData: KeysBackupScenarioData,
|
||||
total: Int,
|
||||
imported: Int) {
|
||||
// - Imported keys number must be correct
|
||||
Assert.assertEquals(testData.aliceKeys.size, total)
|
||||
Assert.assertEquals(total, imported)
|
||||
|
||||
// - The new device must have the same count of megolm keys
|
||||
Assert.assertEquals(testData.aliceKeys.size, testData.aliceSession2.cryptoService().inboundGroupSessionsCount(false))
|
||||
|
||||
// - Alice must have the same keys on both devices
|
||||
for (aliceKey1 in testData.aliceKeys) {
|
||||
val aliceKey2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store
|
||||
.getInboundGroupSession(aliceKey1.olmInboundGroupSession!!.sessionIdentifier(), aliceKey1.senderKey!!)
|
||||
Assert.assertNotNull(aliceKey2)
|
||||
assertKeysEquals(aliceKey1.exportKeys(), aliceKey2!!.exportKeys())
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.keysbackup
|
||||
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
||||
|
||||
data class PrepareKeysBackupDataResult(val megolmBackupCreationInfo: MegolmBackupCreationInfo,
|
||||
val version: String)
|
@@ -71,7 +71,7 @@ class QuadSTests : InstrumentedTest {
|
||||
val TEST_KEY_ID = "my.test.Key"
|
||||
|
||||
mTestHelper.doSync<SsssKeyCreationInfo> {
|
||||
quadS.generateKey(TEST_KEY_ID, "Test Key", emptyKeySigner, it)
|
||||
quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner, it)
|
||||
}
|
||||
|
||||
// Assert Account data is updated
|
||||
@@ -177,7 +177,7 @@ class QuadSTests : InstrumentedTest {
|
||||
val TEST_KEY_ID = "my.test.Key"
|
||||
|
||||
mTestHelper.doSync<SsssKeyCreationInfo> {
|
||||
quadS.generateKey(TEST_KEY_ID, "Test Key", emptyKeySigner, it)
|
||||
quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner, it)
|
||||
}
|
||||
|
||||
// Test that we don't need to wait for an account data sync to access directly the keyid from DB
|
||||
@@ -322,7 +322,7 @@ class QuadSTests : InstrumentedTest {
|
||||
val quadS = session.sharedSecretStorageService
|
||||
|
||||
val creationInfo = mTestHelper.doSync<SsssKeyCreationInfo> {
|
||||
quadS.generateKey(keyId, keyId, emptyKeySigner, it)
|
||||
quadS.generateKey(keyId, null, keyId, emptyKeySigner, it)
|
||||
}
|
||||
|
||||
assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId")
|
||||
|
@@ -16,6 +16,7 @@
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.verification
|
||||
|
||||
import android.util.Log
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
@@ -23,6 +24,7 @@ import im.vector.matrix.android.api.session.crypto.verification.CancelCode
|
||||
import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.verification.OutgoingSasVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.verification.SasMode
|
||||
import im.vector.matrix.android.api.session.crypto.verification.SasVerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationService
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction
|
||||
@@ -355,6 +357,7 @@ class SASTest : InstrumentedTest {
|
||||
val aliceAcceptedLatch = CountDownLatch(1)
|
||||
val aliceListener = object : VerificationService.Listener {
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
Log.v("TEST", "== aliceTx state ${tx.state} => ${(tx as? OutgoingSasVerificationTransaction)?.uxState}")
|
||||
if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnAccepted) {
|
||||
val at = tx as SASDefaultVerificationTransaction
|
||||
accepted = at.accepted
|
||||
@@ -367,7 +370,9 @@ class SASTest : InstrumentedTest {
|
||||
|
||||
val bobListener = object : VerificationService.Listener {
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
Log.v("TEST", "== bobTx state ${tx.state} => ${(tx as? IncomingSasVerificationTransaction)?.uxState}")
|
||||
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
||||
bobVerificationService.removeListener(this)
|
||||
val at = tx as IncomingSasVerificationTransaction
|
||||
at.performAccept()
|
||||
}
|
||||
@@ -463,14 +468,19 @@ class SASTest : InstrumentedTest {
|
||||
|
||||
val aliceSASLatch = CountDownLatch(1)
|
||||
val aliceListener = object : VerificationService.Listener {
|
||||
var matchOnce = true
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
|
||||
Log.v("TEST", "== aliceState ${uxState.name}")
|
||||
when (uxState) {
|
||||
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
tx.userHasVerifiedShortCode()
|
||||
}
|
||||
OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||
aliceSASLatch.countDown()
|
||||
if (matchOnce) {
|
||||
matchOnce = false
|
||||
aliceSASLatch.countDown()
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
@@ -480,14 +490,23 @@ class SASTest : InstrumentedTest {
|
||||
|
||||
val bobSASLatch = CountDownLatch(1)
|
||||
val bobListener = object : VerificationService.Listener {
|
||||
var acceptOnce = true
|
||||
var matchOnce = true
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
val uxState = (tx as IncomingSasVerificationTransaction).uxState
|
||||
Log.v("TEST", "== bobState ${uxState.name}")
|
||||
when (uxState) {
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
||||
tx.performAccept()
|
||||
if (acceptOnce) {
|
||||
acceptOnce = false
|
||||
tx.performAccept()
|
||||
}
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
tx.userHasVerifiedShortCode()
|
||||
if (matchOnce) {
|
||||
matchOnce = false
|
||||
tx.userHasVerifiedShortCode()
|
||||
}
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||
bobSASLatch.countDown()
|
||||
@@ -515,4 +534,96 @@ class SASTest : InstrumentedTest {
|
||||
assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_ConcurrentStart() {
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession
|
||||
|
||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
||||
|
||||
val req = aliceVerificationService.requestKeyVerificationInDMs(
|
||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||
bobSession.myUserId,
|
||||
cryptoTestData.roomId
|
||||
)
|
||||
|
||||
var requestID : String? = null
|
||||
|
||||
mTestHelper.waitWithLatch {
|
||||
mTestHelper.retryPeriodicallyWithLatch(it) {
|
||||
val prAlicePOV = aliceVerificationService.getExistingVerificationRequest(bobSession.myUserId)?.firstOrNull()
|
||||
requestID = prAlicePOV?.transactionId
|
||||
Log.v("TEST", "== alicePOV is $prAlicePOV")
|
||||
prAlicePOV?.transactionId != null && prAlicePOV.localId == req.localId
|
||||
}
|
||||
}
|
||||
|
||||
Log.v("TEST", "== requestID is $requestID")
|
||||
|
||||
mTestHelper.waitWithLatch {
|
||||
mTestHelper.retryPeriodicallyWithLatch(it) {
|
||||
val prBobPOV = bobVerificationService.getExistingVerificationRequest(aliceSession.myUserId)?.firstOrNull()
|
||||
Log.v("TEST", "== prBobPOV is $prBobPOV")
|
||||
prBobPOV?.transactionId == requestID
|
||||
}
|
||||
}
|
||||
|
||||
bobVerificationService.readyPendingVerification(
|
||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||
aliceSession.myUserId,
|
||||
requestID!!
|
||||
)
|
||||
|
||||
// wait for alice to get the ready
|
||||
mTestHelper.waitWithLatch {
|
||||
mTestHelper.retryPeriodicallyWithLatch(it) {
|
||||
val prAlicePOV = aliceVerificationService.getExistingVerificationRequest(bobSession.myUserId)?.firstOrNull()
|
||||
Log.v("TEST", "== prAlicePOV is $prAlicePOV")
|
||||
prAlicePOV?.transactionId == requestID && prAlicePOV?.isReady != null
|
||||
}
|
||||
}
|
||||
|
||||
// Start concurrent!
|
||||
aliceVerificationService.beginKeyVerificationInDMs(
|
||||
VerificationMethod.SAS,
|
||||
requestID!!,
|
||||
cryptoTestData.roomId,
|
||||
bobSession.myUserId,
|
||||
bobSession.sessionParams.deviceId!!,
|
||||
null)
|
||||
|
||||
bobVerificationService.beginKeyVerificationInDMs(
|
||||
VerificationMethod.SAS,
|
||||
requestID!!,
|
||||
cryptoTestData.roomId,
|
||||
aliceSession.myUserId,
|
||||
aliceSession.sessionParams.deviceId!!,
|
||||
null)
|
||||
|
||||
// we should reach SHOW SAS on both
|
||||
var alicePovTx: SasVerificationTransaction?
|
||||
var bobPovTx: SasVerificationTransaction?
|
||||
|
||||
mTestHelper.waitWithLatch {
|
||||
mTestHelper.retryPeriodicallyWithLatch(it) {
|
||||
alicePovTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, requestID!!) as? SasVerificationTransaction
|
||||
Log.v("TEST", "== alicePovTx is $alicePovTx")
|
||||
alicePovTx?.state == VerificationTxState.ShortCodeReady
|
||||
}
|
||||
}
|
||||
// wait for alice to get the ready
|
||||
mTestHelper.waitWithLatch {
|
||||
mTestHelper.retryPeriodicallyWithLatch(it) {
|
||||
bobPovTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, requestID!!) as? SasVerificationTransaction
|
||||
Log.v("TEST", "== bobPovTx is $bobPovTx")
|
||||
bobPovTx?.state == VerificationTxState.ShortCodeReady
|
||||
}
|
||||
}
|
||||
|
||||
cryptoTestData.cleanUp(mTestHelper)
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.session.room.timeline
|
||||
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.api.extensions.orFalse
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.CryptoTestHelper
|
||||
import im.vector.matrix.android.common.checkSendOrder
|
||||
import org.amshove.kluent.shouldBeFalse
|
||||
import org.amshove.kluent.shouldBeTrue
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.junit.runners.MethodSorters
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class TimelineBackToPreviousLastForwardTest : InstrumentedTest {
|
||||
|
||||
private val commonTestHelper = CommonTestHelper(context())
|
||||
private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
|
||||
/**
|
||||
* This test ensure that if we have a chunk in the timeline which is due to a sync, and we click to permalink of an
|
||||
* even contained in a previous lastForward chunk, we will be able to go back to the live
|
||||
*/
|
||||
@Test
|
||||
fun backToPreviousLastForwardTest() {
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession!!
|
||||
val aliceRoomId = cryptoTestData.roomId
|
||||
|
||||
aliceSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
bobSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
|
||||
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
|
||||
val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
|
||||
|
||||
val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(30))
|
||||
bobTimeline.start()
|
||||
|
||||
var roomCreationEventId: String? = null
|
||||
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root}")
|
||||
}
|
||||
|
||||
roomCreationEventId = snapshot.lastOrNull()?.root?.eventId
|
||||
// Ok, we have the 8 first messages of the initial sync (room creation and bob join event)
|
||||
snapshot.size == 8
|
||||
}
|
||||
|
||||
bobTimeline.addListener(eventsListener)
|
||||
commonTestHelper.await(lock)
|
||||
bobTimeline.removeAllListeners()
|
||||
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
|
||||
}
|
||||
|
||||
// Bob stop to sync
|
||||
bobSession.stopSync()
|
||||
|
||||
val messageRoot = "First messages from Alice"
|
||||
|
||||
// Alice sends 30 messages
|
||||
commonTestHelper.sendTextMessage(
|
||||
roomFromAlicePOV,
|
||||
messageRoot,
|
||||
30)
|
||||
|
||||
// Bob start to sync
|
||||
bobSession.startSync(true)
|
||||
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root}")
|
||||
}
|
||||
|
||||
// Ok, we have the 10 last messages from Alice.
|
||||
snapshot.size == 10
|
||||
&& snapshot.all { it.root.content.toModel<MessageContent>()?.body?.startsWith(messageRoot).orFalse() }
|
||||
}
|
||||
|
||||
bobTimeline.addListener(eventsListener)
|
||||
commonTestHelper.await(lock)
|
||||
bobTimeline.removeAllListeners()
|
||||
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeTrue()
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
|
||||
}
|
||||
|
||||
// Bob navigate to the first event (room creation event), so inside the previous last forward chunk
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root}")
|
||||
}
|
||||
|
||||
// The event is in db, so it is fetch and auto pagination occurs, half of the number of events we have for this chunk (?)
|
||||
snapshot.size == 4
|
||||
}
|
||||
|
||||
bobTimeline.addListener(eventsListener)
|
||||
|
||||
// Restart the timeline to the first sent event, which is already in the database, so pagination should start automatically
|
||||
assertTrue(roomFromBobPOV.getTimeLineEvent(roomCreationEventId!!) != null)
|
||||
|
||||
bobTimeline.restartWithEventId(roomCreationEventId)
|
||||
|
||||
commonTestHelper.await(lock)
|
||||
bobTimeline.removeAllListeners()
|
||||
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeTrue()
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
|
||||
}
|
||||
|
||||
// Bob scroll to the future
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root}")
|
||||
}
|
||||
|
||||
// Bob can see the first event of the room (so Back pagination has worked)
|
||||
snapshot.lastOrNull()?.root?.getClearType() == EventType.STATE_ROOM_CREATE
|
||||
// 8 for room creation item, and 30 for the forward pagination
|
||||
&& snapshot.size == 38
|
||||
&& snapshot.checkSendOrder(messageRoot, 30, 0)
|
||||
}
|
||||
|
||||
bobTimeline.addListener(eventsListener)
|
||||
|
||||
bobTimeline.paginate(Timeline.Direction.FORWARDS, 50)
|
||||
|
||||
commonTestHelper.await(lock)
|
||||
bobTimeline.removeAllListeners()
|
||||
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
|
||||
}
|
||||
bobTimeline.dispose()
|
||||
|
||||
cryptoTestData.cleanUp(commonTestHelper)
|
||||
}
|
||||
}
|
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.session.room.timeline
|
||||
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.api.extensions.orFalse
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.CryptoTestHelper
|
||||
import im.vector.matrix.android.common.checkSendOrder
|
||||
import org.amshove.kluent.shouldBeFalse
|
||||
import org.amshove.kluent.shouldBeTrue
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.junit.runners.MethodSorters
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class TimelineForwardPaginationTest : InstrumentedTest {
|
||||
|
||||
private val commonTestHelper = CommonTestHelper(context())
|
||||
private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
|
||||
/**
|
||||
* This test ensure that if we click to permalink, we will be able to go back to the live
|
||||
*/
|
||||
@Test
|
||||
fun forwardPaginationTest() {
|
||||
val numberOfMessagesToSend = 90
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false)
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceRoomId = cryptoTestData.roomId
|
||||
|
||||
aliceSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
|
||||
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
|
||||
|
||||
// Alice sends X messages
|
||||
val message = "Message from Alice"
|
||||
val sentMessages = commonTestHelper.sendTextMessage(
|
||||
roomFromAlicePOV,
|
||||
message,
|
||||
numberOfMessagesToSend)
|
||||
|
||||
// Alice clear the cache
|
||||
commonTestHelper.doSync<Unit> {
|
||||
aliceSession.clearCache(it)
|
||||
}
|
||||
|
||||
// And restarts the sync
|
||||
aliceSession.startSync(true)
|
||||
|
||||
val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(30))
|
||||
aliceTimeline.start()
|
||||
|
||||
// Alice sees the 10 last message of the room, and can only navigate BACKWARD
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Alice timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root.content}")
|
||||
}
|
||||
|
||||
// Ok, we have the 10 last messages of the initial sync
|
||||
snapshot.size == 10
|
||||
&& snapshot.all { it.root.content.toModel<MessageContent>()?.body?.startsWith(message).orFalse() }
|
||||
}
|
||||
|
||||
// Open the timeline at last sent message
|
||||
aliceTimeline.addListener(eventsListener)
|
||||
commonTestHelper.await(lock)
|
||||
aliceTimeline.removeAllListeners()
|
||||
|
||||
aliceTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeTrue()
|
||||
aliceTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
|
||||
}
|
||||
|
||||
// Alice navigates to the first message of the room, which is not in its database. A GET /context is performed
|
||||
// Then she can paginate BACKWARD and FORWARD
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val aliceEventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Alice timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root.content}")
|
||||
}
|
||||
|
||||
// The event is not in db, so it is fetch alone
|
||||
snapshot.size == 1
|
||||
&& snapshot.all { it.root.content.toModel<MessageContent>()?.body?.startsWith("Message from Alice").orFalse() }
|
||||
}
|
||||
|
||||
aliceTimeline.addListener(aliceEventsListener)
|
||||
|
||||
// Restart the timeline to the first sent event
|
||||
aliceTimeline.restartWithEventId(sentMessages.last().eventId)
|
||||
|
||||
commonTestHelper.await(lock)
|
||||
aliceTimeline.removeAllListeners()
|
||||
|
||||
aliceTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeTrue()
|
||||
aliceTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeTrue()
|
||||
}
|
||||
|
||||
// Alice paginates BACKWARD and FORWARD of 50 events each
|
||||
// Then she can only navigate FORWARD
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val aliceEventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Alice timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root.content}")
|
||||
}
|
||||
|
||||
// Alice can see the first event of the room (so Back pagination has worked)
|
||||
snapshot.lastOrNull()?.root?.getClearType() == EventType.STATE_ROOM_CREATE
|
||||
// 6 for room creation item (backward pagination), 1 for the context, and 50 for the forward pagination
|
||||
&& snapshot.size == 6 + 1 + 50
|
||||
}
|
||||
|
||||
aliceTimeline.addListener(aliceEventsListener)
|
||||
|
||||
// Restart the timeline to the first sent event
|
||||
// We ask to load event backward and forward
|
||||
aliceTimeline.paginate(Timeline.Direction.BACKWARDS, 50)
|
||||
aliceTimeline.paginate(Timeline.Direction.FORWARDS, 50)
|
||||
|
||||
commonTestHelper.await(lock)
|
||||
aliceTimeline.removeAllListeners()
|
||||
|
||||
aliceTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeTrue()
|
||||
aliceTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
|
||||
}
|
||||
|
||||
// Alice paginates once again FORWARD for 50 events
|
||||
// All the timeline is retrieved, she cannot paginate anymore in both direction
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val aliceEventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Alice timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root.content}")
|
||||
}
|
||||
// 6 for room creation item (backward pagination),and numberOfMessagesToSend (all the message of the room)
|
||||
snapshot.size == 6 + numberOfMessagesToSend
|
||||
&& snapshot.checkSendOrder(message, numberOfMessagesToSend, 0)
|
||||
}
|
||||
|
||||
aliceTimeline.addListener(aliceEventsListener)
|
||||
|
||||
// Ask for a forward pagination
|
||||
aliceTimeline.paginate(Timeline.Direction.FORWARDS, 50)
|
||||
|
||||
commonTestHelper.await(lock)
|
||||
aliceTimeline.removeAllListeners()
|
||||
|
||||
// The timeline is fully loaded
|
||||
aliceTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
|
||||
aliceTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
|
||||
}
|
||||
|
||||
aliceTimeline.dispose()
|
||||
|
||||
cryptoTestData.cleanUp(commonTestHelper)
|
||||
}
|
||||
}
|
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.session.room.timeline
|
||||
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.api.extensions.orFalse
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.CryptoTestHelper
|
||||
import im.vector.matrix.android.common.checkSendOrder
|
||||
import org.amshove.kluent.shouldBeFalse
|
||||
import org.amshove.kluent.shouldBeTrue
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.junit.runners.MethodSorters
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class TimelinePreviousLastForwardTest : InstrumentedTest {
|
||||
|
||||
private val commonTestHelper = CommonTestHelper(context())
|
||||
private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
|
||||
/**
|
||||
* This test ensure that if we have a chunk in the timeline which is due to a sync, and we click to permalink, we will be able to go back to the live
|
||||
*/
|
||||
@Test
|
||||
fun previousLastForwardTest() {
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession!!
|
||||
val aliceRoomId = cryptoTestData.roomId
|
||||
|
||||
aliceSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
bobSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
|
||||
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
|
||||
val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
|
||||
|
||||
val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(30))
|
||||
bobTimeline.start()
|
||||
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root}")
|
||||
}
|
||||
|
||||
// Ok, we have the 8 first messages of the initial sync (room creation and bob invite and join events)
|
||||
snapshot.size == 8
|
||||
}
|
||||
|
||||
bobTimeline.addListener(eventsListener)
|
||||
commonTestHelper.await(lock)
|
||||
bobTimeline.removeAllListeners()
|
||||
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
|
||||
}
|
||||
|
||||
// Bob stop to sync
|
||||
bobSession.stopSync()
|
||||
|
||||
val firstMessage = "First messages from Alice"
|
||||
// Alice sends 30 messages
|
||||
val firstMessageFromAliceId = commonTestHelper.sendTextMessage(
|
||||
roomFromAlicePOV,
|
||||
firstMessage,
|
||||
30)
|
||||
.last()
|
||||
.eventId
|
||||
|
||||
// Bob start to sync
|
||||
bobSession.startSync(true)
|
||||
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root}")
|
||||
}
|
||||
|
||||
// Ok, we have the 10 last messages from Alice. This will be our future previous lastForward chunk
|
||||
snapshot.size == 10
|
||||
&& snapshot.all { it.root.content.toModel<MessageContent>()?.body?.startsWith(firstMessage).orFalse() }
|
||||
}
|
||||
|
||||
bobTimeline.addListener(eventsListener)
|
||||
commonTestHelper.await(lock)
|
||||
bobTimeline.removeAllListeners()
|
||||
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeTrue()
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
|
||||
}
|
||||
|
||||
// Bob stop to sync
|
||||
bobSession.stopSync()
|
||||
|
||||
val secondMessage = "Second messages from Alice"
|
||||
// Alice sends again 30 messages
|
||||
commonTestHelper.sendTextMessage(
|
||||
roomFromAlicePOV,
|
||||
secondMessage,
|
||||
30)
|
||||
|
||||
// Bob start to sync
|
||||
bobSession.startSync(true)
|
||||
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root}")
|
||||
}
|
||||
|
||||
// Ok, we have the 10 last messages from Alice. This will be our future previous lastForward chunk
|
||||
snapshot.size == 10
|
||||
&& snapshot.all { it.root.content.toModel<MessageContent>()?.body?.startsWith(secondMessage).orFalse() }
|
||||
}
|
||||
|
||||
bobTimeline.addListener(eventsListener)
|
||||
commonTestHelper.await(lock)
|
||||
bobTimeline.removeAllListeners()
|
||||
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeTrue()
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
|
||||
}
|
||||
|
||||
// Bob navigate to the first message sent from Alice
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root}")
|
||||
}
|
||||
|
||||
// The event is not in db, so it is fetch
|
||||
snapshot.size == 1
|
||||
}
|
||||
|
||||
bobTimeline.addListener(eventsListener)
|
||||
|
||||
// Restart the timeline to the first sent event, and paginate in both direction
|
||||
bobTimeline.restartWithEventId(firstMessageFromAliceId)
|
||||
bobTimeline.paginate(Timeline.Direction.BACKWARDS, 50)
|
||||
bobTimeline.paginate(Timeline.Direction.FORWARDS, 50)
|
||||
|
||||
commonTestHelper.await(lock)
|
||||
bobTimeline.removeAllListeners()
|
||||
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeTrue()
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeTrue()
|
||||
}
|
||||
|
||||
// Paginate in both direction
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root}")
|
||||
}
|
||||
|
||||
snapshot.size == 8 + 1 + 35
|
||||
}
|
||||
|
||||
bobTimeline.addListener(eventsListener)
|
||||
|
||||
// Paginate in both direction
|
||||
bobTimeline.paginate(Timeline.Direction.BACKWARDS, 50)
|
||||
// Ensure the chunk in the middle is included in the next pagination
|
||||
bobTimeline.paginate(Timeline.Direction.FORWARDS, 35)
|
||||
|
||||
commonTestHelper.await(lock)
|
||||
bobTimeline.removeAllListeners()
|
||||
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeTrue()
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
|
||||
}
|
||||
|
||||
// Bob scroll to the future, till the live
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
Timber.e("Bob timeline updated: with ${snapshot.size} events:")
|
||||
snapshot.forEach {
|
||||
Timber.w(" event ${it.root}")
|
||||
}
|
||||
|
||||
// Bob can see the first event of the room (so Back pagination has worked)
|
||||
snapshot.lastOrNull()?.root?.getClearType() == EventType.STATE_ROOM_CREATE
|
||||
// 8 for room creation item 60 message from Alice
|
||||
&& snapshot.size == 8 + 60
|
||||
&& snapshot.checkSendOrder(secondMessage, 30, 0)
|
||||
&& snapshot.checkSendOrder(firstMessage, 30, 30)
|
||||
}
|
||||
|
||||
bobTimeline.addListener(eventsListener)
|
||||
|
||||
bobTimeline.paginate(Timeline.Direction.FORWARDS, 50)
|
||||
|
||||
commonTestHelper.await(lock)
|
||||
bobTimeline.removeAllListeners()
|
||||
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse()
|
||||
bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse()
|
||||
}
|
||||
|
||||
bobTimeline.dispose()
|
||||
|
||||
cryptoTestData.cleanUp(commonTestHelper)
|
||||
}
|
||||
}
|
@@ -20,7 +20,6 @@ package im.vector.matrix.android.internal.network.interceptors
|
||||
import im.vector.matrix.android.internal.di.MatrixScope
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import okio.Buffer
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
@@ -37,7 +36,7 @@ import javax.inject.Inject
|
||||
* non-production environment.
|
||||
*/
|
||||
@MatrixScope
|
||||
internal class CurlLoggingInterceptor @Inject constructor(private val logger: HttpLoggingInterceptor.Logger)
|
||||
internal class CurlLoggingInterceptor @Inject constructor()
|
||||
: Interceptor {
|
||||
|
||||
/**
|
||||
@@ -97,8 +96,8 @@ internal class CurlLoggingInterceptor @Inject constructor(private val logger: Ht
|
||||
// Add Json formatting
|
||||
curlCmd += " | python -m json.tool"
|
||||
|
||||
logger.log("--- cURL (" + request.url + ")")
|
||||
logger.log(curlCmd)
|
||||
Timber.d("--- cURL (${request.url})")
|
||||
Timber.d(curlCmd)
|
||||
|
||||
return chain.proceed(request)
|
||||
}
|
||||
|
@@ -23,7 +23,6 @@ import androidx.work.WorkManager
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.BuildConfig
|
||||
import im.vector.matrix.android.api.auth.AuthenticationService
|
||||
import im.vector.matrix.android.api.crypto.MXCryptoConfig
|
||||
import im.vector.matrix.android.internal.SessionManager
|
||||
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
||||
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
|
||||
@@ -32,19 +31,10 @@ import im.vector.matrix.android.internal.network.UserAgentHolder
|
||||
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
||||
import org.matrix.olm.OlmManager
|
||||
import java.io.InputStream
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
|
||||
data class MatrixConfiguration(
|
||||
val applicationFlavor: String = "Default-application-flavor",
|
||||
val cryptoConfig: MXCryptoConfig = MXCryptoConfig()
|
||||
) {
|
||||
|
||||
interface Provider {
|
||||
fun providesMatrixConfiguration(): MatrixConfiguration
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the main entry point to the matrix sdk.
|
||||
* To get the singleton instance, use getInstance static method.
|
||||
@@ -61,7 +51,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
||||
Monarchy.init(context)
|
||||
DaggerMatrixComponent.factory().create(context, matrixConfiguration).inject(this)
|
||||
if (context.applicationContext !is Configuration.Provider) {
|
||||
WorkManager.initialize(context, Configuration.Builder().build())
|
||||
WorkManager.initialize(context, Configuration.Builder().setExecutor(Executors.newCachedThreadPool()).build())
|
||||
}
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
|
||||
}
|
||||
|
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api
|
||||
|
||||
import im.vector.matrix.android.api.crypto.MXCryptoConfig
|
||||
import java.net.Proxy
|
||||
|
||||
data class MatrixConfiguration(
|
||||
val applicationFlavor: String = "Default-application-flavor",
|
||||
val cryptoConfig: MXCryptoConfig = MXCryptoConfig(),
|
||||
/**
|
||||
* Optional proxy to connect to the matrix servers
|
||||
* You can create one using for instance Proxy(proxyType, InetSocketAddress(hostname, port)
|
||||
*/
|
||||
val proxy: Proxy? = null
|
||||
) {
|
||||
|
||||
/**
|
||||
* Can be implemented by your Application class
|
||||
*/
|
||||
interface Provider {
|
||||
fun providesMatrixConfiguration(): MatrixConfiguration
|
||||
}
|
||||
}
|
@@ -20,9 +20,9 @@ import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||
import im.vector.matrix.android.api.auth.data.LoginFlowResult
|
||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||
import im.vector.matrix.android.api.auth.login.LoginWizard
|
||||
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
||||
import im.vector.matrix.android.api.auth.wellknown.WellknownResult
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
@@ -30,13 +30,17 @@ import im.vector.matrix.android.api.util.Cancelable
|
||||
* This interface defines methods to authenticate or to create an account to a matrix server.
|
||||
*/
|
||||
interface AuthenticationService {
|
||||
|
||||
/**
|
||||
* 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<LoginFlowResult>): Cancelable
|
||||
|
||||
/**
|
||||
* Request the supported login flows for the corresponding sessionId.
|
||||
*/
|
||||
fun getLoginFlowOfSession(sessionId: String, callback: MatrixCallback<LoginFlowResult>): Cancelable
|
||||
|
||||
/**
|
||||
* Return a LoginWizard, to login to the homeserver. The login flow has to be retrieved first.
|
||||
*/
|
||||
@@ -74,19 +78,26 @@ interface AuthenticationService {
|
||||
*/
|
||||
fun getLastAuthenticatedSession(): Session?
|
||||
|
||||
/**
|
||||
* Get an authenticated session. You should at least call authenticate one time before.
|
||||
* If you logout, this session will no longer be valid.
|
||||
*
|
||||
* @param sessionParams the sessionParams to open with.
|
||||
* @return the associated session if any, or null
|
||||
*/
|
||||
fun getSession(sessionParams: SessionParams): Session?
|
||||
|
||||
/**
|
||||
* Create a session after a SSO successful login
|
||||
*/
|
||||
fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
credentials: Credentials,
|
||||
callback: MatrixCallback<Session>): Cancelable
|
||||
|
||||
/**
|
||||
* Perform a wellknown request, using the domain from the matrixId
|
||||
*/
|
||||
fun getWellKnownData(matrixId: String,
|
||||
callback: MatrixCallback<WellknownResult>): Cancelable
|
||||
|
||||
/**
|
||||
* Authenticate with a matrixId and a password
|
||||
* Usually call this after a successful call to getWellKnownData()
|
||||
*/
|
||||
fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
matrixId: String,
|
||||
password: String,
|
||||
initialDeviceName: String,
|
||||
callback: MatrixCallback<Session>): Cancelable
|
||||
}
|
||||
|
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.auth
|
||||
|
||||
/**
|
||||
* Path to use when the client does not supported any or all login flows
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#login-fallback
|
||||
* */
|
||||
const val LOGIN_FALLBACK_PATH = "/_matrix/static/client/login/"
|
||||
|
||||
/**
|
||||
* Path to use when the client does not supported any or all registration flows
|
||||
* Not documented
|
||||
*/
|
||||
const val REGISTER_FALLBACK_PATH = "/_matrix/static/client/register/"
|
||||
|
||||
/**
|
||||
* Path to use when the client want to connect using SSO
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#sso-client-login
|
||||
*/
|
||||
const val SSO_FALLBACK_PATH = "/_matrix/client/r0/login/sso/redirect"
|
||||
|
||||
const val SSO_REDIRECT_URL_PARAM = "redirectUrl"
|
@@ -24,16 +24,38 @@ import im.vector.matrix.android.internal.util.md5
|
||||
* This data class hold credentials user data.
|
||||
* You shouldn't have to instantiate it.
|
||||
* The access token should be use to authenticate user in all server requests.
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-login
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Credentials(
|
||||
/**
|
||||
* The fully-qualified Matrix ID that has been registered.
|
||||
*/
|
||||
@Json(name = "user_id") val userId: String,
|
||||
@Json(name = "home_server") val homeServer: String,
|
||||
/**
|
||||
* An access token for the account. This access token can then be used to authorize other requests.
|
||||
*/
|
||||
@Json(name = "access_token") val accessToken: String,
|
||||
/**
|
||||
* Not documented
|
||||
*/
|
||||
@Json(name = "refresh_token") val refreshToken: String?,
|
||||
/**
|
||||
* The server_name of the homeserver on which the account has been registered.
|
||||
* @Deprecated. Clients should extract the server_name from user_id (by splitting at the first colon)
|
||||
* if they require it. Note also that homeserver is not spelt this way.
|
||||
*/
|
||||
@Json(name = "home_server") val homeServer: String,
|
||||
/**
|
||||
* ID of the logged-in device. Will be the same as the corresponding parameter in the request, if one was specified.
|
||||
*/
|
||||
@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
|
||||
/**
|
||||
* Optional client configuration provided by the server. If present, clients SHOULD use the provided object to
|
||||
* reconfigure themselves, optionally validating the URLs within.
|
||||
* This object takes the same form as the one returned from .well-known autodiscovery.
|
||||
*/
|
||||
@Json(name = "well_known") val discoveryInformation: DiscoveryInformation? = null
|
||||
)
|
||||
|
||||
internal fun Credentials.sessionId(): String {
|
||||
|
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.auth.data
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* This is a light version of Wellknown model, used for login response
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-login
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class DiscoveryInformation(
|
||||
/**
|
||||
* Required. Used by clients to discover homeserver information.
|
||||
*/
|
||||
@Json(name = "m.homeserver")
|
||||
val homeServer: WellKnownBaseConfig? = null,
|
||||
|
||||
/**
|
||||
* Used by clients to discover identity server information.
|
||||
* Note: matrix.org does not send this field
|
||||
*/
|
||||
@Json(name = "m.identity_server")
|
||||
val identityServer: WellKnownBaseConfig? = null
|
||||
)
|
@@ -21,7 +21,48 @@ package im.vector.matrix.android.api.auth.data
|
||||
* You don't have to manually instantiate it.
|
||||
*/
|
||||
data class SessionParams(
|
||||
/**
|
||||
* Please consider using shortcuts instead
|
||||
*/
|
||||
val credentials: Credentials,
|
||||
|
||||
/**
|
||||
* Please consider using shortcuts instead
|
||||
*/
|
||||
val homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
|
||||
/**
|
||||
* Set to false if the current token is not valid anymore. Application should not have to use this info.
|
||||
*/
|
||||
val isTokenValid: Boolean
|
||||
)
|
||||
) {
|
||||
/*
|
||||
* Shortcuts. Usually the application should only need to use these shortcuts
|
||||
*/
|
||||
|
||||
/**
|
||||
* The userId of the session (Ex: "@user:domain.org")
|
||||
*/
|
||||
val userId = credentials.userId
|
||||
|
||||
/**
|
||||
* The deviceId of the session (Ex: "ABCDEFGH")
|
||||
*/
|
||||
val deviceId = credentials.deviceId
|
||||
|
||||
/**
|
||||
* The current homeserver Url. It can be different that the homeserver url entered
|
||||
* during login phase, because a redirection may have occurred
|
||||
*/
|
||||
val homeServerUrl = homeServerConnectionConfig.homeServerUri.toString()
|
||||
|
||||
/**
|
||||
* The current homeserver host
|
||||
*/
|
||||
val homeServerHost = homeServerConnectionConfig.homeServerUri.host
|
||||
|
||||
/**
|
||||
* The default identity server url if any, returned by the homeserver during login phase
|
||||
*/
|
||||
val defaultIdentityServerUrl = homeServerConnectionConfig.identityServerUri?.toString()
|
||||
}
|
||||
|
@@ -18,6 +18,7 @@ package im.vector.matrix.android.api.auth.data
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
|
||||
/**
|
||||
* https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
|
||||
@@ -52,7 +53,7 @@ data class WellKnown(
|
||||
val identityServer: WellKnownBaseConfig? = null,
|
||||
|
||||
@Json(name = "m.integrations")
|
||||
val integrations: Map<String, @JvmSuppressWildcards Any>? = null
|
||||
val integrations: JsonDict? = null
|
||||
) {
|
||||
/**
|
||||
* Returns the list of integration managers proposed
|
||||
|
@@ -16,6 +16,6 @@
|
||||
package im.vector.matrix.android.api.auth.data
|
||||
|
||||
data class WellKnownManagerConfig(
|
||||
val apiUrl : String,
|
||||
val apiUrl: String,
|
||||
val uiUrl: String
|
||||
)
|
||||
|
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.auth.wellknown
|
||||
|
||||
import im.vector.matrix.android.api.auth.data.WellKnown
|
||||
|
||||
/**
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#well-known-uri
|
||||
*/
|
||||
sealed class WellknownResult {
|
||||
/**
|
||||
* The provided matrixId is no valid. Unable to extract a domain name.
|
||||
*/
|
||||
object InvalidMatrixId : WellknownResult()
|
||||
|
||||
/**
|
||||
* Retrieve the specific piece of information from the user in a way which fits within the existing client user experience,
|
||||
* if the client is inclined to do so. Failure can take place instead if no good user experience for this is possible at this point.
|
||||
*/
|
||||
data class Prompt(val homeServerUrl: String,
|
||||
val identityServerUrl: String?,
|
||||
val wellKnown: WellKnown) : WellknownResult()
|
||||
|
||||
/**
|
||||
* Stop the current auto-discovery mechanism. If no more auto-discovery mechanisms are available,
|
||||
* then the client may use other methods of determining the required parameters, such as prompting the user, or using default values.
|
||||
*/
|
||||
object Ignore : WellknownResult()
|
||||
|
||||
/**
|
||||
* Inform the user that auto-discovery failed due to invalid/empty data and PROMPT for the parameter.
|
||||
*/
|
||||
object FailPrompt : WellknownResult()
|
||||
|
||||
/**
|
||||
* Inform the user that auto-discovery did not return any usable URLs. Do not continue further with the current login process.
|
||||
* At this point, valid data was obtained, but no homeserver is available to serve the client.
|
||||
* No further guess should be attempted and the user should make a conscientious decision what to do next.
|
||||
*/
|
||||
object FailError : WellknownResult()
|
||||
}
|
@@ -19,9 +19,17 @@ package im.vector.matrix.android.api.crypto
|
||||
/**
|
||||
* Class to define the parameters used to customize or configure the end-to-end crypto.
|
||||
*/
|
||||
data class MXCryptoConfig(
|
||||
data class MXCryptoConfig constructor(
|
||||
// Tell whether the encryption of the event content is enabled for the invited members.
|
||||
// SDK clients can disable this by settings it to false.
|
||||
// Note that the encryption for the invited members will be blocked if the history visibility is "joined".
|
||||
var enableEncryptionForInvitedMembers: Boolean = true
|
||||
val enableEncryptionForInvitedMembers: Boolean = true,
|
||||
|
||||
/**
|
||||
* If set to true, the SDK will automatically ignore room key request (gossiping)
|
||||
* coming from your other untrusted sessions (or blocked).
|
||||
* If set to false, the request will be forwarded to the application layer; in this
|
||||
* case the application can decide to prompt the user.
|
||||
*/
|
||||
val discardRoomKeyRequestsFromUntrustedDevices: Boolean = true
|
||||
)
|
||||
|
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.extensions
|
||||
|
||||
inline fun <A> tryThis(operation: () -> A): A? {
|
||||
return try {
|
||||
operation()
|
||||
} catch (any: Throwable) {
|
||||
null
|
||||
}
|
||||
}
|
@@ -16,6 +16,10 @@
|
||||
|
||||
package im.vector.matrix.android.api.failure
|
||||
|
||||
import im.vector.matrix.android.api.extensions.tryThis
|
||||
import im.vector.matrix.android.internal.auth.registration.RegistrationFlowResponse
|
||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
import java.io.IOException
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
||||
fun Throwable.is401() =
|
||||
@@ -29,5 +33,27 @@ fun Throwable.isTokenError() =
|
||||
|
||||
fun Throwable.shouldBeRetried(): Boolean {
|
||||
return this is Failure.NetworkConnection
|
||||
|| this is IOException
|
||||
|| (this is Failure.ServerError && error.code == MatrixError.M_LIMIT_EXCEEDED)
|
||||
}
|
||||
|
||||
fun Throwable.isInvalidPassword(): Boolean {
|
||||
return this is Failure.ServerError
|
||||
&& error.code == MatrixError.M_FORBIDDEN
|
||||
&& error.message == "Invalid password"
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to convert to a RegistrationFlowResponse. Return null in the cases it's not possible
|
||||
*/
|
||||
fun Throwable.toRegistrationFlowResponse(): RegistrationFlowResponse? {
|
||||
return if (this is Failure.OtherServerError && this.httpCode == 401) {
|
||||
tryThis {
|
||||
MoshiProvider.providesMoshi()
|
||||
.adapter(RegistrationFlowResponse::class.java)
|
||||
.fromJson(this.errorBody)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
@@ -39,7 +39,10 @@ data class MatrixError(
|
||||
// For M_LIMIT_EXCEEDED
|
||||
@Json(name = "retry_after_ms") val retryAfterMillis: Long? = null,
|
||||
// For M_UNKNOWN_TOKEN
|
||||
@Json(name = "soft_logout") val isSoftLogout: Boolean = false
|
||||
@Json(name = "soft_logout") val isSoftLogout: Boolean = false,
|
||||
// For M_INVALID_PEPPER
|
||||
// {"error": "pepper does not match 'erZvr'", "lookup_pepper": "pQgMS", "algorithm": "sha256", "errcode": "M_INVALID_PEPPER"}
|
||||
@Json(name = "lookup_pepper") val newLookupPepper: String? = null
|
||||
) {
|
||||
|
||||
companion object {
|
||||
@@ -129,6 +132,11 @@ data class MatrixError(
|
||||
/** (Not documented yet) */
|
||||
const val M_WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION"
|
||||
|
||||
const val M_TERMS_NOT_SIGNED = "M_TERMS_NOT_SIGNED"
|
||||
|
||||
// For identity service
|
||||
const val M_INVALID_PEPPER = "M_INVALID_PEPPER"
|
||||
|
||||
// Possible value for "limit_type"
|
||||
const val LIMIT_TYPE_MAU = "monthly_active_user"
|
||||
}
|
||||
|
@@ -21,6 +21,7 @@ import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||
import im.vector.matrix.android.api.failure.GlobalError
|
||||
import im.vector.matrix.android.api.pushrules.PushRuleService
|
||||
import im.vector.matrix.android.api.session.account.AccountService
|
||||
import im.vector.matrix.android.api.session.accountdata.AccountDataService
|
||||
import im.vector.matrix.android.api.session.cache.CacheService
|
||||
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
|
||||
@@ -29,6 +30,7 @@ import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||
import im.vector.matrix.android.api.session.file.FileService
|
||||
import im.vector.matrix.android.api.session.group.GroupService
|
||||
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService
|
||||
import im.vector.matrix.android.api.session.identity.IdentityService
|
||||
import im.vector.matrix.android.api.session.profile.ProfileService
|
||||
import im.vector.matrix.android.api.session.pushers.PushersService
|
||||
import im.vector.matrix.android.api.session.room.RoomDirectoryService
|
||||
@@ -38,6 +40,7 @@ import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageSer
|
||||
import im.vector.matrix.android.api.session.signout.SignOutService
|
||||
import im.vector.matrix.android.api.session.sync.FilterService
|
||||
import im.vector.matrix.android.api.session.sync.SyncState
|
||||
import im.vector.matrix.android.api.session.terms.TermsService
|
||||
import im.vector.matrix.android.api.session.user.UserService
|
||||
|
||||
/**
|
||||
@@ -53,13 +56,15 @@ interface Session :
|
||||
SignOutService,
|
||||
FilterService,
|
||||
FileService,
|
||||
TermsService,
|
||||
ProfileService,
|
||||
PushRuleService,
|
||||
PushersService,
|
||||
InitialSyncProgressService,
|
||||
HomeServerCapabilitiesService,
|
||||
SecureStorageService,
|
||||
AccountDataService {
|
||||
AccountDataService,
|
||||
AccountService {
|
||||
|
||||
/**
|
||||
* The params associated to the session
|
||||
@@ -75,7 +80,7 @@ interface Session :
|
||||
* Useful shortcut to get access to the userId
|
||||
*/
|
||||
val myUserId: String
|
||||
get() = sessionParams.credentials.userId
|
||||
get() = sessionParams.userId
|
||||
|
||||
/**
|
||||
* The sessionId
|
||||
@@ -143,6 +148,11 @@ interface Session :
|
||||
*/
|
||||
fun cryptoService(): CryptoService
|
||||
|
||||
/**
|
||||
* Returns the identity service associated with the session
|
||||
*/
|
||||
fun identityService(): IdentityService
|
||||
|
||||
/**
|
||||
* Add a listener to the session.
|
||||
* @param listener the listener to add.
|
||||
|
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.account
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
/**
|
||||
* This interface defines methods to manage the account. It's implemented at the session level.
|
||||
*/
|
||||
interface AccountService {
|
||||
/**
|
||||
* Ask the homeserver to change the password.
|
||||
* @param password Current password.
|
||||
* @param newPassword New password
|
||||
*/
|
||||
fun changePassword(password: String, newPassword: String, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Deactivate the account.
|
||||
*
|
||||
* This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register
|
||||
* the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account
|
||||
* details from your identity server. <b>This action is irreversible</b>.\n\nDeactivating your account <b>does not by default
|
||||
* cause us to forget messages you have sent</b>. If you would like us to forget your messages, please tick the box below.
|
||||
*
|
||||
* Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not
|
||||
* be shared with any new or unregistered users, but registered users who already have access to these messages will still
|
||||
* have access to their copy.
|
||||
*
|
||||
* @param password the account password
|
||||
* @param eraseAllData set to true to forget all messages that have been sent. Warning: this will cause future users to see
|
||||
* an incomplete view of conversations
|
||||
*/
|
||||
fun deactivateAccount(password: String, eraseAllData: Boolean, callback: MatrixCallback<Unit>): Cancelable
|
||||
}
|
@@ -16,6 +16,7 @@
|
||||
|
||||
package im.vector.matrix.android.api.session.content
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Parcelable
|
||||
import androidx.exifinterface.media.ExifInterface
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
@@ -29,8 +30,7 @@ data class ContentAttachmentData(
|
||||
val width: Long? = 0,
|
||||
val exifOrientation: Int = ExifInterface.ORIENTATION_UNDEFINED,
|
||||
val name: String? = null,
|
||||
val queryUri: String,
|
||||
val path: String,
|
||||
val queryUri: Uri,
|
||||
private val mimeType: String?,
|
||||
val type: Type
|
||||
) : Parcelable {
|
||||
|
@@ -26,6 +26,11 @@ interface ContentUrlResolver {
|
||||
SCALE("scale")
|
||||
}
|
||||
|
||||
/**
|
||||
* URL to use to upload content
|
||||
*/
|
||||
val uploadUrl: String
|
||||
|
||||
/**
|
||||
* Get the actual URL for accessing the full-size image of a Matrix media content URI.
|
||||
*
|
||||
|
@@ -22,12 +22,14 @@ import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
|
||||
import im.vector.matrix.android.api.session.crypto.keyshare.RoomKeysRequestListener
|
||||
import im.vector.matrix.android.api.session.crypto.keyshare.GossipingRequestListener
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationService
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
|
||||
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
||||
import im.vector.matrix.android.internal.crypto.NewSessionListener
|
||||
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
||||
@@ -86,15 +88,19 @@ interface CryptoService {
|
||||
|
||||
fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo?
|
||||
|
||||
fun requestRoomKeyForEvent(event: Event)
|
||||
|
||||
fun reRequestRoomKeyForEvent(event: Event)
|
||||
|
||||
fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody)
|
||||
|
||||
fun addRoomKeysRequestListener(listener: RoomKeysRequestListener)
|
||||
fun addRoomKeysRequestListener(listener: GossipingRequestListener)
|
||||
|
||||
fun removeRoomKeysRequestListener(listener: RoomKeysRequestListener)
|
||||
fun removeRoomKeysRequestListener(listener: GossipingRequestListener)
|
||||
|
||||
fun getDevicesList(callback: MatrixCallback<DevicesListResponse>)
|
||||
fun fetchDevicesList(callback: MatrixCallback<DevicesListResponse>)
|
||||
fun getMyDevicesInfo() : List<DeviceInfo>
|
||||
fun getLiveMyDevicesInfo() : LiveData<List<DeviceInfo>>
|
||||
|
||||
fun getDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>)
|
||||
|
||||
@@ -107,6 +113,8 @@ interface CryptoService {
|
||||
roomId: String,
|
||||
callback: MatrixCallback<MXEncryptEventContentResult>)
|
||||
|
||||
fun discardOutboundSession(roomId: String)
|
||||
|
||||
@Throws(MXCryptoError::class)
|
||||
fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
|
||||
|
||||
@@ -129,4 +137,8 @@ interface CryptoService {
|
||||
fun addNewSessionListener(newSessionListener: NewSessionListener)
|
||||
|
||||
fun removeSessionListener(listener: NewSessionListener)
|
||||
|
||||
fun getOutgoingRoomKeyRequest(): List<OutgoingRoomKeyRequest>
|
||||
fun getIncomingRoomKeyRequest(): List<IncomingRoomKeyRequest>
|
||||
fun getGossipingEventsTrail(): List<Event>
|
||||
}
|
||||
|
@@ -22,6 +22,7 @@ import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustResult
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.UserTrustResult
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
||||
import im.vector.matrix.android.internal.crypto.store.PrivateKeysInfo
|
||||
|
||||
interface CrossSigningService {
|
||||
|
||||
@@ -52,6 +53,10 @@ interface CrossSigningService {
|
||||
|
||||
fun getMyCrossSigningKeys(): MXCrossSigningInfo?
|
||||
|
||||
fun getCrossSigningPrivateKeys(): PrivateKeysInfo?
|
||||
|
||||
fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>>
|
||||
|
||||
fun canCrossSign(): Boolean
|
||||
|
||||
fun trustUser(otherUserId: String,
|
||||
@@ -68,4 +73,7 @@ interface CrossSigningService {
|
||||
fun checkDeviceTrust(otherUserId: String,
|
||||
otherDeviceId: String,
|
||||
locallyTrusted: Boolean?): DeviceTrustResult
|
||||
|
||||
fun onSecretSSKGossip(sskPrivateKey: String)
|
||||
fun onSecretUSKGossip(uskPrivateKey: String)
|
||||
}
|
||||
|
@@ -21,3 +21,5 @@ const val MASTER_KEY_SSSS_NAME = "m.cross_signing.master"
|
||||
const val USER_SIGNING_KEY_SSSS_NAME = "m.cross_signing.user_signing"
|
||||
|
||||
const val SELF_SIGNING_KEY_SSSS_NAME = "m.cross_signing.self_signing"
|
||||
|
||||
const val KEYBACKUP_SECRET_SSSS_NAME = "m.megolm_backup.v1"
|
||||
|
@@ -24,6 +24,7 @@ import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCre
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
|
||||
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
||||
import im.vector.matrix.android.internal.crypto.store.SavedKeyBackupKeyInfo
|
||||
|
||||
interface KeysBackupService {
|
||||
/**
|
||||
@@ -172,6 +173,8 @@ interface KeysBackupService {
|
||||
password: String,
|
||||
callback: MatrixCallback<Unit>)
|
||||
|
||||
fun onSecretKeyGossip(secret: String)
|
||||
|
||||
/**
|
||||
* Restore a backup with a recovery key from a given backup version stored on the homeserver.
|
||||
*
|
||||
@@ -210,4 +213,10 @@ interface KeysBackupService {
|
||||
val isEnabled: Boolean
|
||||
val isStucked: Boolean
|
||||
val state: KeysBackupState
|
||||
|
||||
// For gossiping
|
||||
fun saveBackupRecoveryKey(recoveryKey: String?, version: String?)
|
||||
fun getKeyBackupRecoveryKeyInfo() : SavedKeyBackupKeyInfo?
|
||||
|
||||
fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String, callback: MatrixCallback<Boolean>)
|
||||
}
|
||||
|
@@ -17,12 +17,13 @@
|
||||
package im.vector.matrix.android.api.session.crypto.keyshare
|
||||
|
||||
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
|
||||
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequestCancellation
|
||||
import im.vector.matrix.android.internal.crypto.IncomingRequestCancellation
|
||||
import im.vector.matrix.android.internal.crypto.IncomingSecretShareRequest
|
||||
|
||||
/**
|
||||
* Room keys events listener
|
||||
*/
|
||||
interface RoomKeysRequestListener {
|
||||
interface GossipingRequestListener {
|
||||
/**
|
||||
* An room key request has been received.
|
||||
*
|
||||
@@ -30,10 +31,16 @@ interface RoomKeysRequestListener {
|
||||
*/
|
||||
fun onRoomKeyRequest(request: IncomingRoomKeyRequest)
|
||||
|
||||
/**
|
||||
* Returns the secret value to be shared
|
||||
* @return true if is handled
|
||||
*/
|
||||
fun onSecretShareRequest(request: IncomingSecretShareRequest) : Boolean
|
||||
|
||||
/**
|
||||
* A room key request cancellation has been received.
|
||||
*
|
||||
* @param request the cancellation request
|
||||
*/
|
||||
fun onRoomKeyRequestCancellation(request: IncomingRoomKeyRequestCancellation)
|
||||
fun onRoomKeyRequestCancellation(request: IncomingRequestCancellation)
|
||||
}
|
@@ -16,7 +16,10 @@
|
||||
|
||||
package im.vector.matrix.android.api.session.crypto.verification
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
|
||||
data class EmojiRepresentation(val emoji: String,
|
||||
@StringRes val nameResId: Int)
|
||||
@StringRes val nameResId: Int,
|
||||
@DrawableRes val drawableRes: Int? = null
|
||||
)
|
||||
|
@@ -17,6 +17,7 @@
|
||||
package im.vector.matrix.android.api.session.crypto.verification
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.LocalEcho
|
||||
|
||||
/**
|
||||
@@ -59,6 +60,8 @@ interface VerificationService {
|
||||
roomId: String,
|
||||
localId: String? = LocalEcho.createLocalEchoId()): PendingVerificationRequest
|
||||
|
||||
fun cancelVerificationRequest(request: PendingVerificationRequest)
|
||||
|
||||
/**
|
||||
* Request a key verification from another user using toDevice events.
|
||||
*/
|
||||
@@ -136,4 +139,6 @@ interface VerificationService {
|
||||
return age in tooInThePast..tooInTheFuture
|
||||
}
|
||||
}
|
||||
|
||||
fun onPotentiallyInterestingEventRoomFailToDecrypt(event: Event)
|
||||
}
|
||||
|
@@ -43,6 +43,7 @@ sealed class VerificationTxState {
|
||||
|
||||
// Will be used to ask the user if the other user has correctly scanned
|
||||
object QrScannedByOther : VerificationQrTxState()
|
||||
object WaitingOtherReciprocateConfirm : VerificationQrTxState()
|
||||
|
||||
// Terminal states
|
||||
abstract class TerminalTxState : VerificationTxState()
|
||||
|
@@ -142,12 +142,12 @@ data class Event(
|
||||
}
|
||||
|
||||
fun toContentStringWithIndent(): String {
|
||||
val contentMap = toContent().toMutableMap()
|
||||
val contentMap = toContent()
|
||||
return JSONObject(contentMap).toString(4)
|
||||
}
|
||||
|
||||
fun toClearContentStringWithIndent(): String? {
|
||||
val contentMap = this.mxDecryptionResult?.payload?.toMutableMap()
|
||||
val contentMap = this.mxDecryptionResult?.payload
|
||||
val adapter = MoshiProvider.providesMoshi().adapter(Map::class.java)
|
||||
return contentMap?.let { JSONObject(adapter.toJson(it)).toString(4) }
|
||||
}
|
||||
@@ -220,3 +220,11 @@ fun Event.isImageMessage(): Boolean {
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun Event.isVideoMessage(): Boolean {
|
||||
return getClearType() == EventType.MESSAGE
|
||||
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
|
||||
MessageType.MSGTYPE_VIDEO -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
@@ -81,6 +81,9 @@ object EventType {
|
||||
// Relation Events
|
||||
const val REACTION = "m.reaction"
|
||||
|
||||
// Unwedging
|
||||
internal const val DUMMY = "m.dummy"
|
||||
|
||||
private val STATE_EVENTS = listOf(
|
||||
STATE_ROOM_NAME,
|
||||
STATE_ROOM_TOPIC,
|
||||
|
@@ -17,10 +17,22 @@
|
||||
package im.vector.matrix.android.api.session.homeserver
|
||||
|
||||
data class HomeServerCapabilities(
|
||||
/**
|
||||
* True if it is possible to change the password of the account.
|
||||
*/
|
||||
val canChangePassword: Boolean = true,
|
||||
/**
|
||||
* Max size of file which can be uploaded to the homeserver in bytes. [MAX_UPLOAD_FILE_SIZE_UNKNOWN] if unknown or not retrieved yet
|
||||
*/
|
||||
val maxUploadFileSize: Long = MAX_UPLOAD_FILE_SIZE_UNKNOWN
|
||||
val maxUploadFileSize: Long = MAX_UPLOAD_FILE_SIZE_UNKNOWN,
|
||||
/**
|
||||
* Last version identity server and binding supported
|
||||
*/
|
||||
val lastVersionIdentityServerSupported: Boolean = false,
|
||||
/**
|
||||
* Default identity server url, provided in Wellknown
|
||||
*/
|
||||
val defaultIdentityServerUrl: String? = null
|
||||
) {
|
||||
companion object {
|
||||
const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L
|
||||
|
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.identity
|
||||
|
||||
data class FoundThreePid(
|
||||
val threePid: ThreePid,
|
||||
val matrixId: String
|
||||
)
|
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.identity
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
/**
|
||||
* Provides access to the identity server configuration and services identity server can provide
|
||||
*/
|
||||
interface IdentityService {
|
||||
/**
|
||||
* Return the default identity server of the user, which may have been provided at login time by the homeserver,
|
||||
* or by the Well-known setup of the homeserver
|
||||
* It may be different from the current configured identity server
|
||||
*/
|
||||
fun getDefaultIdentityServer(): String?
|
||||
|
||||
/**
|
||||
* Return the current identity server URL used by this account. Returns null if no identity server is configured.
|
||||
*/
|
||||
fun getCurrentIdentityServerUrl(): String?
|
||||
|
||||
/**
|
||||
* Check if the identity server is valid
|
||||
* See https://matrix.org/docs/spec/identity_service/latest#status-check
|
||||
* RiotX SDK only supports identity server API v2
|
||||
*/
|
||||
fun isValidIdentityServer(url: String, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Update the identity server url.
|
||||
* If successful, any previous identity server will be disconnected.
|
||||
* In case of error, any previous identity server will remain configured.
|
||||
* @param url the new url.
|
||||
* @param callback will notify the user if change is successful. The String will be the final url of the identity server.
|
||||
* The SDK can prepend "https://" for instance.
|
||||
*/
|
||||
fun setNewIdentityServer(url: String, callback: MatrixCallback<String>): Cancelable
|
||||
|
||||
/**
|
||||
* Disconnect (logout) from the current identity server
|
||||
*/
|
||||
fun disconnect(callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* This will ask the identity server to send an email or an SMS to let the user confirm he owns the ThreePid
|
||||
*/
|
||||
fun startBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* This will cancel a pending binding of threePid.
|
||||
*/
|
||||
fun cancelBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* This will ask the identity server to send an new email or a new SMS to let the user confirm he owns the ThreePid
|
||||
*/
|
||||
fun sendAgainValidationCode(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Submit the code that the identity server has sent to the user (in email or SMS)
|
||||
* Once successful, you will have to call [finalizeBindThreePid]
|
||||
* @param code the code sent to the user
|
||||
*/
|
||||
fun submitValidationToken(threePid: ThreePid, code: String, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* This will perform the actual association of ThreePid and Matrix account
|
||||
*/
|
||||
fun finalizeBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Unbind a threePid
|
||||
* The request will actually be done on the homeserver
|
||||
*/
|
||||
fun unbindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Search MatrixId of users providing email and phone numbers
|
||||
*/
|
||||
fun lookUp(threePids: List<ThreePid>, callback: MatrixCallback<List<FoundThreePid>>): Cancelable
|
||||
|
||||
/**
|
||||
* Get the status of the current user's threePid
|
||||
* A lookup will be performed, but also pending binding state will be restored
|
||||
*
|
||||
* @param threePids the list of threePid the user owns (retrieved form the homeserver)
|
||||
* @param callback onSuccess will be called with a map of ThreePid -> SharedState
|
||||
*/
|
||||
fun getShareStatus(threePids: List<ThreePid>, callback: MatrixCallback<Map<ThreePid, SharedState>>): Cancelable
|
||||
|
||||
fun addListener(listener: IdentityServiceListener)
|
||||
fun removeListener(listener: IdentityServiceListener)
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.identity
|
||||
|
||||
sealed class IdentityServiceError : Throwable() {
|
||||
object OutdatedIdentityServer : IdentityServiceError()
|
||||
object OutdatedHomeServer : IdentityServiceError()
|
||||
object NoIdentityServerConfigured : IdentityServiceError()
|
||||
object TermsNotSignedException : IdentityServiceError()
|
||||
object BulkLookupSha256NotSupported : IdentityServiceError()
|
||||
object BindingError : IdentityServiceError()
|
||||
object NoCurrentBindingError : IdentityServiceError()
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.identity
|
||||
|
||||
interface IdentityServiceListener {
|
||||
fun onIdentityServerChange()
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.identity
|
||||
|
||||
enum class SharedState {
|
||||
SHARED,
|
||||
NOT_SHARED,
|
||||
BINDING_IN_PROGRESS
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.identity
|
||||
|
||||
import com.google.i18n.phonenumbers.NumberParseException
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil
|
||||
import im.vector.matrix.android.internal.session.profile.ThirdPartyIdentifier
|
||||
|
||||
sealed class ThreePid(open val value: String) {
|
||||
data class Email(val email: String) : ThreePid(email)
|
||||
data class Msisdn(val msisdn: String) : ThreePid(msisdn)
|
||||
}
|
||||
|
||||
internal fun ThreePid.toMedium(): String {
|
||||
return when (this) {
|
||||
is ThreePid.Email -> ThirdPartyIdentifier.MEDIUM_EMAIL
|
||||
is ThreePid.Msisdn -> ThirdPartyIdentifier.MEDIUM_MSISDN
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(NumberParseException::class)
|
||||
internal fun ThreePid.Msisdn.getCountryCode(): String {
|
||||
return with(PhoneNumberUtil.getInstance()) {
|
||||
getRegionCodeForCountryCode(parse("+$msisdn", null).countryCode)
|
||||
}
|
||||
}
|
@@ -17,7 +17,9 @@
|
||||
|
||||
package im.vector.matrix.android.api.session.profile
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
@@ -53,4 +55,15 @@ interface ProfileService {
|
||||
*
|
||||
*/
|
||||
fun getProfile(userId: String, matrixCallback: MatrixCallback<JsonDict>): Cancelable
|
||||
|
||||
/**
|
||||
* Get the current user 3Pids
|
||||
*/
|
||||
fun getThreePids(): List<ThreePid>
|
||||
|
||||
/**
|
||||
* Get the current user 3Pids Live
|
||||
* @param refreshData set to true to fetch data from the homeserver
|
||||
*/
|
||||
fun getThreePidsLive(refreshData: Boolean): LiveData<List<ThreePid>>
|
||||
}
|
||||
|
@@ -29,6 +29,7 @@ import im.vector.matrix.android.api.session.room.send.SendService
|
||||
import im.vector.matrix.android.api.session.room.state.StateService
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineService
|
||||
import im.vector.matrix.android.api.session.room.typing.TypingService
|
||||
import im.vector.matrix.android.api.session.room.uploads.UploadsService
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
|
||||
/**
|
||||
@@ -42,6 +43,7 @@ interface Room :
|
||||
TypingService,
|
||||
MembershipService,
|
||||
StateService,
|
||||
UploadsService,
|
||||
ReportingService,
|
||||
RelationService,
|
||||
RoomCryptoService,
|
||||
|
@@ -46,10 +46,10 @@ data class RoomSummary constructor(
|
||||
val readMarkerId: String? = null,
|
||||
val userDrafts: List<UserDraft> = emptyList(),
|
||||
val isEncrypted: Boolean,
|
||||
val encryptionEventTs: Long?,
|
||||
val inviterId: String? = null,
|
||||
val typingRoomMemberIds: List<String> = emptyList(),
|
||||
val breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS,
|
||||
// TODO Plug it
|
||||
val roomEncryptionTrustLevel: RoomEncryptionTrustLevel? = null
|
||||
) {
|
||||
|
||||
|
@@ -24,7 +24,7 @@ data class AudioInfo(
|
||||
/**
|
||||
* The mimetype of the audio e.g. "audio/aac".
|
||||
*/
|
||||
@Json(name = "mimetype") val mimeType: String,
|
||||
@Json(name = "mimetype") val mimeType: String?,
|
||||
|
||||
/**
|
||||
* The size of the audio clip in bytes.
|
||||
|
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.room.model.message
|
||||
|
||||
interface MessageContentWithFormattedBody : MessageContent {
|
||||
/**
|
||||
* The format used in the formatted_body. Currently only "org.matrix.custom.html" is supported.
|
||||
*/
|
||||
val format: String?
|
||||
|
||||
/**
|
||||
* The formatted version of the body. This is required if format is specified.
|
||||
*/
|
||||
val formattedBody: String?
|
||||
|
||||
/**
|
||||
* Get the formattedBody, only if not blank and if the format is equal to "org.matrix.custom.html"
|
||||
*/
|
||||
val matrixFormattedBody: String?
|
||||
get() = formattedBody?.takeIf { it.isNotBlank() && format == MessageFormat.FORMAT_MATRIX_HTML }
|
||||
}
|
@@ -34,15 +34,15 @@ data class MessageEmoteContent(
|
||||
@Json(name = "body") override val body: String,
|
||||
|
||||
/**
|
||||
* The format used in the formatted_body. Currently only org.matrix.custom.html is supported.
|
||||
* The format used in the formatted_body. Currently only "org.matrix.custom.html" is supported.
|
||||
*/
|
||||
@Json(name = "format") val format: String? = null,
|
||||
@Json(name = "format") override val format: String? = null,
|
||||
|
||||
/**
|
||||
* The formatted version of the body. This is required if format is specified.
|
||||
*/
|
||||
@Json(name = "formatted_body") val formattedBody: String? = null,
|
||||
@Json(name = "formatted_body") override val formattedBody: String? = null,
|
||||
|
||||
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||
@Json(name = "m.new_content") override val newContent: Content? = null
|
||||
) : MessageContent
|
||||
) : MessageContentWithFormattedBody
|
||||
|
@@ -29,7 +29,8 @@ data class MessageLocationContent(
|
||||
@Json(name = "msgtype") override val msgType: String,
|
||||
|
||||
/**
|
||||
* Required. A description of the location e.g. 'Big Ben, London, UK', or some kind of content description for accessibility e.g. 'location attachment'.
|
||||
* Required. A description of the location e.g. 'Big Ben, London, UK', or some kind
|
||||
* of content description for accessibility e.g. 'location attachment'.
|
||||
*/
|
||||
@Json(name = "body") override val body: String,
|
||||
|
||||
|
@@ -34,15 +34,15 @@ data class MessageNoticeContent(
|
||||
@Json(name = "body") override val body: String,
|
||||
|
||||
/**
|
||||
* The format used in the formatted_body. Currently only org.matrix.custom.html is supported.
|
||||
* The format used in the formatted_body. Currently only "org.matrix.custom.html" is supported.
|
||||
*/
|
||||
@Json(name = "format") val format: String? = null,
|
||||
@Json(name = "format") override val format: String? = null,
|
||||
|
||||
/**
|
||||
* The formatted version of the body. This is required if format is specified.
|
||||
*/
|
||||
@Json(name = "formatted_body") val formattedBody: String? = null,
|
||||
@Json(name = "formatted_body") override val formattedBody: String? = null,
|
||||
|
||||
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||
@Json(name = "m.new_content") override val newContent: Content? = null
|
||||
) : MessageContent
|
||||
) : MessageContentWithFormattedBody
|
||||
|
@@ -34,15 +34,15 @@ data class MessageTextContent(
|
||||
@Json(name = "body") override val body: String,
|
||||
|
||||
/**
|
||||
* The format used in the formatted_body. Currently only org.matrix.custom.html is supported.
|
||||
* The format used in the formatted_body. Currently only "org.matrix.custom.html" is supported.
|
||||
*/
|
||||
@Json(name = "format") val format: String? = null,
|
||||
@Json(name = "format") override val format: String? = null,
|
||||
|
||||
/**
|
||||
* The formatted version of the body. This is required if format is specified.
|
||||
*/
|
||||
@Json(name = "formatted_body") val formattedBody: String? = null,
|
||||
@Json(name = "formatted_body") override val formattedBody: String? = null,
|
||||
|
||||
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||
@Json(name = "m.new_content") override val newContent: Content? = null
|
||||
) : MessageContent
|
||||
) : MessageContentWithFormattedBody
|
||||
|
@@ -39,5 +39,5 @@ data class ThumbnailInfo(
|
||||
/**
|
||||
* The mimetype of the image, e.g. "image/jpeg".
|
||||
*/
|
||||
@Json(name = "mimetype") val mimeType: String
|
||||
@Json(name = "mimetype") val mimeType: String?
|
||||
)
|
||||
|
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.room.sender
|
||||
|
||||
data class SenderInfo(
|
||||
val userId: String,
|
||||
/**
|
||||
* Consider using [disambiguatedDisplayName]
|
||||
*/
|
||||
val displayName: String?,
|
||||
val isUniqueDisplayName: Boolean,
|
||||
val avatarUrl: String?
|
||||
) {
|
||||
val disambiguatedDisplayName: String
|
||||
get() = when {
|
||||
displayName.isNullOrBlank() -> userId
|
||||
isUniqueDisplayName -> displayName
|
||||
else -> "$displayName ($userId)"
|
||||
}
|
||||
}
|
@@ -58,7 +58,7 @@ interface Timeline {
|
||||
|
||||
/**
|
||||
* Check if the timeline can be enriched by paginating.
|
||||
* @param the direction to check in
|
||||
* @param direction the direction to check in
|
||||
* @return true if timeline can be enriched
|
||||
*/
|
||||
fun hasMoreToLoad(direction: Direction): Boolean
|
||||
@@ -104,6 +104,7 @@ interface Timeline {
|
||||
interface Listener {
|
||||
/**
|
||||
* Call when the timeline has been updated through pagination or sync.
|
||||
* The latest event is the first in the list
|
||||
* @param snapshot the most up to date snapshot
|
||||
*/
|
||||
fun onTimelineUpdated(snapshot: List<TimelineEvent>)
|
||||
|
@@ -16,6 +16,7 @@
|
||||
|
||||
package im.vector.matrix.android.api.session.room.timeline
|
||||
|
||||
import im.vector.matrix.android.BuildConfig
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.RelationType
|
||||
@@ -25,6 +26,7 @@ import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageStickerContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.isReply
|
||||
import im.vector.matrix.android.api.session.room.sender.SenderInfo
|
||||
import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply
|
||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||
|
||||
@@ -38,13 +40,17 @@ data class TimelineEvent(
|
||||
val localId: Long,
|
||||
val eventId: String,
|
||||
val displayIndex: Int,
|
||||
val senderName: String?,
|
||||
val isUniqueDisplayName: Boolean,
|
||||
val senderAvatar: String?,
|
||||
val senderInfo: SenderInfo,
|
||||
val annotations: EventAnnotationsSummary? = null,
|
||||
val readReceipts: List<ReadReceipt> = emptyList()
|
||||
) {
|
||||
|
||||
init {
|
||||
if (BuildConfig.DEBUG) {
|
||||
assert(eventId == root.eventId)
|
||||
}
|
||||
}
|
||||
|
||||
val metadata = HashMap<String, Any>()
|
||||
|
||||
/**
|
||||
@@ -62,14 +68,6 @@ data class TimelineEvent(
|
||||
}
|
||||
}
|
||||
|
||||
fun getDisambiguatedDisplayName(): String {
|
||||
return when {
|
||||
senderName.isNullOrBlank() -> root.senderId ?: ""
|
||||
isUniqueDisplayName -> senderName
|
||||
else -> "$senderName (${root.senderId})"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the metadata associated with a key.
|
||||
* @param key the key to get the metadata
|
||||
|
@@ -28,6 +28,10 @@ data class TimelineSettings(
|
||||
* A flag to filter edit events
|
||||
*/
|
||||
val filterEdits: Boolean = false,
|
||||
/**
|
||||
* A flag to filter redacted events
|
||||
*/
|
||||
val filterRedacted: Boolean = false,
|
||||
/**
|
||||
* A flag to filter by types. It should be used with [allowedTypes] field
|
||||
*/
|
||||
|
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.room.uploads
|
||||
|
||||
data class GetUploadsResult(
|
||||
// List of fetched Events, most recent first
|
||||
val uploadEvents: List<UploadEvent>,
|
||||
// token to get more events
|
||||
val nextToken: String,
|
||||
// True if there are more event to load
|
||||
val hasMore: Boolean
|
||||
)
|
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.room.uploads
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageWithAttachmentContent
|
||||
import im.vector.matrix.android.api.session.room.sender.SenderInfo
|
||||
|
||||
/**
|
||||
* Wrapper around on Event.
|
||||
* Similar to [im.vector.matrix.android.api.session.room.timeline.TimelineEvent], contains an Event with extra useful data
|
||||
*/
|
||||
data class UploadEvent(
|
||||
val root: Event,
|
||||
val eventId: String,
|
||||
val contentWithAttachmentContent: MessageWithAttachmentContent,
|
||||
val senderInfo: SenderInfo
|
||||
)
|
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.room.uploads
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
/**
|
||||
* This interface defines methods to get event with uploads (= attachments) sent to a room. It's implemented at the room level.
|
||||
*/
|
||||
interface UploadsService {
|
||||
|
||||
/**
|
||||
* Get a list of events containing URL sent to a room, from most recent to oldest one
|
||||
* @param numberOfEvents the expected number of events to retrieve. The result can contain less events.
|
||||
* @param since token to get next page, or null to get the first page
|
||||
*/
|
||||
fun getUploads(numberOfEvents: Int,
|
||||
since: String?,
|
||||
callback: MatrixCallback<GetUploadsResult>): Cancelable
|
||||
}
|
@@ -19,3 +19,7 @@ package im.vector.matrix.android.api.session.securestorage
|
||||
interface KeySigner {
|
||||
fun sign(canonicalJson: String): Map<String, Map<String, String>>?
|
||||
}
|
||||
|
||||
class EmptyKeySigner : KeySigner {
|
||||
override fun sign(canonicalJson: String): Map<String, Map<String, String>>? = null
|
||||
}
|
||||
|
@@ -35,12 +35,14 @@ interface SharedSecretStorageService {
|
||||
* Use the SsssKeyCreationInfo object returned by the callback to get more information about the created key (recovery key ...)
|
||||
*
|
||||
* @param keyId the ID of the key
|
||||
* @param key keep null if you want to generate a random key
|
||||
* @param keyName a human readable name
|
||||
* @param keySigner Used to add a signature to the key (client should check key signature before storing secret)
|
||||
*
|
||||
* @param callback Get key creation info
|
||||
*/
|
||||
fun generateKey(keyId: String,
|
||||
key: SsssKeySpec?,
|
||||
keyName: String,
|
||||
keySigner: KeySigner?,
|
||||
callback: MatrixCallback<SsssKeyCreationInfo>)
|
||||
@@ -111,6 +113,8 @@ interface SharedSecretStorageService {
|
||||
|
||||
fun checkShouldBeAbleToAccessSecrets(secretNames: List<String>, keyId: String?) : IntegrityResult
|
||||
|
||||
fun requestSecret(name: String, myOtherDeviceId: String)
|
||||
|
||||
data class KeyRef(
|
||||
val keyId: String?,
|
||||
val keySpec: SsssKeySpec?
|
||||
|
@@ -19,5 +19,6 @@ package im.vector.matrix.android.api.session.securestorage
|
||||
data class SsssKeyCreationInfo(
|
||||
val keyId: String = "",
|
||||
var content: SecretStorageKeyContent?,
|
||||
val recoveryKey: String = ""
|
||||
val recoveryKey: String = "",
|
||||
val keySpec: SsssKeySpec
|
||||
)
|
||||
|
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.terms
|
||||
|
||||
import im.vector.matrix.android.internal.session.terms.TermsResponse
|
||||
|
||||
data class GetTermsResponse(
|
||||
val serverResponse: TermsResponse,
|
||||
val alreadyAcceptedTermUrls: Set<String>
|
||||
)
|
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.terms
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
interface TermsService {
|
||||
enum class ServiceType {
|
||||
IntegrationManager,
|
||||
IdentityService
|
||||
}
|
||||
|
||||
fun getTerms(serviceType: ServiceType,
|
||||
baseUrl: String,
|
||||
callback: MatrixCallback<GetTermsResponse>): Cancelable
|
||||
|
||||
fun agreeToTerms(serviceType: ServiceType,
|
||||
baseUrl: String,
|
||||
agreedUrls: List<String>,
|
||||
token: String?,
|
||||
callback: MatrixCallback<Unit>): Cancelable
|
||||
}
|
@@ -61,9 +61,10 @@ interface UserService {
|
||||
/**
|
||||
* Observe a live [PagedList] of users sorted alphabetically. You can filter the users.
|
||||
* @param filter the filter. It will look into userId and displayName.
|
||||
* @param excludedUserIds userId list which will be excluded from the result list.
|
||||
* @return a Livedata of users
|
||||
*/
|
||||
fun getPagedUsersLive(filter: String? = null): LiveData<PagedList<User>>
|
||||
fun getPagedUsersLive(filter: String? = null, excludedUserIds: Set<String>? = null): LiveData<PagedList<User>>
|
||||
|
||||
/**
|
||||
* Get list of ignored users
|
||||
|
@@ -22,6 +22,9 @@ package im.vector.matrix.android.api.session.user.model
|
||||
*/
|
||||
data class User(
|
||||
val userId: String,
|
||||
/**
|
||||
* For usage in UI, consider using [getBestName]
|
||||
*/
|
||||
val displayName: String? = null,
|
||||
val avatarUrl: String? = null
|
||||
) {
|
||||
|
@@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
||||
import im.vector.matrix.android.api.session.room.sender.SenderInfo
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
import java.util.Locale
|
||||
|
||||
@@ -154,3 +155,5 @@ fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlia
|
||||
fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name ?: getPrimaryAlias() ?: "", avatarUrl)
|
||||
|
||||
fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
|
||||
|
||||
fun SenderInfo.toMatrixItem() = MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl)
|
||||
|
@@ -25,12 +25,15 @@ import im.vector.matrix.android.internal.auth.db.AuthRealmMigration
|
||||
import im.vector.matrix.android.internal.auth.db.AuthRealmModule
|
||||
import im.vector.matrix.android.internal.auth.db.RealmPendingSessionStore
|
||||
import im.vector.matrix.android.internal.auth.db.RealmSessionParamsStore
|
||||
import im.vector.matrix.android.internal.auth.login.DefaultDirectLoginTask
|
||||
import im.vector.matrix.android.internal.auth.login.DirectLoginTask
|
||||
import im.vector.matrix.android.internal.database.RealmKeysUtils
|
||||
import im.vector.matrix.android.internal.di.AuthDatabase
|
||||
import im.vector.matrix.android.internal.wellknown.WellknownModule
|
||||
import io.realm.RealmConfiguration
|
||||
import java.io.File
|
||||
|
||||
@Module
|
||||
@Module(includes = [WellknownModule::class])
|
||||
internal abstract class AuthModule {
|
||||
|
||||
@Module
|
||||
@@ -59,14 +62,17 @@ internal abstract class AuthModule {
|
||||
}
|
||||
|
||||
@Binds
|
||||
abstract fun bindSessionParamsStore(sessionParamsStore: RealmSessionParamsStore): SessionParamsStore
|
||||
abstract fun bindSessionParamsStore(store: RealmSessionParamsStore): SessionParamsStore
|
||||
|
||||
@Binds
|
||||
abstract fun bindPendingSessionStore(pendingSessionStore: RealmPendingSessionStore): PendingSessionStore
|
||||
abstract fun bindPendingSessionStore(store: RealmPendingSessionStore): PendingSessionStore
|
||||
|
||||
@Binds
|
||||
abstract fun bindAuthenticationService(authenticationService: DefaultAuthenticationService): AuthenticationService
|
||||
abstract fun bindAuthenticationService(service: DefaultAuthenticationService): AuthenticationService
|
||||
|
||||
@Binds
|
||||
abstract fun bindSessionCreator(sessionCreator: DefaultSessionCreator): SessionCreator
|
||||
abstract fun bindSessionCreator(creator: DefaultSessionCreator): SessionCreator
|
||||
|
||||
@Binds
|
||||
abstract fun bindDirectLoginTask(task: DefaultDirectLoginTask): DirectLoginTask
|
||||
}
|
||||
|
@@ -23,28 +23,33 @@ import im.vector.matrix.android.api.auth.AuthenticationService
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||
import im.vector.matrix.android.api.auth.data.LoginFlowResult
|
||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||
import im.vector.matrix.android.api.auth.data.Versions
|
||||
import im.vector.matrix.android.api.auth.data.isLoginAndRegistrationSupportedBySdk
|
||||
import im.vector.matrix.android.api.auth.data.isSupportedBySdk
|
||||
import im.vector.matrix.android.api.auth.login.LoginWizard
|
||||
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
||||
import im.vector.matrix.android.api.auth.wellknown.WellknownResult
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
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.SessionManager
|
||||
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
|
||||
import im.vector.matrix.android.internal.auth.data.RiotConfig
|
||||
import im.vector.matrix.android.internal.auth.db.PendingSessionData
|
||||
import im.vector.matrix.android.internal.auth.login.DefaultLoginWizard
|
||||
import im.vector.matrix.android.internal.auth.login.DirectLoginTask
|
||||
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.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import im.vector.matrix.android.internal.task.launchToCallback
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import im.vector.matrix.android.internal.util.exhaustive
|
||||
import im.vector.matrix.android.internal.util.toCancelable
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import im.vector.matrix.android.internal.wellknown.GetWellknownTask
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
@@ -59,7 +64,10 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||
private val sessionParamsStore: SessionParamsStore,
|
||||
private val sessionManager: SessionManager,
|
||||
private val sessionCreator: SessionCreator,
|
||||
private val pendingSessionStore: PendingSessionStore
|
||||
private val pendingSessionStore: PendingSessionStore,
|
||||
private val getWellknownTask: GetWellknownTask,
|
||||
private val directLoginTask: DirectLoginTask,
|
||||
private val taskExecutor: TaskExecutor
|
||||
) : AuthenticationService {
|
||||
|
||||
private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData()
|
||||
@@ -78,14 +86,21 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSession(sessionParams: SessionParams): Session? {
|
||||
return sessionManager.getOrCreateSession(sessionParams)
|
||||
override fun getLoginFlowOfSession(sessionId: String, callback: MatrixCallback<LoginFlowResult>): Cancelable {
|
||||
val homeServerConnectionConfig = sessionParamsStore.get(sessionId)?.homeServerConnectionConfig
|
||||
|
||||
return if (homeServerConnectionConfig == null) {
|
||||
callback.onFailure(IllegalStateException("Session not found"))
|
||||
NoOpCancellable
|
||||
} else {
|
||||
getLoginFlow(homeServerConnectionConfig, callback)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResult>): Cancelable {
|
||||
pendingSessionData = null
|
||||
|
||||
return GlobalScope.launch(coroutineDispatchers.main) {
|
||||
return taskExecutor.executorScope.launch(coroutineDispatchers.main) {
|
||||
pendingSessionStore.delete()
|
||||
|
||||
val result = runCatching {
|
||||
@@ -148,27 +163,71 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||
val authAPI = buildAuthAPI(homeServerConnectionConfig)
|
||||
|
||||
// Ok, try to get the config.json file of a RiotWeb client
|
||||
val riotConfig = executeRequest<RiotConfig>(null) {
|
||||
apiCall = authAPI.getRiotConfig()
|
||||
}
|
||||
|
||||
if (riotConfig.defaultHomeServerUrl?.isNotBlank() == true) {
|
||||
// Ok, good sign, we got a default hs url
|
||||
val newHomeServerConnectionConfig = homeServerConnectionConfig.copy(
|
||||
homeServerUri = Uri.parse(riotConfig.defaultHomeServerUrl)
|
||||
)
|
||||
|
||||
val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig)
|
||||
|
||||
val versions = executeRequest<Versions>(null) {
|
||||
apiCall = newAuthAPI.versions()
|
||||
return runCatching {
|
||||
executeRequest<RiotConfig>(null) {
|
||||
apiCall = authAPI.getRiotConfig()
|
||||
}
|
||||
|
||||
return getLoginFlowResult(newAuthAPI, versions, riotConfig.defaultHomeServerUrl)
|
||||
} else {
|
||||
// Config exists, but there is no default homeserver url (ex: https://riot.im/app)
|
||||
throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */)
|
||||
}
|
||||
.map { riotConfig ->
|
||||
if (riotConfig.defaultHomeServerUrl?.isNotBlank() == true) {
|
||||
// Ok, good sign, we got a default hs url
|
||||
val newHomeServerConnectionConfig = homeServerConnectionConfig.copy(
|
||||
homeServerUri = Uri.parse(riotConfig.defaultHomeServerUrl)
|
||||
)
|
||||
|
||||
val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig)
|
||||
|
||||
val versions = executeRequest<Versions>(null) {
|
||||
apiCall = newAuthAPI.versions()
|
||||
}
|
||||
|
||||
getLoginFlowResult(newAuthAPI, versions, riotConfig.defaultHomeServerUrl)
|
||||
} else {
|
||||
// Config exists, but there is no default homeserver url (ex: https://riot.im/app)
|
||||
throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */)
|
||||
}
|
||||
}
|
||||
.fold(
|
||||
{
|
||||
it
|
||||
},
|
||||
{
|
||||
if (it is Failure.OtherServerError
|
||||
&& it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
|
||||
// Try with wellknown
|
||||
getWellknownLoginFlowInternal(homeServerConnectionConfig)
|
||||
} else {
|
||||
throw it
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getWellknownLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
|
||||
val domain = homeServerConnectionConfig.homeServerUri.host
|
||||
?: throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */)
|
||||
|
||||
// Create a fake userId, for the getWellknown task
|
||||
val fakeUserId = "@alice:$domain"
|
||||
val wellknownResult = getWellknownTask.execute(GetWellknownTask.Params(fakeUserId))
|
||||
|
||||
return when (wellknownResult) {
|
||||
is WellknownResult.Prompt -> {
|
||||
val newHomeServerConnectionConfig = homeServerConnectionConfig.copy(
|
||||
homeServerUri = Uri.parse(wellknownResult.homeServerUrl),
|
||||
identityServerUri = wellknownResult.identityServerUrl?.let { Uri.parse(it) }
|
||||
)
|
||||
|
||||
val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig)
|
||||
|
||||
val versions = executeRequest<Versions>(null) {
|
||||
apiCall = newAuthAPI.versions()
|
||||
}
|
||||
|
||||
getLoginFlowResult(newAuthAPI, versions, wellknownResult.homeServerUrl)
|
||||
}
|
||||
else -> throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private suspend fun getLoginFlowResult(authAPI: AuthAPI, versions: Versions, homeServerUrl: String): LoginFlowResult {
|
||||
@@ -193,7 +252,8 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||
retrofitFactory,
|
||||
coroutineDispatchers,
|
||||
sessionCreator,
|
||||
pendingSessionStore
|
||||
pendingSessionStore,
|
||||
taskExecutor.executorScope
|
||||
).also {
|
||||
currentRegistrationWizard = it
|
||||
}
|
||||
@@ -213,7 +273,8 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||
retrofitFactory,
|
||||
coroutineDispatchers,
|
||||
sessionCreator,
|
||||
pendingSessionStore
|
||||
pendingSessionStore,
|
||||
taskExecutor.executorScope
|
||||
).also {
|
||||
currentLoginWizard = it
|
||||
}
|
||||
@@ -230,7 +291,7 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||
pendingSessionData = pendingSessionData?.homeServerConnectionConfig
|
||||
?.let { PendingSessionData(it) }
|
||||
.also {
|
||||
GlobalScope.launch(coroutineDispatchers.main) {
|
||||
taskExecutor.executorScope.launch(coroutineDispatchers.main) {
|
||||
if (it == null) {
|
||||
// Should not happen
|
||||
pendingSessionStore.delete()
|
||||
@@ -247,7 +308,7 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||
|
||||
pendingSessionData = null
|
||||
|
||||
GlobalScope.launch(coroutineDispatchers.main) {
|
||||
taskExecutor.executorScope.launch(coroutineDispatchers.main) {
|
||||
pendingSessionStore.delete()
|
||||
}
|
||||
}
|
||||
@@ -255,11 +316,31 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||
override fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
credentials: Credentials,
|
||||
callback: MatrixCallback<Session>): Cancelable {
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
createSessionFromSso(credentials, homeServerConnectionConfig)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getWellKnownData(matrixId: String, callback: MatrixCallback<WellknownResult>): Cancelable {
|
||||
return getWellknownTask
|
||||
.configureWith(GetWellknownTask.Params(matrixId)) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
matrixId: String,
|
||||
password: String,
|
||||
initialDeviceName: String,
|
||||
callback: MatrixCallback<Session>): Cancelable {
|
||||
return directLoginTask
|
||||
.configureWith(DirectLoginTask.Params(homeServerConnectionConfig, matrixId, password, initialDeviceName)) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
private suspend fun createSessionFromSso(credentials: Credentials,
|
||||
homeServerConnectionConfig: HomeServerConnectionConfig): Session = withContext(coroutineDispatchers.computation) {
|
||||
sessionCreator.createSession(credentials, homeServerConnectionConfig)
|
||||
|
@@ -46,14 +46,14 @@ internal class DefaultSessionCreator @Inject constructor(
|
||||
val sessionParams = SessionParams(
|
||||
credentials = credentials,
|
||||
homeServerConnectionConfig = homeServerConnectionConfig.copy(
|
||||
homeServerUri = credentials.wellKnown?.homeServer?.baseURL
|
||||
homeServerUri = credentials.discoveryInformation?.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
|
||||
identityServerUri = credentials.discoveryInformation?.identityServer?.baseURL
|
||||
// remove trailing "/"
|
||||
?.trim { it == '/' }
|
||||
?.takeIf { it.isNotBlank() }
|
||||
|
@@ -51,7 +51,7 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
|
||||
}
|
||||
return SessionParamsEntity(
|
||||
sessionParams.credentials.sessionId(),
|
||||
sessionParams.credentials.userId,
|
||||
sessionParams.userId,
|
||||
credentialsJson,
|
||||
homeServerConnectionConfigJson,
|
||||
sessionParams.isTokenValid)
|
||||
|
@@ -38,7 +38,7 @@ 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.CoroutineScope
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
@@ -47,7 +47,8 @@ internal class DefaultLoginWizard(
|
||||
retrofitFactory: RetrofitFactory,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val sessionCreator: SessionCreator,
|
||||
private val pendingSessionStore: PendingSessionStore
|
||||
private val pendingSessionStore: PendingSessionStore,
|
||||
private val coroutineScope: CoroutineScope
|
||||
) : LoginWizard {
|
||||
|
||||
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
|
||||
@@ -59,7 +60,7 @@ internal class DefaultLoginWizard(
|
||||
password: String,
|
||||
deviceName: String,
|
||||
callback: MatrixCallback<Session>): Cancelable {
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
loginInternal(login, password, deviceName)
|
||||
}
|
||||
}
|
||||
@@ -80,7 +81,7 @@ internal class DefaultLoginWizard(
|
||||
}
|
||||
|
||||
override fun resetPassword(email: String, newPassword: String, callback: MatrixCallback<Unit>): Cancelable {
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
resetPasswordInternal(email, newPassword)
|
||||
}
|
||||
}
|
||||
@@ -108,7 +109,7 @@ internal class DefaultLoginWizard(
|
||||
callback.onFailure(IllegalStateException("developer error, no reset password in progress"))
|
||||
return NoOpCancellable
|
||||
}
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
resetPasswordMailConfirmedInternal(safeResetPasswordData)
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.auth.login
|
||||
|
||||
import dagger.Lazy
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.internal.auth.AuthAPI
|
||||
import im.vector.matrix.android.internal.auth.SessionCreator
|
||||
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
|
||||
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.Task
|
||||
import okhttp3.OkHttpClient
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface DirectLoginTask : Task<DirectLoginTask.Params, Session> {
|
||||
data class Params(
|
||||
val homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
val userId: String,
|
||||
val password: String,
|
||||
val deviceName: String
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultDirectLoginTask @Inject constructor(
|
||||
@Unauthenticated
|
||||
private val okHttpClient: Lazy<OkHttpClient>,
|
||||
private val retrofitFactory: RetrofitFactory,
|
||||
private val sessionCreator: SessionCreator
|
||||
) : DirectLoginTask {
|
||||
|
||||
override suspend fun execute(params: DirectLoginTask.Params): Session {
|
||||
val authAPI = retrofitFactory.create(okHttpClient, params.homeServerConnectionConfig.homeServerUri.toString())
|
||||
.create(AuthAPI::class.java)
|
||||
|
||||
val loginParams = PasswordLoginParams.userIdentifier(params.userId, params.password, params.deviceName)
|
||||
|
||||
val credentials = executeRequest<Credentials>(null) {
|
||||
apiCall = authAPI.login(loginParams)
|
||||
}
|
||||
|
||||
return sessionCreator.createSession(credentials, params.homeServerConnectionConfig)
|
||||
}
|
||||
}
|
@@ -33,7 +33,7 @@ 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.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
@@ -45,7 +45,8 @@ internal class DefaultRegistrationWizard(
|
||||
private val retrofitFactory: RetrofitFactory,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val sessionCreator: SessionCreator,
|
||||
private val pendingSessionStore: PendingSessionStore
|
||||
private val pendingSessionStore: PendingSessionStore,
|
||||
private val coroutineScope: CoroutineScope
|
||||
) : RegistrationWizard {
|
||||
|
||||
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
|
||||
@@ -72,7 +73,7 @@ internal class DefaultRegistrationWizard(
|
||||
|
||||
override fun getRegistrationFlow(callback: MatrixCallback<RegistrationResult>): Cancelable {
|
||||
val params = RegistrationParams()
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
performRegistrationRequest(params)
|
||||
}
|
||||
}
|
||||
@@ -86,7 +87,7 @@ internal class DefaultRegistrationWizard(
|
||||
password = password,
|
||||
initialDeviceDisplayName = initialDeviceDisplayName
|
||||
)
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
performRegistrationRequest(params)
|
||||
.also {
|
||||
pendingSessionData = pendingSessionData.copy(isRegistrationStarted = true)
|
||||
@@ -101,7 +102,7 @@ internal class DefaultRegistrationWizard(
|
||||
return NoOpCancellable
|
||||
}
|
||||
val params = RegistrationParams(auth = AuthParams.createForCaptcha(safeSession, response))
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
performRegistrationRequest(params)
|
||||
}
|
||||
}
|
||||
@@ -112,13 +113,13 @@ internal class DefaultRegistrationWizard(
|
||||
return NoOpCancellable
|
||||
}
|
||||
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.TERMS, session = safeSession))
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
performRegistrationRequest(params)
|
||||
}
|
||||
}
|
||||
|
||||
override fun addThreePid(threePid: RegisterThreePid, callback: MatrixCallback<RegistrationResult>): Cancelable {
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
pendingSessionData = pendingSessionData.copy(currentThreePidData = null)
|
||||
.also { pendingSessionStore.savePendingSessionData(it) }
|
||||
|
||||
@@ -131,7 +132,7 @@ internal class DefaultRegistrationWizard(
|
||||
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
|
||||
return NoOpCancellable
|
||||
}
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
sendThreePid(safeCurrentThreePid)
|
||||
}
|
||||
}
|
||||
@@ -177,13 +178,13 @@ internal class DefaultRegistrationWizard(
|
||||
callback.onFailure(IllegalStateException("developer error, no pending three pid"))
|
||||
return NoOpCancellable
|
||||
}
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
performRegistrationRequest(safeParam, delayMillis)
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleValidateThreePid(code: String, callback: MatrixCallback<RegistrationResult>): Cancelable {
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
validateThreePid(code)
|
||||
}
|
||||
}
|
||||
@@ -199,7 +200,7 @@ internal class DefaultRegistrationWizard(
|
||||
code = code
|
||||
)
|
||||
val validationResponse = validateCodeTask.execute(ValidateCodeTask.Params(url, validationBody))
|
||||
if (validationResponse.success == true) {
|
||||
if (validationResponse.isSuccess()) {
|
||||
// The entered code is correct
|
||||
// Same than validate email
|
||||
return performRegistrationRequest(registrationParams, 3_000)
|
||||
@@ -214,7 +215,7 @@ internal class DefaultRegistrationWizard(
|
||||
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
|
||||
return NoOpCancellable
|
||||
}
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.DUMMY, session = safeSession))
|
||||
performRegistrationRequest(params)
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user