forked from GitHub-Mirror/riotX-android
Compare commits
1105 Commits
feature/fi
...
v0.13.0
Author | SHA1 | Date | |
---|---|---|---|
f471d9cff8 | |||
7604748d3f | |||
d46a3ab87f | |||
b375463aec | |||
cfb752c220 | |||
c59712732a | |||
626ebd9c63 | |||
5148d7ab46 | |||
8c2a55a5a2 | |||
34b6dd4270 | |||
79a68a36bd | |||
78408fa0ec | |||
b56ee9a377 | |||
9eff459ed6 | |||
cd44b60bd5 | |||
17ed8da57a | |||
8ae51a95c5 | |||
f80f22fed2 | |||
fa57d0f6d5 | |||
1104b5d483 | |||
464462022c | |||
1c63d3f8f0 | |||
a916bd4ba8 | |||
15e2c9f3a3 | |||
f1a2fb51f5 | |||
b0aa9fbd8f | |||
d72f1ac576 | |||
ca157c7567 | |||
ae26bf3369 | |||
159c96681f | |||
96d6b75037 | |||
72e6181f00 | |||
4ae09b8716 | |||
f128ed437f | |||
11c8c8c2bd | |||
2c331671ee | |||
b44ddcfd61 | |||
7c0f2d6e32 | |||
52de14b1b5 | |||
d6e6092eea | |||
9671a77e5d | |||
d3415d345f | |||
df4df81ef3 | |||
9d5197b1c8 | |||
162f0949fa | |||
ae1a24e948 | |||
b5fead18fe | |||
3c682430d9 | |||
717965bc37 | |||
ecc463e920 | |||
f3e52b96c0 | |||
dd81fce8d8 | |||
1ae58aa6ad | |||
095216349e | |||
550908fa70 | |||
0dbca829ea | |||
03b5b098c7 | |||
171ec4fbdc | |||
32d2daee3c | |||
de84bb7535 | |||
81712ae736 | |||
289951ea4a | |||
8c9c65837d | |||
24a7ce7d98 | |||
d486e613d8 | |||
598d67234c | |||
fea4f2e129 | |||
687ea1b5b3 | |||
47e3b8ec46 | |||
dd8c908dc7 | |||
9775e8c32b | |||
e3205fb493 | |||
35f011ba37 | |||
ed773dbb96 | |||
fa821826d2 | |||
293e3e3ce6 | |||
4244c0e48d | |||
76e45431da | |||
f3fb07079e | |||
3ceac70536 | |||
0f7209df1f | |||
e177251ec0 | |||
6746f68411 | |||
fc6d845c0d | |||
93cdce6c3e | |||
ae3381227c | |||
b6a1ff1ca4 | |||
7ec0227528 | |||
15639b45cf | |||
f18ec8d021 | |||
898bf234da | |||
3d0d95c371 | |||
bd4a595f96 | |||
0f7d59a8c7 | |||
e14b9b3b20 | |||
43c4e20819 | |||
5312b44359 | |||
8c4d8763a2 | |||
383605274c | |||
8032490606 | |||
a458997ce0 | |||
29f152f349 | |||
6ca3ba6c9b | |||
f4492e570d | |||
587fefedb5 | |||
943be39e1a | |||
616f3d3345 | |||
2b8ecae8e3 | |||
17c4013383 | |||
d662b4a9b4 | |||
501ac36040 | |||
7575cb286e | |||
c60b4ddb5a | |||
9970d7ffa0 | |||
2dd2a8db6c | |||
8ef5c60e2e | |||
03c3c9ae57 | |||
38c198fe02 | |||
42c7421b05 | |||
19fb3ce032 | |||
5a7f4bed43 | |||
03734a7ad5 | |||
d710106bbb | |||
f09bf61750 | |||
f9487f8995 | |||
99c523b710 | |||
3cc15387ae | |||
6245763577 | |||
448552d287 | |||
9ecceafb96 | |||
92d7ebe94f | |||
0e5fcd071c | |||
c8e67f8ab4 | |||
5fa2acf60b | |||
9e73e95f55 | |||
8b4c51139d | |||
8597c2b9a2 | |||
d88e5d8af8 | |||
c4fe0bdb7f | |||
d73a1135ae | |||
ed097bcf37 | |||
01db856a5d | |||
a00f51a264 | |||
9e8217082c | |||
ce73007157 | |||
f432d15757 | |||
215abea10a | |||
160927e7b5 | |||
c2e7e33050 | |||
455448806d | |||
a969443517 | |||
1bd85082c3 | |||
de1d79b637 | |||
8e478e78e1 | |||
96c9293edc | |||
5c26f66523 | |||
5a24f78c05 | |||
703a1a034d | |||
7d744f7d7f | |||
8dff196716 | |||
6b2703f6ce | |||
e32d242e38 | |||
787908287c | |||
8156b754c1 | |||
03fd474aa8 | |||
6ad914154a | |||
cba7e460eb | |||
6794173321 | |||
92f4288d3e | |||
8109262cbb | |||
037bf45884 | |||
833a5a37a2 | |||
00f316ba5d | |||
dfd8181754 | |||
90f2199eb7 | |||
63828bc159 | |||
35b4d90e0d | |||
4fe9c52737 | |||
c54358831f | |||
a9d016ae79 | |||
fcdaf14b01 | |||
83126d5f55 | |||
e13281dc97 | |||
3cc65b1e71 | |||
0ccb975d43 | |||
54f2ac0d8c | |||
3ee5a7f54d | |||
3b0624ea40 | |||
c992d32afd | |||
c5739abe32 | |||
3ac473d945 | |||
c79b35b089 | |||
8dce98c538 | |||
543c07fd69 | |||
05a788453f | |||
c31b64771b | |||
92f43a591a | |||
3a829bdfe8 | |||
237b22df59 | |||
c18be94986 | |||
d342356f29 | |||
07817b69c2 | |||
e73970d61b | |||
0eb0870d6c | |||
55748a4af4 | |||
51d6b8828d | |||
358fcb6b34 | |||
9a7cd3f270 | |||
92315a4189 | |||
a6afd2e904 | |||
156cc1aa4a | |||
0d36e9d8a6 | |||
13439769a1 | |||
7bb8cb0682 | |||
a4ea9a09ad | |||
bf69810f8f | |||
bb9510e59b | |||
4b0dfa49f4 | |||
6652965e48 | |||
5bde7b9f17 | |||
c8f0c83cd3 | |||
b0ff2cb4bb | |||
648691656a | |||
7eae85a394 | |||
123ffe9f9c | |||
7697278bb2 | |||
c48a439eea | |||
9d26ba3186 | |||
08970ad8c1 | |||
4c88c12cfe | |||
79f11ad686 | |||
7fa76b9d35 | |||
65faedb06b | |||
1ceddd9607 | |||
42cdb1db11 | |||
1c727c1ee4 | |||
2316c98a65 | |||
a4aa38ee43 | |||
4a11d028c0 | |||
c286f2a744 | |||
e2b4899b36 | |||
aa82cd2064 | |||
bc568343a2 | |||
abf0796794 | |||
02febfb01b | |||
91c98d4bfb | |||
cfee6a43fd | |||
f14f1db0e0 | |||
3feb2d8980 | |||
9fc3093c2c | |||
7d910f2566 | |||
0a0eda3e34 | |||
cecef5b8da | |||
c9ed95ed21 | |||
3dfd6f5a69 | |||
8fc1400bab | |||
3e4b07cec3 | |||
fbb1846694 | |||
b435212c87 | |||
5dd46e82d7 | |||
1108ad5705 | |||
fe2be90002 | |||
f073342954 | |||
38b40efac3 | |||
e60bda7806 | |||
92e60c939d | |||
6e4830e325 | |||
c6b98f3654 | |||
12d54140e5 | |||
1de85daad9 | |||
050519e998 | |||
1af44ce5f7 | |||
8d1a36425d | |||
4e74b545ad | |||
183d6b53bd | |||
14562f7285 | |||
17bcd680b0 | |||
954019547d | |||
782635ec8e | |||
e609f4a57e | |||
907fa35547 | |||
00d0c34363 | |||
6811d31a6d | |||
a464c910f8 | |||
d69881f321 | |||
efc1f38f8c | |||
b9e8da1fbb | |||
d2fea275d8 | |||
a5af949c15 | |||
eab94b4f03 | |||
6b61c95843 | |||
261b4be287 | |||
205fc0d9d6 | |||
7699560458 | |||
a193b2659d | |||
bb85d41f05 | |||
9bfe904745 | |||
284dc8602f | |||
29087d4a87 | |||
18649ebddb | |||
d5935a13ac | |||
670d4dc34e | |||
5435a1739e | |||
853518fbb2 | |||
3a269be2ef | |||
0b93f34fa0 | |||
5338f93852 | |||
902a9aa243 | |||
5915cebd6d | |||
d91ff87fb9 | |||
79ef055bfb | |||
3a761be6b4 | |||
a9e2c31c32 | |||
3ac53d20e9 | |||
3c18fd5335 | |||
f00f34b244 | |||
63e0b15f3d | |||
80306f20df | |||
2972177541 | |||
1ad8f47dc1 | |||
8527d3f162 | |||
99423bacb2 | |||
edc6c3dd4f | |||
a761a0dbd2 | |||
d431ab23c8 | |||
f0aa34774e | |||
742136abe8 | |||
36aba8554d | |||
da14ae432a | |||
9a01b4ace9 | |||
109c1fe482 | |||
dbd4525404 | |||
c714266a81 | |||
8b1701e537 | |||
41d1b77370 | |||
ac75fe12bf | |||
2f26f4b8bb | |||
6d82ac7c59 | |||
411afb0bf3 | |||
57354cbd69 | |||
03d51281a2 | |||
415511f3e0 | |||
e0e778909d | |||
b9efc9f4bd | |||
872b14373b | |||
d28700e2bf | |||
18beef14cf | |||
e73923dca3 | |||
94afd3e66d | |||
5f540a5b45 | |||
a41617e8aa | |||
ff1745b5dc | |||
8e3e9876b8 | |||
9a4d8f87f6 | |||
aef76241a3 | |||
0768bd5c88 | |||
65333e6031 | |||
849e7c613c | |||
60169d53d7 | |||
4234c27af9 | |||
f9c0256afd | |||
b9eb85e0a6 | |||
6d8850b3d6 | |||
d88edd578f | |||
5373d9aa21 | |||
ad4d5e5c02 | |||
eb9775e307 | |||
aa9d66b991 | |||
4ff12605e9 | |||
7c561ae622 | |||
cec08a20e5 | |||
fb8ba32fb4 | |||
8e9ac8198d | |||
7a05207ae4 | |||
6b39cf3b70 | |||
994759e11a | |||
f31c1b69cb | |||
bdb9d2fbb8 | |||
9fb50dde32 | |||
a145aae0aa | |||
3623072f08 | |||
2717ad475a | |||
a6f8fe9317 | |||
f9eb80b4ec | |||
9510d71cd3 | |||
e7a47ae32a | |||
7890b929a7 | |||
0376de08f4 | |||
90c472fef9 | |||
8e873672a9 | |||
bba52e77d1 | |||
64d73ae8e6 | |||
d9982076f9 | |||
ab489df83d | |||
5e07e96bdb | |||
c495aa4914 | |||
ff267ba9bc | |||
69f923383c | |||
c69852c849 | |||
6d7f2670df | |||
71de8fdad3 | |||
998d9f2c59 | |||
4f3da353e4 | |||
4154cb2b85 | |||
3c6eb4bccf | |||
7b4398404b | |||
9b882978ed | |||
49178dc633 | |||
490ce4b51d | |||
5b63856d96 | |||
538c4d1a64 | |||
1cadbb8eed | |||
3f4f7457c7 | |||
ebf21fe9d8 | |||
a343da594f | |||
938289e8eb | |||
e23763e6db | |||
c06b8486ea | |||
67fe776d91 | |||
10cc270273 | |||
46d96429e0 | |||
9f9c418085 | |||
c412006f0e | |||
5d3c376267 | |||
a3f8f138a6 | |||
4b273e8746 | |||
f11cd47df3 | |||
f984758d37 | |||
97766404d6 | |||
38b93c527b | |||
62bae67080 | |||
2a4cdec020 | |||
6bd7257cf2 | |||
500eb113b6 | |||
1bec8c29b8 | |||
0ecb23199c | |||
33925fcf57 | |||
bf9ce4f690 | |||
d2a4163dff | |||
a0d7aef92e | |||
29f32cf8eb | |||
bb1c988a49 | |||
f063abe068 | |||
db87d8f644 | |||
efa858a337 | |||
fd90f3b9fc | |||
aa51764068 | |||
0a19ded167 | |||
2e3763e8b4 | |||
0c4e0890b1 | |||
e532d97ec1 | |||
fbde8d7d18 | |||
f96bea742e | |||
86bfdd011e | |||
d5c2c1938c | |||
fd18bcb97f | |||
ab4cab05cf | |||
7ce8a13ddf | |||
9bd4dbb65f | |||
ee875b359b | |||
3eb2e1655f | |||
9b207dd5dc | |||
3f1540b54e | |||
2b30925163 | |||
4690754f5f | |||
a9526cdd92 | |||
ab4d42fb20 | |||
0014e8ef06 | |||
311d8ddf7b | |||
6cb3c222a9 | |||
f84ec08847 | |||
9d0188cbf1 | |||
73462a3045 | |||
3eebf965e5 | |||
bba58d25e1 | |||
fedb45b019 | |||
9b83f08654 | |||
91fcf428dd | |||
7e1a279fd9 | |||
8de1fa835b | |||
af45c554fd | |||
11bc7051fd | |||
489a594027 | |||
3f83c161e4 | |||
e0a36b794f | |||
d2b516bdc2 | |||
37166caea2 | |||
9fa131c297 | |||
71ae3c4a8c | |||
f87526e615 | |||
51f53e2ae9 | |||
ef35f0a044 | |||
5db3f51ddb | |||
49f7ce3554 | |||
a3111dc2d8 | |||
be95542110 | |||
6723a566c2 | |||
90027cc4d5 | |||
810b226f21 | |||
42c5adf08d | |||
5edfb78721 | |||
491a38a79f | |||
051f77087e | |||
1a603742d0 | |||
edb65f1787 | |||
9af8355c07 | |||
dd44078297 | |||
a1f96a5b5a | |||
5770023593 | |||
2789268c23 | |||
eb4355890e | |||
4e41156db3 | |||
1a0b8b35f8 | |||
5f9cdcb4b4 | |||
2e4c3f850a | |||
127916a8d9 | |||
248a584e1a | |||
b8a3ad0c43 | |||
1f161b7e23 | |||
23315ede92 | |||
20ad3abb60 | |||
ac377fceba | |||
abbe56acfa | |||
f74cabd145 | |||
0e2237226f | |||
62d5aba796 | |||
f12e6c941d | |||
7caa8ce3bc | |||
20f969d563 | |||
a8f24e5c39 | |||
8ae9544b48 | |||
3758334824 | |||
6d8e5b892e | |||
c18c140ec9 | |||
1dc7dfc896 | |||
1c03163a33 | |||
9aa270c7ad | |||
3f80076fb1 | |||
dfbf448bb7 | |||
95fc20dca0 | |||
381084b2ab | |||
41ac2c6d70 | |||
08ea3d049e | |||
f24889230c | |||
b5f9549a8b | |||
e3e38d4c8a | |||
416bef7903 | |||
823acebf78 | |||
3e91125872 | |||
9a628c7b5d | |||
fb46a14172 | |||
ca4e75a1a0 | |||
2871e4f5b1 | |||
b7bfb20a2e | |||
a1aa16715d | |||
55add4734d | |||
2849e1f846 | |||
5b9876a20c | |||
adf299081d | |||
d50b690523 | |||
c6b0ae63ea | |||
3c93807fe6 | |||
7f1f98c2e5 | |||
6525314af8 | |||
da8d6fb4f4 | |||
fa6a9cab7e | |||
bdfc4ad8a7 | |||
6ab7209e4d | |||
4485d1c685 | |||
8b63f78d76 | |||
2e87e0b4c1 | |||
507134407b | |||
7663cd4e23 | |||
ec2954200e | |||
eb32c5455f | |||
fc367b3c3e | |||
57dcd569f3 | |||
3673520ef6 | |||
8aab46804b | |||
fe17050580 | |||
0edc953a23 | |||
fa67509ac2 | |||
ec40a8c969 | |||
6b1b3bec85 | |||
6bd6ececb7 | |||
c7db695e67 | |||
4cefdfedce | |||
6ce241163e | |||
79350899c5 | |||
f265724a3c | |||
2e50d2a36e | |||
643c062858 | |||
0e0db67aef | |||
6dc5b126d6 | |||
d2acabddd9 | |||
ec71b53c1e | |||
fc3d4187d1 | |||
a25f309990 | |||
5449592422 | |||
19b415871d | |||
6463f3439f | |||
f2320f9571 | |||
fc91694bdd | |||
dbb41108ef | |||
08c864bad7 | |||
9c5c65a243 | |||
b6199b1f27 | |||
38da54119a | |||
65b09ad4f0 | |||
603b8fae45 | |||
50e2e6a823 | |||
bb237e3bbb | |||
1bd2c0d220 | |||
bcb811a7e8 | |||
ec4d7e29ec | |||
a6df63f6d9 | |||
ea7213a5ae | |||
590a13334d | |||
631448335d | |||
12376368c7 | |||
f17564d743 | |||
a6fcc7dca6 | |||
70bce9e7dd | |||
17f3614288 | |||
238d1d87c6 | |||
82f639b91f | |||
c8bc553caa | |||
fa5d44af65 | |||
cbdbe5033f | |||
61ac250e2b | |||
04f72dfcb8 | |||
10ca5d94ea | |||
c5b8c69ae5 | |||
d3d7f7cc61 | |||
b6bb714264 | |||
a87310ac15 | |||
032e1b3d19 | |||
d9f15c1d21 | |||
99d09f71ad | |||
9c952b6bc8 | |||
fbae3d27c2 | |||
2f7d1f9f01 | |||
114101699d | |||
f5c0dcb5ea | |||
241220ce1f | |||
98d97e574c | |||
96e610970a | |||
2027802f82 | |||
54f93db632 | |||
3af7ca9ab0 | |||
93ef3edab3 | |||
c85852262e | |||
d0c3271628 | |||
ad9a48d5fa | |||
219d1383e5 | |||
8871280fab | |||
fb3e953e28 | |||
10712fd6ab | |||
9d478dbfe2 | |||
3013d67c16 | |||
bee8c2d159 | |||
945e5d5a74 | |||
93df8c56a8 | |||
cd1a964067 | |||
e4b829f0cf | |||
7206d84a6b | |||
b3233d3eb7 | |||
3c4c0ed46a | |||
24f1262005 | |||
86667a6d8a | |||
42e0d0f769 | |||
e976055253 | |||
84d6c8ec16 | |||
9fdfd091ac | |||
e66766f41c | |||
6177e69855 | |||
5c71cabb5f | |||
6ebe5532c5 | |||
8030c44f44 | |||
a85b5af761 | |||
d780c74abf | |||
5d7efa7f8f | |||
7e467443ed | |||
8439c337f7 | |||
151ad01038 | |||
73267442bb | |||
43fd794c96 | |||
36060fe332 | |||
3483debcc1 | |||
4324f6abbd | |||
43f8d8d8aa | |||
fb1ff77ec4 | |||
e355a7f6dd | |||
33e35368fc | |||
0e49a11e5e | |||
d47cf7e932 | |||
101057520b | |||
30b2e53002 | |||
5ab31a0ef5 | |||
b4ae331086 | |||
3f447df13c | |||
3517873156 | |||
118870bc41 | |||
d001ab5bef | |||
7496a88dcd | |||
6567c5e6c7 | |||
361427488f | |||
7272343e6d | |||
f0b3151d71 | |||
035359cb35 | |||
57b640622b | |||
de4c389c76 | |||
199456487c | |||
00ca5dc70a | |||
a04802b238 | |||
cb275aee37 | |||
fbf73c7c8e | |||
0040f8e924 | |||
6cca242f77 | |||
2929b8f617 | |||
8422c6de17 | |||
7c567b04bb | |||
1ac99e92a6 | |||
5ab975cc5c | |||
2cf63ea92a | |||
9e8d8ce878 | |||
b766bce07d | |||
01452efd8d | |||
0a0af221f0 | |||
af08759af6 | |||
e52f0faaa7 | |||
8fa676d034 | |||
b6594599c4 | |||
8be8cc9ef7 | |||
9762d5be40 | |||
b17b54d218 | |||
187e2a26db | |||
2f5fdbb7e2 | |||
8b1411f533 | |||
bdee5e0687 | |||
ce4e244a3b | |||
ff81715783 | |||
3196dcb57e | |||
50bf6df7fe | |||
02914495ce | |||
70a14f6350 | |||
cac5fb725a | |||
dbc17ae515 | |||
1de02c2fbb | |||
6d55c15761 | |||
377a228f88 | |||
2974f8b200 | |||
7388a408b8 | |||
f43dcb1183 | |||
492ed3954a | |||
7890e83204 | |||
00d1a2c380 | |||
78dfd6b3e6 | |||
3abce34484 | |||
4204ab262c | |||
c7a4d34192 | |||
7416fec93e | |||
3c40f64fb7 | |||
b57c71b1c9 | |||
fea54952d3 | |||
3dc5ef54ab | |||
9092b97fb8 | |||
cebd8136da | |||
64b3568d51 | |||
5e4e54153c | |||
d071324694 | |||
2c8cd89533 | |||
11b5c2c3ba | |||
9d7c4abb97 | |||
8e3234d188 | |||
b253722b98 | |||
fce576e3a4 | |||
7ed7b18ccd | |||
053bf7aeac | |||
6ccd083451 | |||
e39c4a7925 | |||
abdb83b9fd | |||
0bcc84cbd6 | |||
b2f6fb8c91 | |||
36042ed145 | |||
6ad1932fe5 | |||
4a6237b50e | |||
a7a19dab11 | |||
8d0aa0437c | |||
0a79b8b315 | |||
1dacfa6744 | |||
723a007c39 | |||
eaa1b04a4a | |||
b1710fde60 | |||
cd0a40c18d | |||
17636019e0 | |||
8078c39d6e | |||
be94b2f90a | |||
eff04be247 | |||
3986839801 | |||
9e436483de | |||
05a069be04 | |||
a1a71e2f1d | |||
203da0f37e | |||
6cd04525aa | |||
3c3c6aeac6 | |||
e71311f576 | |||
e4d0e0b0bf | |||
28e5e42ab1 | |||
b860c3b0e3 | |||
f7f97e2098 | |||
e28e2dadb9 | |||
c28be6adb0 | |||
c57af9cf3e | |||
679b0fff98 | |||
946fc36a26 | |||
13a5f784dc | |||
0ca8696e88 | |||
3622c0ecb4 | |||
116d569fa8 | |||
ee5ebb4b83 | |||
0a0c344bfb | |||
82fc97f619 | |||
20696353b8 | |||
ae5b6bd2b9 | |||
1e11d4492b | |||
6e39164b20 | |||
0a9ebb6bf6 | |||
db009ce683 | |||
55c80d3743 | |||
fbb23dfb66 | |||
e5779d425a | |||
99d9704a50 | |||
3f8ddbe880 | |||
30e43e47cd | |||
15dc4d6369 | |||
dceb5ffd8d | |||
eec470f2ce | |||
68db9c1cc0 | |||
cdfc402599 | |||
72d3f1e909 | |||
255fa11e89 | |||
119e4c0d32 | |||
a9c474105a | |||
6de64cbedd | |||
546c537e3b | |||
36c5f9af13 | |||
c2682c7f4b | |||
3073470c38 | |||
549f749682 | |||
d4dfb76e80 | |||
e80191b2e0 | |||
c62c77f14c | |||
d6e5c5a857 | |||
50a0660ab6 | |||
ecdb3c3326 | |||
2cd1d697fe | |||
3f9b7813bc | |||
f34f28b668 | |||
53572a3be6 | |||
90b6199e10 | |||
0aa299aa37 | |||
d387c310c8 | |||
8bd1fb08f7 | |||
ac6aff9175 | |||
adf0382d28 | |||
51554f7be0 | |||
c1c1c3f999 | |||
8b04fdab77 | |||
f8b665a245 | |||
d68a9a5342 | |||
5d2ff589f8 | |||
e85a0783fc | |||
d6c278288d | |||
4ad86a13a0 | |||
4f7ec91255 | |||
979b42aa30 | |||
fc49de080c | |||
d2b9668d4e | |||
0632870be1 | |||
8e39fd2a70 | |||
abbc62dd35 | |||
77de059dc9 | |||
1931a1a4a4 | |||
9c5987b682 | |||
4e4fb4c565 | |||
0582d0f641 | |||
ef2af14529 | |||
525da17678 | |||
aab41d7358 | |||
5db3c81aa9 | |||
c763635845 | |||
11d72b81f6 | |||
53543453b3 | |||
d4be68191c | |||
7ef471ad0d | |||
73dd735ba6 | |||
2f6d3adb17 | |||
2edfd4e830 | |||
ff7856c535 | |||
650a151b18 | |||
275dd20412 | |||
44f6391cb4 | |||
588e5d6e63 | |||
716999eec6 | |||
42e0a45f3f | |||
31397869b2 | |||
e842bf13b2 | |||
aea34da81e | |||
0814f53fed | |||
b5c6c1af0d | |||
de30e7c1c6 | |||
2d95fe921d | |||
84542326f4 | |||
53b1b89c47 | |||
28315be7b9 | |||
8605095668 | |||
737959f616 | |||
7817f49072 | |||
a060431aaf | |||
a3f561d788 | |||
0ea878af8a | |||
99de40c980 | |||
810a97c639 | |||
f02f16d9c5 | |||
62b7a83a31 | |||
4a80df082c | |||
60f6b3ef02 | |||
a0b1ef3216 | |||
1b66d1f746 | |||
643a2baabf | |||
cd62e87266 | |||
17cba1a432 | |||
f077cc8467 | |||
f3039601bf | |||
4c04014e4d | |||
ae8bceacba | |||
9b91b6ea87 | |||
b24a372262 | |||
63b43de4b8 | |||
d1a61f29e4 | |||
f6373221de | |||
ec0974f72c | |||
b5f2f01c8d | |||
21d808c1ce | |||
1e963bc0dc | |||
0d80750507 | |||
1c9cf7a810 | |||
c6d01fbcf4 | |||
9e1ded941f | |||
af433266c8 | |||
05d09bf950 | |||
6890f83810 | |||
51568c30a6 | |||
cc832633a5 | |||
e019ec6596 | |||
eadea9016b | |||
6422d946c9 | |||
5cc3dc00e3 | |||
5a2a9f908a | |||
c1f2e9f171 | |||
f6d34ec7fd | |||
620ba279d8 | |||
3fcfa33364 | |||
546da0f173 | |||
001711d5a3 | |||
8e1a964679 | |||
b25a130db1 | |||
8a9e6497e8 | |||
47e3797b7e | |||
5cbc90e06a | |||
b6e18e4a8f | |||
7e29665fd0 | |||
e04bf31faa | |||
d25cf79b07 | |||
faa8e6bbb2 | |||
d3d4deb884 | |||
f6b8e0c479 | |||
2a726f54a2 | |||
1197d4021d | |||
03f8120b7d | |||
acd7a709de | |||
5651ea515b | |||
9794b3a49d | |||
b3e1c3969d | |||
90eeb68d36 | |||
d1ff3314a7 | |||
f24bed17a2 | |||
a993a30203 | |||
ea0809ff87 | |||
9668487b6b | |||
91cc78d2ad | |||
562acc9702 | |||
dfab88ed95 | |||
36866dd24e | |||
c728834273 | |||
f5020d0f63 | |||
7da9cafcc2 | |||
6f09eea248 | |||
468bd5bcc9 | |||
3169093c50 | |||
d60d766354 | |||
0ffb5e627e | |||
b4a13f9504 | |||
88fb9667a3 | |||
ffa8b7e73a | |||
528958b3de | |||
3ffe2f7d40 | |||
3066d5f303 | |||
bf42b73713 | |||
ed93f4a6c1 | |||
b3d649a4d9 | |||
3739e50d46 | |||
9bf484cf1e | |||
6c2faff1f0 | |||
07fca0922b | |||
282de21708 | |||
ba9d119892 | |||
4453f0ced9 | |||
77168bfd6a | |||
25e9a179d2 | |||
73ec0f5a83 | |||
993fa74252 | |||
38fc4984fe | |||
695d8cce00 | |||
07e99901e1 | |||
20f53e9a58 | |||
ced72aff4f | |||
fdaaca49c2 | |||
3485f023b0 | |||
384dd100e9 | |||
1ba8a58219 | |||
69fb7bdf95 | |||
c8010561fc | |||
1f127335bc | |||
138a210a73 | |||
ca6bcde82d | |||
6bda437f5d | |||
5d6d0202a9 | |||
3e6b65e174 | |||
137dcab734 | |||
b22b8fba02 | |||
3ccdf4a244 | |||
5fbd271b1c | |||
db8ea0f5e8 | |||
a47a3ead1f | |||
05b2092ffc | |||
f4ab770be9 | |||
6249a59203 | |||
d4111d053d | |||
618e9a4f52 | |||
b8ebe3570b | |||
f2c8d4ad02 | |||
be524472ec | |||
1b82a1a24d | |||
cf0b331c3b | |||
2a92a3dc80 | |||
012840abba | |||
a5975a099e | |||
38da4b9ee5 | |||
242e60fcaa | |||
a23be05cbf | |||
ed39b02924 | |||
fe931b5361 | |||
90d9cd0587 | |||
9cedb18921 | |||
e89ba7b87b | |||
902657c22a | |||
eec2abf164 | |||
6879cc8ca8 | |||
fd6bbbd3b5 | |||
0ff0b014a9 | |||
a89f0ddd1d | |||
fdc9e84dd5 | |||
58f878fca9 | |||
88095e4bd9 | |||
47d22a3d5e | |||
28e82cb8ea | |||
35817245cb | |||
75266f42bb | |||
95c4c9ce56 | |||
ce5570105d | |||
188a9aebfa | |||
c95223f5d2 | |||
ef0362ba9c | |||
ea242f6737 | |||
cbc08d834b | |||
0ab6b33fb6 | |||
1b394527b6 | |||
a8f1388721 | |||
166be4e289 | |||
b49ccefe63 | |||
825760d17e | |||
b5af62c3ea | |||
a51d96bf00 | |||
7e142d201d | |||
2be6058971 | |||
49d73f360e | |||
51a4c93676 | |||
d8f449388c | |||
9cd69d1e33 | |||
456908c851 | |||
215324a03e | |||
02e342849f | |||
df6080b1da |
@ -1,15 +1,38 @@
|
||||
# Use Docker file from https://hub.docker.com/r/runmymind/docker-android-sdk
|
||||
# Last docker plugin version can be found here:
|
||||
# https://github.com/buildkite-plugins/docker-buildkite-plugin/releases
|
||||
|
||||
# Build debug version of the RiotX application, from the develop branch and the features branches
|
||||
# We propagate the environment to the container (sse https://github.com/buildkite-plugins/docker-buildkite-plugin#propagate-environment-optional-boolean)
|
||||
|
||||
steps:
|
||||
- label: "Assemble GPlay Debug version"
|
||||
- label: "Compile and run Unit tests"
|
||||
agents:
|
||||
# We use a medium sized instance instead of the normal small ones because
|
||||
# gradle build is long
|
||||
# gradle build can be memory hungry
|
||||
queue: "medium"
|
||||
commands:
|
||||
- "./gradlew clean test --stacktrace"
|
||||
plugins:
|
||||
- docker#v3.1.0:
|
||||
image: "runmymind/docker-android-sdk"
|
||||
propagate-environment: true
|
||||
|
||||
- label: "Compile Android tests"
|
||||
agents:
|
||||
# We use a medium sized instance instead of the normal small ones because
|
||||
# gradle build can be memory hungry
|
||||
queue: "medium"
|
||||
commands:
|
||||
- "./gradlew clean assembleAndroidTest --stacktrace"
|
||||
plugins:
|
||||
- docker#v3.1.0:
|
||||
image: "runmymind/docker-android-sdk"
|
||||
propagate-environment: true
|
||||
|
||||
- label: "Assemble GPlay Debug version"
|
||||
agents:
|
||||
# We use a xlarge sized instance instead of the normal small ones because
|
||||
# gradle build can be memory hungry
|
||||
queue: "xlarge"
|
||||
commands:
|
||||
- "./gradlew clean lintGplayRelease assembleGplayDebug --stacktrace"
|
||||
artifact_paths:
|
||||
@ -18,12 +41,13 @@ steps:
|
||||
plugins:
|
||||
- docker#v3.1.0:
|
||||
image: "runmymind/docker-android-sdk"
|
||||
propagate-environment: true
|
||||
|
||||
- label: "Assemble FDroid Debug version"
|
||||
agents:
|
||||
# We use a medium sized instance instead of the normal small ones because
|
||||
# gradle build is long
|
||||
queue: "medium"
|
||||
# We use a xlarge sized instance instead of the normal small ones because
|
||||
# gradle build can be memory hungry
|
||||
queue: "xlarge"
|
||||
commands:
|
||||
- "./gradlew clean lintFdroidRelease assembleFdroidDebug --stacktrace"
|
||||
artifact_paths:
|
||||
@ -32,12 +56,13 @@ steps:
|
||||
plugins:
|
||||
- docker#v3.1.0:
|
||||
image: "runmymind/docker-android-sdk"
|
||||
propagate-environment: true
|
||||
|
||||
- label: "Build Google Play unsigned APK"
|
||||
agents:
|
||||
# We use a medium sized instance instead of the normal small ones because
|
||||
# gradle build is long
|
||||
queue: "medium"
|
||||
# We use a xlarge sized instance instead of the normal small ones because
|
||||
# gradle build can be memory hungry
|
||||
queue: "xlarge"
|
||||
commands:
|
||||
- "./gradlew clean assembleGplayRelease --stacktrace"
|
||||
artifact_paths:
|
||||
@ -46,8 +71,18 @@ steps:
|
||||
plugins:
|
||||
- docker#v3.1.0:
|
||||
image: "runmymind/docker-android-sdk"
|
||||
propagate-environment: true
|
||||
|
||||
# Code quality
|
||||
|
||||
- label: "Code quality"
|
||||
command: "./tools/check/check_code_quality.sh"
|
||||
command:
|
||||
- "./tools/check/check_code_quality.sh"
|
||||
|
||||
- label: "ktlint"
|
||||
command:
|
||||
- "curl -sSLO https://github.com/pinterest/ktlint/releases/download/0.34.2/ktlint && chmod a+x ktlint"
|
||||
- "./ktlint --android --experimental -v"
|
||||
plugins:
|
||||
- docker#v3.1.0:
|
||||
image: "openjdk"
|
||||
|
32
.editorconfig
Normal file
32
.editorconfig
Normal file
@ -0,0 +1,32 @@
|
||||
# For ktlint configuration. Ref: https://ktlint.github.io/
|
||||
|
||||
[*.{kt,kts}]
|
||||
# possible values: number (e.g. 2), "unset" (makes ktlint ignore indentation completely)
|
||||
indent_size=unset
|
||||
# true (recommended) / false
|
||||
insert_final_newline=true
|
||||
# possible values: number (e.g. 120) (package name, imports & comments are ignored), "off"
|
||||
# it's automatically set to 100 on `ktlint --android ...` (per Android Kotlin Style Guide)
|
||||
max_line_length=off
|
||||
|
||||
# Comma-separated list of rules to disable (Since 0.34.0)
|
||||
# Note that rules in any ruleset other than the standard ruleset will need to be prefixed
|
||||
# by the ruleset identifier.
|
||||
disabled_rules=no-wildcard-imports,no-multi-spaces,colon-spacing,chain-wrapping,import-ordering,experimental:annotation
|
||||
|
||||
# The following (so far identified) rules are kept:
|
||||
# no-blank-line-before-rbrace
|
||||
# final-newline
|
||||
# no-consecutive-blank-lines
|
||||
# comment-spacing
|
||||
# filename
|
||||
# comma-spacing
|
||||
# paren-spacing
|
||||
# op-spacing
|
||||
# string-template
|
||||
# no-unused-imports
|
||||
# curly-spacing
|
||||
# no-semi
|
||||
# no-empty-class-body
|
||||
# experimental:multiline-if-else
|
||||
# experimental:no-empty-first-line-in-method-block
|
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -7,4 +7,4 @@
|
||||
- [ ] Pull request is based on the develop branch
|
||||
- [ ] Pull request updates [CHANGES.md](https://github.com/vector-im/riotX-android/blob/develop/CHANGES.md)
|
||||
- [ ] Pull request includes screenshots or videos if containing UI changes
|
||||
- [ ] Pull request includes a [sign off](https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst#sign-off)
|
||||
- [ ] Pull request includes a [sign off](https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.md#sign-off)
|
||||
|
13
.gitignore
vendored
13
.gitignore
vendored
@ -1,14 +1,17 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
.idea/*
|
||||
/.idea/*
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
# idea files: exclude everything except dictionnaries
|
||||
.idea/caches
|
||||
.idea/libraries
|
||||
.idea/*.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
|
||||
/tmp
|
||||
|
||||
ktlint
|
||||
.idea/copyright/New_vector.xml
|
||||
.idea/copyright/profiles_settings.xml
|
||||
|
158
.idea/codeStyles/Project.xml
generated
Normal file
158
.idea/codeStyles/Project.xml
generated
Normal file
@ -0,0 +1,158 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<AndroidXmlCodeStyleSettings>
|
||||
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
|
||||
</AndroidXmlCodeStyleSettings>
|
||||
<JetCodeStyleSettings>
|
||||
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||
<value>
|
||||
<package name="kotlinx.android.synthetic" withSubpackages="true" static="false" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="ALIGN_IN_COLUMNS_CASE_BRANCH" value="true" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
|
||||
<option name="CONTINUATION_INDENT_IN_PARAMETER_LISTS" value="true" />
|
||||
<option name="CONTINUATION_INDENT_IN_ARGUMENT_LISTS" value="true" />
|
||||
<option name="CONTINUATION_INDENT_FOR_EXPRESSION_BODIES" value="true" />
|
||||
<option name="CONTINUATION_INDENT_FOR_CHAINED_CALLS" value="true" />
|
||||
<option name="CONTINUATION_INDENT_IN_SUPERTYPE_LISTS" value="true" />
|
||||
<option name="CONTINUATION_INDENT_IN_IF_CONDITIONS" value="true" />
|
||||
<option name="CONTINUATION_INDENT_IN_ELVIS" value="true" />
|
||||
<option name="WRAP_EXPRESSION_BODY_FUNCTIONS" value="0" />
|
||||
<option name="IF_RPAREN_ON_NEW_LINE" value="false" />
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<option name="RIGHT_MARGIN" value="160" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
<option name="RIGHT_MARGIN" value="160" />
|
||||
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
|
||||
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
|
||||
<option name="CALL_PARAMETERS_WRAP" value="0" />
|
||||
<option name="CALL_PARAMETERS_LPAREN_ON_NEXT_LINE" value="false" />
|
||||
<option name="CALL_PARAMETERS_RPAREN_ON_NEXT_LINE" value="false" />
|
||||
<option name="METHOD_PARAMETERS_WRAP" value="0" />
|
||||
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="false" />
|
||||
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="false" />
|
||||
<option name="EXTENDS_LIST_WRAP" value="0" />
|
||||
<option name="METHOD_CALL_CHAIN_WRAP" value="0" />
|
||||
<option name="ASSIGNMENT_WRAP" value="0" />
|
||||
<option name="CLASS_ANNOTATION_WRAP" value="0" />
|
||||
<option name="FIELD_ANNOTATION_WRAP" value="1" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
26
.idea/dictionaries/bmarty.xml
generated
Normal file
26
.idea/dictionaries/bmarty.xml
generated
Normal file
@ -0,0 +1,26 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="bmarty">
|
||||
<words>
|
||||
<w>backstack</w>
|
||||
<w>bytearray</w>
|
||||
<w>checkables</w>
|
||||
<w>ciphertext</w>
|
||||
<w>coroutine</w>
|
||||
<w>decryptor</w>
|
||||
<w>emoji</w>
|
||||
<w>emojis</w>
|
||||
<w>hmac</w>
|
||||
<w>ktlint</w>
|
||||
<w>linkified</w>
|
||||
<w>linkify</w>
|
||||
<w>megolm</w>
|
||||
<w>msisdn</w>
|
||||
<w>pbkdf</w>
|
||||
<w>pkcs</w>
|
||||
<w>signin</w>
|
||||
<w>signout</w>
|
||||
<w>signup</w>
|
||||
<w>threepid</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
244
CHANGES.md
244
CHANGES.md
@ -1,25 +1,235 @@
|
||||
Changes in RiotX 0.4.0 (2019-XX-XX)
|
||||
Changes in RiotX 0.13.0 (2020-01-17)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
- Send and render typing events (#564)
|
||||
- Create Room Profile screen (#54)
|
||||
- Create Room Member Profile screen (#59)
|
||||
|
||||
Improvements 🙌:
|
||||
- Render events m.room.encryption and m.room.guest_access in the timeline
|
||||
|
||||
Bugfix 🐛:
|
||||
- Fix broken background sync in F-Droid version
|
||||
- Fix issue with downloaded file on encrypted rooms. The file was not properly decrypted
|
||||
|
||||
Build 🧱:
|
||||
- Change the way versionCode is computed (#827)
|
||||
|
||||
Changes in RiotX 0.12.0 (2020-01-09)
|
||||
===================================================
|
||||
|
||||
Improvements 🙌:
|
||||
- The initial sync is now handled by a foreground service
|
||||
- Render aliases and canonical alias change in the timeline
|
||||
- Introduce developer mode in the settings (#745, #796)
|
||||
- Improve devices list screen
|
||||
- Add settings for rageshake sensibility
|
||||
- Fix autocompletion issues and add support for rooms, groups, and emoji (#780)
|
||||
- Show skip to bottom FAB while scrolling down (#752)
|
||||
- Enable encryption on a room, SDK part (#212)
|
||||
|
||||
Other changes:
|
||||
- Change the way RiotX identifies a session to allow the SDK to support several sessions with the same user (#800)
|
||||
- Exclude play-services-oss-licenses library from F-Droid build (#814)
|
||||
- Email domain can be limited on some homeservers, i18n of the displayed error (#754)
|
||||
|
||||
Bugfix 🐛:
|
||||
- Fix crash when opening room creation screen from the room filtering screen
|
||||
- Fix avatar image disappearing (#777)
|
||||
- Fix read marker banner when permalink
|
||||
- Fix joining upgraded rooms (#697)
|
||||
- Fix matrix.org room directory not being browsable (#807)
|
||||
- Hide non working settings (#751)
|
||||
|
||||
Changes in RiotX 0.11.0 (2019-12-19)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
- Implement soft logout (#281)
|
||||
|
||||
Improvements 🙌:
|
||||
- Handle navigation to room via room alias (#201)
|
||||
- Open matrix.to link in RiotX (#57)
|
||||
- Limit sticker size in the timeline
|
||||
|
||||
Other changes:
|
||||
- Use same default room colors than Riot-Web
|
||||
|
||||
Bugfix 🐛:
|
||||
- Scroll breadcrumbs to top when opened
|
||||
- Render default room name when it starts with an emoji (#477)
|
||||
- Do not display " (IRC)" in display names https://github.com/vector-im/riot-android/issues/444
|
||||
- Fix rendering issue with HTML formatted body
|
||||
- Disable click on Stickers (#703)
|
||||
|
||||
Build 🧱:
|
||||
- Include diff-match-patch sources as dependency
|
||||
|
||||
Changes in RiotX 0.10.0 (2019-12-10)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
- Breadcrumbs: switch from one room to another quickly (#571)
|
||||
|
||||
Improvements 🙌:
|
||||
- Support entering a RiotWeb client URL instead of the homeserver URL during connection (#744)
|
||||
|
||||
Other changes:
|
||||
- Add reason for all membership events (https://github.com/matrix-org/matrix-doc/pull/2367)
|
||||
|
||||
Bugfix 🐛:
|
||||
- When automardown is ON, pills are sent as MD in body (#739)
|
||||
- "ban" event are not rendered correctly (#716)
|
||||
- Fix crash when rotating screen in Room timeline
|
||||
|
||||
Changes in RiotX 0.9.1 (2019-12-05)
|
||||
===================================================
|
||||
|
||||
Bugfix 🐛:
|
||||
- Fix an issue with DB transaction (#740)
|
||||
|
||||
Changes in RiotX 0.9.0 (2019-12-05)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
- Account creation. It's now possible to create account on any homeserver with RiotX (#34)
|
||||
- Iteration of the login flow (#613)
|
||||
|
||||
Improvements 🙌:
|
||||
- Send mention Pills from composer
|
||||
- Links in message preview in the bottom sheet are now active.
|
||||
- Rework the read marker to make it more usable
|
||||
|
||||
Other changes:
|
||||
- Fix a small grammatical error when an empty room list is shown.
|
||||
|
||||
Bugfix 🐛:
|
||||
- Do not show long click help if only invitation are displayed
|
||||
- Fix emoji filtering not working
|
||||
- Fix issue of closing Realm in another thread (#725)
|
||||
- Attempt to properly cancel the crypto module when user signs out (#724)
|
||||
|
||||
Changes in RiotX 0.8.0 (2019-11-19)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
- Handle long click on room in the room list (#395)
|
||||
- Ignore/UnIgnore users, and display list of ignored users (#542, #617)
|
||||
|
||||
Improvements 🙌:
|
||||
- Search reaction by name or keyword in emoji picker
|
||||
- Handle code tags (#567)
|
||||
- Support spoiler messages
|
||||
- Support m.sticker and m.room.join_rules events in timeline
|
||||
|
||||
Other changes:
|
||||
- Markdown set to off by default (#412)
|
||||
- Accessibility improvements to the attachment file type chooser
|
||||
|
||||
Bugfix 🐛:
|
||||
- Fix issues with some member events rendering (#498)
|
||||
- Passphrase does not match (Export room keys) (#644)
|
||||
- Ask for permission to write external storage when uri comes from the keyboard (#658)
|
||||
- Fix issue with english US/GB translation (#671)
|
||||
|
||||
Changes in RiotX 0.7.0 (2019-10-24)
|
||||
===================================================
|
||||
|
||||
Features:
|
||||
- Share elements from other app to RiotX (#58)
|
||||
- Read marker (#84)
|
||||
- Add ability to report content (#515)
|
||||
|
||||
Improvements:
|
||||
- Persist active tab between sessions (#503)
|
||||
- Do not upload file too big for the homeserver (#587)
|
||||
- Attachments: start using system pickers (#52)
|
||||
- Mark all messages as read (#396)
|
||||
|
||||
|
||||
Other changes:
|
||||
- Accessibility improvements to read receipts in the room timeline and reactions emoji chooser
|
||||
|
||||
Bugfix:
|
||||
- Fix issue on upload error in loop (#587)
|
||||
- Fix opening a permalink: the targeted event is displayed twice (#556)
|
||||
- Fix opening a permalink paginates all the history up to the last event (#282)
|
||||
- after login, the icon in the top left is a green 'A' for (all communities) rather than my avatar (#267)
|
||||
- Picture uploads are unreliable, pictures are shown in wrong aspect ratio on desktop client (#517)
|
||||
- Invitation notifications are not dismissed automatically if room is joined from another client (#347)
|
||||
- Opening links from RiotX reuses browser tab (#599)
|
||||
|
||||
Changes in RiotX 0.6.1 (2019-09-24)
|
||||
===================================================
|
||||
|
||||
Bugfix:
|
||||
- Fix crash: MergedHeaderItem was missing dimensionConverter
|
||||
|
||||
Changes in RiotX 0.6.0 (2019-09-24)
|
||||
===================================================
|
||||
|
||||
Features:
|
||||
- Save draft of a message when exiting a room with non empty composer (#329)
|
||||
|
||||
Improvements:
|
||||
- Add unread indent on room list (#485)
|
||||
- Message Editing: Update notifications (#128)
|
||||
- Remove any notification of a redacted event (#563)
|
||||
|
||||
Other changes:
|
||||
- Fix a few accessibility issues
|
||||
|
||||
Bugfix:
|
||||
- Fix characters erased from the Search field when the result are coming (#545)
|
||||
- "No connection" banner was displayed by mistake
|
||||
- Leaving community (from another client) has no effect on RiotX (#497)
|
||||
- Push rules was not retrieved after a clear cache
|
||||
- m.notice messages trigger push notifications (#238)
|
||||
- Embiggen messages with multiple emojis also for edited messages (#458)
|
||||
|
||||
Build:
|
||||
- Fix (again) issue with bad versionCode generated by Buildkite (#553)
|
||||
|
||||
Changes in RiotX 0.5.0 (2019-09-17)
|
||||
===================================================
|
||||
|
||||
Features:
|
||||
- Implementation of login to homeserver with SSO (#557)
|
||||
- Handle M_CONSENT_NOT_GIVEN error (#64)
|
||||
- Auto configure homeserver and identity server URLs of LoginActivity with a magic link
|
||||
|
||||
Improvements:
|
||||
- Reduce default release build log level, and lab option to enable more logs.
|
||||
- Display a no network indicator when there is no network (#559)
|
||||
|
||||
Bugfix:
|
||||
- Fix crash due to missing informationData (#535)
|
||||
- Progress in initial sync dialog is decreasing for a step and should not (#532)
|
||||
- Fix rendering issue of accepted third party invitation event
|
||||
- All current notifications were dismissed by mistake when the app is launched from the launcher
|
||||
|
||||
Build:
|
||||
- Fix issue with version name (#533)
|
||||
- Fix issue with bad versionCode generated by Buildkite (#553)
|
||||
|
||||
Changes in RiotX 0.4.0 (2019-08-30)
|
||||
===================================================
|
||||
|
||||
Features:
|
||||
- Display read receipts in timeline (#81)
|
||||
|
||||
Improvements:
|
||||
-
|
||||
|
||||
Other changes:
|
||||
-
|
||||
- Reactions: Reinstate the ability to react with non-unicode keys (#307)
|
||||
|
||||
Bugfix:
|
||||
- Fix text diff linebreak display (#441)
|
||||
- Date change message repeats for each redaction until a normal message (#358)
|
||||
- Slide-in reply icon is distorted (#423)
|
||||
|
||||
Translations:
|
||||
-
|
||||
|
||||
Build:
|
||||
-
|
||||
- Regression / e2e replies not encrypted
|
||||
- Some video won't play
|
||||
- Privacy: remove log of notifiable event (#519)
|
||||
- Fix crash with EmojiCompat (#530)
|
||||
|
||||
Changes in RiotX 0.3.0 (2019-08-08)
|
||||
===================================================
|
||||
@ -90,24 +300,24 @@ Mode details here: https://medium.com/@RiotChat/introducing-the-riotx-beta-for-a
|
||||
=======================================================
|
||||
|
||||
|
||||
Changes in RiotX 0.0.0 (2019-XX-XX)
|
||||
Changes in RiotX 0.0.0 (2020-XX-XX)
|
||||
===================================================
|
||||
|
||||
Features:
|
||||
Features ✨:
|
||||
-
|
||||
|
||||
Improvements:
|
||||
Improvements 🙌:
|
||||
-
|
||||
|
||||
Other changes:
|
||||
-
|
||||
|
||||
Bugfix:
|
||||
Bugfix 🐛:
|
||||
-
|
||||
|
||||
Translations:
|
||||
Translations 🗣:
|
||||
-
|
||||
|
||||
Build:
|
||||
Build 🧱:
|
||||
-
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Contributing code to Matrix
|
||||
|
||||
Please read https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst
|
||||
Please read https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.md
|
||||
|
||||
Android support can be found in this [](https://matrix.to/#/#riot-android:matrix.org) room.
|
||||
|
||||
@ -11,6 +11,7 @@ Dedicated room for RiotX: [![RiotX Android Matrix room #riot-android:matrix.org]
|
||||
## Android Studio settings
|
||||
|
||||
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.
|
||||
|
||||
## Compilation
|
||||
|
||||
@ -40,19 +41,45 @@ Please add a line to the top of the file `CHANGES.md` describing your change.
|
||||
|
||||
Make sure the following commands execute without any error:
|
||||
|
||||
> ./tools/check/check_code_quality.sh
|
||||
#### Internal tool
|
||||
|
||||
> ./gradlew lintGplayRelease
|
||||
<pre>
|
||||
./tools/check/check_code_quality.sh
|
||||
</pre>
|
||||
|
||||
#### ktlint
|
||||
|
||||
<pre>
|
||||
curl -sSLO https://github.com/pinterest/ktlint/releases/download/0.34.2/ktlint && chmod a+x ktlint
|
||||
./ktlint --android --experimental -v
|
||||
</pre>
|
||||
|
||||
Note that you can run
|
||||
|
||||
<pre>
|
||||
./ktlint --android --experimental -v -F
|
||||
</pre>
|
||||
|
||||
For ktlint to fix some detected errors for you (you still have to check and commit the fix of course)
|
||||
|
||||
#### lint
|
||||
|
||||
<pre>
|
||||
./gradlew lintGplayRelease
|
||||
./gradlew lintFdroidRelease
|
||||
</pre>
|
||||
|
||||
### Unit tests
|
||||
|
||||
Make sure the following commands execute without any error:
|
||||
|
||||
> ./gradlew testGplayReleaseUnitTest
|
||||
<pre>
|
||||
./gradlew testGplayReleaseUnitTest
|
||||
</pre>
|
||||
|
||||
### Tests
|
||||
|
||||
RiotX is currently supported on Android Jelly Bean (API 16+): please test your change on an Android device (or Android emulator) running with API 16. Many issues can happen (including crashes) on older devices.
|
||||
RiotX is currently supported on Android KitKat (API 19+): please test your change on an Android device (or Android emulator) running with API 19. Many issues can happen (including crashes) on older devices.
|
||||
Also, if possible, please test your change on a real device. Testing on Android emulator may not be sufficient.
|
||||
|
||||
### Internationalisation
|
||||
@ -60,6 +87,10 @@ Also, if possible, please test your change on a real device. Testing on Android
|
||||
When adding new string resources, please only add new entries in file `value/strings.xml`. Translations will be added later by the community of translators with a specific tool named [Weblate](https://translate.riot.im/projects/riot-android/).
|
||||
Do not hesitate to use plurals when appropriate.
|
||||
|
||||
### Accessibility
|
||||
|
||||
Please consider accessibility as an important point. As a minimum requirement, in layout XML files please use attributes such as `android:contentDescription` and `android:importantForAccessibility`, and test with a screen reader if it's working well. You can add new string resources, dedicated to accessibility, in this case, please prefix theirs id with `a11y_`.
|
||||
|
||||
### Layout
|
||||
|
||||
When adding or editing layouts, make sure the layout will render correctly if device uses a RTL (Right To Left) language.
|
||||
|
23
build.gradle
23
build.gradle
@ -1,9 +1,7 @@
|
||||
import javax.tools.JavaCompiler
|
||||
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.3.21'
|
||||
ext.kotlin_version = '1.3.50'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
@ -12,11 +10,11 @@ buildscript {
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.4.1'
|
||||
classpath 'com.google.gms:google-services:4.2.0'
|
||||
classpath "com.airbnb.okreplay:gradle-plugin:1.4.0"
|
||||
classpath 'com.android.tools.build:gradle:3.5.3'
|
||||
classpath 'com.google.gms:google-services:4.3.2'
|
||||
classpath "com.airbnb.okreplay:gradle-plugin:1.5.0"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.6.2'
|
||||
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1'
|
||||
classpath 'com.google.android.gms:oss-licenses-plugin:0.9.5'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
@ -47,12 +45,6 @@ allprojects {
|
||||
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
|
||||
google()
|
||||
jcenter()
|
||||
maven {
|
||||
url 'https://repo.adobe.com/nexus/content/repositories/public/'
|
||||
content {
|
||||
includeGroupByRegex "diff_match_patch"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).all {
|
||||
@ -61,6 +53,11 @@ allprojects {
|
||||
]
|
||||
}
|
||||
|
||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||
// Warnings are potential errors, so stop ignoring them
|
||||
kotlinOptions.allWarningsAsErrors = true
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
extensions.findByName("kapt")?.arguments {
|
||||
arg("dagger.gradle.incremental", "enabled")
|
||||
|
1
diff-match-patch/.gitignore
vendored
Normal file
1
diff-match-patch/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
8
diff-match-patch/build.gradle
Normal file
8
diff-match-patch/build.gradle
Normal file
@ -0,0 +1,8 @@
|
||||
apply plugin: 'java-library'
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
}
|
||||
|
||||
sourceCompatibility = "8"
|
||||
targetCompatibility = "8"
|
File diff suppressed because it is too large
Load Diff
260
docs/signin.md
Normal file
260
docs/signin.md
Normal file
@ -0,0 +1,260 @@
|
||||
# Sign in to a homeserver
|
||||
|
||||
This document describes the flow of signin to a homeserver, and also the flow when user want to reset his password. Examples come from the `matrix.org` homeserver.
|
||||
|
||||
## Sign up flows
|
||||
|
||||
### Get the flow
|
||||
|
||||
Client request the sign-in flows, once the homeserver is chosen by the user and its url is known (in the example it's `https://matrix.org`)
|
||||
|
||||
> curl -X GET 'https://matrix.org/_matrix/client/r0/login'
|
||||
|
||||
200
|
||||
|
||||
```json
|
||||
{
|
||||
"flows": [
|
||||
{
|
||||
"type": "m.login.password"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Login with username
|
||||
|
||||
The user is able to connect using `m.login.password`
|
||||
|
||||
> curl -X POST --data $'{"identifier":{"type":"m.id.user","user":"alice"},"password":"weak_password","type":"m.login.password","initial_device_display_name":"Portable"}' 'https://matrix.org/_matrix/client/r0/login'
|
||||
|
||||
```json
|
||||
{
|
||||
"identifier": {
|
||||
"type": "m.id.user",
|
||||
"user": "alice"
|
||||
},
|
||||
"password": "weak_password",
|
||||
"type": "m.login.password",
|
||||
"initial_device_display_name": "Portable"
|
||||
}
|
||||
```
|
||||
|
||||
#### Incorrect password
|
||||
|
||||
403
|
||||
|
||||
```json
|
||||
{
|
||||
"errcode": "M_FORBIDDEN",
|
||||
"error": "Invalid password"
|
||||
}
|
||||
```
|
||||
|
||||
#### Correct password:
|
||||
|
||||
We get credential (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"user_id": "@benoit0816:matrix.org",
|
||||
"access_token": "MDAxOGxvY2F0aW9uIG1hdHREDACTEDb2l0MDgxNjptYXRyaXgub3JnCjAwMTZjaWQgdHlwZSA9IGFjY2VzcwowMDIxY2lkIG5vbmNlID0gfnYrSypfdTtkNXIuNWx1KgowMDJmc2lnbmF0dXJlIOsh1XqeAkXexh4qcofl_aR4kHJoSOWYGOhE7-ubX-DZCg",
|
||||
"home_server": "matrix.org",
|
||||
"device_id": "GTVREDALBF",
|
||||
"well_known": {
|
||||
"m.homeserver": {
|
||||
"base_url": "https:\/\/matrix.org\/"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Login with email
|
||||
|
||||
If the user has associated an email with its account, he can signin using the email.
|
||||
|
||||
> curl -X POST --data $'{"identifier":{"type":"m.id.thirdparty","medium":"email","address":"alice@yopmail.com"},"password":"weak_password","type":"m.login.password","initial_device_display_name":"Portable"}' 'https://matrix.org/_matrix/client/r0/login'
|
||||
|
||||
```json
|
||||
{
|
||||
"identifier": {
|
||||
"type": "m.id.thirdparty",
|
||||
"medium": "email",
|
||||
"address": "alice@yopmail.com"
|
||||
},
|
||||
"password": "weak_password",
|
||||
"type": "m.login.password",
|
||||
"initial_device_display_name": "Portable"
|
||||
}
|
||||
```
|
||||
|
||||
#### Unknown email
|
||||
|
||||
403
|
||||
|
||||
```json
|
||||
{
|
||||
"errcode": "M_FORBIDDEN",
|
||||
"error": ""
|
||||
}
|
||||
```
|
||||
|
||||
#### Known email, wrong password
|
||||
|
||||
403
|
||||
|
||||
```json
|
||||
{
|
||||
"errcode": "M_FORBIDDEN",
|
||||
"error": "Invalid password"
|
||||
}
|
||||
```
|
||||
|
||||
##### Known email, correct password
|
||||
|
||||
We get the credentials (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"user_id": "@alice:matrix.org",
|
||||
"access_token": "MDAxOGxvY2F0aW9uIG1hdHJpeC5vcmREDACTEDZXJfaWQgPSBAYmVub2l0MDgxNjptYXRyaXgub3JnCjAwMTZjaWQgdHlwZSA9IGFjY2VzcwowMDIxY2lkIG5vbmNlID0gNjtDY0MwRlNPSFFoOC5wOgowMDJmc2lnbmF0dXJlIGiTRm1mYLLxQywxOh3qzQVT8HoEorSokEP2u-bAwtnYCg",
|
||||
"home_server": "matrix.org",
|
||||
"device_id": "WBSREDASND",
|
||||
"well_known": {
|
||||
"m.homeserver": {
|
||||
"base_url": "https:\/\/matrix.org\/"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Login with Msisdn
|
||||
|
||||
Not supported yet in RiotX
|
||||
|
||||
### Login with SSO
|
||||
|
||||
> curl -X GET 'https://homeserver.with.sso/_matrix/client/r0/login'
|
||||
|
||||
200
|
||||
|
||||
```json
|
||||
{
|
||||
"flows": [
|
||||
{
|
||||
"type": "m.login.sso"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
In this case, the user can click on "Sign in with SSO" and the web screen will be displayed on the page `https://homeserver.with.sso/_matrix/static/client/login/` and the credentials will be passed back to the native code through the JS bridge
|
||||
|
||||
## Reset password
|
||||
|
||||
Ref: `https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-account-password-email-requesttoken`
|
||||
|
||||
When the user has forgotten his password, he can reset it by providing an email and a new password.
|
||||
|
||||
Here is the flow:
|
||||
|
||||
### Send email
|
||||
|
||||
User is asked to enter the email linked to his account and a new password.
|
||||
We display a warning regarding e2e.
|
||||
|
||||
At the first step, we do not send the password, only the email and a client secret, generated by the application
|
||||
|
||||
> curl -X POST --data $'{"client_secret":"6c57f284-85e2-421b-8270-fb1795a120a7","send_attempt":0,"email":"user@domain.com"}' 'https://matrix.org/_matrix/client/r0/account/password/email/requestToken'
|
||||
|
||||
```json
|
||||
{
|
||||
"client_secret": "6c57f284-85e2-421b-8270-fb1795a120a7",
|
||||
"send_attempt": 0,
|
||||
"email": "user@domain.com"
|
||||
}
|
||||
```
|
||||
|
||||
#### When the email is not known
|
||||
|
||||
We get a 400
|
||||
|
||||
```json
|
||||
{
|
||||
"errcode": "M_THREEPID_NOT_FOUND",
|
||||
"error": "Email not found"
|
||||
}
|
||||
```
|
||||
|
||||
#### When the email is known
|
||||
|
||||
We get a 200 with a `sid`
|
||||
|
||||
```json
|
||||
{
|
||||
"sid": "tQNbrREDACTEDldA"
|
||||
}
|
||||
```
|
||||
|
||||
Then the user is asked to click on the link in the email he just received, and to confirm when it's done.
|
||||
|
||||
During this step, the new password is sent to the homeserver.
|
||||
|
||||
If the user confirms before the link is clicked, we get an error:
|
||||
|
||||
> curl -X POST --data $'{"auth":{"type":"m.login.email.identity","threepid_creds":{"client_secret":"6c57f284-85e2-421b-8270-fb1795a120a7","sid":"tQNbrREDACTEDldA"}},"new_password":"weak_password"}' 'https://matrix.org/_matrix/client/r0/account/password'
|
||||
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"type": "m.login.email.identity",
|
||||
"threepid_creds": {
|
||||
"client_secret": "6c57f284-85e2-421b-8270-fb1795a120a7",
|
||||
"sid": "tQNbrREDACTEDldA"
|
||||
}
|
||||
},
|
||||
"new_password": "weak_password"
|
||||
}
|
||||
```
|
||||
|
||||
401
|
||||
|
||||
```json
|
||||
{
|
||||
"errcode": "M_UNAUTHORIZED",
|
||||
"error": ""
|
||||
}
|
||||
```
|
||||
|
||||
### User clicks on the link
|
||||
|
||||
The link has the form:
|
||||
|
||||
https://matrix.org/_matrix/client/unstable/password_reset/email/submit_token?token=fzZLBlcqhTKeaFQFSRbsQnQCkzbwtGAD&client_secret=6c57f284-85e2-421b-8270-fb1795a120a7&sid=tQNbrREDACTEDldA
|
||||
|
||||
It contains the client secret, a token and the sid
|
||||
|
||||
When the user click the link, if validate his ownership and the new password can now be ent by the application (on user demand):
|
||||
|
||||
> curl -X POST --data $'{"auth":{"type":"m.login.email.identity","threepid_creds":{"client_secret":"6c57f284-85e2-421b-8270-fb1795a120a7","sid":"tQNbrREDACTEDldA"}},"new_password":"weak_password"}' 'https://matrix.org/_matrix/client/r0/account/password'
|
||||
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"type": "m.login.email.identity",
|
||||
"threepid_creds": {
|
||||
"client_secret": "6c57f284-85e2-421b-8270-fb1795a120a7",
|
||||
"sid": "tQNbrREDACTEDldA"
|
||||
}
|
||||
},
|
||||
"new_password": "weak_password"
|
||||
}
|
||||
```
|
||||
|
||||
200
|
||||
|
||||
```json
|
||||
{}
|
||||
```
|
||||
|
||||
The password has been changed, and all the existing token are invalidated. User can now login with the new password.
|
579
docs/signup.md
Normal file
579
docs/signup.md
Normal file
@ -0,0 +1,579 @@
|
||||
# Sign up to a homeserver
|
||||
|
||||
This document describes the flow of registration to a homeserver. Examples come from the `matrix.org` homeserver.
|
||||
|
||||
*Ref*: https://matrix.org/docs/spec/client_server/latest#account-registration-and-management
|
||||
|
||||
## Sign up flows
|
||||
|
||||
### First step
|
||||
|
||||
Client request the sign-up flows, once the homeserver is chosen by the user and its url is known (in the example it's `https://matrix.org`)
|
||||
|
||||
> curl -X POST --data $'{}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
|
||||
```json
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
We get the flows with a 401, which also means that the registration is possible on this homeserver.
|
||||
|
||||
```json
|
||||
{
|
||||
"session": "vwehdKMtkRedactedAMwgCACZ",
|
||||
"flows": [
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.dummy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.email.identity"
|
||||
]
|
||||
}
|
||||
],
|
||||
"params": {
|
||||
"m.login.recaptcha": {
|
||||
"public_key": "6LcgI54UAAAAAoREDACTEDoDdOocFpYVdjYBRe4zb"
|
||||
},
|
||||
"m.login.terms": {
|
||||
"policies": {
|
||||
"privacy_policy": {
|
||||
"version": "1.0",
|
||||
"en": {
|
||||
"name": "Terms and Conditions",
|
||||
"url": "https:\/\/matrix.org\/_matrix\/consent?v=1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If the registration is not possible, we get a 403
|
||||
|
||||
```json
|
||||
{
|
||||
"errcode": "M_FORBIDDEN",
|
||||
"error": "Registration is disabled"
|
||||
}
|
||||
```
|
||||
|
||||
### Step 1: entering user name and password
|
||||
|
||||
The app is displaying a form to enter username and password.
|
||||
|
||||
> curl -X POST --data $'{"initial_device_display_name":"Mobile device","username":"alice","password": "weak_password"}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
|
||||
```json
|
||||
{
|
||||
"initial_device_display_name": "Mobile device",
|
||||
"username": "alice",
|
||||
"password": "weak_password"
|
||||
}
|
||||
```
|
||||
|
||||
401. Note that the `session` value has changed (because we did not provide the previous value in the request body), but it's ok, we will use the new value for the next steps.
|
||||
|
||||
```json
|
||||
{
|
||||
"session": "xptUYoREDACTEDogOWAGVnbJQ",
|
||||
"flows": [
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.dummy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.email.identity"
|
||||
]
|
||||
}
|
||||
],
|
||||
"params": {
|
||||
"m.login.recaptcha": {
|
||||
"public_key": "6LcgI54UAAAAAoREDACTEDoDdOocFpYVdjYBRe4zb"
|
||||
},
|
||||
"m.login.terms": {
|
||||
"policies": {
|
||||
"privacy_policy": {
|
||||
"version": "1.0",
|
||||
"en": {
|
||||
"name": "Terms and Conditions",
|
||||
"url": "https:\/\/matrix.org\/_matrix\/consent?v=1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### If username already exists
|
||||
|
||||
We get a 400:
|
||||
|
||||
```json
|
||||
{
|
||||
"errcode": "M_USER_IN_USE",
|
||||
"error": "User ID already taken."
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: entering email
|
||||
|
||||
User is proposed to enter an email. We skip this step.
|
||||
|
||||
> curl -X POST --data $'{"auth":{"session":"xptUYoREDACTEDogOWAGVnbJQ","type":"m.login.dummy"}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"session": "xptUYoREDACTEDogOWAGVnbJQ",
|
||||
"type": "m.login.dummy"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
401
|
||||
|
||||
```json
|
||||
{
|
||||
"session": "xptUYoREDACTEDogOWAGVnbJQ",
|
||||
"flows": [
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.dummy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.email.identity"
|
||||
]
|
||||
}
|
||||
],
|
||||
"params": {
|
||||
"m.login.recaptcha": {
|
||||
"public_key": "6LcgI54UAAAAAoREDACTEDoDdOocFpYVdjYBRe4zb"
|
||||
},
|
||||
"m.login.terms": {
|
||||
"policies": {
|
||||
"privacy_policy": {
|
||||
"version": "1.0",
|
||||
"en": {
|
||||
"name": "Terms and Conditions",
|
||||
"url": "https:\/\/matrix.org\/_matrix\/consent?v=1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"completed": [
|
||||
"m.login.dummy"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2 bis: we enter an email
|
||||
|
||||
We request a token to the homeserver. The `client_secret` is generated by the application
|
||||
|
||||
> curl -X POST --data $'{"client_secret":"53e679ea-oRED-ACTED-92b8-3012c49c6cfa","email":"alice@yopmail.com","send_attempt":0}' 'https://matrix.org/_matrix/client/r0/register/email/requestToken'
|
||||
|
||||
```json
|
||||
{
|
||||
"client_secret": "53e679ea-oRED-ACTED-92b8-3012c49c6cfa",
|
||||
"email": "alice@yopmail.com",
|
||||
"send_attempt": 0
|
||||
}
|
||||
```
|
||||
|
||||
200
|
||||
|
||||
```json
|
||||
{
|
||||
"sid": "qlBCREDACTEDEtgxD"
|
||||
}
|
||||
```
|
||||
|
||||
And
|
||||
|
||||
> curl -X POST --data $'{"auth":{"threepid_creds":{"client_secret":"53e679ea-oRED-ACTED-92b8-3012c49c6cfa","sid":"qlBCREDACTEDEtgxD"},"session":"xptUYoREDACTEDogOWAGVnbJQ","type":"m.login.email.identity"}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"threepid_creds": {
|
||||
"client_secret": "53e679ea-oRED-ACTED-92b8-3012c49c6cfa",
|
||||
"sid": "qlBCREDACTEDEtgxD"
|
||||
},
|
||||
"session": "xptUYoREDACTEDogOWAGVnbJQ",
|
||||
"type": "m.login.email.identity"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We get 401 since the email is not validated yet:
|
||||
|
||||
```json
|
||||
{
|
||||
"errcode": "M_UNAUTHORIZED",
|
||||
"error": ""
|
||||
}
|
||||
```
|
||||
|
||||
The app is now polling on
|
||||
|
||||
> curl -X POST --data $'{"auth":{"threepid_creds":{"client_secret":"53e679ea-oRED-ACTED-92b8-3012c49c6cfa","sid":"qlBCREDACTEDEtgxD"},"session":"xptUYoREDACTEDogOWAGVnbJQ","type":"m.login.email.identity"}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"threepid_creds": {
|
||||
"client_secret": "53e679ea-oRED-ACTED-92b8-3012c49c6cfa",
|
||||
"sid": "qlBCREDACTEDEtgxD"
|
||||
},
|
||||
"session": "xptUYoREDACTEDogOWAGVnbJQ",
|
||||
"type": "m.login.email.identity"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We click on the link received by email `https://matrix.org/_matrix/client/unstable/registration/email/submit_token?token=vtQjQIZfwdoREDACTEDozrmKYSWlCXsJ&client_secret=53e679ea-oRED-ACTED-92b8-3012c49c6cfa&sid=qlBCREDACTEDEtgxD` which contains:
|
||||
- A `token` vtQjQIZfwdoREDACTEDozrmKYSWlCXsJ
|
||||
- The `client_secret`: 53e679ea-oRED-ACTED-92b8-3012c49c6cfa
|
||||
- A `sid`: qlBCREDACTEDEtgxD
|
||||
|
||||
Once the link is clicked, the registration request (polling) returns a 401 with the following content:
|
||||
|
||||
```json
|
||||
{
|
||||
"session": "xptUYoREDACTEDogOWAGVnbJQ",
|
||||
"flows": [
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.dummy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.email.identity"
|
||||
]
|
||||
}
|
||||
],
|
||||
"params": {
|
||||
"m.login.recaptcha": {
|
||||
"public_key": "6LcgI54UAAAAAoREDACTEDoDdOocFpYVdjYBRe4zb"
|
||||
},
|
||||
"m.login.terms": {
|
||||
"policies": {
|
||||
"privacy_policy": {
|
||||
"version": "1.0",
|
||||
"en": {
|
||||
"name": "Terms and Conditions",
|
||||
"url": "https:\/\/matrix.org\/_matrix\/consent?v=1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"completed": [
|
||||
"m.login.email.identity"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Accepting T&C
|
||||
|
||||
User is proposed to accept T&C and he accepts them
|
||||
|
||||
> curl -X POST --data $'{"auth":{"session":"xptUYoREDACTEDogOWAGVnbJQ","type":"m.login.terms"}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"session": "xptUYoREDACTEDogOWAGVnbJQ",
|
||||
"type": "m.login.terms"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
401
|
||||
|
||||
```json
|
||||
{
|
||||
"session": "xptUYoREDACTEDogOWAGVnbJQ",
|
||||
"flows": [
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.dummy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.email.identity"
|
||||
]
|
||||
}
|
||||
],
|
||||
"params": {
|
||||
"m.login.recaptcha": {
|
||||
"public_key": "6LcgI54UAAAAAoREDACTEDoDdOocFpYVdjYBRe4zb"
|
||||
},
|
||||
"m.login.terms": {
|
||||
"policies": {
|
||||
"privacy_policy": {
|
||||
"version": "1.0",
|
||||
"en": {
|
||||
"name": "Terms and Conditions",
|
||||
"url": "https:\/\/matrix.org\/_matrix\/consent?v=1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"completed": [
|
||||
"m.login.dummy",
|
||||
"m.login.terms"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Captcha
|
||||
|
||||
User is proposed to prove he is not a robot and he does it:
|
||||
|
||||
> curl -X POST --data $'{"auth":{"response":"03AOLTBLSiGS9GhFDpAMblJ2nlXOmHXqAYJ5OvHCPUjiVLBef3k9snOYI_BDC32-t4D2jv-tpvkaiEI_uloobFd9RUTPpJ7con2hMddbKjSCYqXqcUQFhzhbcX6kw8uBnh2sbwBe80_ihrHGXEoACXQkL0ki1Q0uEtOeW20YBRjbNABsZPpLNZhGIWC0QVXnQ4FouAtZrl3gOAiyM-oG3cgP6M9pcANIAC_7T2P2amAHbtsTlSR9CsazNyS-rtDR9b5MywdtnWN9Aw8fTJb8cXQk_j7nvugMxzofPjSOrPKcr8h5OqPlpUCyxxnFtag6cuaPSUwh43D2L0E-ZX7djzaY2Yh_U2n6HegFNPOQ22CJmfrKwDlodmAfMPvAXyq77n3HpoREDACTEDo3830RHF4BfkGXUaZjctgg-A1mvC17hmQmQpkG7IhDqyw0onU-0vF_-ehCjq_CcQEDpS_O3uiHJaG5xGf-0rhLm57v_wA3deugbsZuO4uTuxZZycN_mKxZ97jlDVBetl9hc_5REPbhcT1w3uzTCSx7Q","session":"xptUYoREDACTEDogOWAGVnbJQ","type":"m.login.recaptcha"}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"response": "03AOLTBLSiGS9GhFDpAMblJ2nlXOmHXqAYJ5OvHCPUjiVLBef3k9snOYI_BDC32-t4D2jv-tpvkaiEI_uloobFd9RUTPpJ7con2hMddbKjSCYqXqcUQFhzhbcX6kw8uBnh2sbwBe80_ihrHGXEoACXQkL0ki1Q0uEtOeW20YBRjbNABsZPpLNZhGIWC0QVXnQ4FouAtZrl3gOAiyM-oG3cgP6M9pcANIAC_7T2P2amAHbtsTlSR9CsazNyS-rtDR9b5MywdtnWN9Aw8fTJb8cXQk_j7nvugMxzofPjSOrPKcr8h5OqPlpUCyxxnFtag6cuaPSUwh43D2L0E-ZX7djzaY2Yh_U2n6HegFNPOQ22CJmfrKwDlodmAfMPvAXyq77n3HpoREDACTEDo3830RHF4BfkGXUaZjctgg-A1mvC17hmQmQpkG7IhDqyw0onU-0vF_-ehCjq_CcQEDpS_O3uiHJaG5xGf-0rhLm57v_wA3deugbsZuO4uTuxZZycN_mKxZ97jlDVBetl9hc_5REPbhcT1w3uzTCSx7Q",
|
||||
"session": "xptUYoREDACTEDogOWAGVnbJQ",
|
||||
"type": "m.login.recaptcha"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
200
|
||||
|
||||
```json
|
||||
{
|
||||
"user_id": "@alice:matrix.org",
|
||||
"home_server": "matrix.org",
|
||||
"access_token": "MDAxOGxvY2F0aW9uIG1hdHJpeC5vcmcKMoREDACTEDo50aWZpZXIga2V5CjAwMTBjaWQgZ2VuID0gMQowMDI5Y2lkIHVzZXJfaWQgPSBAYmVub2l0eHh4eDptYXRoREDACTEDoCjAwMTZjaWQgdHlwZSA9IGFjY2VzcwowMDIxY2lkIG5vbmNlID0gNHVSVm00aVFDaWlKdoREDACTEDoJmc2lnbmF0dXJlIOmHnTLRfxiPjhrWhS-dThUX-qAzZktfRThzH1YyAsxaCg",
|
||||
"device_id": "FLBAREDAJZ"
|
||||
}
|
||||
```
|
||||
|
||||
The account is created!
|
||||
|
||||
### Step 5: MSISDN
|
||||
|
||||
Some homeservers may require the user to enter MSISDN.
|
||||
|
||||
On matrix.org, it's not required, and not even optional, but it's still possible for the app to add a MSISDN during the registration.
|
||||
|
||||
The user enter a phone number and select a country, the `client_secret` is generated by the application
|
||||
|
||||
> curl -X POST --data $'{"client_secret":"d3e285f6-972a-496c-9a22-7915a2db57c7","send_attempt":1,"country":"FR","phone_number":"+33611223344"}' 'https://matrix.org/_matrix/client/r0/register/msisdn/requestToken'
|
||||
|
||||
```json
|
||||
{
|
||||
"client_secret": "d3e285f6-972a-496c-9a22-7915a2db57c7",
|
||||
"send_attempt": 1,
|
||||
"country": "FR",
|
||||
"phone_number": "+33611223344"
|
||||
}
|
||||
```
|
||||
|
||||
If the msisdn is already associated to another account, you will received an error:
|
||||
|
||||
```json
|
||||
{
|
||||
"errcode": "M_THREEPID_IN_USE",
|
||||
"error": "Phone number is already in use"
|
||||
}
|
||||
```
|
||||
|
||||
If it is not the case, the homeserver send the SMS and returns some data, especially a `sid` and a `submit_url`:
|
||||
|
||||
```json
|
||||
{
|
||||
"msisdn": "33611223344",
|
||||
"intl_fmt": "+336 11 22 33 44",
|
||||
"success": true,
|
||||
"sid": "1678881798",
|
||||
"submit_url": "https:\/\/matrix.org\/_matrix\/client\/unstable\/add_threepid\/msisdn\/submit_token"
|
||||
}
|
||||
```
|
||||
|
||||
When you execute the register request, with the received `sid`, you get an error since the MSISDN is not validated yet:
|
||||
|
||||
> curl -X POST --data $'{"auth":{"type":"m.login.msisdn","session":"xptUYoREDACTEDogOWAGVnbJQ","threepid_creds":{"client_secret":"d3e285f6-972a-496c-9a22-7915a2db57c7","sid":"1678881798"}}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
|
||||
|
||||
```json
|
||||
"auth": {
|
||||
"type": "m.login.msisdn",
|
||||
"session": "xptUYoREDACTEDogOWAGVnbJQ",
|
||||
"threepid_creds": {
|
||||
"client_secret": "d3e285f6-972a-496c-9a22-7915a2db57c7",
|
||||
"sid": "1678881798"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There is an issue on Synapse, which return a 401, it sends too much data along with the classical MatrixError fields:
|
||||
|
||||
```json
|
||||
{
|
||||
"session": "xptUYoREDACTEDogOWAGVnbJQ",
|
||||
"flows": [
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.dummy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.email.identity"
|
||||
]
|
||||
}
|
||||
],
|
||||
"params": {
|
||||
"m.login.recaptcha": {
|
||||
"public_key": "6LcgI54UAAAAABGdGmruw6DdOocFpYVdjYBRe4zb"
|
||||
},
|
||||
"m.login.terms": {
|
||||
"policies": {
|
||||
"privacy_policy": {
|
||||
"version": "1.0",
|
||||
"en": {
|
||||
"name": "Terms and Conditions",
|
||||
"url": "https:\/\/matrix.org\/_matrix\/consent?v=1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"completed": [],
|
||||
"error": "",
|
||||
"errcode": "M_UNAUTHORIZED"
|
||||
}
|
||||
```
|
||||
|
||||
The user receive the SMS, he can enter the SMS code in the app, which is sent using the "submit_url" received ie the response of the `requestToken` request:
|
||||
|
||||
> curl -X POST --data $'{"client_secret":"d3e285f6-972a-496c-9a22-7915a2db57c7","sid":"1678881798","token":"123456"}' 'https://matrix.org/_matrix/client/unstable/add_threepid/msisdn/submit_token'
|
||||
|
||||
```json
|
||||
{
|
||||
"client_secret": "d3e285f6-972a-496c-9a22-7915a2db57c7",
|
||||
"sid": "1678881798",
|
||||
"token": "123456"
|
||||
}
|
||||
```
|
||||
|
||||
If the code is not correct, we get a 200 with:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
And if the code is correct we get a 200 with:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
We can now execute the registration request, to the homeserver
|
||||
|
||||
> curl -X POST --data $'{"auth":{"type":"m.login.msisdn","session":"xptUYoREDACTEDogOWAGVnbJQ","threepid_creds":{"client_secret":"d3e285f6-972a-496c-9a22-7915a2db57c7","sid":"1678881798"}}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"type": "m.login.msisdn",
|
||||
"session": "xptUYoREDACTEDogOWAGVnbJQ",
|
||||
"threepid_creds": {
|
||||
"client_secret": "d3e285f6-972a-496c-9a22-7915a2db57c7",
|
||||
"sid": "1678881798"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now the homeserver consider that the `m.login.msisdn` step is completed (401):
|
||||
|
||||
```json
|
||||
{
|
||||
"session": "xptUYoREDACTEDogOWAGVnbJQ",
|
||||
"flows": [
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.dummy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"stages": [
|
||||
"m.login.recaptcha",
|
||||
"m.login.terms",
|
||||
"m.login.email.identity"
|
||||
]
|
||||
}
|
||||
],
|
||||
"params": {
|
||||
"m.login.recaptcha": {
|
||||
"public_key": "6LcgI54UAAAAABGdGmruw6DdOocFpYVdjYBRe4zb"
|
||||
},
|
||||
"m.login.terms": {
|
||||
"policies": {
|
||||
"privacy_policy": {
|
||||
"version": "1.0",
|
||||
"en": {
|
||||
"name": "Terms and Conditions",
|
||||
"url": "https:\/\/matrix.org\/_matrix\/consent?v=1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"completed": [
|
||||
"m.login.msisdn"
|
||||
]
|
||||
}
|
||||
```
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
||||
#Tue Mar 19 09:53:05 CET 2019
|
||||
#Fri Sep 27 10:10:35 CEST 2019
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
|
||||
|
@ -5,16 +5,15 @@ apply plugin: 'kotlin-kapt'
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
|
||||
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 28
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
// Multidex is useful for tests
|
||||
multiDexEnabled true
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@ -29,12 +28,14 @@ android {
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(":matrix-sdk-android")
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0-beta01'
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||
// Paging
|
||||
|
@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.rx;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
@Test
|
||||
public void useAppContext() {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getTargetContext();
|
||||
|
||||
assertEquals("im.vector.matrix.rx.test", appContext.getPackageName());
|
||||
}
|
||||
}
|
@ -20,7 +20,6 @@ import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.MainThreadDisposable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
|
||||
private class LiveDataObservable<T>(
|
||||
@ -60,4 +59,4 @@ private class LiveDataObservable<T>(
|
||||
|
||||
fun <T> LiveData<T>.asObservable(): Observable<T> {
|
||||
return LiveDataObservable(this).observeOn(Schedulers.computation())
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.rx
|
||||
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import io.reactivex.Observable
|
||||
|
||||
fun <T : Any> Observable<Optional<T>>.unwrap(): Observable<T> {
|
||||
return filter { it.hasValue() }.map { it.get() }
|
||||
}
|
||||
|
||||
fun <T : Any, U : Any> Observable<Optional<T>>.mapOptional(fn: (T) -> U?): Observable<Optional<U>> {
|
||||
return map {
|
||||
it.map(fn)
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.rx
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Single
|
||||
|
||||
fun <T> singleBuilder(builder: (callback: MatrixCallback<T>) -> Cancelable): Single<T> = Single.create {
|
||||
val callback: MatrixCallback<T> = object : MatrixCallback<T> {
|
||||
override fun onSuccess(data: T) {
|
||||
it.onSuccess(data)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
it.tryOnError(failure)
|
||||
}
|
||||
}
|
||||
val cancelable = builder(callback)
|
||||
it.setCancellable {
|
||||
cancelable.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> completableBuilder(builder: (callback: MatrixCallback<T>) -> Cancelable): Completable = Completable.create {
|
||||
val callback: MatrixCallback<T> = object : MatrixCallback<T> {
|
||||
override fun onSuccess(data: T) {
|
||||
it.onComplete()
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
it.tryOnError(failure)
|
||||
}
|
||||
}
|
||||
val cancelable = builder(callback)
|
||||
it.setCancellable {
|
||||
cancelable.cancel()
|
||||
}
|
||||
}
|
@ -16,46 +16,78 @@
|
||||
|
||||
package im.vector.matrix.rx
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.Room
|
||||
import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
|
||||
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
||||
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
|
||||
import im.vector.matrix.android.api.session.room.send.UserDraft
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.api.util.toOptional
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
|
||||
class RxRoom(private val room: Room) {
|
||||
|
||||
fun liveRoomSummary(): Observable<RoomSummary> {
|
||||
return room.liveRoomSummary().asObservable()
|
||||
fun liveRoomSummary(): Observable<Optional<RoomSummary>> {
|
||||
return room.getRoomSummaryLive().asObservable()
|
||||
.startWith(room.roomSummary().toOptional())
|
||||
}
|
||||
|
||||
fun liveRoomMemberIds(): Observable<List<String>> {
|
||||
return room.getRoomMemberIdsLive().asObservable()
|
||||
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMemberSummary>> {
|
||||
return room.getRoomMembersLive(queryParams).asObservable()
|
||||
.startWith(room.getRoomMembers(queryParams))
|
||||
}
|
||||
|
||||
fun liveAnnotationSummary(eventId: String): Observable<EventAnnotationsSummary> {
|
||||
return room.getEventSummaryLive(eventId).asObservable()
|
||||
fun liveAnnotationSummary(eventId: String): Observable<Optional<EventAnnotationsSummary>> {
|
||||
return room.getEventAnnotationsSummaryLive(eventId).asObservable()
|
||||
.startWith(room.getEventAnnotationsSummary(eventId).toOptional())
|
||||
}
|
||||
|
||||
fun liveTimelineEvent(eventId: String): Observable<TimelineEvent> {
|
||||
return room.liveTimeLineEvent(eventId).asObservable()
|
||||
fun liveTimelineEvent(eventId: String): Observable<Optional<TimelineEvent>> {
|
||||
return room.getTimeLineEventLive(eventId).asObservable()
|
||||
.startWith(room.getTimeLineEvent(eventId).toOptional())
|
||||
}
|
||||
|
||||
fun loadRoomMembersIfNeeded(): Single<Unit> = Single.create {
|
||||
room.loadRoomMembersIfNeeded(MatrixCallbackSingle(it)).toSingle(it)
|
||||
fun liveStateEvent(eventType: String): Observable<Optional<Event>> {
|
||||
return room.getStateEventLive(eventType).asObservable()
|
||||
.startWith(room.getStateEvent(eventType).toOptional())
|
||||
}
|
||||
|
||||
fun joinRoom(viaServers: List<String> = emptyList()): Single<Unit> = Single.create {
|
||||
room.join(viaServers, MatrixCallbackSingle(it)).toSingle(it)
|
||||
fun liveReadMarker(): Observable<Optional<String>> {
|
||||
return room.getReadMarkerLive().asObservable()
|
||||
}
|
||||
|
||||
fun liveReadReceipt(): Observable<Optional<String>> {
|
||||
return room.getMyReadReceiptLive().asObservable()
|
||||
}
|
||||
|
||||
fun loadRoomMembersIfNeeded(): Single<Unit> = singleBuilder {
|
||||
room.loadRoomMembersIfNeeded(it)
|
||||
}
|
||||
|
||||
fun joinRoom(reason: String? = null,
|
||||
viaServers: List<String> = emptyList()): Single<Unit> = singleBuilder {
|
||||
room.join(reason, viaServers, it)
|
||||
}
|
||||
|
||||
fun liveEventReadReceipts(eventId: String): Observable<List<ReadReceipt>> {
|
||||
return room.getEventReadReceiptsLive(eventId).asObservable()
|
||||
}
|
||||
|
||||
fun liveDrafts(): Observable<List<UserDraft>> {
|
||||
return room.getDraftsLive().asObservable()
|
||||
}
|
||||
|
||||
fun liveNotificationState(): Observable<RoomNotificationState> {
|
||||
return room.getLiveRoomNotificationState().asObservable()
|
||||
}
|
||||
}
|
||||
|
||||
fun Room.rx(): RxRoom {
|
||||
return RxRoom(this)
|
||||
}
|
||||
}
|
||||
|
@ -18,57 +18,88 @@ package im.vector.matrix.rx
|
||||
|
||||
import androidx.paging.PagedList
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams
|
||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||
import im.vector.matrix.android.api.session.pushers.Pusher
|
||||
import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||
import im.vector.matrix.android.api.session.sync.SyncState
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
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 io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
|
||||
class RxSession(private val session: Session) {
|
||||
|
||||
fun liveRoomSummaries(): Observable<List<RoomSummary>> {
|
||||
return session.liveRoomSummaries().asObservable()
|
||||
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
|
||||
return session.getRoomSummariesLive(queryParams).asObservable()
|
||||
.startWith(session.getRoomSummaries(queryParams))
|
||||
}
|
||||
|
||||
fun liveGroupSummaries(): Observable<List<GroupSummary>> {
|
||||
return session.liveGroupSummaries().asObservable()
|
||||
fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Observable<List<GroupSummary>> {
|
||||
return session.getGroupSummariesLive(queryParams).asObservable()
|
||||
.startWith(session.getGroupSummaries(queryParams))
|
||||
}
|
||||
|
||||
fun liveBreadcrumbs(): Observable<List<RoomSummary>> {
|
||||
return session.getBreadcrumbsLive().asObservable()
|
||||
.startWith(session.getBreadcrumbs())
|
||||
}
|
||||
|
||||
fun liveSyncState(): Observable<SyncState> {
|
||||
return session.syncState().asObservable()
|
||||
return session.getSyncStateLive().asObservable()
|
||||
}
|
||||
|
||||
fun livePushers(): Observable<List<Pusher>> {
|
||||
return session.livePushers().asObservable()
|
||||
return session.getPushersLive().asObservable()
|
||||
}
|
||||
|
||||
fun liveUser(userId: String): Observable<Optional<User>> {
|
||||
return session.getUserLive(userId).asObservable()
|
||||
.startWith(session.getUser(userId).toOptional())
|
||||
}
|
||||
|
||||
fun liveUsers(): Observable<List<User>> {
|
||||
return session.liveUsers().asObservable()
|
||||
return session.getUsersLive().asObservable()
|
||||
}
|
||||
|
||||
fun liveIgnoredUsers(): Observable<List<User>> {
|
||||
return session.getIgnoredUsersLive().asObservable()
|
||||
}
|
||||
|
||||
fun livePagedUsers(filter: String? = null): Observable<PagedList<User>> {
|
||||
return session.livePagedUsers(filter).asObservable()
|
||||
return session.getPagedUsersLive(filter).asObservable()
|
||||
}
|
||||
|
||||
fun createRoom(roomParams: CreateRoomParams): Single<String> = Single.create {
|
||||
session.createRoom(roomParams, MatrixCallbackSingle(it)).toSingle(it)
|
||||
fun createRoom(roomParams: CreateRoomParams): Single<String> = singleBuilder {
|
||||
session.createRoom(roomParams, it)
|
||||
}
|
||||
|
||||
fun searchUsersDirectory(search: String,
|
||||
limit: Int,
|
||||
excludedUserIds: Set<String>): Single<List<User>> = Single.create {
|
||||
session.searchUsersDirectory(search, limit, excludedUserIds, MatrixCallbackSingle(it)).toSingle(it)
|
||||
excludedUserIds: Set<String>): Single<List<User>> = singleBuilder {
|
||||
session.searchUsersDirectory(search, limit, excludedUserIds, it)
|
||||
}
|
||||
|
||||
fun joinRoom(roomId: String, viaServers: List<String> = emptyList()): Single<Unit> = Single.create {
|
||||
session.joinRoom(roomId, viaServers, MatrixCallbackSingle(it)).toSingle(it)
|
||||
fun joinRoom(roomId: String,
|
||||
reason: String? = null,
|
||||
viaServers: List<String> = emptyList()): Single<Unit> = singleBuilder {
|
||||
session.joinRoom(roomId, reason, viaServers, it)
|
||||
}
|
||||
|
||||
fun getRoomIdByAlias(roomAlias: String,
|
||||
searchOnServer: Boolean): Single<Optional<String>> = singleBuilder {
|
||||
session.getRoomIdByAlias(roomAlias, searchOnServer, it)
|
||||
}
|
||||
|
||||
fun getProfileInfo(userId: String): Single<JsonDict> = singleBuilder {
|
||||
session.getProfile(userId, it)
|
||||
}
|
||||
}
|
||||
|
||||
fun Session.rx(): RxSession {
|
||||
return RxSession(this)
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath "io.realm:realm-gradle-plugin:5.12.0"
|
||||
classpath "io.realm:realm-gradle-plugin:6.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,6 +67,10 @@ android {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
|
||||
static def gitRevision() {
|
||||
@ -86,41 +90,42 @@ static def gitRevisionDate() {
|
||||
|
||||
dependencies {
|
||||
|
||||
def arrow_version = "0.8.0"
|
||||
def support_version = '1.1.0-beta01'
|
||||
def arrow_version = "0.8.2"
|
||||
def moshi_version = '1.8.0'
|
||||
def lifecycle_version = '2.0.0'
|
||||
def coroutines_version = "1.0.1"
|
||||
def markwon_version = '3.0.0'
|
||||
def daggerVersion = '2.23.1'
|
||||
def lifecycle_version = '2.1.0'
|
||||
def coroutines_version = "1.3.2"
|
||||
def markwon_version = '3.1.0'
|
||||
def daggerVersion = '2.24'
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||
|
||||
implementation "androidx.appcompat:appcompat:1.1.0-rc01"
|
||||
implementation "androidx.recyclerview:recyclerview:1.1.0-beta01"
|
||||
implementation "androidx.appcompat:appcompat:1.1.0"
|
||||
|
||||
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
||||
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
|
||||
|
||||
// Network
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
|
||||
implementation 'com.squareup.retrofit2:converter-moshi:2.4.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp:3.14.1'
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.6.2'
|
||||
implementation 'com.squareup.retrofit2:converter-moshi:2.6.2'
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.2.2'
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:4.2.2'
|
||||
implementation 'com.novoda:merlin:1.2.0'
|
||||
implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
|
||||
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"
|
||||
|
||||
implementation "ru.noties.markwon:core:$markwon_version"
|
||||
|
||||
// Image
|
||||
implementation 'androidx.exifinterface:exifinterface:1.1.0'
|
||||
|
||||
// Database
|
||||
implementation 'com.github.Zhuinden:realm-monarchy:0.5.1'
|
||||
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
|
||||
|
||||
// Work
|
||||
implementation "androidx.work:work-runtime-ktx:2.1.0-rc01"
|
||||
implementation "androidx.work:work-runtime-ktx:2.3.0-beta02"
|
||||
|
||||
// FP
|
||||
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
||||
@ -132,21 +137,25 @@ dependencies {
|
||||
// DI
|
||||
implementation "com.google.dagger:dagger:$daggerVersion"
|
||||
kapt "com.google.dagger:dagger-compiler:$daggerVersion"
|
||||
compileOnly 'com.squareup.inject:assisted-inject-annotations-dagger2:0.4.0'
|
||||
kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.4.0'
|
||||
compileOnly 'com.squareup.inject:assisted-inject-annotations-dagger2:0.5.0'
|
||||
kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.5.0'
|
||||
|
||||
// Logging
|
||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||
implementation 'com.facebook.stetho:stetho-okhttp3:1.5.0'
|
||||
implementation 'com.facebook.stetho:stetho-okhttp3:1.5.1'
|
||||
|
||||
debugImplementation 'com.airbnb.okreplay:okreplay:1.4.0'
|
||||
releaseImplementation 'com.airbnb.okreplay:noop:1.4.0'
|
||||
androidTestImplementation 'com.airbnb.okreplay:espresso:1.4.0'
|
||||
// Bus
|
||||
implementation 'org.greenrobot:eventbus:3.1.1'
|
||||
|
||||
debugImplementation 'com.airbnb.okreplay:okreplay:1.5.0'
|
||||
releaseImplementation 'com.airbnb.okreplay:noop:1.5.0'
|
||||
androidTestImplementation 'com.airbnb.okreplay:espresso:1.5.0'
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation 'org.robolectric:robolectric:4.0.2'
|
||||
testImplementation 'org.robolectric:robolectric:4.3'
|
||||
//testImplementation 'org.robolectric:shadows-support-v4:3.0'
|
||||
testImplementation "io.mockk:mockk:1.8.13.kotlin13"
|
||||
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
|
||||
testImplementation 'io.mockk:mockk:1.9.2.kotlin12'
|
||||
testImplementation 'org.amshove.kluent:kluent-android:1.44'
|
||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||
|
||||
@ -156,7 +165,8 @@ dependencies {
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
androidTestImplementation 'org.amshove.kluent:kluent-android:1.44'
|
||||
androidTestImplementation "io.mockk:mockk-android:1.8.13.kotlin13"
|
||||
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
|
||||
androidTestImplementation 'io.mockk:mockk-android:1.9.2.kotlin12'
|
||||
androidTestImplementation "androidx.arch.core:core-testing:$lifecycle_version"
|
||||
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||
|
||||
|
@ -17,15 +17,15 @@
|
||||
package im.vector.matrix.android
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.InstrumentationRegistry
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import java.io.File
|
||||
|
||||
interface InstrumentedTest {
|
||||
fun context(): Context {
|
||||
return InstrumentationRegistry.getTargetContext()
|
||||
return ApplicationProvider.getApplicationContext()
|
||||
}
|
||||
|
||||
fun cacheDir(): File {
|
||||
return context().cacheDir
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,4 +29,4 @@ class OkReplayRuleChainNoActivity(
|
||||
return RuleChain.outerRule(PermissionRule(configuration))
|
||||
.around(RecorderRule(configuration))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,4 +19,4 @@ package im.vector.matrix.android
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
|
||||
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main, Main)
|
||||
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main)
|
||||
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.account
|
||||
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.CryptoTestHelper
|
||||
import im.vector.matrix.android.common.SessionTestParams
|
||||
import im.vector.matrix.android.common.TestConstants
|
||||
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 AccountCreationTest : InstrumentedTest {
|
||||
|
||||
private val commonTestHelper = CommonTestHelper(context())
|
||||
private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
|
||||
@Test
|
||||
fun createAccountTest() {
|
||||
val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true))
|
||||
|
||||
commonTestHelper.signout(session)
|
||||
|
||||
session.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createAccountAndLoginAgainTest() {
|
||||
val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true))
|
||||
|
||||
// Log again to the same account
|
||||
val session2 = commonTestHelper.logIntoAccount(session.myUserId, SessionTestParams(withInitialSync = true))
|
||||
|
||||
session.close()
|
||||
session2.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun simpleE2eTest() {
|
||||
val res = cryptoTestHelper.doE2ETestWithAliceInARoom()
|
||||
|
||||
res.close()
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.auth
|
||||
|
||||
import androidx.test.annotation.UiThreadTest
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.OkReplayRuleChainNoActivity
|
||||
import im.vector.matrix.android.api.auth.Authenticator
|
||||
import okreplay.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
internal class AuthenticatorTest : InstrumentedTest {
|
||||
|
||||
lateinit var authenticator: Authenticator
|
||||
lateinit var okReplayInterceptor: OkReplayInterceptor
|
||||
|
||||
private val okReplayConfig = OkReplayConfig.Builder()
|
||||
.tapeRoot(AndroidTapeRoot(
|
||||
context(), javaClass))
|
||||
.defaultMode(TapeMode.READ_WRITE) // or TapeMode.READ_ONLY
|
||||
.sslEnabled(true)
|
||||
.interceptor(okReplayInterceptor)
|
||||
.build()
|
||||
|
||||
@get:Rule
|
||||
val testRule = OkReplayRuleChainNoActivity(okReplayConfig).get()
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
@OkReplay(tape = "auth", mode = TapeMode.READ_WRITE)
|
||||
fun auth() {
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val grantExternalStoragePermissionRule: GrantPermissionRule =
|
||||
GrantPermissionRule.grant(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,278 @@
|
||||
/*
|
||||
* Copyright 2016 OpenMarket Ltd
|
||||
* Copyright 2018 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.common
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import im.vector.matrix.android.api.Matrix
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.MatrixConfiguration
|
||||
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.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.room.Room
|
||||
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 org.junit.Assert.*
|
||||
import java.util.*
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* This class exposes methods to be used in common cases
|
||||
* Registration, login, Sync, Sending messages...
|
||||
*/
|
||||
class CommonTestHelper(context: Context) {
|
||||
|
||||
val matrix: Matrix
|
||||
|
||||
init {
|
||||
Matrix.initialize(context, MatrixConfiguration("TestFlavor"))
|
||||
|
||||
matrix = Matrix.getInstance(context)
|
||||
}
|
||||
|
||||
fun createAccount(userNamePrefix: String, testParams: SessionTestParams): Session {
|
||||
return createAccount(userNamePrefix, TestConstants.PASSWORD, testParams)
|
||||
}
|
||||
|
||||
fun logIntoAccount(userId: String, testParams: SessionTestParams): Session {
|
||||
return logIntoAccount(userId, TestConstants.PASSWORD, testParams)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Home server configuration, with Http connection allowed for test
|
||||
*/
|
||||
fun createHomeServerConfig(): HomeServerConnectionConfig {
|
||||
return HomeServerConnectionConfig.Builder()
|
||||
.withHomeServerUri(Uri.parse(TestConstants.TESTS_HOME_SERVER_URL))
|
||||
.build()
|
||||
}
|
||||
|
||||
/**
|
||||
* This methods init the event stream and check for initial sync
|
||||
*
|
||||
* @param session the session to sync
|
||||
*/
|
||||
fun syncSession(session: Session) {
|
||||
// val lock = CountDownLatch(1)
|
||||
|
||||
// val observer = androidx.lifecycle.Observer<SyncState> { syncState ->
|
||||
// if (syncState is SyncState.Idle) {
|
||||
// lock.countDown()
|
||||
// }
|
||||
// }
|
||||
|
||||
// TODO observe?
|
||||
// while (session.syncState().value !is SyncState.Idle) {
|
||||
// sleep(100)
|
||||
// }
|
||||
|
||||
session.open()
|
||||
session.startSync(true)
|
||||
// await(lock)
|
||||
// session.syncState().removeObserver(observer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends text messages in a room
|
||||
*
|
||||
* @param room the room where to send the messages
|
||||
* @param message the message to send
|
||||
* @param nbOfMessages the number of time the message will be sent
|
||||
*/
|
||||
fun sendTextMessage(room: Room, message: String, nbOfMessages: Int): List<TimelineEvent> {
|
||||
val sentEvents = ArrayList<TimelineEvent>(nbOfMessages)
|
||||
val latch = CountDownLatch(nbOfMessages)
|
||||
val onEventSentListener = object : Timeline.Listener {
|
||||
override fun onTimelineFailure(throwable: Throwable) {
|
||||
}
|
||||
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
// TODO Count only new messages?
|
||||
if (snapshot.count { it.root.type == EventType.MESSAGE } == nbOfMessages) {
|
||||
sentEvents.addAll(snapshot.filter { it.root.type == EventType.MESSAGE })
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
val timeline = room.createTimeline(null, TimelineSettings(10))
|
||||
timeline.addListener(onEventSentListener)
|
||||
for (i in 0 until nbOfMessages) {
|
||||
room.sendTextMessage(message + " #" + (i + 1))
|
||||
}
|
||||
await(latch)
|
||||
timeline.removeListener(onEventSentListener)
|
||||
|
||||
// Check that all events has been created
|
||||
assertEquals(nbOfMessages.toLong(), sentEvents.size.toLong())
|
||||
|
||||
return sentEvents
|
||||
}
|
||||
|
||||
// PRIVATE METHODS *****************************************************************************
|
||||
|
||||
/**
|
||||
* Creates a unique account
|
||||
*
|
||||
* @param userNamePrefix the user name prefix
|
||||
* @param password the password
|
||||
* @param testParams test params about the session
|
||||
* @return the session associated with the newly created account
|
||||
*/
|
||||
private fun createAccount(userNamePrefix: String,
|
||||
password: String,
|
||||
testParams: SessionTestParams): Session {
|
||||
val session = createAccountAndSync(
|
||||
userNamePrefix + "_" + System.currentTimeMillis() + UUID.randomUUID(),
|
||||
password,
|
||||
testParams
|
||||
)
|
||||
assertNotNull(session)
|
||||
return session
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs into an existing account
|
||||
*
|
||||
* @param userId the userId to log in
|
||||
* @param password the password to log in
|
||||
* @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 {
|
||||
val session = logAccountAndSync(userId, password, testParams)
|
||||
assertNotNull(session)
|
||||
return session
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an account and a dedicated session
|
||||
*
|
||||
* @param userName the account username
|
||||
* @param password the password
|
||||
* @param sessionTestParams parameters for the test
|
||||
*/
|
||||
private fun createAccountAndSync(userName: String,
|
||||
password: String,
|
||||
sessionTestParams: SessionTestParams): Session {
|
||||
val hs = createHomeServerConfig()
|
||||
|
||||
doSync<LoginFlowResult> {
|
||||
matrix.authenticationService
|
||||
.getLoginFlow(hs, it)
|
||||
}
|
||||
|
||||
doSync<RegistrationResult> {
|
||||
matrix.authenticationService
|
||||
.getRegistrationWizard()
|
||||
.createAccount(userName, password, null, it)
|
||||
}
|
||||
|
||||
// Preform dummy step
|
||||
val registrationResult = doSync<RegistrationResult> {
|
||||
matrix.authenticationService
|
||||
.getRegistrationWizard()
|
||||
.dummy(it)
|
||||
}
|
||||
|
||||
assertTrue(registrationResult is RegistrationResult.Success)
|
||||
val session = (registrationResult as RegistrationResult.Success).session
|
||||
if (sessionTestParams.withInitialSync) {
|
||||
syncSession(session)
|
||||
}
|
||||
|
||||
return session
|
||||
}
|
||||
|
||||
/**
|
||||
* Start an account login
|
||||
*
|
||||
* @param userName the account username
|
||||
* @param password the password
|
||||
* @param sessionTestParams session test params
|
||||
*/
|
||||
private fun logAccountAndSync(userName: String,
|
||||
password: String,
|
||||
sessionTestParams: SessionTestParams): Session {
|
||||
val hs = createHomeServerConfig()
|
||||
|
||||
doSync<LoginFlowResult> {
|
||||
matrix.authenticationService
|
||||
.getLoginFlow(hs, it)
|
||||
}
|
||||
|
||||
val session = doSync<Session> {
|
||||
matrix.authenticationService
|
||||
.getLoginWizard()
|
||||
.login(userName, password, "myDevice", it)
|
||||
}
|
||||
|
||||
if (sessionTestParams.withInitialSync) {
|
||||
syncSession(session)
|
||||
}
|
||||
|
||||
return session
|
||||
}
|
||||
|
||||
/**
|
||||
* 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))
|
||||
}
|
||||
|
||||
// Transform a method with a MatrixCallback to a synchronous method
|
||||
inline fun <reified T> doSync(block: (MatrixCallback<T>) -> Unit): T {
|
||||
val lock = CountDownLatch(1)
|
||||
var result: T? = null
|
||||
|
||||
val callback = object : TestMatrixCallback<T>(lock) {
|
||||
override fun onSuccess(data: T) {
|
||||
result = data
|
||||
super.onSuccess(data)
|
||||
}
|
||||
}
|
||||
|
||||
block.invoke(callback)
|
||||
|
||||
await(lock)
|
||||
|
||||
assertNotNull(result)
|
||||
return result!!
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all provided sessions
|
||||
*/
|
||||
fun Iterable<Session>.close() = forEach { it.close() }
|
||||
|
||||
fun signout(session: Session) {
|
||||
val lock = CountDownLatch(1)
|
||||
session.signOut(true, object : TestMatrixCallback<Unit>(lock) {})
|
||||
await(lock)
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2018 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.common
|
||||
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
|
||||
data class CryptoTestData(val firstSession: Session,
|
||||
val roomId: String,
|
||||
val secondSession: Session? = null,
|
||||
val thirdSession: Session? = null) {
|
||||
|
||||
fun close() {
|
||||
firstSession.close()
|
||||
secondSession?.close()
|
||||
secondSession?.close()
|
||||
}
|
||||
}
|
@ -0,0 +1,335 @@
|
||||
/*
|
||||
* Copyright 2018 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.common
|
||||
|
||||
import android.os.SystemClock
|
||||
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.model.create.CreateRoomParams
|
||||
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.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupAuthData
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
||||
import org.junit.Assert.*
|
||||
import java.util.*
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
|
||||
|
||||
val messagesFromAlice: List<String> = Arrays.asList("0 - Hello I'm Alice!", "4 - Go!")
|
||||
val messagesFromBob: List<String> = Arrays.asList("1 - Hello I'm Bob!", "2 - Isn't life grand?", "3 - Let's go to the opera.")
|
||||
|
||||
val defaultSessionParams = SessionTestParams(true)
|
||||
|
||||
/**
|
||||
* @return alice session
|
||||
*/
|
||||
fun doE2ETestWithAliceInARoom(): CryptoTestData {
|
||||
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
|
||||
|
||||
var roomId: String? = null
|
||||
val lock1 = CountDownLatch(1)
|
||||
|
||||
aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }, object : TestMatrixCallback<String>(lock1) {
|
||||
override fun onSuccess(data: String) {
|
||||
roomId = data
|
||||
super.onSuccess(data)
|
||||
}
|
||||
})
|
||||
|
||||
mTestHelper.await(lock1)
|
||||
assertNotNull(roomId)
|
||||
|
||||
val room = aliceSession.getRoom(roomId!!)!!
|
||||
|
||||
val lock2 = CountDownLatch(1)
|
||||
room.enableEncryptionWithAlgorithm(MXCRYPTO_ALGORITHM_MEGOLM, object : TestMatrixCallback<Unit>(lock2) {})
|
||||
mTestHelper.await(lock2)
|
||||
|
||||
return CryptoTestData(aliceSession, roomId!!)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return alice and bob sessions
|
||||
*/
|
||||
fun doE2ETestWithAliceAndBobInARoom(): CryptoTestData {
|
||||
val statuses = HashMap<String, String>()
|
||||
|
||||
val cryptoTestData = doE2ETestWithAliceInARoom()
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceRoomId = cryptoTestData.roomId
|
||||
|
||||
val room = aliceSession.getRoom(aliceRoomId)!!
|
||||
|
||||
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams)
|
||||
|
||||
val lock1 = CountDownLatch(2)
|
||||
|
||||
// val bobEventListener = object : MXEventListener() {
|
||||
// override fun onNewRoom(roomId: String) {
|
||||
// if (TextUtils.equals(roomId, aliceRoomId)) {
|
||||
// if (!statuses.containsKey("onNewRoom")) {
|
||||
// statuses["onNewRoom"] = "onNewRoom"
|
||||
// lock1.countDown()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// bobSession.dataHandler.addListener(bobEventListener)
|
||||
|
||||
room.invite(bobSession.myUserId, callback = 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"))
|
||||
|
||||
// bobSession.dataHandler.removeListener(bobEventListener)
|
||||
|
||||
val lock2 = CountDownLatch(2)
|
||||
|
||||
bobSession.joinRoom(aliceRoomId, callback = TestMatrixCallback(lock2))
|
||||
|
||||
// room.addEventListener(object : MXEventListener() {
|
||||
// override fun onLiveEvent(event: Event, roomState: RoomState) {
|
||||
// if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_STATE_ROOM_MEMBER)) {
|
||||
// val contentToConsider = event.contentAsJsonObject
|
||||
// val member = JsonUtils.toRoomMember(contentToConsider)
|
||||
//
|
||||
// if (TextUtils.equals(member.membership, RoomMember.MEMBERSHIP_JOIN)) {
|
||||
// statuses["AliceJoin"] = "AliceJoin"
|
||||
// lock2.countDown()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
|
||||
mTestHelper.await(lock2)
|
||||
|
||||
// Ensure bob can send messages to the room
|
||||
// val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
|
||||
// assertNotNull(roomFromBobPOV.powerLevels)
|
||||
// assertTrue(roomFromBobPOV.powerLevels.maySendMessage(bobSession.myUserId))
|
||||
|
||||
assertTrue(statuses.toString() + "", statuses.containsKey("AliceJoin"))
|
||||
|
||||
// bobSession.dataHandler.removeListener(bobEventListener)
|
||||
|
||||
return CryptoTestData(aliceSession, aliceRoomId, bobSession)
|
||||
}
|
||||
|
||||
/**
|
||||
* @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"))
|
||||
|
||||
// wait the initial sync
|
||||
SystemClock.sleep(1000)
|
||||
|
||||
// samSession.dataHandler.removeListener(samEventListener)
|
||||
|
||||
return CryptoTestData(aliceSession, aliceRoomId, cryptoTestData.secondSession, samSession)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Alice and Bob sessions
|
||||
*/
|
||||
fun doE2ETestWithAliceAndBobInARoomWithEncryptedMessages(): CryptoTestData {
|
||||
val cryptoTestData = doE2ETestWithAliceAndBobInARoom()
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceRoomId = cryptoTestData.roomId
|
||||
val bobSession = cryptoTestData.secondSession!!
|
||||
|
||||
bobSession.setWarnOnUnknownDevices(false)
|
||||
|
||||
aliceSession.setWarnOnUnknownDevices(false)
|
||||
|
||||
val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
|
||||
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
|
||||
|
||||
var lock = CountDownLatch(1)
|
||||
|
||||
val bobEventsListener = object : Timeline.Listener {
|
||||
override fun onTimelineFailure(throwable: Throwable) {
|
||||
}
|
||||
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
val size = snapshot.filter { it.root.senderId != bobSession.myUserId && it.root.getClearType() == EventType.MESSAGE }
|
||||
.size
|
||||
|
||||
if (size == 3) {
|
||||
lock.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(10))
|
||||
bobTimeline.addListener(bobEventsListener)
|
||||
|
||||
val results = HashMap<String, Any>()
|
||||
|
||||
// bobSession.dataHandler.addListener(object : MXEventListener() {
|
||||
// override fun onToDeviceEvent(event: Event) {
|
||||
// results["onToDeviceEvent"] = event
|
||||
// lock.countDown()
|
||||
// }
|
||||
// })
|
||||
|
||||
// Alice sends a message
|
||||
roomFromAlicePOV.sendTextMessage(messagesFromAlice[0])
|
||||
assertTrue(results.containsKey("onToDeviceEvent"))
|
||||
// assertEquals(1, messagesReceivedByBobCount)
|
||||
|
||||
// Bob send a message
|
||||
lock = CountDownLatch(1)
|
||||
roomFromBobPOV.sendTextMessage(messagesFromBob[0])
|
||||
// android does not echo the messages sent from itself
|
||||
// messagesReceivedByBobCount++
|
||||
mTestHelper.await(lock)
|
||||
// assertEquals(2, messagesReceivedByBobCount)
|
||||
|
||||
// Bob send a message
|
||||
lock = CountDownLatch(1)
|
||||
roomFromBobPOV.sendTextMessage(messagesFromBob[1])
|
||||
// android does not echo the messages sent from itself
|
||||
// messagesReceivedByBobCount++
|
||||
mTestHelper.await(lock)
|
||||
// assertEquals(3, messagesReceivedByBobCount)
|
||||
|
||||
// Bob send a message
|
||||
lock = CountDownLatch(1)
|
||||
roomFromBobPOV.sendTextMessage(messagesFromBob[2])
|
||||
// android does not echo the messages sent from itself
|
||||
// messagesReceivedByBobCount++
|
||||
mTestHelper.await(lock)
|
||||
// assertEquals(4, messagesReceivedByBobCount)
|
||||
|
||||
// Alice sends a message
|
||||
lock = CountDownLatch(2)
|
||||
roomFromAlicePOV.sendTextMessage(messagesFromAlice[1])
|
||||
mTestHelper.await(lock)
|
||||
// assertEquals(5, messagesReceivedByBobCount)
|
||||
|
||||
bobTimeline.removeListener(bobEventsListener)
|
||||
|
||||
return cryptoTestData
|
||||
}
|
||||
|
||||
fun checkEncryptedEvent(event: Event, roomId: String, clearMessage: String, senderSession: Session) {
|
||||
assertEquals(EventType.ENCRYPTED, event.type)
|
||||
assertNotNull(event.content)
|
||||
|
||||
val eventWireContent = event.content.toContent()
|
||||
assertNotNull(eventWireContent)
|
||||
|
||||
assertNull(eventWireContent.get("body"))
|
||||
assertEquals(MXCRYPTO_ALGORITHM_MEGOLM, eventWireContent.get("algorithm"))
|
||||
|
||||
assertNotNull(eventWireContent.get("ciphertext"))
|
||||
assertNotNull(eventWireContent.get("session_id"))
|
||||
assertNotNull(eventWireContent.get("sender_key"))
|
||||
|
||||
assertEquals(senderSession.sessionParams.credentials.deviceId, eventWireContent.get("device_id"))
|
||||
|
||||
assertNotNull(event.eventId)
|
||||
assertEquals(roomId, event.roomId)
|
||||
assertEquals(EventType.MESSAGE, event.getClearType())
|
||||
// TODO assertTrue(event.getAge() < 10000)
|
||||
|
||||
val eventContent = event.toContent()
|
||||
assertNotNull(eventContent)
|
||||
assertEquals(clearMessage, eventContent.get("body"))
|
||||
assertEquals(senderSession.myUserId, event.senderId)
|
||||
}
|
||||
|
||||
fun createFakeMegolmBackupAuthData(): MegolmBackupAuthData {
|
||||
return MegolmBackupAuthData(
|
||||
publicKey = "abcdefg",
|
||||
signatures = HashMap<String, Map<String, String>>().apply {
|
||||
this["something"] = HashMap<String, String>().apply {
|
||||
this["ed25519:something"] = "hijklmnop"
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun createFakeMegolmBackupCreationInfo(): MegolmBackupCreationInfo {
|
||||
return MegolmBackupCreationInfo().apply {
|
||||
algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
||||
authData = createFakeMegolmBackupAuthData()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.matrix.android.common
|
||||
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Protocol
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
||||
/**
|
||||
* Allows to intercept network requests for test purpose by
|
||||
* - re-writing the response
|
||||
* - changing the response code (200/404/etc..).
|
||||
* - Test delays..
|
||||
*
|
||||
* Basic usage:
|
||||
* <code>
|
||||
* val mockInterceptor = MockOkHttpInterceptor()
|
||||
* mockInterceptor.addRule(MockOkHttpInterceptor.SimpleRule(".well-known/matrix/client", 200, "{}"))
|
||||
*
|
||||
* RestHttpClientFactoryProvider.defaultProvider = RestClientHttpClientFactory(mockInterceptor)
|
||||
* AutoDiscovery().findClientConfig("matrix.org", <callback>)
|
||||
* </code>
|
||||
*/
|
||||
class MockOkHttpInterceptor : Interceptor {
|
||||
|
||||
private var rules: ArrayList<Rule> = ArrayList()
|
||||
|
||||
fun addRule(rule: Rule) {
|
||||
rules.add(rule)
|
||||
}
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val originalRequest = chain.request()
|
||||
|
||||
rules.forEach { rule ->
|
||||
if (originalRequest.url.toString().contains(rule.match)) {
|
||||
rule.process(originalRequest)?.let {
|
||||
return it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return chain.proceed(originalRequest)
|
||||
}
|
||||
|
||||
abstract class Rule(val match: String) {
|
||||
abstract fun process(originalRequest: Request): Response?
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple rule that reply with the given body for any request that matches the match param
|
||||
*/
|
||||
class SimpleRule(match: String,
|
||||
private val code: Int = HttpsURLConnection.HTTP_OK,
|
||||
private val body: String = "{}") : Rule(match) {
|
||||
|
||||
override fun process(originalRequest: Request): Response? {
|
||||
return Response.Builder()
|
||||
.protocol(Protocol.HTTP_1_1)
|
||||
.request(originalRequest)
|
||||
.message("mocked answer")
|
||||
.body(body.toResponseBody(null))
|
||||
.code(code)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2018 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.common
|
||||
|
||||
data class SessionTestParams @JvmOverloads constructor(val withInitialSync: Boolean = false)
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2018 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.common
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Compare two lists and their content
|
||||
*/
|
||||
fun assertListEquals(list1: List<Any>?, list2: List<Any>?) {
|
||||
if (list1 == null) {
|
||||
assertNull(list2)
|
||||
} else {
|
||||
assertNotNull(list2)
|
||||
|
||||
assertEquals("List sizes must match", list1.size, list2!!.size)
|
||||
|
||||
for (i in list1.indices) {
|
||||
assertEquals("Elements at index $i are not equal", list1[i], list2[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two maps and their content
|
||||
*/
|
||||
fun assertDictEquals(dict1: Map<String, Any>?, dict2: Map<String, Any>?) {
|
||||
if (dict1 == null) {
|
||||
assertNull(dict2)
|
||||
} else {
|
||||
assertNotNull(dict2)
|
||||
|
||||
assertEquals("Map sizes must match", dict1.size, dict2!!.size)
|
||||
|
||||
for (i in dict1.keys) {
|
||||
assertEquals("Values for key $i are not equal", dict1[i], dict2[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two byte arrays content.
|
||||
* Note that if the arrays have not the same size, it also fails.
|
||||
*/
|
||||
fun assertByteArrayNotEqual(a1: ByteArray, a2: ByteArray) {
|
||||
if (a1.size != a2.size) {
|
||||
fail("Arrays have not the same size.")
|
||||
}
|
||||
|
||||
for (index in a1.indices) {
|
||||
if (a1[index] != a2[index]) {
|
||||
// Difference found!
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fail("Arrays are equals.")
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2018 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.common
|
||||
|
||||
import android.os.Debug
|
||||
|
||||
object TestConstants {
|
||||
|
||||
const val TESTS_HOME_SERVER_URL = "http://10.0.2.2:8080"
|
||||
|
||||
// Time out to use when waiting for server response. 60s
|
||||
private const val AWAIT_TIME_OUT_MILLIS = 60000
|
||||
|
||||
// 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 * 60000
|
||||
|
||||
const val USER_ALICE = "Alice"
|
||||
const val USER_BOB = "Bob"
|
||||
const val USER_SAM = "Sam"
|
||||
|
||||
const val PASSWORD = "password"
|
||||
|
||||
val timeOutMillis: Long
|
||||
get() = if (Debug.isDebuggerConnected()) {
|
||||
// Wait more
|
||||
AWAIT_TIME_OUT_WITH_DEBUGGER_MILLIS.toLong()
|
||||
} else {
|
||||
AWAIT_TIME_OUT_MILLIS.toLong()
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2018 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.common
|
||||
|
||||
import androidx.annotation.CallSuper
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import org.junit.Assert.fail
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
/**
|
||||
* Simple implementation of MatrixCallback, which count down the CountDownLatch on each API callback
|
||||
* @param onlySuccessful true to fail if an error occurs. This is the default behavior
|
||||
* @param <T>
|
||||
*/
|
||||
open class TestMatrixCallback<T>(private val countDownLatch: CountDownLatch,
|
||||
private val onlySuccessful: Boolean = true) : MatrixCallback<T> {
|
||||
|
||||
@CallSuper
|
||||
override fun onSuccess(data: T) {
|
||||
countDownLatch.countDown()
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onFailure(failure: Throwable) {
|
||||
Timber.e(failure, "TestApiCallback")
|
||||
|
||||
if (onlySuccessful) {
|
||||
fail("onFailure " + failure.localizedMessage)
|
||||
}
|
||||
|
||||
countDownLatch.countDown()
|
||||
}
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.crypto
|
||||
|
||||
import android.os.MemoryFile
|
||||
import android.util.Base64
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileKey
|
||||
import org.junit.Assert.*
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* Unit tests AttachmentEncryptionTest.
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
class AttachmentEncryptionTest {
|
||||
|
||||
private fun checkDecryption(input: String, encryptedFileInfo: EncryptedFileInfo): String {
|
||||
val `in` = Base64.decode(input, Base64.DEFAULT)
|
||||
|
||||
val inputStream: InputStream
|
||||
|
||||
inputStream = if (`in`.isEmpty()) {
|
||||
ByteArrayInputStream(`in`)
|
||||
} else {
|
||||
val memoryFile = MemoryFile("file" + System.currentTimeMillis(), `in`.size)
|
||||
memoryFile.outputStream.write(`in`)
|
||||
memoryFile.inputStream
|
||||
}
|
||||
|
||||
val decryptedStream = MXEncryptedAttachments.decryptAttachment(inputStream, encryptedFileInfo)
|
||||
|
||||
assertNotNull(decryptedStream)
|
||||
|
||||
val buffer = ByteArray(100)
|
||||
|
||||
val len = decryptedStream!!.read(buffer)
|
||||
|
||||
decryptedStream.close()
|
||||
|
||||
return Base64.encodeToString(buffer, 0, len, Base64.DEFAULT).replace("\n".toRegex(), "").replace("=".toRegex(), "")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkDecrypt1() {
|
||||
val encryptedFileInfo = EncryptedFileInfo(
|
||||
v = "v2",
|
||||
hashes = mapOf("sha256" to "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU"),
|
||||
key = EncryptedFileKey(
|
||||
alg = "A256CTR",
|
||||
k = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
key_ops = listOf("encrypt", "decrypt"),
|
||||
kty = "oct",
|
||||
ext = true
|
||||
),
|
||||
iv = "AAAAAAAAAAAAAAAAAAAAAA",
|
||||
url = "dummyUrl"
|
||||
)
|
||||
|
||||
assertEquals("", checkDecryption("", encryptedFileInfo))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkDecrypt2() {
|
||||
val encryptedFileInfo = EncryptedFileInfo(
|
||||
v = "v2",
|
||||
hashes = mapOf("sha256" to "YzF08lARDdOCzJpzuSwsjTNlQc4pHxpdHcXiD/wpK6k"),
|
||||
key = EncryptedFileKey(
|
||||
alg = "A256CTR",
|
||||
k = "__________________________________________8",
|
||||
key_ops = listOf("encrypt", "decrypt"),
|
||||
kty = "oct",
|
||||
ext = true
|
||||
),
|
||||
iv = "//////////8AAAAAAAAAAA",
|
||||
url = "dummyUrl"
|
||||
)
|
||||
|
||||
assertEquals("SGVsbG8sIFdvcmxk", checkDecryption("5xJZTt5cQicm+9f4", encryptedFileInfo))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkDecrypt3() {
|
||||
val encryptedFileInfo = EncryptedFileInfo(
|
||||
v = "v2",
|
||||
hashes = mapOf("sha256" to "IOq7/dHHB+mfHfxlRY5XMeCWEwTPmlf4cJcgrkf6fVU"),
|
||||
key = EncryptedFileKey(
|
||||
alg = "A256CTR",
|
||||
k = "__________________________________________8",
|
||||
key_ops = listOf("encrypt", "decrypt"),
|
||||
kty = "oct",
|
||||
ext = true
|
||||
),
|
||||
iv = "//////////8AAAAAAAAAAA",
|
||||
url = "dummyUrl"
|
||||
)
|
||||
|
||||
assertEquals("YWxwaGFudW1lcmljYWxseWFscGhhbnVtZXJpY2FsbHlhbHBoYW51bWVyaWNhbGx5YWxwaGFudW1lcmljYWxseQ",
|
||||
checkDecryption("zhtFStAeFx0s+9L/sSQO+WQMtldqYEHqTxMduJrCIpnkyer09kxJJuA4K+adQE4w+7jZe/vR9kIcqj9rOhDR8Q",
|
||||
encryptedFileInfo))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkDecrypt4() {
|
||||
val encryptedFileInfo = EncryptedFileInfo(
|
||||
v = "v2",
|
||||
hashes = mapOf("sha256" to "LYG/orOViuFwovJpv2YMLSsmVKwLt7pY3f8SYM7KU5E"),
|
||||
key = EncryptedFileKey(
|
||||
alg = "A256CTR",
|
||||
k = "__________________________________________8",
|
||||
key_ops = listOf("encrypt", "decrypt"),
|
||||
kty = "oct",
|
||||
ext = true
|
||||
),
|
||||
iv = "/////////////////////w",
|
||||
url = "dummyUrl"
|
||||
)
|
||||
|
||||
assertNotEquals("YWxwaGFudW1lcmljYWxseWFscGhhbnVtZXJpY2FsbHlhbHBoYW51bWVyaWNhbGx5YWxwaGFudW1lcmljYWxseQ",
|
||||
checkDecryption("tJVNBVJ/vl36UQt4Y5e5m84bRUrQHhcdLPvS/7EkDvlkDLZXamBB6k8THbiawiKZ5Mnq9PZMSSbgOCvmnUBOMA",
|
||||
encryptedFileInfo))
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@ 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 io.realm.RealmConfiguration
|
||||
import java.util.*
|
||||
import kotlin.random.Random
|
||||
|
||||
internal class CryptoStoreHelper {
|
||||
|
||||
@ -35,10 +35,10 @@ internal class CryptoStoreHelper {
|
||||
}
|
||||
|
||||
fun createCredential() = Credentials(
|
||||
userId = "userId_" + Random().nextInt(),
|
||||
userId = "userId_" + Random.nextInt(),
|
||||
homeServer = "http://matrix.org",
|
||||
accessToken = "access_token",
|
||||
refreshToken = null,
|
||||
deviceId = "deviceId_sample"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -16,20 +16,31 @@
|
||||
|
||||
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.internal.crypto.model.OlmSessionWrapper
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import io.realm.Realm
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.matrix.olm.OlmAccount
|
||||
import org.matrix.olm.OlmManager
|
||||
import org.matrix.olm.OlmSession
|
||||
|
||||
private const val DUMMY_DEVICE_KEY = "DeviceKey"
|
||||
|
||||
class CryptoStoreTest {
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class CryptoStoreTest : InstrumentedTest {
|
||||
|
||||
private val cryptoStoreHelper = CryptoStoreHelper()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
Realm.init(context())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_metadata_realm_ok() {
|
||||
val cryptoStore: IMXCryptoStore = cryptoStoreHelper.createStore()
|
||||
@ -114,4 +125,4 @@ class CryptoStoreTest {
|
||||
olmAccount1.releaseAccount()
|
||||
olmAccount2.releaseAccount()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Copyright 2018 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.crypto
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.Assert.*
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
|
||||
/**
|
||||
* Unit tests ExportEncryptionTest.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
class ExportEncryptionTest {
|
||||
|
||||
@Test
|
||||
fun checkExportError1() {
|
||||
val password = "password"
|
||||
val input = "-----"
|
||||
var failed = false
|
||||
|
||||
try {
|
||||
MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password)
|
||||
} catch (e: Exception) {
|
||||
failed = true
|
||||
}
|
||||
|
||||
assertTrue(failed)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkExportError2() {
|
||||
val password = "password"
|
||||
val input = "-----BEGIN MEGOLM SESSION DATA-----\n" + "-----"
|
||||
var failed = false
|
||||
|
||||
try {
|
||||
MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password)
|
||||
} catch (e: Exception) {
|
||||
failed = true
|
||||
}
|
||||
|
||||
assertTrue(failed)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkExportError3() {
|
||||
val password = "password"
|
||||
val input = "-----BEGIN MEGOLM SESSION DATA-----\n" +
|
||||
" AXNhbHRzYWx0c2FsdHNhbHSIiIiIiIiIiIiIiIiIiIiIAAAACmIRUW2OjZ3L2l6j9h0lHlV3M2dx\n" +
|
||||
" cissyYBxjsfsAn\n" +
|
||||
" -----END MEGOLM SESSION DATA-----"
|
||||
var failed = false
|
||||
|
||||
try {
|
||||
MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password)
|
||||
} catch (e: Exception) {
|
||||
failed = true
|
||||
}
|
||||
|
||||
assertTrue(failed)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkExportDecrypt1() {
|
||||
val password = "password"
|
||||
val input = "-----BEGIN MEGOLM SESSION DATA-----\nAXNhbHRzYWx0c2FsdHNhbHSIiIiIiIiIiIiIiIiIiIiIAAAACmIRUW2OjZ3L2l6j9h0lHlV3M2dx\n" + "cissyYBxjsfsAndErh065A8=\n-----END MEGOLM SESSION DATA-----"
|
||||
val expectedString = "plain"
|
||||
|
||||
var decodedString: String? = null
|
||||
try {
|
||||
decodedString = MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password)
|
||||
} catch (e: Exception) {
|
||||
fail("## checkExportDecrypt1() failed : " + e.message)
|
||||
}
|
||||
|
||||
assertEquals("## checkExportDecrypt1() : expectedString $expectedString -- decodedString $decodedString",
|
||||
expectedString,
|
||||
decodedString)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkExportDecrypt2() {
|
||||
val password = "betterpassword"
|
||||
val input = "-----BEGIN MEGOLM SESSION DATA-----\nAW1vcmVzYWx0bW9yZXNhbHT//////////wAAAAAAAAAAAAAD6KyBpe1Niv5M5NPm4ZATsJo5nghk\n" + "KYu63a0YQ5DRhUWEKk7CcMkrKnAUiZny\n-----END MEGOLM SESSION DATA-----"
|
||||
val expectedString = "Hello, World"
|
||||
|
||||
var decodedString: String? = null
|
||||
try {
|
||||
decodedString = MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password)
|
||||
} catch (e: Exception) {
|
||||
fail("## checkExportDecrypt2() failed : " + e.message)
|
||||
}
|
||||
|
||||
assertEquals("## checkExportDecrypt2() : expectedString $expectedString -- decodedString $decodedString",
|
||||
expectedString,
|
||||
decodedString)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkExportDecrypt3() {
|
||||
val password = "SWORDFISH"
|
||||
val input = "-----BEGIN MEGOLM SESSION DATA-----\nAXllc3NhbHR5Z29vZG5lc3P//////////wAAAAAAAAAAAAAD6OIW+Je7gwvjd4kYrb+49gKCfExw\n" + "MgJBMD4mrhLkmgAngwR1pHjbWXaoGybtiAYr0moQ93GrBQsCzPbvl82rZhaXO3iH5uHo/RCEpOqp\nPgg29363BGR+/Ripq/VCLKGNbw==\n-----END MEGOLM SESSION DATA-----"
|
||||
val expectedString = "alphanumericallyalphanumericallyalphanumericallyalphanumerically"
|
||||
|
||||
var decodedString: String? = null
|
||||
try {
|
||||
decodedString = MXMegolmExportEncryption.decryptMegolmKeyFile(input.toByteArray(charset("UTF-8")), password)
|
||||
} catch (e: Exception) {
|
||||
fail("## checkExportDecrypt3() failed : " + e.message)
|
||||
}
|
||||
|
||||
assertEquals("## checkExportDecrypt3() : expectedString $expectedString -- decodedString $decodedString",
|
||||
expectedString,
|
||||
decodedString)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkExportEncrypt1() {
|
||||
val password = "password"
|
||||
val expectedString = "plain"
|
||||
var decodedString: String? = null
|
||||
|
||||
try {
|
||||
decodedString = MXMegolmExportEncryption
|
||||
.decryptMegolmKeyFile(MXMegolmExportEncryption.encryptMegolmKeyFile(expectedString, password, 1000), password)
|
||||
} catch (e: Exception) {
|
||||
fail("## checkExportEncrypt1() failed : " + e.message)
|
||||
}
|
||||
|
||||
assertEquals("## checkExportEncrypt1() : expectedString $expectedString -- decodedString $decodedString",
|
||||
expectedString,
|
||||
decodedString)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkExportEncrypt2() {
|
||||
val password = "betterpassword"
|
||||
val expectedString = "Hello, World"
|
||||
var decodedString: String? = null
|
||||
|
||||
try {
|
||||
decodedString = MXMegolmExportEncryption
|
||||
.decryptMegolmKeyFile(MXMegolmExportEncryption.encryptMegolmKeyFile(expectedString, password, 1000), password)
|
||||
} catch (e: Exception) {
|
||||
fail("## checkExportEncrypt2() failed : " + e.message)
|
||||
}
|
||||
|
||||
assertEquals("## checkExportEncrypt2() : expectedString $expectedString -- decodedString $decodedString",
|
||||
expectedString,
|
||||
decodedString)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkExportEncrypt3() {
|
||||
val password = "SWORDFISH"
|
||||
val expectedString = "alphanumericallyalphanumericallyalphanumericallyalphanumerically"
|
||||
var decodedString: String? = null
|
||||
|
||||
try {
|
||||
decodedString = MXMegolmExportEncryption
|
||||
.decryptMegolmKeyFile(MXMegolmExportEncryption.encryptMegolmKeyFile(expectedString, password, 1000), password)
|
||||
} catch (e: Exception) {
|
||||
fail("## checkExportEncrypt3() failed : " + e.message)
|
||||
}
|
||||
|
||||
assertEquals("## checkExportEncrypt3() : expectedString $expectedString -- decodedString $decodedString",
|
||||
expectedString,
|
||||
decodedString)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkExportEncrypt4() {
|
||||
val password = "passwordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpassword" + "passwordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpassword"
|
||||
val expectedString = "alphanumericallyalphanumericallyalphanumericallyalphanumerically"
|
||||
var decodedString: String? = null
|
||||
|
||||
try {
|
||||
decodedString = MXMegolmExportEncryption
|
||||
.decryptMegolmKeyFile(MXMegolmExportEncryption.encryptMegolmKeyFile(expectedString, password, 1000), password)
|
||||
} catch (e: Exception) {
|
||||
fail("## checkExportEncrypt4() failed : " + e.message)
|
||||
}
|
||||
|
||||
assertEquals("## checkExportEncrypt4() : expectedString $expectedString -- decodedString $decodedString",
|
||||
expectedString,
|
||||
decodedString)
|
||||
}
|
||||
}
|
@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.keysbackup
|
||||
|
||||
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.common.assertByteArrayNotEqual
|
||||
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.OlmManager
|
||||
import org.matrix.olm.OlmPkDecryption
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class KeysBackupPasswordTest : InstrumentedTest {
|
||||
|
||||
@Before
|
||||
fun ensureLibLoaded() {
|
||||
OlmManager()
|
||||
}
|
||||
|
||||
/**
|
||||
* Check KeysBackupPassword utilities
|
||||
*/
|
||||
@Test
|
||||
fun passwordConverter_ok() {
|
||||
val generatePrivateKeyResult = generatePrivateKeyWithPassword(PASSWORD, null)
|
||||
|
||||
assertEquals(32, generatePrivateKeyResult.salt.length)
|
||||
assertEquals(500_000, generatePrivateKeyResult.iterations)
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
|
||||
|
||||
// Reverse operation
|
||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(PASSWORD,
|
||||
generatePrivateKeyResult.salt,
|
||||
generatePrivateKeyResult.iterations)
|
||||
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
|
||||
assertArrayEquals(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check generatePrivateKeyWithPassword progress listener behavior
|
||||
*/
|
||||
@Test
|
||||
fun passwordConverter_progress_ok() {
|
||||
val progressValues = ArrayList<Int>(101)
|
||||
var lastTotal = 0
|
||||
|
||||
generatePrivateKeyWithPassword(PASSWORD, object : ProgressListener {
|
||||
override fun onProgress(progress: Int, total: Int) {
|
||||
if (!progressValues.contains(progress)) {
|
||||
progressValues.add(progress)
|
||||
}
|
||||
|
||||
lastTotal = total
|
||||
}
|
||||
})
|
||||
|
||||
assertEquals(100, lastTotal)
|
||||
|
||||
// Ensure all values are here
|
||||
assertEquals(101, progressValues.size)
|
||||
|
||||
for (i in 0..100) {
|
||||
assertTrue(progressValues[i] == i)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check KeysBackupPassword utilities, with bad password
|
||||
*/
|
||||
@Test
|
||||
fun passwordConverter_badPassword_ok() {
|
||||
val generatePrivateKeyResult = generatePrivateKeyWithPassword(PASSWORD, null)
|
||||
|
||||
assertEquals(32, generatePrivateKeyResult.salt.length)
|
||||
assertEquals(500_000, generatePrivateKeyResult.iterations)
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
|
||||
|
||||
// Reverse operation, with bad password
|
||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(BAD_PASSWORD,
|
||||
generatePrivateKeyResult.salt,
|
||||
generatePrivateKeyResult.iterations)
|
||||
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
|
||||
assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check KeysBackupPassword utilities, with bad password
|
||||
*/
|
||||
@Test
|
||||
fun passwordConverter_badIteration_ok() {
|
||||
val generatePrivateKeyResult = generatePrivateKeyWithPassword(PASSWORD, null)
|
||||
|
||||
assertEquals(32, generatePrivateKeyResult.salt.length)
|
||||
assertEquals(500_000, generatePrivateKeyResult.iterations)
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
|
||||
|
||||
// Reverse operation, with bad iteration
|
||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(PASSWORD,
|
||||
generatePrivateKeyResult.salt,
|
||||
500_001)
|
||||
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
|
||||
assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check KeysBackupPassword utilities, with bad salt
|
||||
*/
|
||||
@Test
|
||||
fun passwordConverter_badSalt_ok() {
|
||||
val generatePrivateKeyResult = generatePrivateKeyWithPassword(PASSWORD, null)
|
||||
|
||||
assertEquals(32, generatePrivateKeyResult.salt.length)
|
||||
assertEquals(500_000, generatePrivateKeyResult.iterations)
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
|
||||
|
||||
// Reverse operation, with bad iteration
|
||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(PASSWORD,
|
||||
BAD_SALT,
|
||||
generatePrivateKeyResult.iterations)
|
||||
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
|
||||
assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check [retrievePrivateKeyWithPassword] with data coming from another platform (RiotWeb).
|
||||
*/
|
||||
@Test
|
||||
fun passwordConverter_crossPlatform_ok() {
|
||||
val password = "This is a passphrase!"
|
||||
val salt = "TO0lxhQ9aYgGfMsclVWPIAublg8h9Nlu"
|
||||
val iteration = 500_000
|
||||
|
||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(password, salt, iteration)
|
||||
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
|
||||
|
||||
// Data from RiotWeb
|
||||
val privateKeyBytes = byteArrayOf(
|
||||
116.toByte(), 224.toByte(), 229.toByte(), 224.toByte(), 9.toByte(), 3.toByte(), 178.toByte(), 162.toByte(),
|
||||
120.toByte(), 23.toByte(), 108.toByte(), 218.toByte(), 22.toByte(), 61.toByte(), 241.toByte(), 200.toByte(),
|
||||
235.toByte(), 173.toByte(), 236.toByte(), 100.toByte(), 115.toByte(), 247.toByte(), 33.toByte(), 132.toByte(),
|
||||
195.toByte(), 154.toByte(), 64.toByte(), 158.toByte(), 184.toByte(), 148.toByte(), 20.toByte(), 85.toByte())
|
||||
|
||||
assertArrayEquals(privateKeyBytes, retrievedPrivateKey)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PASSWORD = "password"
|
||||
private const val BAD_PASSWORD = "passw0rd"
|
||||
|
||||
private const val BAD_SALT = "AA0lxhQ9aYgGfMsclVWPIAublg8h9Nlu"
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.keysbackup
|
||||
|
||||
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 org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
/**
|
||||
* This class observe the state change of a KeysBackup object and provide a method to check the several state change
|
||||
* It checks all state transitions and detected forbidden transition
|
||||
*/
|
||||
internal class StateObserver(private val keysBackup: KeysBackupService,
|
||||
private val latch: CountDownLatch? = null,
|
||||
private val expectedStateChange: Int = -1) : KeysBackupStateListener {
|
||||
|
||||
private val allowedStateTransitions = listOf(
|
||||
KeysBackupState.BackingUp to KeysBackupState.ReadyToBackUp,
|
||||
KeysBackupState.BackingUp to KeysBackupState.WrongBackUpVersion,
|
||||
|
||||
KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.Disabled,
|
||||
KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.NotTrusted,
|
||||
KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.ReadyToBackUp,
|
||||
KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.Unknown,
|
||||
KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.WrongBackUpVersion,
|
||||
|
||||
KeysBackupState.Disabled to KeysBackupState.Enabling,
|
||||
|
||||
KeysBackupState.Enabling to KeysBackupState.Disabled,
|
||||
KeysBackupState.Enabling to KeysBackupState.ReadyToBackUp,
|
||||
|
||||
KeysBackupState.NotTrusted to KeysBackupState.CheckingBackUpOnHomeserver,
|
||||
// This transition happens when we trust the device
|
||||
KeysBackupState.NotTrusted to KeysBackupState.ReadyToBackUp,
|
||||
|
||||
KeysBackupState.ReadyToBackUp to KeysBackupState.WillBackUp,
|
||||
|
||||
KeysBackupState.Unknown to KeysBackupState.CheckingBackUpOnHomeserver,
|
||||
|
||||
KeysBackupState.WillBackUp to KeysBackupState.BackingUp,
|
||||
|
||||
KeysBackupState.WrongBackUpVersion to KeysBackupState.CheckingBackUpOnHomeserver,
|
||||
|
||||
// FIXME These transitions are observed during test, and I'm not sure they should occur. Don't have time to investigate now
|
||||
KeysBackupState.ReadyToBackUp to KeysBackupState.BackingUp,
|
||||
KeysBackupState.ReadyToBackUp to KeysBackupState.ReadyToBackUp,
|
||||
KeysBackupState.WillBackUp to KeysBackupState.ReadyToBackUp,
|
||||
KeysBackupState.WillBackUp to KeysBackupState.Unknown
|
||||
)
|
||||
|
||||
private val stateList = ArrayList<KeysBackupState>()
|
||||
private var lastTransitionError: String? = null
|
||||
|
||||
init {
|
||||
keysBackup.addListener(this)
|
||||
}
|
||||
|
||||
// TODO Make expectedStates mandatory to enforce test
|
||||
fun stopAndCheckStates(expectedStates: List<KeysBackupState>?) {
|
||||
keysBackup.removeListener(this)
|
||||
|
||||
expectedStates?.let {
|
||||
assertEquals(it.size, stateList.size)
|
||||
|
||||
for (i in it.indices) {
|
||||
assertEquals("The state $i is not correct. states: " + stateList.joinToString(separator = " "), it[i], stateList[i])
|
||||
}
|
||||
}
|
||||
|
||||
assertNull("states: " + stateList.joinToString(separator = " "), lastTransitionError)
|
||||
}
|
||||
|
||||
override fun onStateChange(newState: KeysBackupState) {
|
||||
stateList.add(newState)
|
||||
|
||||
// Check that state transition is valid
|
||||
if (stateList.size >= 2
|
||||
&& !allowedStateTransitions.contains(stateList[stateList.size - 2] to newState)) {
|
||||
// Forbidden transition detected
|
||||
lastTransitionError = "Forbidden transition detected from " + stateList[stateList.size - 2] + " to " + newState
|
||||
}
|
||||
|
||||
if (expectedStateChange == stateList.size) {
|
||||
latch?.countDown()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,525 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.verification
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.sas.*
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.common.CommonTestHelper
|
||||
import im.vector.matrix.android.common.CryptoTestHelper
|
||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationCancel
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
|
||||
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.*
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
class SASTest : InstrumentedTest {
|
||||
private val mTestHelper = CommonTestHelper(context())
|
||||
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
|
||||
|
||||
@Test
|
||||
fun test_aliceStartThenAliceCancel() {
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession
|
||||
|
||||
val aliceSasMgr = aliceSession.getSasVerificationService()
|
||||
val bobSasMgr = bobSession!!.getSasVerificationService()
|
||||
|
||||
val bobTxCreatedLatch = CountDownLatch(1)
|
||||
val bobListener = object : SasVerificationService.SasVerificationListener {
|
||||
override fun transactionCreated(tx: SasVerificationTransaction) {}
|
||||
|
||||
override fun transactionUpdated(tx: SasVerificationTransaction) {
|
||||
bobTxCreatedLatch.countDown()
|
||||
}
|
||||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
bobSasMgr.addListener(bobListener)
|
||||
|
||||
val txID = aliceSasMgr.beginKeyVerificationSAS(bobSession.myUserId, bobSession.getMyDevice().deviceId)
|
||||
assertNotNull("Alice should have a started transaction", txID)
|
||||
|
||||
val aliceKeyTx = aliceSasMgr.getExistingTransaction(bobSession.myUserId, txID!!)
|
||||
assertNotNull("Alice should have a started transaction", aliceKeyTx)
|
||||
|
||||
mTestHelper.await(bobTxCreatedLatch)
|
||||
bobSasMgr.removeListener(bobListener)
|
||||
|
||||
val bobKeyTx = bobSasMgr.getExistingTransaction(aliceSession.myUserId, txID)
|
||||
|
||||
assertNotNull("Bob should have started verif transaction", bobKeyTx)
|
||||
assertTrue(bobKeyTx is SASVerificationTransaction)
|
||||
assertNotNull("Bob should have starting a SAS transaction", bobKeyTx)
|
||||
assertTrue(aliceKeyTx is SASVerificationTransaction)
|
||||
assertEquals("Alice and Bob have same transaction id", aliceKeyTx!!.transactionId, bobKeyTx!!.transactionId)
|
||||
|
||||
val aliceSasTx = aliceKeyTx as SASVerificationTransaction?
|
||||
val bobSasTx = bobKeyTx as SASVerificationTransaction?
|
||||
|
||||
assertEquals("Alice state should be started", SasVerificationTxState.Started, aliceSasTx!!.state)
|
||||
assertEquals("Bob state should be started by alice", SasVerificationTxState.OnStarted, bobSasTx!!.state)
|
||||
|
||||
// Let's cancel from alice side
|
||||
val cancelLatch = CountDownLatch(1)
|
||||
|
||||
val bobListener2 = object : SasVerificationService.SasVerificationListener {
|
||||
override fun transactionCreated(tx: SasVerificationTransaction) {}
|
||||
|
||||
override fun transactionUpdated(tx: SasVerificationTransaction) {
|
||||
if (tx.transactionId == txID) {
|
||||
if ((tx as SASVerificationTransaction).state === SasVerificationTxState.OnCancelled) {
|
||||
cancelLatch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
bobSasMgr.addListener(bobListener2)
|
||||
|
||||
aliceSasTx.cancel(CancelCode.User)
|
||||
mTestHelper.await(cancelLatch)
|
||||
|
||||
assertEquals("Should be cancelled on alice side",
|
||||
SasVerificationTxState.Cancelled, aliceSasTx.state)
|
||||
assertEquals("Should be cancelled on bob side",
|
||||
SasVerificationTxState.OnCancelled, bobSasTx.state)
|
||||
|
||||
assertEquals("Should be User cancelled on alice side",
|
||||
CancelCode.User, aliceSasTx.cancelledReason)
|
||||
assertEquals("Should be User cancelled on bob side",
|
||||
CancelCode.User, aliceSasTx.cancelledReason)
|
||||
|
||||
assertNull(bobSasMgr.getExistingTransaction(aliceSession.myUserId, txID))
|
||||
assertNull(aliceSasMgr.getExistingTransaction(bobSession.myUserId, txID))
|
||||
|
||||
cryptoTestData.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_key_agreement_protocols_must_include_curve25519() {
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val bobSession = cryptoTestData.secondSession!!
|
||||
|
||||
val protocols = listOf("meh_dont_know")
|
||||
val tid = "00000000"
|
||||
|
||||
// Bob should receive a cancel
|
||||
var canceledToDeviceEvent: Event? = null
|
||||
val cancelLatch = CountDownLatch(1)
|
||||
// TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
|
||||
// TODO override fun onToDeviceEvent(event: Event?) {
|
||||
// TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) {
|
||||
// TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) {
|
||||
// TODO canceledToDeviceEvent = event
|
||||
// TODO cancelLatch.countDown()
|
||||
// TODO }
|
||||
// TODO }
|
||||
// TODO }
|
||||
// TODO })
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceUserID = aliceSession.myUserId
|
||||
val aliceDevice = aliceSession.getMyDevice().deviceId
|
||||
|
||||
val aliceListener = object : SasVerificationService.SasVerificationListener {
|
||||
override fun transactionCreated(tx: SasVerificationTransaction) {}
|
||||
|
||||
override fun transactionUpdated(tx: SasVerificationTransaction) {
|
||||
if ((tx as IncomingSASVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
||||
(tx as IncomingSASVerificationTransaction).performAccept()
|
||||
}
|
||||
}
|
||||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
aliceSession.getSasVerificationService().addListener(aliceListener)
|
||||
|
||||
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, protocols = protocols)
|
||||
|
||||
mTestHelper.await(cancelLatch)
|
||||
|
||||
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
|
||||
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
|
||||
|
||||
cryptoTestData.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_key_agreement_macs_Must_include_hmac_sha256() {
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val bobSession = cryptoTestData.secondSession!!
|
||||
|
||||
val mac = listOf("shaBit")
|
||||
val tid = "00000000"
|
||||
|
||||
// Bob should receive a cancel
|
||||
var canceledToDeviceEvent: Event? = null
|
||||
val cancelLatch = CountDownLatch(1)
|
||||
// TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
|
||||
// TODO override fun onToDeviceEvent(event: Event?) {
|
||||
// TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) {
|
||||
// TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) {
|
||||
// TODO canceledToDeviceEvent = event
|
||||
// TODO cancelLatch.countDown()
|
||||
// TODO }
|
||||
// TODO }
|
||||
// TODO }
|
||||
// TODO })
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceUserID = aliceSession.myUserId
|
||||
val aliceDevice = aliceSession.getMyDevice().deviceId
|
||||
|
||||
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, mac = mac)
|
||||
|
||||
mTestHelper.await(cancelLatch)
|
||||
|
||||
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
|
||||
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
|
||||
|
||||
cryptoTestData.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_key_agreement_short_code_include_decimal() {
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val bobSession = cryptoTestData.secondSession!!
|
||||
|
||||
val codes = listOf("bin", "foo", "bar")
|
||||
val tid = "00000000"
|
||||
|
||||
// Bob should receive a cancel
|
||||
var canceledToDeviceEvent: Event? = null
|
||||
val cancelLatch = CountDownLatch(1)
|
||||
// TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
|
||||
// TODO override fun onToDeviceEvent(event: Event?) {
|
||||
// TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) {
|
||||
// TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) {
|
||||
// TODO canceledToDeviceEvent = event
|
||||
// TODO cancelLatch.countDown()
|
||||
// TODO }
|
||||
// TODO }
|
||||
// TODO }
|
||||
// TODO })
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceUserID = aliceSession.myUserId
|
||||
val aliceDevice = aliceSession.getMyDevice().deviceId
|
||||
|
||||
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, codes = codes)
|
||||
|
||||
mTestHelper.await(cancelLatch)
|
||||
|
||||
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
|
||||
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
|
||||
|
||||
cryptoTestData.close()
|
||||
}
|
||||
|
||||
private fun fakeBobStart(bobSession: Session,
|
||||
aliceUserID: String?,
|
||||
aliceDevice: String?,
|
||||
tid: String,
|
||||
protocols: List<String> = SASVerificationTransaction.KNOWN_AGREEMENT_PROTOCOLS,
|
||||
hashes: List<String> = SASVerificationTransaction.KNOWN_HASHES,
|
||||
mac: List<String> = SASVerificationTransaction.KNOWN_MACS,
|
||||
codes: List<String> = SASVerificationTransaction.KNOWN_SHORT_CODES) {
|
||||
val startMessage = KeyVerificationStart()
|
||||
startMessage.fromDevice = bobSession.getMyDevice().deviceId
|
||||
startMessage.method = KeyVerificationStart.VERIF_METHOD_SAS
|
||||
startMessage.transactionID = tid
|
||||
startMessage.keyAgreementProtocols = protocols
|
||||
startMessage.hashes = hashes
|
||||
startMessage.messageAuthenticationCodes = mac
|
||||
startMessage.shortAuthenticationStrings = codes
|
||||
|
||||
val contentMap = MXUsersDevicesMap<Any>()
|
||||
contentMap.setObject(aliceUserID, aliceDevice, startMessage)
|
||||
|
||||
// TODO val sendLatch = CountDownLatch(1)
|
||||
// TODO bobSession.cryptoRestClient.sendToDevice(
|
||||
// TODO EventType.KEY_VERIFICATION_START,
|
||||
// TODO contentMap,
|
||||
// TODO tid,
|
||||
// TODO TestMatrixCallback<Void>(sendLatch)
|
||||
// TODO )
|
||||
}
|
||||
|
||||
// any two devices may only have at most one key verification in flight at a time.
|
||||
// If a device has two verifications in progress with the same device, then it should cancel both verifications.
|
||||
@Test
|
||||
fun test_aliceStartTwoRequests() {
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession
|
||||
|
||||
val aliceSasMgr = aliceSession.getSasVerificationService()
|
||||
|
||||
val aliceCreatedLatch = CountDownLatch(2)
|
||||
val aliceCancelledLatch = CountDownLatch(2)
|
||||
val createdTx = ArrayList<SASVerificationTransaction>()
|
||||
val aliceListener = object : SasVerificationService.SasVerificationListener {
|
||||
override fun transactionCreated(tx: SasVerificationTransaction) {
|
||||
createdTx.add(tx as SASVerificationTransaction)
|
||||
aliceCreatedLatch.countDown()
|
||||
}
|
||||
|
||||
override fun transactionUpdated(tx: SasVerificationTransaction) {
|
||||
if ((tx as SASVerificationTransaction).state === SasVerificationTxState.OnCancelled) {
|
||||
aliceCancelledLatch.countDown()
|
||||
}
|
||||
}
|
||||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
aliceSasMgr.addListener(aliceListener)
|
||||
|
||||
val bobUserId = bobSession!!.myUserId
|
||||
val bobDeviceId = bobSession.getMyDevice().deviceId
|
||||
aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId)
|
||||
aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId)
|
||||
|
||||
mTestHelper.await(aliceCreatedLatch)
|
||||
mTestHelper.await(aliceCancelledLatch)
|
||||
|
||||
cryptoTestData.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that when alice starts a 'correct' request, bob agrees.
|
||||
*/
|
||||
@Test
|
||||
fun test_aliceAndBobAgreement() {
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession
|
||||
|
||||
val aliceSasMgr = aliceSession.getSasVerificationService()
|
||||
val bobSasMgr = bobSession!!.getSasVerificationService()
|
||||
|
||||
var accepted: KeyVerificationAccept? = null
|
||||
var startReq: KeyVerificationStart? = null
|
||||
|
||||
val aliceAcceptedLatch = CountDownLatch(1)
|
||||
val aliceListener = object : SasVerificationService.SasVerificationListener {
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
|
||||
override fun transactionCreated(tx: SasVerificationTransaction) {}
|
||||
|
||||
override fun transactionUpdated(tx: SasVerificationTransaction) {
|
||||
if ((tx as SASVerificationTransaction).state === SasVerificationTxState.OnAccepted) {
|
||||
val at = tx as SASVerificationTransaction
|
||||
accepted = at.accepted
|
||||
startReq = at.startReq
|
||||
aliceAcceptedLatch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
aliceSasMgr.addListener(aliceListener)
|
||||
|
||||
val bobListener = object : SasVerificationService.SasVerificationListener {
|
||||
override fun transactionCreated(tx: SasVerificationTransaction) {}
|
||||
|
||||
override fun transactionUpdated(tx: SasVerificationTransaction) {
|
||||
if ((tx as IncomingSASVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
||||
val at = tx as IncomingSASVerificationTransaction
|
||||
at.performAccept()
|
||||
}
|
||||
}
|
||||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
bobSasMgr.addListener(bobListener)
|
||||
|
||||
val bobUserId = bobSession.myUserId
|
||||
val bobDeviceId = bobSession.getMyDevice().deviceId
|
||||
aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId)
|
||||
mTestHelper.await(aliceAcceptedLatch)
|
||||
|
||||
assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false)
|
||||
|
||||
// check that agreement is valid
|
||||
assertTrue("Agreed Protocol should be Valid", accepted!!.isValid())
|
||||
assertTrue("Agreed Protocol should be known by alice", startReq!!.keyAgreementProtocols!!.contains(accepted!!.keyAgreementProtocol))
|
||||
assertTrue("Hash should be known by alice", startReq!!.hashes!!.contains(accepted!!.hash))
|
||||
assertTrue("Hash should be known by alice", startReq!!.messageAuthenticationCodes!!.contains(accepted!!.messageAuthenticationCode))
|
||||
|
||||
accepted!!.shortAuthenticationStrings?.forEach {
|
||||
assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings!!.contains(it))
|
||||
}
|
||||
|
||||
cryptoTestData.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_aliceAndBobSASCode() {
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession
|
||||
|
||||
val aliceSasMgr = aliceSession.getSasVerificationService()
|
||||
val bobSasMgr = bobSession!!.getSasVerificationService()
|
||||
|
||||
val aliceSASLatch = CountDownLatch(1)
|
||||
val aliceListener = object : SasVerificationService.SasVerificationListener {
|
||||
override fun transactionCreated(tx: SasVerificationTransaction) {}
|
||||
|
||||
override fun transactionUpdated(tx: SasVerificationTransaction) {
|
||||
val uxState = (tx as OutgoingSASVerificationRequest).uxState
|
||||
when (uxState) {
|
||||
OutgoingSasVerificationRequest.UxState.SHOW_SAS -> {
|
||||
aliceSASLatch.countDown()
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
aliceSasMgr.addListener(aliceListener)
|
||||
|
||||
val bobSASLatch = CountDownLatch(1)
|
||||
val bobListener = object : SasVerificationService.SasVerificationListener {
|
||||
override fun transactionCreated(tx: SasVerificationTransaction) {}
|
||||
|
||||
override fun transactionUpdated(tx: SasVerificationTransaction) {
|
||||
val uxState = (tx as IncomingSASVerificationTransaction).uxState
|
||||
when (uxState) {
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
||||
tx.performAccept()
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
if (uxState === IncomingSasVerificationTransaction.UxState.SHOW_SAS) {
|
||||
bobSASLatch.countDown()
|
||||
}
|
||||
}
|
||||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
bobSasMgr.addListener(bobListener)
|
||||
|
||||
val bobUserId = bobSession.myUserId
|
||||
val bobDeviceId = bobSession.getMyDevice().deviceId
|
||||
val verificationSAS = aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId)
|
||||
mTestHelper.await(aliceSASLatch)
|
||||
mTestHelper.await(bobSASLatch)
|
||||
|
||||
val aliceTx = aliceSasMgr.getExistingTransaction(bobUserId, verificationSAS!!) as SASVerificationTransaction
|
||||
val bobTx = bobSasMgr.getExistingTransaction(aliceSession.myUserId, verificationSAS) as SASVerificationTransaction
|
||||
|
||||
assertEquals("Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL),
|
||||
bobTx.getShortCodeRepresentation(SasMode.DECIMAL))
|
||||
|
||||
cryptoTestData.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_happyPath() {
|
||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession
|
||||
|
||||
val aliceSasMgr = aliceSession.getSasVerificationService()
|
||||
val bobSasMgr = bobSession!!.getSasVerificationService()
|
||||
|
||||
val aliceSASLatch = CountDownLatch(1)
|
||||
val aliceListener = object : SasVerificationService.SasVerificationListener {
|
||||
override fun transactionCreated(tx: SasVerificationTransaction) {}
|
||||
|
||||
override fun transactionUpdated(tx: SasVerificationTransaction) {
|
||||
val uxState = (tx as OutgoingSASVerificationRequest).uxState
|
||||
when (uxState) {
|
||||
OutgoingSasVerificationRequest.UxState.SHOW_SAS -> {
|
||||
tx.userHasVerifiedShortCode()
|
||||
}
|
||||
OutgoingSasVerificationRequest.UxState.VERIFIED -> {
|
||||
aliceSASLatch.countDown()
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
aliceSasMgr.addListener(aliceListener)
|
||||
|
||||
val bobSASLatch = CountDownLatch(1)
|
||||
val bobListener = object : SasVerificationService.SasVerificationListener {
|
||||
override fun transactionCreated(tx: SasVerificationTransaction) {}
|
||||
|
||||
override fun transactionUpdated(tx: SasVerificationTransaction) {
|
||||
val uxState = (tx as IncomingSASVerificationTransaction).uxState
|
||||
when (uxState) {
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
||||
tx.performAccept()
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
tx.userHasVerifiedShortCode()
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||
bobSASLatch.countDown()
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||
}
|
||||
bobSasMgr.addListener(bobListener)
|
||||
|
||||
val bobUserId = bobSession.myUserId
|
||||
val bobDeviceId = bobSession.getMyDevice().deviceId
|
||||
aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId)
|
||||
mTestHelper.await(aliceSASLatch)
|
||||
mTestHelper.await(bobSASLatch)
|
||||
|
||||
// Assert that devices are verified
|
||||
val bobDeviceInfoFromAlicePOV: MXDeviceInfo? = aliceSession.getDeviceInfo(bobUserId, bobDeviceId)
|
||||
val aliceDeviceInfoFromBobPOV: MXDeviceInfo? = bobSession.getDeviceInfo(aliceSession.myUserId, aliceSession.getMyDevice().deviceId)
|
||||
|
||||
// latch wait a bit again
|
||||
Thread.sleep(1000)
|
||||
|
||||
assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified)
|
||||
assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
|
||||
cryptoTestData.close()
|
||||
}
|
||||
}
|
@ -62,8 +62,6 @@ internal class JsonCanonicalizerTest : InstrumentedTest {
|
||||
JsonCanonicalizer.canonicalize("{\"a\":\"\\\"\"}"))
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ==========================================================================================
|
||||
* Test from https://matrix.org/docs/spec/appendices.html#examples
|
||||
* ========================================================================================== */
|
||||
@ -74,7 +72,6 @@ internal class JsonCanonicalizerTest : InstrumentedTest {
|
||||
JsonCanonicalizer.canonicalize("""{}"""))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun matrixOrg002Test() {
|
||||
assertEquals("""{"one":1,"two":"Two"}""",
|
||||
@ -84,7 +81,6 @@ internal class JsonCanonicalizerTest : InstrumentedTest {
|
||||
}"""))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun matrixOrg003Test() {
|
||||
assertEquals("""{"a":"1","b":"2"}""",
|
||||
@ -94,14 +90,12 @@ internal class JsonCanonicalizerTest : InstrumentedTest {
|
||||
}"""))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun matrixOrg004Test() {
|
||||
assertEquals("""{"a":"1","b":"2"}""",
|
||||
JsonCanonicalizer.canonicalize("""{"b":"2","a":"1"}"""))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun matrixOrg005Test() {
|
||||
assertEquals("""{"auth":{"mxid":"@john.doe:example.com","profile":{"display_name":"John Doe","three_pids":[{"address":"john.doe@example.org","medium":"email"},{"address":"123456789","medium":"msisdn"}]},"success":true}}""",
|
||||
@ -126,7 +120,6 @@ internal class JsonCanonicalizerTest : InstrumentedTest {
|
||||
}"""))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun matrixOrg006Test() {
|
||||
assertEquals("""{"a":"日本語"}""",
|
||||
@ -135,7 +128,6 @@ internal class JsonCanonicalizerTest : InstrumentedTest {
|
||||
}"""))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun matrixOrg007Test() {
|
||||
assertEquals("""{"日":1,"本":2}""",
|
||||
@ -145,7 +137,6 @@ internal class JsonCanonicalizerTest : InstrumentedTest {
|
||||
}"""))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun matrixOrg008Test() {
|
||||
assertEquals("""{"a":"日"}""",
|
||||
@ -159,4 +150,4 @@ internal class JsonCanonicalizerTest : InstrumentedTest {
|
||||
"a": null
|
||||
}"""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,8 +19,12 @@ package im.vector.matrix.android.session.room.timeline
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.internal.database.helper.*
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.internal.database.helper.add
|
||||
import im.vector.matrix.android.internal.database.helper.lastStateIndex
|
||||
import im.vector.matrix.android.internal.database.helper.merge
|
||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||
import im.vector.matrix.android.internal.database.model.SessionRealmModule
|
||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeListOfEvents
|
||||
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeMessageEvent
|
||||
@ -28,14 +32,12 @@ import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeR
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import io.realm.kotlin.createObject
|
||||
import org.amshove.kluent.shouldBeFalse
|
||||
import org.amshove.kluent.shouldBeTrue
|
||||
import org.amshove.kluent.shouldEqual
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
internal class ChunkEntityTest : InstrumentedTest {
|
||||
|
||||
@ -44,11 +46,14 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||
@Before
|
||||
fun setup() {
|
||||
Realm.init(context())
|
||||
val testConfig = RealmConfiguration.Builder().inMemory().name("test-realm").build()
|
||||
val testConfig = RealmConfiguration.Builder()
|
||||
.inMemory()
|
||||
.name("test-realm")
|
||||
.modules(SessionRealmModule())
|
||||
.build()
|
||||
monarchy = Monarchy.Builder().setRealmConfiguration(testConfig).build()
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun add_shouldAdd_whenNotAlreadyIncluded() {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
@ -143,30 +148,6 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun merge_shouldEventsBeLinked_whenMergingLinkedWithUnlinked() {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
val chunk1: ChunkEntity = realm.createObject()
|
||||
val chunk2: ChunkEntity = realm.createObject()
|
||||
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
||||
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = false)
|
||||
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
||||
chunk1.isUnlinked().shouldBeFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun merge_shouldEventsBeUnlinked_whenMergingUnlinkedWithUnlinked() {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
val chunk1: ChunkEntity = realm.createObject()
|
||||
val chunk2: ChunkEntity = realm.createObject()
|
||||
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
||||
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
||||
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
||||
chunk1.isUnlinked().shouldBeTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun merge_shouldPrevTokenMerged_whenMergingForwards() {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
@ -174,8 +155,8 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||
val chunk2: ChunkEntity = realm.createObject()
|
||||
val prevToken = "prev_token"
|
||||
chunk1.prevToken = prevToken
|
||||
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
||||
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
||||
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||
chunk1.merge("roomId", chunk2, PaginationDirection.FORWARDS)
|
||||
chunk1.prevToken shouldEqual prevToken
|
||||
}
|
||||
@ -188,11 +169,19 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||
val chunk2: ChunkEntity = realm.createObject()
|
||||
val nextToken = "next_token"
|
||||
chunk1.nextToken = nextToken
|
||||
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
||||
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
||||
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
||||
chunk1.nextToken shouldEqual nextToken
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
private fun ChunkEntity.addAll(roomId: String,
|
||||
events: List<Event>,
|
||||
direction: PaginationDirection,
|
||||
stateIndexOffset: Int = 0) {
|
||||
events.forEach { event ->
|
||||
add(roomId, event, direction, stateIndexOffset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,4 @@ internal class FakeGetContextOfEventTask constructor(private val tokenChunkEvent
|
||||
)
|
||||
return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, PaginationDirection.BACKWARDS)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,4 @@ internal class FakePaginationTask @Inject constructor(private val tokenChunkEven
|
||||
val tokenChunkEvent = FakeTokenChunkEvent(params.from, Random.nextLong(System.currentTimeMillis()).toString(), fakeEvents)
|
||||
return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, params.direction)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -23,4 +23,4 @@ internal data class FakeTokenChunkEvent(override val start: String?,
|
||||
override val end: String?,
|
||||
override val events: List<Event> = emptyList(),
|
||||
override val stateEvents: List<Event> = emptyList()
|
||||
) : TokenChunkEvent
|
||||
) : TokenChunkEvent
|
||||
|
@ -16,21 +16,14 @@
|
||||
|
||||
package im.vector.matrix.android.session.room.timeline
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toContent
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||
import im.vector.matrix.android.internal.database.helper.addAll
|
||||
import im.vector.matrix.android.internal.database.helper.addOrUpdate
|
||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||
import io.realm.kotlin.createObject
|
||||
import kotlin.random.Random
|
||||
|
||||
object RoomDataHelper {
|
||||
@ -70,24 +63,7 @@ object RoomDataHelper {
|
||||
}
|
||||
|
||||
fun createFakeRoomMemberEvent(): Event {
|
||||
val roomMember = RoomMember(Membership.JOIN, "Fake name #${Random.nextLong()}").toContent()
|
||||
val roomMember = RoomMemberSummary(Membership.JOIN, "Fake name #${Random.nextLong()}").toContent()
|
||||
return createFakeEvent(EventType.STATE_ROOM_MEMBER, roomMember)
|
||||
}
|
||||
|
||||
fun fakeInitialSync(monarchy: Monarchy, roomId: String) {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
val roomEntity = realm.createObject<RoomEntity>(roomId)
|
||||
roomEntity.membership = Membership.JOIN
|
||||
val eventList = createFakeListOfEvents(10)
|
||||
val chunkEntity = realm.createObject<ChunkEntity>().apply {
|
||||
nextToken = null
|
||||
prevToken = Random.nextLong(System.currentTimeMillis()).toString()
|
||||
isLastForward = true
|
||||
}
|
||||
chunkEntity.addAll(roomId, eventList, PaginationDirection.FORWARDS)
|
||||
roomEntity.addOrUpdate(chunkEntity)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ internal class TimelineTest : InstrumentedTest {
|
||||
// val latch = CountDownLatch(2)
|
||||
// var timelineEvents: List<TimelineEvent> = emptyList()
|
||||
// timeline.listener = object : Timeline.Listener {
|
||||
// override fun onUpdated(snapshot: List<TimelineEvent>) {
|
||||
// override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
// if (snapshot.isNotEmpty()) {
|
||||
// if (initialLoad == 0) {
|
||||
// initialLoad = snapshot.size
|
||||
@ -81,6 +81,4 @@ internal class TimelineTest : InstrumentedTest {
|
||||
// timelineEvents.size shouldEqual initialLoad + paginationCount
|
||||
// timeline.dispose()
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import okio.Buffer
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
import java.nio.charset.Charset
|
||||
import javax.inject.Inject
|
||||
@ -51,27 +52,33 @@ internal class CurlLoggingInterceptor @Inject constructor(private val logger: Ht
|
||||
var compressed = false
|
||||
|
||||
var curlCmd = "curl"
|
||||
if (curlOptions != null) {
|
||||
curlCmd += " " + curlOptions!!
|
||||
curlOptions?.let {
|
||||
curlCmd += " $it"
|
||||
}
|
||||
curlCmd += " -X " + request.method()
|
||||
curlCmd += " -X " + request.method
|
||||
|
||||
val requestBody = request.body()
|
||||
val requestBody = request.body
|
||||
if (requestBody != null) {
|
||||
val buffer = Buffer()
|
||||
requestBody.writeTo(buffer)
|
||||
var charset: Charset? = UTF8
|
||||
val contentType = requestBody.contentType()
|
||||
if (contentType != null) {
|
||||
charset = contentType.charset(UTF8)
|
||||
if (requestBody.contentLength() > 100_000) {
|
||||
Timber.w("Unable to log curl command data, size is too big (${requestBody.contentLength()})")
|
||||
// Ensure the curl command will failed
|
||||
curlCmd += "DATA IS TOO BIG"
|
||||
} else {
|
||||
val buffer = Buffer()
|
||||
requestBody.writeTo(buffer)
|
||||
var charset: Charset? = UTF8
|
||||
val contentType = requestBody.contentType()
|
||||
if (contentType != null) {
|
||||
charset = contentType.charset(UTF8)
|
||||
}
|
||||
// try to keep to a single line and use a subshell to preserve any line breaks
|
||||
curlCmd += " --data $'" + buffer.readString(charset!!).replace("\n", "\\n") + "'"
|
||||
}
|
||||
// try to keep to a single line and use a subshell to preserve any line breaks
|
||||
curlCmd += " --data $'" + buffer.readString(charset!!).replace("\n", "\\n") + "'"
|
||||
}
|
||||
|
||||
val headers = request.headers()
|
||||
val headers = request.headers
|
||||
var i = 0
|
||||
val count = headers.size()
|
||||
val count = headers.size
|
||||
while (i < count) {
|
||||
val name = headers.name(i)
|
||||
val value = headers.value(i)
|
||||
@ -82,7 +89,7 @@ internal class CurlLoggingInterceptor @Inject constructor(private val logger: Ht
|
||||
i++
|
||||
}
|
||||
|
||||
curlCmd += ((if (compressed) " --compressed " else " ") + "'" + request.url().toString()
|
||||
curlCmd += ((if (compressed) " --compressed " else " ") + "'" + request.url.toString()
|
||||
// Replace localhost for emulator by localhost for shell
|
||||
.replace("://10.0.2.2:8080/".toRegex(), "://127.0.0.1:8080/")
|
||||
+ "'")
|
||||
@ -90,7 +97,7 @@ internal class CurlLoggingInterceptor @Inject constructor(private val logger: Ht
|
||||
// Add Json formatting
|
||||
curlCmd += " | python -m json.tool"
|
||||
|
||||
logger.log("--- cURL (" + request.url() + ")")
|
||||
logger.log("--- cURL (" + request.url + ")")
|
||||
logger.log(curlCmd)
|
||||
|
||||
return chain.proceed(request)
|
||||
|
@ -51,7 +51,6 @@ class FormattedJsonHttpLogger : HttpLoggingInterceptor.Logger {
|
||||
// Finally this is not a JSON string...
|
||||
Timber.e(e)
|
||||
}
|
||||
|
||||
} else if (message.startsWith("[")) {
|
||||
// JSON Array detected
|
||||
try {
|
||||
@ -61,7 +60,6 @@ class FormattedJsonHttpLogger : HttpLoggingInterceptor.Logger {
|
||||
// Finally not JSON...
|
||||
Timber.e(e)
|
||||
}
|
||||
|
||||
}
|
||||
// Else not a json string to log
|
||||
}
|
||||
@ -73,4 +71,4 @@ class FormattedJsonHttpLogger : HttpLoggingInterceptor.Logger {
|
||||
Timber.v(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,12 +22,15 @@ import androidx.work.Configuration
|
||||
import androidx.work.WorkManager
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.BuildConfig
|
||||
import im.vector.matrix.android.api.auth.Authenticator
|
||||
import im.vector.matrix.android.api.auth.AuthenticationService
|
||||
import im.vector.matrix.android.internal.SessionManager
|
||||
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
||||
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
|
||||
import im.vector.matrix.android.internal.di.DaggerMatrixComponent
|
||||
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.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -38,7 +41,6 @@ data class MatrixConfiguration(
|
||||
interface Provider {
|
||||
fun providesMatrixConfiguration(): MatrixConfiguration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -47,7 +49,7 @@ data class MatrixConfiguration(
|
||||
*/
|
||||
class Matrix private constructor(context: Context, matrixConfiguration: MatrixConfiguration) {
|
||||
|
||||
@Inject internal lateinit var authenticator: Authenticator
|
||||
@Inject internal lateinit var authenticationService: AuthenticationService
|
||||
@Inject internal lateinit var userAgentHolder: UserAgentHolder
|
||||
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
|
||||
@Inject internal lateinit var olmManager: OlmManager
|
||||
@ -65,8 +67,8 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
||||
|
||||
fun getUserAgent() = userAgentHolder.userAgent
|
||||
|
||||
fun authenticator(): Authenticator {
|
||||
return authenticator
|
||||
fun authenticationService(): AuthenticationService {
|
||||
return authenticationService
|
||||
}
|
||||
|
||||
companion object {
|
||||
@ -97,6 +99,9 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
||||
fun getSdkVersion(): String {
|
||||
return BuildConfig.VERSION_NAME + " (" + BuildConfig.GIT_SDK_REVISION + ")"
|
||||
}
|
||||
}
|
||||
|
||||
fun decryptStream(inputStream: InputStream?, elementToDecrypt: ElementToDecrypt): InputStream? {
|
||||
return MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ interface MatrixCallback<in T> {
|
||||
* @param data the data successfully returned from the async function
|
||||
*/
|
||||
fun onSuccess(data: T) {
|
||||
//no-op
|
||||
// no-op
|
||||
}
|
||||
|
||||
/**
|
||||
@ -35,7 +35,6 @@ interface MatrixCallback<in T> {
|
||||
* @param failure the failure data returned from the async function
|
||||
*/
|
||||
fun onFailure(failure: Throwable) {
|
||||
//no-op
|
||||
// no-op
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
package im.vector.matrix.android.api
|
||||
|
||||
|
||||
/**
|
||||
* This class contains pattern to match the different Matrix ids
|
||||
*/
|
||||
@ -154,6 +153,5 @@ object MatrixPatterns {
|
||||
return if (index == -1) {
|
||||
null
|
||||
} else matrixId.substring(index + 1)
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.auth
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||
import im.vector.matrix.android.api.auth.data.LoginFlowResult
|
||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||
import im.vector.matrix.android.api.auth.login.LoginWizard
|
||||
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* Return a LoginWizard, to login to the homeserver. The login flow has to be retrieved first.
|
||||
*/
|
||||
fun getLoginWizard(): LoginWizard
|
||||
|
||||
/**
|
||||
* Return a RegistrationWizard, to create an matrix account on the homeserver. The login flow has to be retrieved first.
|
||||
*/
|
||||
fun getRegistrationWizard(): RegistrationWizard
|
||||
|
||||
/**
|
||||
* True when login and password has been sent with success to the homeserver
|
||||
*/
|
||||
val isRegistrationStarted: Boolean
|
||||
|
||||
/**
|
||||
* Cancel pending login or pending registration
|
||||
*/
|
||||
fun cancelPendingLoginOrRegistration()
|
||||
|
||||
/**
|
||||
* Reset all pending settings, including current HomeServerConnectionConfig
|
||||
*/
|
||||
fun reset()
|
||||
|
||||
/**
|
||||
* Check if there is an authenticated [Session].
|
||||
* @return true if there is at least one active session.
|
||||
*/
|
||||
fun hasAuthenticatedSessions(): Boolean
|
||||
|
||||
/**
|
||||
* Get the last authenticated [Session], if there is an active session.
|
||||
* @return the last active session if any, or null
|
||||
*/
|
||||
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
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.auth
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
/**
|
||||
* This interface defines methods to authenticate to a matrix server.
|
||||
*/
|
||||
interface Authenticator {
|
||||
|
||||
/**
|
||||
* @param homeServerConnectionConfig this param is used to configure the Homeserver
|
||||
* @param login the login field
|
||||
* @param password the password field
|
||||
* @param callback the matrix callback on which you'll receive the result of authentication.
|
||||
* @return return a [Cancelable]
|
||||
*/
|
||||
fun authenticate(homeServerConnectionConfig: HomeServerConnectionConfig, login: String, password: String, callback: MatrixCallback<Session>): Cancelable
|
||||
|
||||
/**
|
||||
* Check if there is an authenticated [Session].
|
||||
* @return true if there is at least one active session.
|
||||
*/
|
||||
fun hasAuthenticatedSessions(): Boolean
|
||||
|
||||
/**
|
||||
* Get the last authenticated [Session], if there is an active session.
|
||||
* @return the last active session if any, or null
|
||||
*/
|
||||
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?
|
||||
}
|
@ -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.internal.util.md5
|
||||
|
||||
/**
|
||||
* This data class hold credentials user data.
|
||||
@ -30,4 +31,11 @@ data class Credentials(
|
||||
@Json(name = "home_server") val homeServer: String,
|
||||
@Json(name = "access_token") val accessToken: String,
|
||||
@Json(name = "refresh_token") val refreshToken: String?,
|
||||
@Json(name = "device_id") val deviceId: String?)
|
||||
@Json(name = "device_id") val deviceId: String?,
|
||||
// Optional data that may contain info to override home server and/or identity server
|
||||
@Json(name = "well_known") val wellKnown: WellKnown? = null
|
||||
)
|
||||
|
||||
internal fun Credentials.sessionId(): String {
|
||||
return (if (deviceId.isNullOrBlank()) userId else "$userId|$deviceId").md5()
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ import okhttp3.TlsVersion
|
||||
|
||||
/**
|
||||
* This data class holds how to connect to a specific Homeserver.
|
||||
* It's used with [im.vector.matrix.android.api.auth.Authenticator] class.
|
||||
* It's used with [im.vector.matrix.android.api.auth.AuthenticationService] class.
|
||||
* You should use the [Builder] to create one.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
@ -253,13 +253,5 @@ data class HomeServerConnectionConfig(
|
||||
forceUsageTlsVersions
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.auth.data
|
||||
|
||||
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
|
||||
|
||||
// Either a LoginFlowResponse, or an error if the homeserver is outdated
|
||||
sealed class LoginFlowResult {
|
||||
data class Success(
|
||||
val loginFlowResponse: LoginFlowResponse,
|
||||
val isLoginAndRegistrationSupported: Boolean,
|
||||
val homeServerUrl: String
|
||||
) : LoginFlowResult()
|
||||
|
||||
object OutdatedHomeserver : LoginFlowResult()
|
||||
}
|
@ -22,5 +22,6 @@ package im.vector.matrix.android.api.auth.data
|
||||
*/
|
||||
data class SessionParams(
|
||||
val credentials: Credentials,
|
||||
val homeServerConnectionConfig: HomeServerConnectionConfig
|
||||
val homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
val isTokenValid: Boolean
|
||||
)
|
||||
|
@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2018 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.auth.data
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* Model for https://matrix.org/docs/spec/client_server/latest#get-matrix-client-versions
|
||||
*
|
||||
* Ex:
|
||||
* <pre>
|
||||
* {
|
||||
* "unstable_features": {
|
||||
* "m.lazy_load_members": true
|
||||
* },
|
||||
* "versions": [
|
||||
* "r0.0.1",
|
||||
* "r0.1.0",
|
||||
* "r0.2.0",
|
||||
* "r0.3.0"
|
||||
* ]
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Versions(
|
||||
@Json(name = "versions")
|
||||
val supportedVersions: List<String>? = null,
|
||||
|
||||
@Json(name = "unstable_features")
|
||||
val unstableFeatures: Map<String, Boolean>? = null
|
||||
)
|
||||
|
||||
// MatrixClientServerAPIVersion
|
||||
private const val r0_0_1 = "r0.0.1"
|
||||
private const val r0_1_0 = "r0.1.0"
|
||||
private const val r0_2_0 = "r0.2.0"
|
||||
private const val r0_3_0 = "r0.3.0"
|
||||
private const val r0_4_0 = "r0.4.0"
|
||||
private const val r0_5_0 = "r0.5.0"
|
||||
private const val r0_6_0 = "r0.6.0"
|
||||
|
||||
// MatrixVersionsFeature
|
||||
private const val FEATURE_LAZY_LOAD_MEMBERS = "m.lazy_load_members"
|
||||
private const val FEATURE_REQUIRE_IDENTITY_SERVER = "m.require_identity_server"
|
||||
private const val FEATURE_ID_ACCESS_TOKEN = "m.id_access_token"
|
||||
private const val FEATURE_SEPARATE_ADD_AND_BIND = "m.separate_add_and_bind"
|
||||
|
||||
/**
|
||||
* Return true if the SDK supports this homeserver version
|
||||
*/
|
||||
fun Versions.isSupportedBySdk(): Boolean {
|
||||
return supportLazyLoadMembers()
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the SDK supports this homeserver version for login and registration
|
||||
*/
|
||||
fun Versions.isLoginAndRegistrationSupportedBySdk(): Boolean {
|
||||
return !doesServerRequireIdentityServerParam()
|
||||
&& doesServerAcceptIdentityAccessToken()
|
||||
&& doesServerSeparatesAddAndBind()
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the server support the lazy loading of room members
|
||||
*
|
||||
* @return true if the server support the lazy loading of room members
|
||||
*/
|
||||
private fun Versions.supportLazyLoadMembers(): Boolean {
|
||||
return supportedVersions?.contains(r0_5_0) == true
|
||||
|| unstableFeatures?.get(FEATURE_LAZY_LOAD_MEMBERS) == true
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate if the `id_server` parameter is required when registering with an 3pid,
|
||||
* adding a 3pid or resetting password.
|
||||
*/
|
||||
private fun Versions.doesServerRequireIdentityServerParam(): Boolean {
|
||||
if (supportedVersions?.contains(r0_6_0) == true) return false
|
||||
return unstableFeatures?.get(FEATURE_REQUIRE_IDENTITY_SERVER) ?: true
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate if the `id_access_token` parameter can be safely passed to the homeserver.
|
||||
* Some homeservers may trigger errors if they are not prepared for the new parameter.
|
||||
*/
|
||||
private fun Versions.doesServerAcceptIdentityAccessToken(): Boolean {
|
||||
return supportedVersions?.contains(r0_6_0) == true
|
||||
|| unstableFeatures?.get(FEATURE_ID_ACCESS_TOKEN) ?: false
|
||||
}
|
||||
|
||||
private fun Versions.doesServerSeparatesAddAndBind(): Boolean {
|
||||
return supportedVersions?.contains(r0_6_0) == true
|
||||
|| unstableFeatures?.get(FEATURE_SEPARATE_ADD_AND_BIND) ?: false
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.auth.data
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
|
||||
* <pre>
|
||||
* {
|
||||
* "m.homeserver": {
|
||||
* "base_url": "https://matrix.org"
|
||||
* },
|
||||
* "m.identity_server": {
|
||||
* "base_url": "https://vector.im"
|
||||
* }
|
||||
* "m.integrations": {
|
||||
* "managers": [
|
||||
* {
|
||||
* "api_url": "https://integrations.example.org",
|
||||
* "ui_url": "https://integrations.example.org/ui"
|
||||
* },
|
||||
* {
|
||||
* "api_url": "https://bots.example.org"
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WellKnown(
|
||||
@Json(name = "m.homeserver")
|
||||
var homeServer: WellKnownBaseConfig? = null,
|
||||
|
||||
@Json(name = "m.identity_server")
|
||||
var identityServer: WellKnownBaseConfig? = null,
|
||||
|
||||
@Json(name = "m.integrations")
|
||||
var integrations: Map<String, @JvmSuppressWildcards Any>? = null
|
||||
) {
|
||||
/**
|
||||
* Returns the list of integration managers proposed
|
||||
*/
|
||||
fun getIntegrationManagers(): List<WellKnownManagerConfig> {
|
||||
val managers = ArrayList<WellKnownManagerConfig>()
|
||||
integrations?.get("managers")?.let {
|
||||
(it as? ArrayList<*>)?.let { configs ->
|
||||
configs.forEach { config ->
|
||||
(config as? Map<*, *>)?.let { map ->
|
||||
val apiUrl = map["api_url"] as? String
|
||||
val uiUrl = map["ui_url"] as? String ?: apiUrl
|
||||
if (apiUrl != null
|
||||
&& apiUrl.startsWith("https://")
|
||||
&& uiUrl!!.startsWith("https://")) {
|
||||
managers.add(WellKnownManagerConfig(
|
||||
apiUrl = apiUrl,
|
||||
uiUrl = uiUrl
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return managers
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.auth.data
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
|
||||
* <pre>
|
||||
* {
|
||||
* "base_url": "https://vector.im"
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WellKnownBaseConfig(
|
||||
@Json(name = "base_url")
|
||||
val baseURL: String? = null
|
||||
)
|
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.matrix.android.api.auth.data
|
||||
|
||||
data class WellKnownManagerConfig(
|
||||
val apiUrl : String,
|
||||
val uiUrl: String
|
||||
)
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.auth.login
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
interface LoginWizard {
|
||||
|
||||
/**
|
||||
* @param login the login field
|
||||
* @param password the password field
|
||||
* @param deviceName the initial device name
|
||||
* @param callback the matrix callback on which you'll receive the result of authentication.
|
||||
* @return return a [Cancelable]
|
||||
*/
|
||||
fun login(login: String,
|
||||
password: String,
|
||||
deviceName: String,
|
||||
callback: MatrixCallback<Session>): Cancelable
|
||||
|
||||
/**
|
||||
* Reset user password
|
||||
*/
|
||||
fun resetPassword(email: String,
|
||||
newPassword: String,
|
||||
callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Confirm the new password, once the user has checked his email
|
||||
*/
|
||||
fun resetPasswordMailConfirmed(callback: MatrixCallback<Unit>): Cancelable
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.auth.registration
|
||||
|
||||
sealed class RegisterThreePid {
|
||||
data class Email(val email: String) : RegisterThreePid()
|
||||
data class Msisdn(val msisdn: String, val countryCode: String) : RegisterThreePid()
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.auth.registration
|
||||
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
|
||||
// Either a session or an object containing data about registration stages
|
||||
sealed class RegistrationResult {
|
||||
data class Success(val session: Session) : RegistrationResult()
|
||||
data class FlowResponse(val flowResult: FlowResult) : RegistrationResult()
|
||||
}
|
||||
|
||||
data class FlowResult(
|
||||
val missingStages: List<Stage>,
|
||||
val completedStages: List<Stage>
|
||||
)
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.auth.registration
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
interface RegistrationWizard {
|
||||
|
||||
fun getRegistrationFlow(callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
|
||||
fun createAccount(userName: String, password: String, initialDeviceDisplayName: String?, callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
|
||||
fun performReCaptcha(response: String, callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
|
||||
fun acceptTerms(callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
|
||||
fun dummy(callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
|
||||
fun addThreePid(threePid: RegisterThreePid, callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
|
||||
fun sendAgainThreePid(callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
|
||||
fun handleValidateThreePid(code: String, callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
|
||||
fun checkIfEmailHasBeenValidated(delayMillis: Long, callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
|
||||
val currentThreePid: String?
|
||||
|
||||
// True when login and password has been sent with success to the homeserver
|
||||
val isRegistrationStarted: Boolean
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.auth.registration
|
||||
|
||||
sealed class Stage(open val mandatory: Boolean) {
|
||||
|
||||
// m.login.recaptcha
|
||||
data class ReCaptcha(override val mandatory: Boolean, val publicKey: String) : Stage(mandatory)
|
||||
|
||||
// m.login.oauth2
|
||||
// m.login.email.identity
|
||||
data class Email(override val mandatory: Boolean) : Stage(mandatory)
|
||||
|
||||
// m.login.msisdn
|
||||
data class Msisdn(override val mandatory: Boolean) : Stage(mandatory)
|
||||
|
||||
// m.login.token
|
||||
|
||||
// m.login.dummy, can be mandatory if there is no other stages. In this case the account cannot be created by just sending a username
|
||||
// and a password, the dummy stage has to be done
|
||||
data class Dummy(override val mandatory: Boolean) : Stage(mandatory)
|
||||
|
||||
// Undocumented yet: m.login.terms
|
||||
data class Terms(override val mandatory: Boolean, val policies: TermPolicies) : Stage(mandatory)
|
||||
|
||||
// For unknown stages
|
||||
data class Other(override val mandatory: Boolean, val type: String, val params: Map<*, *>?) : Stage(mandatory)
|
||||
}
|
||||
|
||||
typealias TermPolicies = Map<*, *>
|
@ -17,7 +17,6 @@
|
||||
package im.vector.matrix.android.api.comparators
|
||||
|
||||
import im.vector.matrix.android.api.interfaces.DatedObject
|
||||
import java.util.*
|
||||
|
||||
object DatedObjectComparators {
|
||||
|
||||
@ -38,4 +37,4 @@ object DatedObjectComparators {
|
||||
(datedObject2.date - datedObject1.date).toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.crypto
|
||||
|
||||
import im.vector.matrix.android.api.session.crypto.sas.EmojiRepresentation
|
||||
import im.vector.matrix.android.internal.crypto.verification.getEmojiForCode
|
||||
|
||||
/**
|
||||
* Provide all the emojis used for SAS verification (for debug purpose)
|
||||
*/
|
||||
fun getAllVerificationEmojis(): List<EmojiRepresentation> {
|
||||
return (0..63).map { getEmojiForCode(it) }
|
||||
}
|
@ -19,7 +19,6 @@ package im.vector.matrix.android.api.extensions
|
||||
import im.vector.matrix.android.api.comparators.DatedObjectComparators
|
||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||
import java.util.*
|
||||
|
||||
/* ==========================================================================================
|
||||
* MXDeviceInfo
|
||||
@ -29,7 +28,12 @@ fun MXDeviceInfo.getFingerprintHumanReadable() = fingerprint()
|
||||
?.chunked(4)
|
||||
?.joinToString(separator = " ")
|
||||
|
||||
/* ==========================================================================================
|
||||
* DeviceInfo
|
||||
* ========================================================================================== */
|
||||
|
||||
fun List<DeviceInfo>.sortByLastSeen() {
|
||||
Collections.sort(this, DatedObjectComparators.descComparator)
|
||||
}
|
||||
fun List<DeviceInfo>.sortByLastSeen(): List<DeviceInfo> {
|
||||
val list = toMutableList()
|
||||
list.sortWith(DatedObjectComparators.descComparator)
|
||||
return list
|
||||
}
|
||||
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.failure
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
||||
fun Throwable.is401() =
|
||||
this is Failure.ServerError
|
||||
&& httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED /* 401 */
|
||||
&& error.code == MatrixError.M_UNAUTHORIZED
|
||||
|
||||
fun Throwable.isTokenError() =
|
||||
this is Failure.ServerError
|
||||
&& (error.code == MatrixError.M_UNKNOWN_TOKEN || error.code == MatrixError.M_MISSING_TOKEN)
|
@ -31,15 +31,16 @@ import java.io.IOException
|
||||
*/
|
||||
sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
|
||||
data class Unknown(val throwable: Throwable? = null) : Failure(throwable)
|
||||
data class Cancelled(val throwable: Throwable? = null) : Failure(throwable)
|
||||
data class NetworkConnection(val ioException: IOException? = null) : Failure(ioException)
|
||||
data class ServerError(val error: MatrixError, val httpCode: Int) : Failure(RuntimeException(error.toString()))
|
||||
object SuccessError : Failure(RuntimeException(RuntimeException("SuccessResult is false")))
|
||||
// When server send an error, but it cannot be interpreted as a MatrixError
|
||||
data class OtherServerError(val errorBody: String, val httpCode: Int) : Failure(RuntimeException(errorBody))
|
||||
data class OtherServerError(val errorBody: String, val httpCode: Int) : Failure(RuntimeException("HTTP $httpCode: $errorBody"))
|
||||
|
||||
data class RegistrationFlowError(val registrationFlowResponse: RegistrationFlowResponse) : Failure(RuntimeException(registrationFlowResponse.toString()))
|
||||
|
||||
data class CryptoError(val error: MXCryptoError) : Failure(error)
|
||||
|
||||
abstract class FeatureFailure : Failure()
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.failure
|
||||
|
||||
// This class will be sent to the bus
|
||||
sealed class GlobalError {
|
||||
data class InvalidToken(val softLogout: Boolean) : GlobalError()
|
||||
data class ConsentNotGivenError(val consentUri: String) : GlobalError()
|
||||
}
|
@ -22,46 +22,114 @@ import com.squareup.moshi.JsonClass
|
||||
/**
|
||||
* This data class holds the error defined by the matrix specifications.
|
||||
* You shouldn't have to instantiate it.
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#api-standards
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MatrixError(
|
||||
/** unique string which can be used to handle an error message */
|
||||
@Json(name = "errcode") val code: String,
|
||||
/** human-readable error message */
|
||||
@Json(name = "error") val message: String,
|
||||
|
||||
// For M_CONSENT_NOT_GIVEN
|
||||
@Json(name = "consent_uri") val consentUri: String? = null,
|
||||
// RESOURCE_LIMIT_EXCEEDED data
|
||||
// For M_RESOURCE_LIMIT_EXCEEDED
|
||||
@Json(name = "limit_type") val limitType: String? = null,
|
||||
@Json(name = "admin_contact") val adminUri: String? = null) {
|
||||
|
||||
@Json(name = "admin_contact") val adminUri: String? = null,
|
||||
// For M_LIMIT_EXCEEDED
|
||||
@Json(name = "retry_after_ms") val retryAfterMillis: Long? = null,
|
||||
// For M_UNKNOWN_TOKEN
|
||||
@Json(name = "soft_logout") val isSoftLogout: Boolean = false
|
||||
) {
|
||||
|
||||
companion object {
|
||||
const val FORBIDDEN = "M_FORBIDDEN"
|
||||
const val UNKNOWN = "M_UNKNOWN"
|
||||
const val UNKNOWN_TOKEN = "M_UNKNOWN_TOKEN"
|
||||
const val MISSING_TOKEN = "M_MISSING_TOKEN"
|
||||
const val BAD_JSON = "M_BAD_JSON"
|
||||
const val NOT_JSON = "M_NOT_JSON"
|
||||
const val NOT_FOUND = "M_NOT_FOUND"
|
||||
const val LIMIT_EXCEEDED = "M_LIMIT_EXCEEDED"
|
||||
const val USER_IN_USE = "M_USER_IN_USE"
|
||||
const val ROOM_IN_USE = "M_ROOM_IN_USE"
|
||||
const val BAD_PAGINATION = "M_BAD_PAGINATION"
|
||||
const val UNAUTHORIZED = "M_UNAUTHORIZED"
|
||||
const val OLD_VERSION = "M_OLD_VERSION"
|
||||
const val UNRECOGNIZED = "M_UNRECOGNIZED"
|
||||
/** Forbidden access, e.g. joining a room without permission, failed login. */
|
||||
const val M_FORBIDDEN = "M_FORBIDDEN"
|
||||
/** An unknown error has occurred. */
|
||||
const val M_UNKNOWN = "M_UNKNOWN"
|
||||
/** The access token specified was not recognised. */
|
||||
const val M_UNKNOWN_TOKEN = "M_UNKNOWN_TOKEN"
|
||||
/** No access token was specified for the request. */
|
||||
const val M_MISSING_TOKEN = "M_MISSING_TOKEN"
|
||||
/** Request contained valid JSON, but it was malformed in some way, e.g. missing required keys, invalid values for keys. */
|
||||
const val M_BAD_JSON = "M_BAD_JSON"
|
||||
/** Request did not contain valid JSON. */
|
||||
const val M_NOT_JSON = "M_NOT_JSON"
|
||||
/** No resource was found for this request. */
|
||||
const val M_NOT_FOUND = "M_NOT_FOUND"
|
||||
/** Too many requests have been sent in a short period of time. Wait a while then try again. */
|
||||
const val M_LIMIT_EXCEEDED = "M_LIMIT_EXCEEDED"
|
||||
|
||||
const val LOGIN_EMAIL_URL_NOT_YET = "M_LOGIN_EMAIL_URL_NOT_YET"
|
||||
const val THREEPID_AUTH_FAILED = "M_THREEPID_AUTH_FAILED"
|
||||
// Error code returned by the server when no account matches the given 3pid
|
||||
const val THREEPID_NOT_FOUND = "M_THREEPID_NOT_FOUND"
|
||||
const val THREEPID_IN_USE = "M_THREEPID_IN_USE"
|
||||
const val SERVER_NOT_TRUSTED = "M_SERVER_NOT_TRUSTED"
|
||||
const val TOO_LARGE = "M_TOO_LARGE"
|
||||
/* ==========================================================================================
|
||||
* Other error codes the client might encounter are
|
||||
* ========================================================================================== */
|
||||
|
||||
/** Encountered when trying to register a user ID which has been taken. */
|
||||
const val M_USER_IN_USE = "M_USER_IN_USE"
|
||||
/** Sent when the room alias given to the createRoom API is already in use. */
|
||||
const val M_ROOM_IN_USE = "M_ROOM_IN_USE"
|
||||
/** (Not documented yet) */
|
||||
const val M_BAD_PAGINATION = "M_BAD_PAGINATION"
|
||||
/** The request was not correctly authorized. Usually due to login failures. */
|
||||
const val M_UNAUTHORIZED = "M_UNAUTHORIZED"
|
||||
/** (Not documented yet) */
|
||||
const val M_OLD_VERSION = "M_OLD_VERSION"
|
||||
/** The server did not understand the request. */
|
||||
const val M_UNRECOGNIZED = "M_UNRECOGNIZED"
|
||||
/** (Not documented yet) */
|
||||
const val M_LOGIN_EMAIL_URL_NOT_YET = "M_LOGIN_EMAIL_URL_NOT_YET"
|
||||
/** Authentication could not be performed on the third party identifier. */
|
||||
const val M_THREEPID_AUTH_FAILED = "M_THREEPID_AUTH_FAILED"
|
||||
/** Sent when a threepid given to an API cannot be used because no record matching the threepid was found. */
|
||||
const val M_THREEPID_NOT_FOUND = "M_THREEPID_NOT_FOUND"
|
||||
/** Sent when a threepid given to an API cannot be used because the same threepid is already in use. */
|
||||
const val M_THREEPID_IN_USE = "M_THREEPID_IN_USE"
|
||||
/** The client's request used a third party server, eg. identity server, that this server does not trust. */
|
||||
const val M_SERVER_NOT_TRUSTED = "M_SERVER_NOT_TRUSTED"
|
||||
/** The request or entity was too large. */
|
||||
const val M_TOO_LARGE = "M_TOO_LARGE"
|
||||
/** (Not documented yet) */
|
||||
const val M_CONSENT_NOT_GIVEN = "M_CONSENT_NOT_GIVEN"
|
||||
const val RESOURCE_LIMIT_EXCEEDED = "M_RESOURCE_LIMIT_EXCEEDED"
|
||||
const val WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION"
|
||||
/** The request cannot be completed because the homeserver has reached a resource limit imposed on it. For example,
|
||||
* a homeserver held in a shared hosting environment may reach a resource limit if it starts using too much memory
|
||||
* or disk space. The error MUST have an admin_contact field to provide the user receiving the error a place to reach
|
||||
* out to. Typically, this error will appear on routes which attempt to modify state (eg: sending messages, account
|
||||
* data, etc) and not routes which only read state (eg: /sync, get account data, etc). */
|
||||
const val M_RESOURCE_LIMIT_EXCEEDED = "M_RESOURCE_LIMIT_EXCEEDED"
|
||||
/** The user ID associated with the request has been deactivated. Typically for endpoints that prove authentication, such as /login. */
|
||||
const val M_USER_DEACTIVATED = "M_USER_DEACTIVATED"
|
||||
/** Encountered when trying to register a user ID which is not valid. */
|
||||
const val M_INVALID_USERNAME = "M_INVALID_USERNAME"
|
||||
/** Sent when the initial state given to the createRoom API is invalid. */
|
||||
const val M_INVALID_ROOM_STATE = "M_INVALID_ROOM_STATE"
|
||||
/** The server does not permit this third party identifier. This may happen if the server only permits,
|
||||
* for example, email addresses from a particular domain. */
|
||||
const val M_THREEPID_DENIED = "M_THREEPID_DENIED"
|
||||
/** The client's request to create a room used a room version that the server does not support. */
|
||||
const val M_UNSUPPORTED_ROOM_VERSION = "M_UNSUPPORTED_ROOM_VERSION"
|
||||
/** The client attempted to join a room that has a version the server does not support.
|
||||
* Inspect the room_version property of the error response for the room's version. */
|
||||
const val M_INCOMPATIBLE_ROOM_VERSION = "M_INCOMPATIBLE_ROOM_VERSION"
|
||||
/** The state change requested cannot be performed, such as attempting to unban a user who is not banned. */
|
||||
const val M_BAD_STATE = "M_BAD_STATE"
|
||||
/** The room or resource does not permit guests to access it. */
|
||||
const val M_GUEST_ACCESS_FORBIDDEN = "M_GUEST_ACCESS_FORBIDDEN"
|
||||
/** A Captcha is required to complete the request. */
|
||||
const val M_CAPTCHA_NEEDED = "M_CAPTCHA_NEEDED"
|
||||
/** The Captcha provided did not match what was expected. */
|
||||
const val M_CAPTCHA_INVALID = "M_CAPTCHA_INVALID"
|
||||
/** A required parameter was missing from the request. */
|
||||
const val M_MISSING_PARAM = "M_MISSING_PARAM"
|
||||
/** A parameter that was specified has the wrong value. For example, the server expected an integer and instead received a string. */
|
||||
const val M_INVALID_PARAM = "M_INVALID_PARAM"
|
||||
/** The resource being requested is reserved by an application service, or the application service making the request has not created the resource. */
|
||||
const val M_EXCLUSIVE = "M_EXCLUSIVE"
|
||||
/** The user is unable to reject an invite to join the server notices room. See the Server Notices module for more information. */
|
||||
const val M_CANNOT_LEAVE_SERVER_NOTICE_ROOM = "M_CANNOT_LEAVE_SERVER_NOTICE_ROOM"
|
||||
/** (Not documented yet) */
|
||||
const val M_WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION"
|
||||
|
||||
// Possible value for "limit_type"
|
||||
const val LIMIT_TYPE_MAU = "monthly_active_user"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,4 +25,4 @@ interface ProgressListener {
|
||||
* @param total
|
||||
*/
|
||||
fun onProgress(progress: Int, total: Int)
|
||||
}
|
||||
}
|
||||
|
@ -31,4 +31,4 @@ interface StepProgressListener {
|
||||
* @param step The current step, containing progress data if available. Else you should consider progress as indeterminate
|
||||
*/
|
||||
fun onStepProgress(step: Step)
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,6 @@
|
||||
package im.vector.matrix.android.api.permalinks
|
||||
|
||||
import android.text.Spannable
|
||||
import im.vector.matrix.android.api.MatrixPatterns
|
||||
|
||||
/**
|
||||
* MatrixLinkify take a piece of text and turns all of the
|
||||
@ -30,9 +29,15 @@ object MatrixLinkify {
|
||||
*
|
||||
* @param spannable the text in which the matrix items has to be clickable.
|
||||
*/
|
||||
fun addLinks(spannable: Spannable?, callback: MatrixPermalinkSpan.Callback?): Boolean {
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun addLinks(spannable: Spannable, callback: MatrixPermalinkSpan.Callback?): Boolean {
|
||||
/**
|
||||
* I disable it because it mess up with pills, and even with pills, it does not work correctly:
|
||||
* The url is not correct. Ex: for @user:matrix.org, the url will be @user:matrix.org, instead of a matrix.to
|
||||
*/
|
||||
/*
|
||||
// sanity checks
|
||||
if (spannable.isNullOrEmpty()) {
|
||||
if (spannable.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
val text = spannable.toString()
|
||||
@ -50,6 +55,7 @@ object MatrixLinkify {
|
||||
}
|
||||
}
|
||||
return hasMatch
|
||||
*/
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,4 @@ class MatrixPermalinkSpan(private val url: String,
|
||||
override fun onClick(widget: View) {
|
||||
callback?.onUrlClicked(url)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -24,14 +24,11 @@ import android.net.Uri
|
||||
*/
|
||||
sealed class PermalinkData {
|
||||
|
||||
data class EventLink(val roomIdOrAlias: String, val eventId: String) : PermalinkData()
|
||||
|
||||
data class RoomLink(val roomIdOrAlias: String) : PermalinkData()
|
||||
data class RoomLink(val roomIdOrAlias: String, val isRoomAlias: Boolean, val eventId: String?) : PermalinkData()
|
||||
|
||||
data class UserLink(val userId: String) : PermalinkData()
|
||||
|
||||
data class GroupLink(val groupId: String) : PermalinkData()
|
||||
|
||||
data class FallbackLink(val uri: Uri) : PermalinkData()
|
||||
|
||||
}
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
package im.vector.matrix.android.api.permalinks
|
||||
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
|
||||
/**
|
||||
@ -48,10 +47,9 @@ object PermalinkFactory {
|
||||
* @return the permalink, or null in case of error
|
||||
*/
|
||||
fun createPermalink(id: String): String? {
|
||||
return if (TextUtils.isEmpty(id)) {
|
||||
return if (id.isEmpty()) {
|
||||
null
|
||||
} else MATRIX_TO_URL_BASE + escape(id)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -72,16 +70,14 @@ object PermalinkFactory {
|
||||
* @param url the universal link, Ex: "https://matrix.to/#/@benoit:matrix.org"
|
||||
* @return the id from the url, ex: "@benoit:matrix.org", or null if the url is not a permalink
|
||||
*/
|
||||
fun getLinkedId(url: String?): String? {
|
||||
val isSupported = url != null && url.startsWith(MATRIX_TO_URL_BASE)
|
||||
fun getLinkedId(url: String): String? {
|
||||
val isSupported = url.startsWith(MATRIX_TO_URL_BASE)
|
||||
|
||||
return if (isSupported) {
|
||||
url!!.substring(MATRIX_TO_URL_BASE.length)
|
||||
url.substring(MATRIX_TO_URL_BASE.length)
|
||||
} else null
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Escape '/' in id, because it is used as a separator
|
||||
*
|
||||
@ -89,6 +85,6 @@ object PermalinkFactory {
|
||||
* @return the escaped id
|
||||
*/
|
||||
private fun escape(id: String): String {
|
||||
return id.replace("/".toRegex(), "%2F")
|
||||
return id.replace("/", "%2F")
|
||||
}
|
||||
}
|
||||
|
@ -56,21 +56,25 @@ object PermalinkParser {
|
||||
|
||||
val identifier = params.getOrNull(0)
|
||||
val extraParameter = params.getOrNull(1)
|
||||
if (identifier.isNullOrEmpty()) {
|
||||
return PermalinkData.FallbackLink(uri)
|
||||
}
|
||||
return when {
|
||||
MatrixPatterns.isUserId(identifier) -> PermalinkData.UserLink(userId = identifier)
|
||||
MatrixPatterns.isGroupId(identifier) -> PermalinkData.GroupLink(groupId = identifier)
|
||||
MatrixPatterns.isRoomId(identifier) -> {
|
||||
if (!extraParameter.isNullOrEmpty() && MatrixPatterns.isEventId(extraParameter)) {
|
||||
PermalinkData.EventLink(roomIdOrAlias = identifier, eventId = extraParameter)
|
||||
} else {
|
||||
PermalinkData.RoomLink(roomIdOrAlias = identifier)
|
||||
}
|
||||
identifier.isNullOrEmpty() -> PermalinkData.FallbackLink(uri)
|
||||
MatrixPatterns.isUserId(identifier) -> PermalinkData.UserLink(userId = identifier)
|
||||
MatrixPatterns.isGroupId(identifier) -> PermalinkData.GroupLink(groupId = identifier)
|
||||
MatrixPatterns.isRoomId(identifier) -> {
|
||||
PermalinkData.RoomLink(
|
||||
roomIdOrAlias = identifier,
|
||||
isRoomAlias = false,
|
||||
eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) }
|
||||
)
|
||||
}
|
||||
else -> PermalinkData.FallbackLink(uri)
|
||||
MatrixPatterns.isRoomAlias(identifier) -> {
|
||||
PermalinkData.RoomLink(
|
||||
roomIdOrAlias = identifier,
|
||||
isRoomAlias = true,
|
||||
eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) }
|
||||
)
|
||||
}
|
||||
else -> PermalinkData.FallbackLink(uri)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -18,78 +18,111 @@ package im.vector.matrix.android.api.pushrules
|
||||
import im.vector.matrix.android.api.pushrules.rest.PushRule
|
||||
import timber.log.Timber
|
||||
|
||||
sealed class Action {
|
||||
object Notify : Action()
|
||||
object DoNotNotify : Action()
|
||||
data class Sound(val sound: String = ACTION_OBJECT_VALUE_VALUE_DEFAULT) : Action()
|
||||
data class Highlight(val highlight: Boolean) : Action()
|
||||
}
|
||||
|
||||
class Action(val type: Type) {
|
||||
private const val ACTION_NOTIFY = "notify"
|
||||
private const val ACTION_DONT_NOTIFY = "dont_notify"
|
||||
private const val ACTION_COALESCE = "coalesce"
|
||||
|
||||
enum class Type(val value: String) {
|
||||
NOTIFY("notify"),
|
||||
DONT_NOTIFY("dont_notify"),
|
||||
COALESCE("coalesce"),
|
||||
SET_TWEAK("set_tweak");
|
||||
// Ref: https://matrix.org/docs/spec/client_server/latest#tweaks
|
||||
private const val ACTION_OBJECT_SET_TWEAK_KEY = "set_tweak"
|
||||
|
||||
companion object {
|
||||
private const val ACTION_OBJECT_SET_TWEAK_VALUE_SOUND = "sound"
|
||||
private const val ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT = "highlight"
|
||||
|
||||
fun safeValueOf(value: String): Type? {
|
||||
try {
|
||||
return valueOf(value)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
return null
|
||||
}
|
||||
private const val ACTION_OBJECT_VALUE_KEY = "value"
|
||||
private const val ACTION_OBJECT_VALUE_VALUE_DEFAULT = "default"
|
||||
|
||||
/**
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#actions
|
||||
*
|
||||
* Convert
|
||||
* <pre>
|
||||
* "actions": [
|
||||
* "notify",
|
||||
* {
|
||||
* "set_tweak": "sound",
|
||||
* "value": "default"
|
||||
* },
|
||||
* {
|
||||
* "set_tweak": "highlight"
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
* To
|
||||
* [
|
||||
* Action.Notify,
|
||||
* Action.Sound("default"),
|
||||
* Action.Highlight(true)
|
||||
* ]
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
|
||||
@Suppress("IMPLICIT_CAST_TO_ANY")
|
||||
fun List<Action>.toJson(): List<Any> {
|
||||
return map { action ->
|
||||
when (action) {
|
||||
is Action.Notify -> ACTION_NOTIFY
|
||||
is Action.DoNotNotify -> ACTION_DONT_NOTIFY
|
||||
is Action.Sound -> {
|
||||
mapOf(
|
||||
ACTION_OBJECT_SET_TWEAK_KEY to ACTION_OBJECT_SET_TWEAK_VALUE_SOUND,
|
||||
ACTION_OBJECT_VALUE_KEY to action.sound
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var tweak_action: String? = null
|
||||
var stringValue: String? = null
|
||||
var boolValue: Boolean? = null
|
||||
|
||||
companion object {
|
||||
fun mapFrom(pushRule: PushRule): List<Action>? {
|
||||
val actions = ArrayList<Action>()
|
||||
pushRule.actions.forEach { actionStrOrObj ->
|
||||
if (actionStrOrObj is String) {
|
||||
when (actionStrOrObj) {
|
||||
Action.Type.NOTIFY.value -> Action(Action.Type.NOTIFY)
|
||||
Action.Type.DONT_NOTIFY.value -> Action(Action.Type.DONT_NOTIFY)
|
||||
else -> {
|
||||
Timber.w("Unsupported action type ${actionStrOrObj}")
|
||||
null
|
||||
}
|
||||
}?.let {
|
||||
actions.add(it)
|
||||
}
|
||||
} else if (actionStrOrObj is Map<*, *>) {
|
||||
val tweakAction = actionStrOrObj["set_tweak"] as? String
|
||||
when (tweakAction) {
|
||||
"sound" -> {
|
||||
(actionStrOrObj["value"] as? String)?.let { stringValue ->
|
||||
Action(Action.Type.SET_TWEAK).also {
|
||||
it.tweak_action = "sound"
|
||||
it.stringValue = stringValue
|
||||
actions.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
"highlight" -> {
|
||||
(actionStrOrObj["value"] as? Boolean)?.let { boolValue ->
|
||||
Action(Action.Type.SET_TWEAK).also {
|
||||
it.tweak_action = "highlight"
|
||||
it.boolValue = boolValue
|
||||
actions.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Timber.w("Unsupported action type ${actionStrOrObj}")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Timber.w("Unsupported action type ${actionStrOrObj}")
|
||||
return null
|
||||
}
|
||||
is Action.Highlight -> {
|
||||
mapOf(
|
||||
ACTION_OBJECT_SET_TWEAK_KEY to ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT,
|
||||
ACTION_OBJECT_VALUE_KEY to action.highlight
|
||||
)
|
||||
}
|
||||
return if (actions.isEmpty()) null else actions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun PushRule.getActions(): List<Action> {
|
||||
val result = ArrayList<Action>()
|
||||
|
||||
actions.forEach { actionStrOrObj ->
|
||||
when (actionStrOrObj) {
|
||||
ACTION_NOTIFY -> Action.Notify
|
||||
ACTION_DONT_NOTIFY -> Action.DoNotNotify
|
||||
is Map<*, *> -> {
|
||||
when (actionStrOrObj[ACTION_OBJECT_SET_TWEAK_KEY]) {
|
||||
ACTION_OBJECT_SET_TWEAK_VALUE_SOUND -> {
|
||||
(actionStrOrObj[ACTION_OBJECT_VALUE_KEY] as? String)?.let { stringValue ->
|
||||
Action.Sound(stringValue)
|
||||
}
|
||||
// When the value is not there, default sound (not specified by the spec)
|
||||
?: Action.Sound(ACTION_OBJECT_VALUE_VALUE_DEFAULT)
|
||||
}
|
||||
ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT -> {
|
||||
(actionStrOrObj[ACTION_OBJECT_VALUE_KEY] as? Boolean)?.let { boolValue ->
|
||||
Action.Highlight(boolValue)
|
||||
}
|
||||
// When the value is not there, default is true, says the spec
|
||||
?: Action.Highlight(true)
|
||||
}
|
||||
else -> {
|
||||
Timber.w("Unsupported set_tweak value ${actionStrOrObj[ACTION_OBJECT_SET_TWEAK_KEY]}")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Timber.w("Unsupported action type $actionStrOrObj")
|
||||
null
|
||||
}
|
||||
}?.let {
|
||||
result.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
@ -35,9 +35,7 @@ abstract class Condition(val kind: Kind) {
|
||||
else -> UNRECOGNIZE
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
abstract fun isSatisfied(conditionResolver: ConditionResolver): Boolean
|
||||
@ -45,4 +43,4 @@ abstract class Condition(val kind: Kind) {
|
||||
open fun technicalDescription(): String {
|
||||
return "Kind: $kind"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,4 +25,4 @@ interface ConditionResolver {
|
||||
fun resolveRoomMemberCountCondition(roomMemberCountCondition: RoomMemberCountCondition): Boolean
|
||||
fun resolveSenderNotificationPermissionCondition(senderNotificationPermissionCondition: SenderNotificationPermissionCondition): Boolean
|
||||
fun resolveContainsDisplayNameCondition(containsDisplayNameCondition: ContainsDisplayNameCondition) : Boolean
|
||||
}
|
||||
}
|
||||
|
@ -15,13 +15,11 @@
|
||||
*/
|
||||
package im.vector.matrix.android.api.pushrules
|
||||
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import timber.log.Timber
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class ContainsDisplayNameCondition : Condition(Kind.contains_display_name) {
|
||||
|
||||
@ -34,11 +32,11 @@ class ContainsDisplayNameCondition : Condition(Kind.contains_display_name) {
|
||||
}
|
||||
|
||||
fun isSatisfied(event: Event, displayName: String): Boolean {
|
||||
var message = when (event.type) {
|
||||
val message = when (event.type) {
|
||||
EventType.MESSAGE -> {
|
||||
event.content.toModel<MessageContent>()
|
||||
}
|
||||
//TODO the spec says:
|
||||
// TODO the spec says:
|
||||
// Matches any message whose content is unencrypted and contains the user's current display name
|
||||
// EventType.ENCRYPTED -> {
|
||||
// event.root.getClearContent()?.toModel<MessageContent>()
|
||||
@ -49,7 +47,6 @@ class ContainsDisplayNameCondition : Condition(Kind.contains_display_name) {
|
||||
return caseInsensitiveFind(displayName, message.body)
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Returns whether a string contains an occurrence of another, as a standalone word, regardless of case.
|
||||
@ -60,20 +57,18 @@ class ContainsDisplayNameCondition : Condition(Kind.contains_display_name) {
|
||||
*/
|
||||
fun caseInsensitiveFind(subString: String, longString: String): Boolean {
|
||||
// add sanity checks
|
||||
if (TextUtils.isEmpty(subString) || TextUtils.isEmpty(longString)) {
|
||||
if (subString.isEmpty() || longString.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
|
||||
var res = false
|
||||
|
||||
try {
|
||||
val pattern = Pattern.compile("(\\W|^)" + Pattern.quote(subString) + "(\\W|$)", Pattern.CASE_INSENSITIVE)
|
||||
res = pattern.matcher(longString).find()
|
||||
val regex = Regex("(\\W|^)" + Regex.escape(subString) + "(\\W|$)", RegexOption.IGNORE_CASE)
|
||||
return regex.containsMatchIn(longString)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## caseInsensitiveFind() : failed")
|
||||
}
|
||||
|
||||
return res
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,28 +29,25 @@ class EventMatchCondition(val key: String, val pattern: String) : Condition(Kind
|
||||
return "'$key' Matches '$pattern'"
|
||||
}
|
||||
|
||||
|
||||
fun isSatisfied(event: Event): Boolean {
|
||||
//TODO encrypted events?
|
||||
// TODO encrypted events?
|
||||
val rawJson = MoshiProvider.providesMoshi().adapter(Event::class.java).toJsonValue(event) as? Map<*, *>
|
||||
?: return false
|
||||
val value = extractField(rawJson, key) ?: return false
|
||||
|
||||
//Patterns with no special glob characters should be treated as having asterisks prepended
|
||||
// Patterns with no special glob characters should be treated as having asterisks prepended
|
||||
// and appended when testing the condition.
|
||||
try {
|
||||
val modPattern = if (hasSpecialGlobChar(pattern)) simpleGlobToRegExp(pattern) else simpleGlobToRegExp("*$pattern*")
|
||||
val regex = Regex(modPattern, RegexOption.DOT_MATCHES_ALL)
|
||||
return regex.containsMatchIn(value)
|
||||
} catch (e: Throwable) {
|
||||
//e.g PatternSyntaxException
|
||||
// e.g PatternSyntaxException
|
||||
Timber.e(e, "Failed to evaluate push condition")
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private fun extractField(jsonObject: Map<*, *>, fieldPath: String): String? {
|
||||
val fieldParts = fieldPath.split(".")
|
||||
if (fieldParts.isEmpty()) return null
|
||||
@ -77,9 +74,9 @@ class EventMatchCondition(val key: String, val pattern: String) : Condition(Kind
|
||||
return glob.contains("*") || glob.contains("?")
|
||||
}
|
||||
|
||||
//Very simple glob to regexp converter
|
||||
// Very simple glob to regexp converter
|
||||
private fun simpleGlobToRegExp(glob: String): String {
|
||||
var out = ""//"^"
|
||||
var out = "" // "^"
|
||||
for (i in 0 until glob.length) {
|
||||
val c = glob[i]
|
||||
when (c) {
|
||||
@ -90,8 +87,8 @@ class EventMatchCondition(val key: String, val pattern: String) : Condition(Kind
|
||||
else -> out += c
|
||||
}
|
||||
}
|
||||
out += ""//'$'.toString()
|
||||
out += "" // '$'.toString()
|
||||
return out
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,14 +25,18 @@ interface PushRuleService {
|
||||
/**
|
||||
* Fetch the push rules from the server
|
||||
*/
|
||||
fun fetchPushRules(scope: String = "global")
|
||||
fun fetchPushRules(scope: String = RuleScope.GLOBAL)
|
||||
|
||||
//TODO get push rule set
|
||||
fun getPushRules(scope: String = "global"): List<PushRule>
|
||||
// TODO get push rule set
|
||||
fun getPushRules(scope: String = RuleScope.GLOBAL): List<PushRule>
|
||||
|
||||
//TODO update rule
|
||||
// TODO update rule
|
||||
|
||||
fun updatePushRuleEnableStatus(kind: String, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable
|
||||
fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
fun addPushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
fun removePushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
fun addPushRuleListener(listener: PushRuleListener)
|
||||
|
||||
@ -42,7 +46,9 @@ interface PushRuleService {
|
||||
|
||||
interface PushRuleListener {
|
||||
fun onMatchRule(event: Event, actions: List<Action>)
|
||||
fun onRoomJoined(roomId: String)
|
||||
fun onRoomLeft(roomId: String)
|
||||
fun onEventRedacted(redactedEventId: String)
|
||||
fun batchFinish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,18 +18,17 @@ package im.vector.matrix.android.api.pushrules
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.RoomService
|
||||
import timber.log.Timber
|
||||
import java.util.regex.Pattern
|
||||
|
||||
private val regex = Pattern.compile("^(==|<=|>=|<|>)?(\\d*)$")
|
||||
private val regex = Regex("^(==|<=|>=|<|>)?(\\d*)$")
|
||||
|
||||
class RoomMemberCountCondition(val `is`: String) : Condition(Kind.room_member_count) {
|
||||
class RoomMemberCountCondition(val iz: String) : Condition(Kind.room_member_count) {
|
||||
|
||||
override fun isSatisfied(conditionResolver: ConditionResolver): Boolean {
|
||||
return conditionResolver.resolveRoomMemberCountCondition(this)
|
||||
}
|
||||
|
||||
override fun technicalDescription(): String {
|
||||
return "Room member count is $`is`"
|
||||
return "Room member count is $iz"
|
||||
}
|
||||
|
||||
fun isSatisfied(event: Event, session: RoomService?): Boolean {
|
||||
@ -56,16 +55,12 @@ class RoomMemberCountCondition(val `is`: String) : Condition(Kind.room_member_co
|
||||
*/
|
||||
private fun parseIsField(): Pair<String?, Int>? {
|
||||
try {
|
||||
val match = regex.matcher(`is`)
|
||||
if (match.find()) {
|
||||
val prefix = match.group(1)
|
||||
val count = match.group(2).toInt()
|
||||
return prefix to count
|
||||
}
|
||||
val match = regex.find(iz) ?: return null
|
||||
val (prefix, count) = match.destructured
|
||||
return prefix to count.toInt()
|
||||
} catch (t: Throwable) {
|
||||
Timber.d(t)
|
||||
}
|
||||
return null
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ object RuleIds {
|
||||
|
||||
// Default Underride Rules
|
||||
const val RULE_ID_CALL = ".m.rule.call"
|
||||
const val RULE_ID_one_to_one_encrypted_room = ".m.rule.encrypted_room_one_to_one"
|
||||
const val RULE_ID_ONE_TO_ONE_ENCRYPTED_ROOM = ".m.rule.encrypted_room_one_to_one"
|
||||
const val RULE_ID_ONE_TO_ONE_ROOM = ".m.rule.room_one_to_one"
|
||||
const val RULE_ID_ALL_OTHER_MESSAGES_ROOMS = ".m.rule.message"
|
||||
const val RULE_ID_ENCRYPTED = ".m.rule.encrypted"
|
||||
|
@ -15,12 +15,6 @@
|
||||
*/
|
||||
package im.vector.matrix.android.api.pushrules
|
||||
|
||||
|
||||
enum class RulesetKey(val value: String) {
|
||||
CONTENT("content"),
|
||||
OVERRIDE("override"),
|
||||
ROOM("room"),
|
||||
SENDER("sender"),
|
||||
UNDERRIDE("underride"),
|
||||
UNKNOWN("")
|
||||
}
|
||||
object RuleScope {
|
||||
const val GLOBAL = "global"
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.pushrules
|
||||
|
||||
/**
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules
|
||||
*/
|
||||
enum class RuleSetKey(val value: String) {
|
||||
CONTENT("content"),
|
||||
OVERRIDE("override"),
|
||||
ROOM("room"),
|
||||
SENDER("sender"),
|
||||
UNDERRIDE("underride")
|
||||
}
|
||||
|
||||
/**
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules-scope-kind-ruleid
|
||||
*/
|
||||
typealias RuleKind = RuleSetKey
|
@ -16,8 +16,8 @@
|
||||
package im.vector.matrix.android.api.pushrules
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.model.PowerLevels
|
||||
|
||||
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
|
||||
import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsHelper
|
||||
|
||||
class SenderNotificationPermissionCondition(val key: String) : Condition(Kind.sender_notification_permission) {
|
||||
|
||||
@ -29,8 +29,8 @@ class SenderNotificationPermissionCondition(val key: String) : Condition(Kind.se
|
||||
return "User power level <$key>"
|
||||
}
|
||||
|
||||
|
||||
fun isSatisfied(event: Event, powerLevels: PowerLevels): Boolean {
|
||||
return event.senderId != null && powerLevels.getUserPowerLevel(event.senderId) >= powerLevels.notificationLevel(key)
|
||||
fun isSatisfied(event: Event, powerLevels: PowerLevelsContent): Boolean {
|
||||
val powerLevelsHelper = PowerLevelsHelper(powerLevels)
|
||||
return event.senderId != null && powerLevelsHelper.getUserPowerLevel(event.senderId) >= powerLevelsHelper.notificationLevel(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -71,9 +71,9 @@ data class PushCondition(
|
||||
this.key?.let { SenderNotificationPermissionCondition(it) }
|
||||
}
|
||||
Condition.Kind.UNRECOGNIZE -> {
|
||||
Timber.e("Unknwon kind $kind")
|
||||
Timber.e("Unknown kind $kind")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ package im.vector.matrix.android.api.pushrules.rest
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PushRule(
|
||||
/**
|
||||
@ -47,4 +46,3 @@ data class PushRule(
|
||||
*/
|
||||
val pattern: String? = null
|
||||
)
|
||||
|
||||
|
@ -24,4 +24,4 @@ data class Ruleset(
|
||||
val room: List<PushRule>? = null,
|
||||
val sender: List<PushRule>? = null,
|
||||
val underride: List<PushRule>? = null
|
||||
)
|
||||
)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user