mirror of
https://github.com/signalapp/Signal-Server.git
synced 2025-12-11 01:40:22 +00:00
Compare commits
1465 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86f9322036 | ||
|
|
12c6af23ee | ||
|
|
69330f47fd | ||
|
|
4f40c128bf | ||
|
|
30b5ad1515 | ||
|
|
665a26d164 | ||
|
|
a5774bf6ff | ||
|
|
d2716fe5cf | ||
|
|
279f877bf2 | ||
|
|
d51e6a43e7 | ||
|
|
6a96756c87 | ||
|
|
df69d9f195 | ||
|
|
26ffa19f36 | ||
|
|
29ef3f0b41 | ||
|
|
106d5e54c7 | ||
|
|
6ac2460eb0 | ||
|
|
79c030b138 | ||
|
|
c8d649e8c2 | ||
|
|
1fdf82dd6c | ||
|
|
4aa4246695 | ||
|
|
1bebceb29c | ||
|
|
a2139ee236 | ||
|
|
8c55f39cdf | ||
|
|
0329184c94 | ||
|
|
cd64390141 | ||
|
|
3e12a8780d | ||
|
|
11e6ff1bbe | ||
|
|
36f85fc97e | ||
|
|
9040cfd200 | ||
|
|
757da3b15a | ||
|
|
d162590a32 | ||
|
|
f41e1716c6 | ||
|
|
4dce0f1b9d | ||
|
|
fef57dce0d | ||
|
|
d884700b61 | ||
|
|
ff9ad4bd1d | ||
|
|
9ce2b7555c | ||
|
|
f90ccd3391 | ||
|
|
5ff092e541 | ||
|
|
dcdf401f64 | ||
|
|
e4fb80b39b | ||
|
|
9745854ab8 | ||
|
|
7124621f66 | ||
|
|
47fd8f5793 | ||
|
|
40d698f2db | ||
|
|
74abe98706 | ||
|
|
86787f3bc8 | ||
|
|
699b0c775a | ||
|
|
ff59ef8094 | ||
|
|
089af7cc1f | ||
|
|
1591a2d9a3 | ||
|
|
f7984ed642 | ||
|
|
be634c6043 | ||
|
|
d1f68eacd9 | ||
|
|
4f45f23094 | ||
|
|
c5dc01ee11 | ||
|
|
587c385936 | ||
|
|
3a641a58b0 | ||
|
|
e944306a28 | ||
|
|
3b44ed6d16 | ||
|
|
0965ab8063 | ||
|
|
fcae100df1 | ||
|
|
24a7762873 | ||
|
|
e441ab60a2 | ||
|
|
50c2bc5edb | ||
|
|
2ab14ca59e | ||
|
|
4475d65780 | ||
|
|
b1d10f5817 | ||
|
|
36664f37de | ||
|
|
c838df90ef | ||
|
|
fb39af67e5 | ||
|
|
2d4d37f96a | ||
|
|
84af984c4b | ||
|
|
26adf20ee8 | ||
|
|
72668ed0a2 | ||
|
|
50f1ed7851 | ||
|
|
cf8f2a3463 | ||
|
|
b483159b3a | ||
|
|
480abebf7e | ||
|
|
b924dea045 | ||
|
|
2c1e7e5ed6 | ||
|
|
4dfd74906c | ||
|
|
fdae6ad94f | ||
|
|
c80225a18c | ||
|
|
0e6242373e | ||
|
|
4305db5579 | ||
|
|
36e7772f74 | ||
|
|
ca05df5172 | ||
|
|
422e8e6f3e | ||
|
|
852b285d84 | ||
|
|
6c13193623 | ||
|
|
61809107c8 | ||
|
|
6bda9d8604 | ||
|
|
1428ca73de | ||
|
|
498ace0488 | ||
|
|
f082b95efb | ||
|
|
4b8fc2950f | ||
|
|
595cc55578 | ||
|
|
91b0c368b4 | ||
|
|
21d0ffc990 | ||
|
|
55b9d84956 | ||
|
|
ffdb0db6c6 | ||
|
|
a5ed07a666 | ||
|
|
da02c90bad | ||
|
|
3820a231ec | ||
|
|
408b065b9e | ||
|
|
238ab84749 | ||
|
|
6894015986 | ||
|
|
f5080f9bd6 | ||
|
|
db4aa99ce0 | ||
|
|
70134507f8 | ||
|
|
360a4793ae | ||
|
|
47bfb25f2c | ||
|
|
b048b0bf65 | ||
|
|
394f9929ad | ||
|
|
bf39be3320 | ||
|
|
4a2cbb9ec7 | ||
|
|
cc6cf8194f | ||
|
|
e934ead85c | ||
|
|
323bfd9a6e | ||
|
|
bf05e47e26 | ||
|
|
d18f576239 | ||
|
|
7d483c711a | ||
|
|
61256d49cd | ||
|
|
184cdc0331 | ||
|
|
ed972a0037 | ||
|
|
a62a6c1cb6 | ||
|
|
ba0c6be3e3 | ||
|
|
f66566aa17 | ||
|
|
b6ecfc7131 | ||
|
|
460dc6224c | ||
|
|
2b688b1a60 | ||
|
|
3c64d9292f | ||
|
|
0f52d2e464 | ||
|
|
1e5fadc440 | ||
|
|
f495ff483a | ||
|
|
4e3b1509a8 | ||
|
|
d1a80cc880 | ||
|
|
e1ad25cee0 | ||
|
|
195f23c347 | ||
|
|
ad6b99be6a | ||
|
|
b9dd9fc47d | ||
|
|
19a8a80a30 | ||
|
|
637792c6d4 | ||
|
|
4d1bca2d97 | ||
|
|
f33a2eba50 | ||
|
|
5d6bea5ec9 | ||
|
|
057d1f07a8 | ||
|
|
25c3f55672 | ||
|
|
c9d4091c1e | ||
|
|
1d55562dc3 | ||
|
|
95bb9a9780 | ||
|
|
06c391cbf6 | ||
|
|
d90dff95b1 | ||
|
|
c93972a322 | ||
|
|
ca47a7b663 | ||
|
|
9d3d4a3698 | ||
|
|
3b509bf820 | ||
|
|
5b7f91827a | ||
|
|
a44491714c | ||
|
|
06800043a9 | ||
|
|
3090de56b8 | ||
|
|
a37acd1f42 | ||
|
|
372e3f83d2 | ||
|
|
de260a2bef | ||
|
|
e9a130f976 | ||
|
|
43f17414ff | ||
|
|
b259eea8ce | ||
|
|
9cfc2ba09a | ||
|
|
bb347999ce | ||
|
|
3548c3df15 | ||
|
|
1167d0ac2e | ||
|
|
f738bc97e7 | ||
|
|
3f9edfe597 | ||
|
|
a024949311 | ||
|
|
609c901867 | ||
|
|
4ce060a963 | ||
|
|
c4ca0fee40 | ||
|
|
8d4acf0330 | ||
|
|
28a981f29f | ||
|
|
c29113d17a | ||
|
|
951f978447 | ||
|
|
07899f35bd | ||
|
|
3cbbf37468 | ||
|
|
6c7a3df5ae | ||
|
|
2054ab2771 | ||
|
|
44145073f1 | ||
|
|
feb933b4df | ||
|
|
c7cc3002d5 | ||
|
|
049b901d63 | ||
|
|
3cf1b92dfc | ||
|
|
5b0fcbe854 | ||
|
|
cca747a1f6 | ||
|
|
417d99a17e | ||
|
|
e9708b9259 | ||
|
|
e5d3be16b0 | ||
|
|
2ab3c97ee8 | ||
|
|
f20d3043d6 | ||
|
|
4efda89358 | ||
|
|
1fb88271e5 | ||
|
|
a843780f68 | ||
|
|
5ad83da4e0 | ||
|
|
949cc9e214 | ||
|
|
50d92265ea | ||
|
|
e084a9f2b6 | ||
|
|
664f9f36e1 | ||
|
|
4c9efdb936 | ||
|
|
45848e7bfe | ||
|
|
4fa10e5783 | ||
|
|
fc0bc85f4d | ||
|
|
5ae2e5281a | ||
|
|
34a943832a | ||
|
|
db17693ba7 | ||
|
|
6cdf8ebd2c | ||
|
|
072b470f46 | ||
|
|
78b2df2ecc | ||
|
|
51a825f25c | ||
|
|
00e72a30c9 | ||
|
|
69990c23a5 | ||
|
|
df421e0182 | ||
|
|
ede9297139 | ||
|
|
85383fe581 | ||
|
|
4cca7aa4bd | ||
|
|
e2037dea6c | ||
|
|
f10f772e94 | ||
|
|
9ecfe15ac4 | ||
|
|
5f0726af8a | ||
|
|
331bbdd4e6 | ||
|
|
37e3bcfc3e | ||
|
|
4f42c10d60 | ||
|
|
20392a567b | ||
|
|
c03249b411 | ||
|
|
22e6584402 | ||
|
|
c18aca9215 | ||
|
|
aa23a5422a | ||
|
|
01fde4f9ca | ||
|
|
3980dec123 | ||
|
|
c97f837f45 | ||
|
|
9c54d2407b | ||
|
|
a027c4ce1f | ||
|
|
b1fd025ea6 | ||
|
|
a05a230085 | ||
|
|
8fbc1dac74 | ||
|
|
f46842c6c9 | ||
|
|
8b95bb0c03 | ||
|
|
202dd8e92d | ||
|
|
1da3f96d10 | ||
|
|
5f6fe4d670 | ||
|
|
a74438d1ee | ||
|
|
c8033f875d | ||
|
|
07c04006df | ||
|
|
521900c048 | ||
|
|
9069c5abb6 | ||
|
|
ff7a5f471b | ||
|
|
42a47406cc | ||
|
|
de10b6de7b | ||
|
|
d6ade0e1ac | ||
|
|
e04b5e5c9f | ||
|
|
15a6c46d47 | ||
|
|
cb1fc734c2 | ||
|
|
db7f18aae7 | ||
|
|
7fbc327591 | ||
|
|
84b56ae1b2 | ||
|
|
041aa8639a | ||
|
|
216ac72ad0 | ||
|
|
c85ddaeb9c | ||
|
|
e09dec330a | ||
|
|
8f7bae54fe | ||
|
|
ce60f13320 | ||
|
|
1ac0140666 | ||
|
|
6cc8b147a9 | ||
|
|
e078161e2f | ||
|
|
7764185c57 | ||
|
|
d4ef2adf0a | ||
|
|
a83378a44e | ||
|
|
a4a4204762 | ||
|
|
acd1140ef6 | ||
|
|
fbf71c93ff | ||
|
|
38bc0c466a | ||
|
|
71e4351743 | ||
|
|
387e4b94b4 | ||
|
|
201c76b861 | ||
|
|
1c3aa87ca6 | ||
|
|
db63ff6b88 | ||
|
|
115431a486 | ||
|
|
d47ff9b7c7 | ||
|
|
b0818148cf | ||
|
|
2bc4412d66 | ||
|
|
6a428b4da9 | ||
|
|
7299067829 | ||
|
|
5659cb2820 | ||
|
|
570aa4b9e2 | ||
|
|
c4079a3b11 | ||
|
|
6b38b538f1 | ||
|
|
ba139dddd8 | ||
|
|
38b581a231 | ||
|
|
3c2675b41a | ||
|
|
0f5c62ade5 | ||
|
|
54bc3bce96 | ||
|
|
3d92e5b8a9 | ||
|
|
325d145ac3 | ||
|
|
b0654a416a | ||
|
|
19930ec2e4 | ||
|
|
e4de6bf4a7 | ||
|
|
21125c2f5a | ||
|
|
6f166425fe | ||
|
|
cf2353bcf9 | ||
|
|
744eb58071 | ||
|
|
9d47a6f41f | ||
|
|
4f4c23b12f | ||
|
|
fb02815c27 | ||
|
|
fd19299ae0 | ||
|
|
9c053e20da | ||
|
|
19d7b5c65d | ||
|
|
7b9d8829da | ||
|
|
3505ac498c | ||
|
|
f0ab52eb5d | ||
|
|
e8cebad27e | ||
|
|
6441d5838d | ||
|
|
ac0c8b1e9a | ||
|
|
8ec062fbef | ||
|
|
5990a100db | ||
|
|
bc35278684 | ||
|
|
c3c7329ebb | ||
|
|
6fd1c84126 | ||
|
|
0100f0fcc9 | ||
|
|
0cdc32cf65 | ||
|
|
601e9eebbd | ||
|
|
eaa868cf06 | ||
|
|
f55504c665 | ||
|
|
b2ff016cc1 | ||
|
|
33b4f17945 | ||
|
|
e310a3560b | ||
|
|
162b27323e | ||
|
|
ae976ef8d6 | ||
|
|
c6b4e2b71d | ||
|
|
33c8bbd0ce | ||
|
|
f2a3b8dba4 | ||
|
|
207ae6129b | ||
|
|
e1aa734c40 | ||
|
|
9b1b03bbfa | ||
|
|
bb7e0528c4 | ||
|
|
010eadcd10 | ||
|
|
c43e0b54f2 | ||
|
|
6522b74e20 | ||
|
|
8c7975d89a | ||
|
|
407070c9fc | ||
|
|
7821a3cd61 | ||
|
|
a00c2fcfdb | ||
|
|
9cd21d1326 | ||
|
|
aaba95f9b8 | ||
|
|
8d1135a2a3 | ||
|
|
f9fabbedce | ||
|
|
16012e6ffe | ||
|
|
d10a132b0c | ||
|
|
0b3af7d824 | ||
|
|
d0fdae3df7 | ||
|
|
a263611746 | ||
|
|
0e989419c6 | ||
|
|
0fa8276d2d | ||
|
|
b594986241 | ||
|
|
9f3ffa3707 | ||
|
|
8e598c19dc | ||
|
|
2601d6e906 | ||
|
|
de41088051 | ||
|
|
f2752b2a02 | ||
|
|
f0544fab89 | ||
|
|
1b9bf01ab1 | ||
|
|
9945367fa1 | ||
|
|
cbc3887226 | ||
|
|
c11b74e9c0 | ||
|
|
2b764c2abd | ||
|
|
845fc338d7 | ||
|
|
977243ebfd | ||
|
|
29ca544c95 | ||
|
|
94b41d3a2c | ||
|
|
92bb783cbb | ||
|
|
8348263fab | ||
|
|
48f633de11 | ||
|
|
b3b9a629f3 | ||
|
|
5934b7344a | ||
|
|
a9a2e40fed | ||
|
|
656326355a | ||
|
|
b89e2e5355 | ||
|
|
2d187abf13 | ||
|
|
b701412295 | ||
|
|
b4dad81220 | ||
|
|
6bccdad998 | ||
|
|
ecd6b0174a | ||
|
|
a1e534a515 | ||
|
|
ebbe19ba63 | ||
|
|
6a37b73463 | ||
|
|
dd18fcaea2 | ||
|
|
5afc058f90 | ||
|
|
5e221fa9a3 | ||
|
|
0e0cb4d422 | ||
|
|
09f6d60ae9 | ||
|
|
9577d552c6 | ||
|
|
093f17dce2 | ||
|
|
6089f49b9c | ||
|
|
cfb910e87e | ||
|
|
376cffc61d | ||
|
|
d338ba5152 | ||
|
|
f181397664 | ||
|
|
708f23a2ee | ||
|
|
2d1a979eba | ||
|
|
ee0be92967 | ||
|
|
7536b75508 | ||
|
|
7237ae6c54 | ||
|
|
ca05753a3e | ||
|
|
9ca8503eac | ||
|
|
754f71ce00 | ||
|
|
619b05e56c | ||
|
|
8b13826949 | ||
|
|
a96ee57c7e | ||
|
|
ff1ef90a6d | ||
|
|
22905fa8ee | ||
|
|
9e218ddd1c | ||
|
|
6f0462622b | ||
|
|
2f17161163 | ||
|
|
17d48b95ac | ||
|
|
eeea97e2fe | ||
|
|
360e101660 | ||
|
|
3501a944a3 | ||
|
|
76305190a2 | ||
|
|
ab83990170 | ||
|
|
8103a22026 | ||
|
|
1f8e4713ef | ||
|
|
ff9fe2c1be | ||
|
|
7f37c8ee5e | ||
|
|
ed0a723fef | ||
|
|
5c31ef43c9 | ||
|
|
43fd8518c0 | ||
|
|
19a08f01e8 | ||
|
|
33498cf147 | ||
|
|
beeb85cf8d | ||
|
|
ccd860207b | ||
|
|
2c835b5c51 | ||
|
|
5caa951c61 | ||
|
|
4d8c4d6693 | ||
|
|
a9d0574ea8 | ||
|
|
3954494eae | ||
|
|
ed6a2c55eb | ||
|
|
b6ee074149 | ||
|
|
f6b3500e92 | ||
|
|
a71dc48b9b | ||
|
|
bc5eed48c3 | ||
|
|
2ecf3cb303 | ||
|
|
bed33d042a | ||
|
|
d7975626be | ||
|
|
3ac7aba6b2 | ||
|
|
1dde612855 | ||
|
|
4ec97cf006 | ||
|
|
d51c6fd2f8 | ||
|
|
d868e3075c | ||
|
|
ae61ee5486 | ||
|
|
58fd9ddb27 | ||
|
|
a953cb33b7 | ||
|
|
95b90e7c5a | ||
|
|
6a3ecb2881 | ||
|
|
6cf4241283 | ||
|
|
42141e51a1 | ||
|
|
b01945ff50 | ||
|
|
a131f2116f | ||
|
|
625637b888 | ||
|
|
c873f62025 | ||
|
|
43d91e5bd6 | ||
|
|
5c4c729703 | ||
|
|
308da3343d | ||
|
|
48c7572dd5 | ||
|
|
dc5f35460b | ||
|
|
69ea9b0296 | ||
|
|
969c6884c0 | ||
|
|
fcf311aab3 | ||
|
|
888879dfb2 | ||
|
|
e003197f77 | ||
|
|
f57910cd97 | ||
|
|
d85e25dba0 | ||
|
|
89a4034fc6 | ||
|
|
f53743d287 | ||
|
|
2d132128e1 | ||
|
|
6e5ffbe7b5 | ||
|
|
a81c9681a0 | ||
|
|
baf98accd0 | ||
|
|
901c950ee6 | ||
|
|
50ac7f9dc2 | ||
|
|
c2ea4a5290 | ||
|
|
b691b8d37d | ||
|
|
4ead8527c8 | ||
|
|
6f4801fd6f | ||
|
|
10689843b0 | ||
|
|
60cc0c482e | ||
|
|
e1a5105c28 | ||
|
|
ed8a1ed579 | ||
|
|
c3fd2e2284 | ||
|
|
872ef5d0a0 | ||
|
|
b44599cd59 | ||
|
|
7a5dcc700e | ||
|
|
705fb93e45 | ||
|
|
9df923d916 | ||
|
|
dc1cb9093a | ||
|
|
e32043ae79 | ||
|
|
881c921d56 | ||
|
|
abb32bd919 | ||
|
|
4a6c7152cf | ||
|
|
cf92007f66 | ||
|
|
f5c57e5741 | ||
|
|
5627209fdd | ||
|
|
0188d314ce | ||
|
|
67343f6bdc | ||
|
|
ade2e9c6cf | ||
|
|
352e1b2249 | ||
|
|
b8d8d349f4 | ||
|
|
e87468fbe0 | ||
|
|
e38a713ccc | ||
|
|
82ed783a2d | ||
|
|
d17c7aaba6 | ||
|
|
8c93368b20 | ||
|
|
41f61c66a3 | ||
|
|
1b7a20619e | ||
|
|
7d19e58953 | ||
|
|
1605676509 | ||
|
|
a0d6146ff5 | ||
|
|
f709b00be3 | ||
|
|
5847300290 | ||
|
|
9aaac0eefd | ||
|
|
c5ae9913fe | ||
|
|
fc2ad20c63 | ||
|
|
6db97f5541 | ||
|
|
adf6c751ee | ||
|
|
c315b34395 | ||
|
|
f592201e4c | ||
|
|
8bf5ee45ed | ||
|
|
25f759dd07 | ||
|
|
e5f4c17148 | ||
|
|
098b177bd3 | ||
|
|
ef1a8fc50f | ||
|
|
76f2e93a2c | ||
|
|
25ea1df299 | ||
|
|
5ced86af1d | ||
|
|
62e02a49df | ||
|
|
540550d72a | ||
|
|
8cb83fb6e4 | ||
|
|
56db925f0e | ||
|
|
2c0fc8fe3e | ||
|
|
08c7baafac | ||
|
|
8edb450d73 | ||
|
|
fedeef4da5 | ||
|
|
b593d49399 | ||
|
|
4a91fc3c3d | ||
|
|
bb9605d7c3 | ||
|
|
1049326a70 | ||
|
|
457ecf145f | ||
|
|
463dd9d7d8 | ||
|
|
bdcd055aaf | ||
|
|
30ae2037e8 | ||
|
|
ce4fdbfb3c | ||
|
|
2d154eb0cf | ||
|
|
a3e82dfae8 | ||
|
|
97a7469432 | ||
|
|
1a1defb055 | ||
|
|
93c78b6e40 | ||
|
|
b852d6681d | ||
|
|
8e48ac4ede | ||
|
|
859f646c55 | ||
|
|
fb39b2edaf | ||
|
|
d7bf815bd5 | ||
|
|
c93af9e31e | ||
|
|
b81a0e99d4 | ||
|
|
f8fefe2e5e | ||
|
|
f26bc70b59 | ||
|
|
b5fd131aba | ||
|
|
06997e19e0 | ||
|
|
97710540c0 | ||
|
|
c78c109577 | ||
|
|
8d995e456e | ||
|
|
cc3cab9c88 | ||
|
|
0122b410be | ||
|
|
2ddd2b9476 | ||
|
|
a768498250 | ||
|
|
a45aadae16 | ||
|
|
25802432c2 | ||
|
|
98578b18aa | ||
|
|
6d81f69785 | ||
|
|
7dce183170 | ||
|
|
f1962a03ef | ||
|
|
cb26bfd807 | ||
|
|
befd336372 | ||
|
|
8501e61eb1 | ||
|
|
ae489e5a52 | ||
|
|
13afdbda97 | ||
|
|
9cfd88a23f | ||
|
|
13456bad3a | ||
|
|
45be85c5ef | ||
|
|
861dc0d021 | ||
|
|
128d709c99 | ||
|
|
e8f01be8ef | ||
|
|
7f1ee015d1 | ||
|
|
17aa5d8e74 | ||
|
|
b27334b0ff | ||
|
|
7fc6b1e802 | ||
|
|
25b7c8f802 | ||
|
|
8ec6a24a2d | ||
|
|
234707169e | ||
|
|
1c8443210a | ||
|
|
aaf43a592f | ||
|
|
2b08742c0a | ||
|
|
cac04146de | ||
|
|
2b266c7beb | ||
|
|
099932ae68 | ||
|
|
8579babde6 | ||
|
|
9c93d379a8 | ||
|
|
085c7a67c8 | ||
|
|
e6917d8427 | ||
|
|
47cc7fd615 | ||
|
|
ecd207f0a1 | ||
|
|
0ab66f2f14 | ||
|
|
d1e38737ce | ||
|
|
f17de58a71 | ||
|
|
dd552e8e8f | ||
|
|
18480e9d18 | ||
|
|
7ffccd9c3a | ||
|
|
0edd99e9cf | ||
|
|
defdc14d5e | ||
|
|
5dcf8edd38 | ||
|
|
a320766bb6 | ||
|
|
91805caa9a | ||
|
|
48d39dccbd | ||
|
|
fc9e1f59a5 | ||
|
|
e7bc8bd6b9 | ||
|
|
23337d7992 | ||
|
|
f513dc0398 | ||
|
|
184969336e | ||
|
|
1534f1aa6a | ||
|
|
cd8f74e60b | ||
|
|
d832eaa759 | ||
|
|
796863341d | ||
|
|
217b68a1e0 | ||
|
|
4a8ad3103c | ||
|
|
a5f853c67a | ||
|
|
70b54e227e | ||
|
|
1ab6bff54e | ||
|
|
c2317e8493 | ||
|
|
b034a088b1 | ||
|
|
ae7cb8036e | ||
|
|
1a5327aece | ||
|
|
8ce2b04fe4 | ||
|
|
a3c37aed47 | ||
|
|
fa8f19fd43 | ||
|
|
c9a9409b9a | ||
|
|
d3e0ba6d44 | ||
|
|
300ac16cf1 | ||
|
|
3e53884979 | ||
|
|
859fbe9ab1 | ||
|
|
6043c1a4e8 | ||
|
|
0d9fd043a4 | ||
|
|
f06eaf13d1 | ||
|
|
66a619a378 | ||
|
|
fb1b1e1c04 | ||
|
|
9450f88c8c | ||
|
|
0706171264 | ||
|
|
287e2fa89a | ||
|
|
8d1c26d07d | ||
|
|
caae27c44c | ||
|
|
34d77e73ff | ||
|
|
0889741f34 | ||
|
|
8c42199baf | ||
|
|
7395b5760a | ||
|
|
c8f97ed065 | ||
|
|
d2baa8b8fb | ||
|
|
1beee5fd04 | ||
|
|
281b91a59a | ||
|
|
2be2b4ff23 | ||
|
|
a83fd1d3fe | ||
|
|
cb72e4f426 | ||
|
|
3214852a41 | ||
|
|
1057bd7e1f | ||
|
|
33903553ab | ||
|
|
c309afc04b | ||
|
|
f6c4ba898b | ||
|
|
7ba86b40aa | ||
|
|
b2b0aee4b7 | ||
|
|
919cc7e5eb | ||
|
|
e38911b2c5 | ||
|
|
bc68b67cdf | ||
|
|
42a9f1b3e4 | ||
|
|
08333d5989 | ||
|
|
0e0c0c5dfe | ||
|
|
59ebe65643 | ||
|
|
4fd2422e4d | ||
|
|
6181d439f6 | ||
|
|
57b6c10dd1 | ||
|
|
3ee5ac4514 | ||
|
|
be176f98ad | ||
|
|
12b58a31a1 | ||
|
|
8d468d17e3 | ||
|
|
30df4c3d29 | ||
|
|
5122a1c466 | ||
|
|
e135d50d82 | ||
|
|
487b5edc75 | ||
|
|
47ad5779ad | ||
|
|
4fb89360ce | ||
|
|
6dfdbeb7bb | ||
|
|
d0ccbd5526 | ||
|
|
031ee57371 | ||
|
|
2043678739 | ||
|
|
dd27e3b0c8 | ||
|
|
1083d8bde0 | ||
|
|
d1eb247d8c | ||
|
|
fd5e9ea016 | ||
|
|
11829d1f9f | ||
|
|
ae70d1113c | ||
|
|
c485d317fb | ||
|
|
350682b83a | ||
|
|
0fe6485038 | ||
|
|
a553093046 | ||
|
|
af0d5adcdc | ||
|
|
61af1ba029 | ||
|
|
8847cb92ac | ||
|
|
5242514874 | ||
|
|
33a6577b6e | ||
|
|
23d5006f70 | ||
|
|
2697872bdd | ||
|
|
7b331edcde | ||
|
|
e4da59c236 | ||
|
|
48ebafa4e0 | ||
|
|
f5726f63bd | ||
|
|
391b070cff | ||
|
|
781cd0ca3f | ||
|
|
84355963f9 | ||
|
|
3ccfeb490b | ||
|
|
0cc84131de | ||
|
|
4fa08fb189 | ||
|
|
2a551d1d41 | ||
|
|
391aa9c518 | ||
|
|
39d9fd0317 | ||
|
|
18b1fcd724 | ||
|
|
f5c62a3d85 | ||
|
|
6075d5137b | ||
|
|
890293e429 | ||
|
|
05b43a878b | ||
|
|
fe9c3982a1 | ||
|
|
82baa892f7 | ||
|
|
ee53260d72 | ||
|
|
a8eb27940d | ||
|
|
fd8918eaff | ||
|
|
a3a7d7108b | ||
|
|
cd27fe0409 | ||
|
|
35606a9afd | ||
|
|
2052e62c01 | ||
|
|
8ccab5c1e0 | ||
|
|
292f69256e | ||
|
|
fbdcb942e8 | ||
|
|
c14ef7e6cf | ||
|
|
a04fe133b6 | ||
|
|
483e444174 | ||
|
|
ebf8aa7b15 | ||
|
|
7c52be2ac1 | ||
|
|
203a49975c | ||
|
|
7d45838a1e | ||
|
|
2683f1c6e7 | ||
|
|
d13413aff2 | ||
|
|
4c85e7ba66 | ||
|
|
46fef4082c | ||
|
|
c06313dd2e | ||
|
|
59bc2c5535 | ||
|
|
437bc1358b | ||
|
|
99e651e902 | ||
|
|
757ce42a35 | ||
|
|
179f3df847 | ||
|
|
8a889516b0 | ||
|
|
7de5c0a27d | ||
|
|
71d234e1e4 | ||
|
|
b5fb33e21e | ||
|
|
2be22c2a8e | ||
|
|
db198237f3 | ||
|
|
d0ccae129a | ||
|
|
ecbef9c6ee | ||
|
|
ef2cc6620e | ||
|
|
b8f363b187 | ||
|
|
c3f4956ead | ||
|
|
047f4a1c00 | ||
|
|
41c0fe9ffa | ||
|
|
6edb0d49e9 | ||
|
|
a5e3b81a50 | ||
|
|
b9b4e3fdd8 | ||
|
|
6ee9c6ad46 | ||
|
|
6d6556eee5 | ||
|
|
7529c35013 | ||
|
|
378b32d44d | ||
|
|
e1fcd3e3f6 | ||
|
|
d7ad8dd448 | ||
|
|
859f2302a9 | ||
|
|
a6d11789e9 | ||
|
|
43f83076fa | ||
|
|
71c0fc8d4a | ||
|
|
d2f723de12 | ||
|
|
1f4f926ce6 | ||
|
|
35286f838e | ||
|
|
e1ea3795bb | ||
|
|
95237a22a9 | ||
|
|
11c93c5f53 | ||
|
|
b59b8621c5 | ||
|
|
44c61d9a58 | ||
|
|
63a17bc14b | ||
|
|
f4f93bb24d | ||
|
|
7561622bc8 | ||
|
|
b041566aba | ||
|
|
cb72158abc | ||
|
|
5c432d094f | ||
|
|
24ac48b3b1 | ||
|
|
c03060fe3c | ||
|
|
3ebd5141ae | ||
|
|
c16006dc4b | ||
|
|
8fc465b3e8 | ||
|
|
ce689bdff3 | ||
|
|
e23386ddc7 | ||
|
|
0f17d63774 | ||
|
|
4fc3949367 | ||
|
|
e19c04377b | ||
|
|
7c3f429c56 | ||
|
|
7558489ad0 | ||
|
|
4a3880b5ae | ||
|
|
ca7a4abd30 | ||
|
|
a4a45de161 | ||
|
|
358a286523 | ||
|
|
3bbab0027b | ||
|
|
8afe917a6c | ||
|
|
f5fec5e6bb | ||
|
|
0b81743683 | ||
|
|
9f715c3224 | ||
|
|
24f515ccb4 | ||
|
|
fd531242c9 | ||
|
|
3855bd257d | ||
|
|
c98b54ff15 | ||
|
|
d93d50d038 | ||
|
|
448365c7a0 | ||
|
|
515a863195 | ||
|
|
8d0e23bde1 | ||
|
|
dc8f62a4ad | ||
|
|
896e65545e | ||
|
|
cd4a4b1dcf | ||
|
|
38a0737afb | ||
|
|
4a2768b81d | ||
|
|
00e08b8402 | ||
|
|
48e8584e13 | ||
|
|
a89e30fe75 | ||
|
|
a01fcdad28 | ||
|
|
2a99529921 | ||
|
|
c934405a3e | ||
|
|
b8d922fcb7 | ||
|
|
eb499833c6 | ||
|
|
dd98f7f043 | ||
|
|
e8978ef91c | ||
|
|
669ff1cadf | ||
|
|
4ce85fdb19 | ||
|
|
035ddc4834 | ||
|
|
c2f40b8503 | ||
|
|
cf738a1c14 | ||
|
|
52d40c2321 | ||
|
|
cbf12d6b46 | ||
|
|
ab26a65b6a | ||
|
|
ee5aaf5383 | ||
|
|
1c1714b2c2 | ||
|
|
accb017ec5 | ||
|
|
304782d583 | ||
|
|
f361f436d8 | ||
|
|
a34b5a6122 | ||
|
|
f75ea18ccb | ||
|
|
9a06c40a28 | ||
|
|
e6ab97dc5a | ||
|
|
ba73f757e2 | ||
|
|
30f131096d | ||
|
|
b8ce922f92 | ||
|
|
11b62345e1 | ||
|
|
77289ecb51 | ||
|
|
dfb0b68997 | ||
|
|
d545f60fc4 | ||
|
|
5cda6e9d84 | ||
|
|
7caba89210 | ||
|
|
b8967b75c6 | ||
|
|
74d9849472 | ||
|
|
96b753cfd0 | ||
|
|
5a89e66fc0 | ||
|
|
b4a143b9de | ||
|
|
050035dd52 | ||
|
|
7018062606 | ||
|
|
9e1485de0a | ||
|
|
4e358b891f | ||
|
|
4044a9df30 | ||
|
|
5a7b675001 | ||
|
|
3be4e4bc57 | ||
|
|
5de51919bb | ||
|
|
b02b00818b | ||
|
|
010f88a2ad | ||
|
|
60edf4835f | ||
|
|
a60450d931 | ||
|
|
d138fa45df | ||
|
|
2c2c497c12 | ||
|
|
cb5d3840d9 | ||
|
|
9aceaa7a4d | ||
|
|
636c8ba384 | ||
|
|
ac78eb1425 | ||
|
|
65ad3fe623 | ||
|
|
dcec90fc52 | ||
|
|
24ac32e6e6 | ||
|
|
26f5ffdde3 | ||
|
|
a883426402 | ||
|
|
2f21e930e2 | ||
|
|
5fb158635c | ||
|
|
6f844f9ebb | ||
|
|
d88e358016 | ||
|
|
9cf2635528 | ||
|
|
d0e7579f13 | ||
|
|
cda82b0ea0 | ||
|
|
2ecbb18fe5 | ||
|
|
d40d2389a9 | ||
|
|
df8fb5cab7 | ||
|
|
99ad211c01 | ||
|
|
fb4ed20ff5 | ||
|
|
cb50b44d8f | ||
|
|
ae57853ec4 | ||
|
|
2881c0fd7e | ||
|
|
483fb0968b | ||
|
|
4d37418c15 | ||
|
|
e8ee4b50ff | ||
|
|
4f8aa2eee2 | ||
|
|
397d3cb45a | ||
|
|
e883d727fb | ||
|
|
986545a140 | ||
|
|
836307b0c7 | ||
|
|
b5a75d3079 | ||
|
|
c32067759c | ||
|
|
7fb7abb593 | ||
|
|
0d50b58c60 | ||
|
|
bdf4e24266 | ||
|
|
f41bdf1acb | ||
|
|
77d691df59 | ||
|
|
12300761ab | ||
|
|
25efcbda81 | ||
|
|
a01f96e0e4 | ||
|
|
1d1e3ba79d | ||
|
|
2c9c50711f | ||
|
|
d3f0ab8c6d | ||
|
|
80a3a8a43c | ||
|
|
e6e6eb323d | ||
|
|
681a5bafb4 | ||
|
|
5bec89ecc8 | ||
|
|
69ed0edb74 | ||
|
|
ad5925908e | ||
|
|
d186245c5c | ||
|
|
bbbab4b8a4 | ||
|
|
f83080eb8d | ||
|
|
e0178fa0ea | ||
|
|
c6a79ca176 | ||
|
|
6426e6cc49 | ||
|
|
b13cb098ce | ||
|
|
afda5ca98f | ||
|
|
eb57d87513 | ||
|
|
fbf6b9826e | ||
|
|
a01b29a6bd | ||
|
|
102992b095 | ||
|
|
bd69905f2e | ||
|
|
ce5a4bd94a | ||
|
|
f65a613815 | ||
|
|
d87c8468bd | ||
|
|
aa829af43b | ||
|
|
c10fda8363 | ||
|
|
4252284405 | ||
|
|
74d65b37a8 | ||
|
|
78f95e4859 | ||
|
|
91626dea45 | ||
|
|
5868d9969a | ||
|
|
90490c9c84 | ||
|
|
8ea794baef | ||
|
|
70a6c3e8e5 | ||
|
|
4813803c49 | ||
|
|
fe60cf003f | ||
|
|
0c357bc340 | ||
|
|
b711288faa | ||
|
|
44a5d86641 | ||
|
|
e7048aa9cf | ||
|
|
0120a85c39 | ||
|
|
a41d047f58 | ||
|
|
cccccb4dd6 | ||
|
|
0a64e31625 | ||
|
|
3c6c6c3706 | ||
|
|
8088b58b3b | ||
|
|
a7d5d51fb4 | ||
|
|
378d7987a8 | ||
|
|
3e0baf82a4 | ||
|
|
7a2683a06b | ||
|
|
17a3c90286 | ||
|
|
6341770768 | ||
|
|
308437ec93 | ||
|
|
d3d4916d6c | ||
|
|
d2fa00f0c6 | ||
|
|
d6c9652a70 | ||
|
|
0d20b73e76 | ||
|
|
3c655cdd5a | ||
|
|
fc5cd3a9ca | ||
|
|
83ab926f96 | ||
|
|
56e54e0724 | ||
|
|
544e4fb89a | ||
|
|
966c3a8f47 | ||
|
|
c2ab72c77e | ||
|
|
4468ee3142 | ||
|
|
c82c2c0ba4 | ||
|
|
6e595a0959 | ||
|
|
a79d709039 | ||
|
|
538a07542e | ||
|
|
07ed765250 | ||
|
|
2e497b5834 | ||
|
|
61b3cecd17 | ||
|
|
a4a666bb80 | ||
|
|
c14621a09f | ||
|
|
d0a8899daf | ||
|
|
65dbcb3e5f | ||
|
|
7f725b67c4 | ||
|
|
e25252dc69 | ||
|
|
8b65c11e1e | ||
|
|
320c5eac53 | ||
|
|
8199e0d2d5 | ||
|
|
53387f5a0c | ||
|
|
7d171a79d7 | ||
|
|
3b99bb9e78 | ||
|
|
132f026c75 | ||
|
|
abd0f9630c | ||
|
|
a4508ec84f | ||
|
|
6119b6ab89 | ||
|
|
307ac47ce0 | ||
|
|
4032ddd4fd | ||
|
|
98c8dc05f1 | ||
|
|
4c677ec2da | ||
|
|
c05692e417 | ||
|
|
1e7aa89664 | ||
|
|
ae1edf3c5c | ||
|
|
b17f41c3e8 | ||
|
|
08db4ba54b | ||
|
|
cb6cc39679 | ||
|
|
b6bf6c994c | ||
|
|
3bb4709563 | ||
|
|
b280c768a4 | ||
|
|
d23e89fb9c | ||
|
|
3a27bd0318 | ||
|
|
616513edaf | ||
|
|
09a51020e9 | ||
|
|
cb8cb94d1a | ||
|
|
2440dc0089 | ||
|
|
2336eef333 | ||
|
|
a0e948627c | ||
|
|
88159af588 | ||
|
|
38b77bb550 | ||
|
|
e72d1d0b6f | ||
|
|
1891622e69 | ||
|
|
628a112b38 | ||
|
|
50f5d760c9 | ||
|
|
7292a88ea3 | ||
|
|
07cb3ab576 | ||
|
|
27b749abbd | ||
|
|
27f67a077c | ||
|
|
393e15815b | ||
|
|
a7f1cd25b9 | ||
|
|
953cd2ae0c | ||
|
|
a84a7dbc3d | ||
|
|
24d01f1ab2 | ||
|
|
06eb890761 | ||
|
|
6d0345d327 | ||
|
|
1c67233eb0 | ||
|
|
b4281c5a70 | ||
|
|
5f6b66dad6 | ||
|
|
c2be0af9d9 | ||
|
|
c111e9a35a | ||
|
|
a53a85d788 | ||
|
|
a44c18e9b7 | ||
|
|
4d78437fe4 | ||
|
|
2bfe2c8ff8 | ||
|
|
65da844d70 | ||
|
|
5275c27ee1 | ||
|
|
390580a19d | ||
|
|
147917454f | ||
|
|
39562775d9 | ||
|
|
4a0ef1f834 | ||
|
|
85b16b674d | ||
|
|
ab5d8ba120 | ||
|
|
28076335e0 | ||
|
|
9e9333424f | ||
|
|
6f0faae4ce | ||
|
|
0d24828539 | ||
|
|
0a6d724f2c | ||
|
|
8956e1e0cf | ||
|
|
c9ae991aa3 | ||
|
|
421d594507 | ||
|
|
9c03f2e468 | ||
|
|
1175ff5867 | ||
|
|
55df593561 | ||
|
|
a06a663b94 | ||
|
|
3d2f7e731f | ||
|
|
2575372639 | ||
|
|
faa6e8324a | ||
|
|
d0e3fb1901 | ||
|
|
04287c5073 | ||
|
|
0c76fdd36c | ||
|
|
d582942244 | ||
|
|
3636626e09 | ||
|
|
3e0919106d | ||
|
|
d385838dc1 | ||
|
|
e28f1e8ceb | ||
|
|
3d875f1ce5 | ||
|
|
c4c5397b44 | ||
|
|
6b6f9b2405 | ||
|
|
7d4a8d03a4 | ||
|
|
e9119da040 | ||
|
|
aa36dc95ef | ||
|
|
a6f9409a39 | ||
|
|
41a113e22c | ||
|
|
4cfcdb0c96 | ||
|
|
36050f580e | ||
|
|
98760b631b | ||
|
|
d00aa1e77a | ||
|
|
dce391a248 | ||
|
|
c252118cfc | ||
|
|
e9fd32de79 | ||
|
|
788246a56f | ||
|
|
bc02fe3831 | ||
|
|
d290aad27b | ||
|
|
6754ec5e10 | ||
|
|
1ba00a66eb | ||
|
|
1dd7d33e23 | ||
|
|
e200548e35 | ||
|
|
fdf7b69996 | ||
|
|
92d36b725f | ||
|
|
4e131858ca | ||
|
|
a45d95905e | ||
|
|
0fdfdabf2a | ||
|
|
a25e967978 | ||
|
|
38e30c7513 | ||
|
|
e38e5fa17d | ||
|
|
c1f9bedf2f | ||
|
|
dd5d0ea2b3 | ||
|
|
42fd29d38b | ||
|
|
bf6d3aa324 | ||
|
|
023ccc6563 | ||
|
|
da49db5b9e | ||
|
|
cc8dda28cc | ||
|
|
47300c1d44 | ||
|
|
d31550d444 | ||
|
|
51f37350eb | ||
|
|
ecfa161da8 | ||
|
|
e3778c17ea | ||
|
|
cbc95415b7 | ||
|
|
776c0aa488 | ||
|
|
327eb0219d | ||
|
|
8507b6a1f0 | ||
|
|
a853748303 | ||
|
|
192e884e4a | ||
|
|
7001ad1445 | ||
|
|
5cfb133f79 | ||
|
|
5df24edebf | ||
|
|
95d0293a96 | ||
|
|
f5a2efb57c | ||
|
|
e4b9ae4eee | ||
|
|
bc1ac5a37f | ||
|
|
96ac56faac | ||
|
|
f0bc444388 | ||
|
|
8584f47d95 | ||
|
|
f6235b8c08 | ||
|
|
d452e90470 | ||
|
|
418a869451 | ||
|
|
cf89e2215c | ||
|
|
a4ca1ef1a8 | ||
|
|
c38572307d | ||
|
|
20902df122 | ||
|
|
d31ddb72f3 | ||
|
|
d5f2d86bd2 | ||
|
|
2ce8bcd565 | ||
|
|
75c92eaa93 | ||
|
|
0445adcac3 | ||
|
|
c45ff61954 | ||
|
|
06dd4c5026 | ||
|
|
058caadf4f | ||
|
|
7b7d309105 | ||
|
|
63be7b93ce | ||
|
|
578ea12b59 | ||
|
|
364e59be57 | ||
|
|
fece4dac9e | ||
|
|
ce85c1aabc | ||
|
|
0ac2ce5e72 | ||
|
|
391c800bf5 | ||
|
|
9c27b58194 | ||
|
|
f6471cf8f9 | ||
|
|
f21e9bcc4d | ||
|
|
1eaff753a6 | ||
|
|
9b3a8897cd | ||
|
|
40f8cddfb2 | ||
|
|
c29d5de1eb | ||
|
|
d94c171d63 | ||
|
|
2717967d61 | ||
|
|
53203dbcef | ||
|
|
9e66f8ac11 | ||
|
|
796fb3b4cd | ||
|
|
473ecbdf2d | ||
|
|
7b3703506b | ||
|
|
5816f76bbe | ||
|
|
355996bafc | ||
|
|
c2bb46f41d | ||
|
|
12f76c24b1 | ||
|
|
4b8ebc9a17 | ||
|
|
42a109e593 | ||
|
|
8064e68873 | ||
|
|
3dc0d0bb92 | ||
|
|
2bb8f92af1 | ||
|
|
5b7d5d2b93 | ||
|
|
2b27db18d8 | ||
|
|
f3c811cc03 | ||
|
|
df415208a4 | ||
|
|
77fd01bd9f | ||
|
|
fa3a9570d6 | ||
|
|
c06a5ac96c | ||
|
|
33467b42da | ||
|
|
13fb641113 | ||
|
|
53f17c2baa | ||
|
|
06a57ef811 | ||
|
|
86a09b16ff | ||
|
|
c70d7535b9 | ||
|
|
8541360bf3 | ||
|
|
2a832d36d7 | ||
|
|
1578c89475 | ||
|
|
5c13e54149 | ||
|
|
8e74cf6633 | ||
|
|
bab6b36e4d | ||
|
|
f75e616397 | ||
|
|
941a9c3b39 | ||
|
|
7ba0f604e6 | ||
|
|
cf8a4cc939 | ||
|
|
ee78daeeef | ||
|
|
2f6b0b1a55 | ||
|
|
c048074c31 | ||
|
|
5ca89709e3 | ||
|
|
5a88ff0811 | ||
|
|
de68c251f8 | ||
|
|
7c9ae3561d | ||
|
|
b608ece57e | ||
|
|
8dfffebaf1 | ||
|
|
109a3bb2b9 | ||
|
|
fef37f739b | ||
|
|
7a5615182a | ||
|
|
02a7003ffe | ||
|
|
1571f14815 | ||
|
|
9cb098ad8a | ||
|
|
6283f5952d | ||
|
|
9b9edbae0e | ||
|
|
491155d1cf | ||
|
|
54207254f1 | ||
|
|
1395dcc0be | ||
|
|
2a68d9095d | ||
|
|
a984b3640e | ||
|
|
f9c1e411aa | ||
|
|
f6cbc32ee7 | ||
|
|
602614acf6 | ||
|
|
3854b7d472 | ||
|
|
5e25481088 | ||
|
|
fe86e15d80 | ||
|
|
179b4a69eb | ||
|
|
eee6307789 | ||
|
|
9fc5002619 | ||
|
|
faa6ae284a | ||
|
|
8b4355b21d | ||
|
|
e8835da740 | ||
|
|
75854e104e | ||
|
|
93d06e3f4d | ||
|
|
c560b9229c | ||
|
|
935e268dec | ||
|
|
3a1c716c73 | ||
|
|
f3457502a6 | ||
|
|
7ded802df4 | ||
|
|
d3cd1d1b15 | ||
|
|
f5a75c6319 | ||
|
|
ae3a5c5f5e | ||
|
|
43792e2426 | ||
|
|
551d639951 | ||
|
|
c367a71223 | ||
|
|
d259ef0348 | ||
|
|
288cbf4a80 | ||
|
|
ba5e5a780f | ||
|
|
73fa3c3fe4 | ||
|
|
579eb85175 | ||
|
|
b2b20072ae | ||
|
|
a2c4d3fe95 | ||
|
|
31e2be2e4d | ||
|
|
9f5d97e1c6 | ||
|
|
baaae6cd9f | ||
|
|
ed398aa7b9 | ||
|
|
6e2ae42dab | ||
|
|
7f832ad783 | ||
|
|
2ce6f8cb6c | ||
|
|
2574125199 | ||
|
|
41bf2b2c42 | ||
|
|
51bac394ec | ||
|
|
b696649c9d | ||
|
|
b4828ad8de | ||
|
|
639d634426 | ||
|
|
5358fc4f43 | ||
|
|
6a654ab90b | ||
|
|
99eda80a78 | ||
|
|
a6182acc9c | ||
|
|
2241e4d8ea | ||
|
|
cbbdea1ba4 | ||
|
|
05e7c98620 | ||
|
|
1f1d618dea | ||
|
|
b18117ef89 | ||
|
|
44cb796574 | ||
|
|
ccf60ffc4b | ||
|
|
f69db11f42 | ||
|
|
96a680dcf0 | ||
|
|
c8367c9b7a | ||
|
|
c612663490 | ||
|
|
de5d967d18 | ||
|
|
7fc63f7847 | ||
|
|
49009cbcad | ||
|
|
b5fbeffb86 | ||
|
|
146655e997 | ||
|
|
87d66f04d8 | ||
|
|
bb27dd0c3b | ||
|
|
f45a1c232f | ||
|
|
d7a3c12bbe | ||
|
|
a1e84f5a88 | ||
|
|
b758737907 | ||
|
|
c488c14d25 | ||
|
|
5e0cca0702 | ||
|
|
8559e46e4a | ||
|
|
4bc00e00e3 | ||
|
|
3e777df86c | ||
|
|
278b4e810d | ||
|
|
346c7cd743 | ||
|
|
867bf97d8f | ||
|
|
8a67949168 | ||
|
|
5baa51d547 | ||
|
|
616db337e1 | ||
|
|
3895871462 | ||
|
|
a87b84fbe2 | ||
|
|
b2f0ace9db | ||
|
|
20c95e2606 | ||
|
|
22dccaeddb | ||
|
|
e611a70ba4 | ||
|
|
66845d7080 | ||
|
|
4ea7278c6f | ||
|
|
2b2e26f14b | ||
|
|
b496ef8d6f | ||
|
|
7f5e83141d | ||
|
|
2d1ca98605 | ||
|
|
eaa4c318e3 | ||
|
|
31373fd1ba | ||
|
|
9086246947 | ||
|
|
7855b70682 | ||
|
|
0ce87153e5 | ||
|
|
dba1711e8d | ||
|
|
a70b057e1c | ||
|
|
9a5ffea0ad | ||
|
|
96f4b771ea | ||
|
|
3df143dd3d | ||
|
|
d78d7c726e | ||
|
|
d0ad580c7d | ||
|
|
4a8a2a70b5 | ||
|
|
20a71b7df2 | ||
|
|
68412b3901 | ||
|
|
93a7c60a15 | ||
|
|
31e5058b15 | ||
|
|
14cff958e9 | ||
|
|
9628f147f1 | ||
|
|
13e346d4eb | ||
|
|
e507ce2f26 | ||
|
|
9c62622733 | ||
|
|
62aa0cef39 | ||
|
|
401953313a | ||
|
|
4d2403d619 | ||
|
|
c5f261305d | ||
|
|
394f58f6cc | ||
|
|
674bf1b0e0 | ||
|
|
606ddd8a9b | ||
|
|
e23a1fac50 | ||
|
|
342323a7e6 | ||
|
|
efb410444b | ||
|
|
17c9b4c5d3 | ||
|
|
706de8e2f1 | ||
|
|
23bc11f3b6 | ||
|
|
4eb7dde1c8 | ||
|
|
064861b930 | ||
|
|
afa910bbd7 | ||
|
|
6aceb24fd2 | ||
|
|
d94e86781f | ||
|
|
0d4a3b1ad4 | ||
|
|
acfcb18f29 | ||
|
|
f7ff8e3837 | ||
|
|
048e17c62b | ||
|
|
d89b4f7e95 | ||
|
|
795b226b90 | ||
|
|
e485c380e0 | ||
|
|
bb4f4bc441 | ||
|
|
65b49b2d9c | ||
|
|
9e7010f185 | ||
|
|
3bb8e5bb00 | ||
|
|
2a4d1da2ca | ||
|
|
6b71b66bd2 | ||
|
|
ebf24fb125 | ||
|
|
46d64b949e | ||
|
|
6919354520 | ||
|
|
a42fe9bfb0 | ||
|
|
ee1f8b34ea | ||
|
|
c910fa406d | ||
|
|
559205e33f | ||
|
|
c0756e9c60 | ||
|
|
bf1190696e | ||
|
|
71dd0890de | ||
|
|
e5acdf1402 | ||
|
|
0f08b6bb59 | ||
|
|
6198a7b69a | ||
|
|
067aee6664 | ||
|
|
138a2ebbd0 | ||
|
|
296f6a7a88 | ||
|
|
069ffa9921 | ||
|
|
f42fd8a840 | ||
|
|
10f27af6f2 | ||
|
|
0bbd34d060 | ||
|
|
282daeb0dc | ||
|
|
d33b313c11 | ||
|
|
fb7316c9ae | ||
|
|
279b0a51d9 | ||
|
|
6547d5ebf3 | ||
|
|
4f1ef9a039 | ||
|
|
4c80714d19 | ||
|
|
077ead71a5 | ||
|
|
caba110266 | ||
|
|
0fdb23c1e9 | ||
|
|
13a84f0c72 | ||
|
|
669bd58e33 | ||
|
|
6e82740a9b | ||
|
|
7ea43a728d | ||
|
|
71b38356b1 | ||
|
|
5a99708f56 | ||
|
|
24191d9599 | ||
|
|
482ea8eb40 | ||
|
|
1dae05651f | ||
|
|
5164e92538 | ||
|
|
f89a20dbc7 | ||
|
|
3a4c5a2bfb | ||
|
|
5e1334e8de | ||
|
|
fa6e3d3690 | ||
|
|
9383e7716b | ||
|
|
cfe34fbf0f | ||
|
|
9fe110625c | ||
|
|
975f753c2b | ||
|
|
e6237480f8 | ||
|
|
966d4e29d4 | ||
|
|
26f876a2cb | ||
|
|
ab9e6ac48a | ||
|
|
c1d6c04ab2 | ||
|
|
888cec3d56 | ||
|
|
1461bcc2c2 | ||
|
|
11f1cf80bd | ||
|
|
c675cc8b26 | ||
|
|
0011b8925b | ||
|
|
73ea6e4251 | ||
|
|
e4441dddbb | ||
|
|
8d1d56f694 |
@@ -146,7 +146,7 @@ ij_java_generate_final_parameters = true
|
||||
ij_java_if_brace_force = always
|
||||
ij_java_imports_layout = $*,|,*
|
||||
ij_java_indent_case_from_switch = true
|
||||
ij_java_insert_inner_class_imports = true
|
||||
ij_java_insert_inner_class_imports = false
|
||||
ij_java_insert_override_annotation = true
|
||||
ij_java_keep_blank_lines_before_right_brace = 2
|
||||
ij_java_keep_blank_lines_between_package_declaration_and_header = 2
|
||||
@@ -158,7 +158,7 @@ ij_java_keep_indents_on_empty_lines = false
|
||||
ij_java_keep_line_breaks = true
|
||||
ij_java_keep_multiple_expressions_in_one_line = false
|
||||
ij_java_keep_simple_blocks_in_one_line = false
|
||||
ij_java_keep_simple_classes_in_one_line = false
|
||||
ij_java_keep_simple_classes_in_one_line = true
|
||||
ij_java_keep_simple_lambdas_in_one_line = false
|
||||
ij_java_keep_simple_methods_in_one_line = false
|
||||
ij_java_label_indent_absolute = false
|
||||
@@ -1135,7 +1135,7 @@ ij_kotlin_field_annotation_wrap = split_into_lines
|
||||
ij_kotlin_finally_on_new_line = false
|
||||
ij_kotlin_if_rparen_on_new_line = false
|
||||
ij_kotlin_import_nested_classes = false
|
||||
ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^
|
||||
ij_kotlin_imports_layout = *
|
||||
ij_kotlin_insert_whitespaces_in_simple_one_line_method = true
|
||||
ij_kotlin_keep_blank_lines_before_right_brace = 2
|
||||
ij_kotlin_keep_blank_lines_in_code = 2
|
||||
@@ -1151,9 +1151,9 @@ ij_kotlin_method_call_chain_wrap = off
|
||||
ij_kotlin_method_parameters_new_line_after_left_paren = false
|
||||
ij_kotlin_method_parameters_right_paren_on_new_line = false
|
||||
ij_kotlin_method_parameters_wrap = off
|
||||
ij_kotlin_name_count_to_use_star_import = 5
|
||||
ij_kotlin_name_count_to_use_star_import_for_members = 3
|
||||
ij_kotlin_packages_to_use_import_on_demand = java.util.*,kotlinx.android.synthetic.**,io.ktor.**
|
||||
ij_kotlin_name_count_to_use_star_import = 999
|
||||
ij_kotlin_name_count_to_use_star_import_for_members = 999
|
||||
ij_kotlin_packages_to_use_import_on_demand =
|
||||
ij_kotlin_parameter_annotation_wrap = off
|
||||
ij_kotlin_space_after_comma = true
|
||||
ij_kotlin_space_after_extend_colon = true
|
||||
|
||||
0
.github/stale.yml
vendored
Normal file
0
.github/stale.yml
vendored
Normal file
33
.github/workflows/documentation.yml
vendored
Normal file
33
.github/workflows/documentation.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: Update Documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
||||
- uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 # v3.11.0
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
cache: 'maven'
|
||||
- name: Compile and Build OpenAPI file
|
||||
run: ./mvnw compile
|
||||
- name: Update Documentation
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
cp -r api-doc/target/openapi/signal-server-openapi.yaml /tmp/
|
||||
git config user.email "github@signal.org"
|
||||
git config user.name "Documentation Updater"
|
||||
git fetch origin gh-pages
|
||||
git checkout gh-pages
|
||||
cp /tmp/signal-server-openapi.yaml .
|
||||
git diff --quiet || git commit -a -m "Updating documentation"
|
||||
git push origin gh-pages -q
|
||||
34
.github/workflows/integration-tests.yml
vendored
Normal file
34
.github/workflows/integration-tests.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: Integration Tests
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 19 * * MON-FRI'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: ${{ vars.INTEGRATION_TESTS_BUCKET != '' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
||||
- uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 # v3.11.0
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
cache: 'maven'
|
||||
- uses: aws-actions/configure-aws-credentials@v2
|
||||
name: Configure AWS credentials from Test account
|
||||
with:
|
||||
role-to-assume: ${{ vars.AWS_ROLE }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
- name: Fetch integration utils library
|
||||
run: |
|
||||
mkdir -p integration-tests/.libs
|
||||
mkdir -p integration-tests/src/main/resources
|
||||
wget -O integration-tests/.libs/software.amazon.awssdk-sso.jar https://repo1.maven.org/maven2/software/amazon/awssdk/sso/2.19.8/sso-2.19.8.jar
|
||||
aws s3 cp "s3://${{ vars.INTEGRATION_TESTS_BUCKET }}/config-latest.yml" integration-tests/src/main/resources/config.yml
|
||||
- name: Run and verify integration tests
|
||||
run: ./mvnw clean compile test-compile failsafe:integration-test failsafe:verify
|
||||
22
.github/workflows/test.yml
vendored
22
.github/workflows/test.yml
vendored
@@ -1,18 +1,26 @@
|
||||
name: Service CI
|
||||
|
||||
on: [push]
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- gh-pages
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container: ubuntu:22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@3bc31aaf88e8fc94dc1e632d48af61be5ca8721c
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 # v3.11.0
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: 11
|
||||
distribution: 'temurin'
|
||||
java-version: 21
|
||||
cache: 'maven'
|
||||
env:
|
||||
# work around an issue with actions/runner setting an incorrect HOME in containers, which breaks maven caching
|
||||
# https://github.com/actions/setup-java/issues/356
|
||||
HOME: /root
|
||||
- name: Build with Maven
|
||||
run: mvn -e -B verify
|
||||
run: ./mvnw -e -B verify
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -16,6 +16,7 @@ config/deploy.properties
|
||||
/service/config/testing.yml
|
||||
/service/config/deploy.properties
|
||||
/service/dependency-reduced-pom.xml
|
||||
.java-version
|
||||
.opsmanage
|
||||
put.sh
|
||||
deployer-staging.properties
|
||||
@@ -25,4 +26,7 @@ deployer.log
|
||||
!/service/src/main/resources/org/signal/badges/Badges_en.properties
|
||||
/service/src/main/resources/org/signal/subscriptions/Subscriptions_*.properties
|
||||
!/service/src/main/resources/org/signal/subscriptions/Subscriptions_en.properties
|
||||
/.tx/config
|
||||
.project
|
||||
.classpath
|
||||
.settings
|
||||
.DS_Store
|
||||
8
.gitmodules
vendored
8
.gitmodules
vendored
@@ -1,11 +1,11 @@
|
||||
# Note that the implmentation of the abusive message filter is private; internal
|
||||
# Note that the implementation of the spam filter is private; internal
|
||||
# developers will need to override this URL with:
|
||||
#
|
||||
# ```
|
||||
# git config submodule.abusive-message-filter.url PRIVATE_URL
|
||||
# git config submodule.spam-filter.url PRIVATE_URL
|
||||
# ```
|
||||
#
|
||||
# External developers may safely ignore this submodule.
|
||||
[submodule "abusive-message-filter"]
|
||||
path = abusive-message-filter
|
||||
[submodule "spam-filter"]
|
||||
path = spam-filter
|
||||
url = REDACTED
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -4,6 +4,6 @@
|
||||
<extension>
|
||||
<groupId>fr.brouillard.oss</groupId>
|
||||
<artifactId>jgitver-maven-plugin</artifactId>
|
||||
<version>1.7.1</version>
|
||||
<version>1.9.0</version>
|
||||
</extension>
|
||||
</extensions>
|
||||
|
||||
BIN
.mvn/wrapper/maven-wrapper.jar
vendored
Normal file
BIN
.mvn/wrapper/maven-wrapper.jar
vendored
Normal file
Binary file not shown.
20
.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
20
.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you 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
|
||||
#
|
||||
# https://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.
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.4/apache-maven-3.9.4-bin.zip
|
||||
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
|
||||
distributionSha256Sum=e896b60329a71b719d77bb4388b251a50aebcd73c62f69d510c858ce360afe0f
|
||||
wrapperSha256Sum=e63a53cfb9c4d291ebe3c2b0edacb7622bbc480326beaa5a0456e412f52f066a
|
||||
2
LICENSE
2
LICENSE
@@ -296,7 +296,7 @@ commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
procedures, authorization keysManager, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
|
||||
@@ -13,7 +13,7 @@ Cryptography Notice
|
||||
|
||||
This distribution includes cryptographic software. The country in which you currently reside may have restrictions on the import, possession, use, and/or re-export to another country, of encryption software.
|
||||
BEFORE using any encryption software, please check your country's laws, regulations and policies concerning the import, possession, or use, and re-export of encryption software, to see if this is permitted.
|
||||
See <http://www.wassenaar.org/> for more information.
|
||||
See <https://www.wassenaar.org/> for more information.
|
||||
|
||||
The U.S. Government Department of Commerce, Bureau of Industry and Security (BIS), has classified this software as Export Commodity Control Number (ECCN) 5D002.C.1, which includes information security software using or performing cryptographic functions with asymmetric algorithms.
|
||||
The form and manner of this distribution makes it eligible for export under the License Exception ENC Technology Software Unrestricted (TSU) exception (see the BIS Export Administration Regulations, Section 740.13) for both object code and source code.
|
||||
@@ -21,6 +21,6 @@ The form and manner of this distribution makes it eligible for export under the
|
||||
License
|
||||
---------------------
|
||||
|
||||
Copyright 2013-2021 Signal Messenger, LLC
|
||||
Copyright 2013-2023 Signal Messenger, LLC
|
||||
|
||||
Licensed under the AGPLv3: https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
Submodule abusive-message-filter deleted from d20873c7d7
53
api-doc/pom.xml
Normal file
53
api-doc/pom.xml
Normal file
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>TextSecureServer</artifactId>
|
||||
<groupId>org.whispersystems.textsecure</groupId>
|
||||
<version>JGITVER</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>api-doc</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.whispersystems.textsecure</groupId>
|
||||
<artifactId>service</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-maven-plugin</artifactId>
|
||||
<version>${swagger.version}</version>
|
||||
<configuration>
|
||||
<outputFileName>signal-server-openapi</outputFileName>
|
||||
<outputPath>${project.build.directory}/openapi</outputPath>
|
||||
<outputFormat>YAML</outputFormat>
|
||||
<configurationFilePath>${project.basedir}/src/main/resources/openapi/openapi-configuration.yaml
|
||||
</configurationFilePath>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>resolve</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.google.cloud.tools</groupId>
|
||||
<artifactId>jib-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<!-- we don't want jib to execute on this module -->
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.openapi;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.type.SimpleType;
|
||||
import io.dropwizard.auth.Auth;
|
||||
import io.swagger.v3.jaxrs2.ResolvedParameter;
|
||||
import io.swagger.v3.jaxrs2.ext.AbstractOpenAPIExtension;
|
||||
import io.swagger.v3.jaxrs2.ext.OpenAPIExtension;
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
import javax.ws.rs.Consumes;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
|
||||
|
||||
/**
|
||||
* One of the extension mechanisms of Swagger Core library (OpenAPI processor) is via custom implementations
|
||||
* of the {@link AbstractOpenAPIExtension} class.
|
||||
* <p/>
|
||||
* The purpose of this extension is to customize certain aspects of the OpenAPI model generation on a lower level.
|
||||
* This extension works in coordination with {@link OpenApiReader} that has access to the model on a higher level.
|
||||
* <p/>
|
||||
* The extension is enabled by being listed in {@code META-INF/services/io.swagger.v3.jaxrs2.ext.OpenAPIExtension} file.
|
||||
* @see ServiceLoader
|
||||
* @see OpenApiReader
|
||||
* @see <a href="https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Extensions">Swagger 2.X Extensions</a>
|
||||
*/
|
||||
public class OpenApiExtension extends AbstractOpenAPIExtension {
|
||||
|
||||
public static final ResolvedParameter AUTHENTICATED_ACCOUNT = new ResolvedParameter();
|
||||
|
||||
public static final ResolvedParameter OPTIONAL_AUTHENTICATED_ACCOUNT = new ResolvedParameter();
|
||||
|
||||
/**
|
||||
* When parsing endpoint methods, Swagger will treat the first parameter not annotated as header/path/query param
|
||||
* as a request body (and will ignore other not annotated parameters). In our case, this behavior conflicts with
|
||||
* the {@code @Auth}-annotated parameters. Here we're checking if parameters are known to be anything other than
|
||||
* a body and return an appropriate {@link ResolvedParameter} representation.
|
||||
*/
|
||||
@Override
|
||||
public ResolvedParameter extractParameters(
|
||||
final List<Annotation> annotations,
|
||||
final Type type,
|
||||
final Set<Type> typesToSkip,
|
||||
final Components components,
|
||||
final Consumes classConsumes,
|
||||
final Consumes methodConsumes,
|
||||
final boolean includeRequestBody,
|
||||
final JsonView jsonViewAnnotation,
|
||||
final Iterator<OpenAPIExtension> chain) {
|
||||
|
||||
if (annotations.stream().anyMatch(a -> a.annotationType().equals(Auth.class))) {
|
||||
// this is the case of authenticated endpoint,
|
||||
if (type instanceof SimpleType simpleType
|
||||
&& simpleType.getRawClass().equals(AuthenticatedAccount.class)) {
|
||||
return AUTHENTICATED_ACCOUNT;
|
||||
}
|
||||
if (type instanceof SimpleType simpleType
|
||||
&& isOptionalOfType(simpleType, AuthenticatedAccount.class)) {
|
||||
return OPTIONAL_AUTHENTICATED_ACCOUNT;
|
||||
}
|
||||
}
|
||||
|
||||
return super.extractParameters(
|
||||
annotations,
|
||||
type,
|
||||
typesToSkip,
|
||||
components,
|
||||
classConsumes,
|
||||
methodConsumes,
|
||||
includeRequestBody,
|
||||
jsonViewAnnotation,
|
||||
chain);
|
||||
}
|
||||
|
||||
private static boolean isOptionalOfType(final SimpleType simpleType, final Class<?> expectedType) {
|
||||
if (!simpleType.getRawClass().equals(Optional.class)) {
|
||||
return false;
|
||||
}
|
||||
final List<JavaType> typeParameters = simpleType.getBindings().getTypeParameters();
|
||||
if (typeParameters.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return typeParameters.get(0) instanceof SimpleType optionalParameterType
|
||||
&& optionalParameterType.getRawClass().equals(expectedType);
|
||||
}
|
||||
}
|
||||
71
api-doc/src/main/java/org/signal/openapi/OpenApiReader.java
Normal file
71
api-doc/src/main/java/org/signal/openapi/OpenApiReader.java
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.openapi;
|
||||
|
||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||
import static org.signal.openapi.OpenApiExtension.AUTHENTICATED_ACCOUNT;
|
||||
import static org.signal.openapi.OpenApiExtension.OPTIONAL_AUTHENTICATED_ACCOUNT;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import io.swagger.v3.jaxrs2.Reader;
|
||||
import io.swagger.v3.jaxrs2.ResolvedParameter;
|
||||
import io.swagger.v3.oas.models.Operation;
|
||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import javax.ws.rs.Consumes;
|
||||
|
||||
/**
|
||||
* One of the extension mechanisms of Swagger Core library (OpenAPI processor) is via custom implementations
|
||||
* of the {@link Reader} class.
|
||||
* <p/>
|
||||
* The purpose of this extension is to customize certain aspects of the OpenAPI model generation on a higher level.
|
||||
* This extension works in coordination with {@link OpenApiExtension} that has access to the model on a lower level.
|
||||
* <p/>
|
||||
* The extension is enabled by being listed in {@code resources/openapi/openapi-configuration.yaml} file.
|
||||
* @see OpenApiExtension
|
||||
* @see <a href="https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Extensions">Swagger 2.X Extensions</a>
|
||||
*/
|
||||
public class OpenApiReader extends Reader {
|
||||
|
||||
private static final String AUTHENTICATED_ACCOUNT_AUTH_SCHEMA = "authenticatedAccount";
|
||||
|
||||
|
||||
/**
|
||||
* Overriding this method allows converting a resolved parameter into other operation entities,
|
||||
* in this case, into security requirements.
|
||||
*/
|
||||
@Override
|
||||
protected ResolvedParameter getParameters(
|
||||
final Type type,
|
||||
final List<Annotation> annotations,
|
||||
final Operation operation,
|
||||
final Consumes classConsumes,
|
||||
final Consumes methodConsumes,
|
||||
final JsonView jsonViewAnnotation) {
|
||||
final ResolvedParameter resolved = super.getParameters(
|
||||
type, annotations, operation, classConsumes, methodConsumes, jsonViewAnnotation);
|
||||
|
||||
if (resolved == AUTHENTICATED_ACCOUNT) {
|
||||
operation.setSecurity(ImmutableList.<SecurityRequirement>builder()
|
||||
.addAll(firstNonNull(operation.getSecurity(), Collections.emptyList()))
|
||||
.add(new SecurityRequirement().addList(AUTHENTICATED_ACCOUNT_AUTH_SCHEMA))
|
||||
.build());
|
||||
}
|
||||
if (resolved == OPTIONAL_AUTHENTICATED_ACCOUNT) {
|
||||
operation.setSecurity(ImmutableList.<SecurityRequirement>builder()
|
||||
.addAll(firstNonNull(operation.getSecurity(), Collections.emptyList()))
|
||||
.add(new SecurityRequirement().addList(AUTHENTICATED_ACCOUNT_AUTH_SCHEMA))
|
||||
.add(new SecurityRequirement())
|
||||
.build());
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.signal.openapi.OpenApiExtension
|
||||
@@ -0,0 +1,25 @@
|
||||
resourcePackages:
|
||||
- org.whispersystems.textsecuregcm
|
||||
prettyPrint: true
|
||||
cacheTTL: 0
|
||||
readerClass: org.signal.openapi.OpenApiReader
|
||||
openAPI:
|
||||
info:
|
||||
title: Signal Server API
|
||||
license:
|
||||
name: AGPL-3.0-only
|
||||
url: https://www.gnu.org/licenses/agpl-3.0.txt
|
||||
servers:
|
||||
- url: https://chat.signal.org
|
||||
description: Production service
|
||||
- url: https://chat.staging.signal.org
|
||||
description: Staging service
|
||||
components:
|
||||
securitySchemes:
|
||||
authenticatedAccount:
|
||||
type: http
|
||||
scheme: basic
|
||||
description: |
|
||||
Account authentication is based on Basic authentication schema,
|
||||
where `username` has a format of `<user_id>[.<device_id>]`. If `device_id` is not specified,
|
||||
user's `main` device is assumed.
|
||||
@@ -1,52 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>TextSecureServer</artifactId>
|
||||
<groupId>org.whispersystems.textsecure</groupId>
|
||||
<version>JGITVER</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>gcm-sender-async</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.github.resilience4j</groupId>
|
||||
<artifactId>resilience4j-retry</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>jcl-over-slf4j</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-nop</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.gcm.server;
|
||||
|
||||
|
||||
public class AuthenticationFailedException extends Exception {
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.gcm.server;
|
||||
|
||||
|
||||
public class InvalidRequestException extends Exception {
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.gcm.server;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.whispersystems.gcm.server.internal.GcmRequestEntity;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class Message {
|
||||
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private final String collapseKey;
|
||||
private final Long ttl;
|
||||
private final Boolean delayWhileIdle;
|
||||
private final Map<String, String> data;
|
||||
private final List<String> registrationIds;
|
||||
private final String priority;
|
||||
|
||||
private Message(String collapseKey, Long ttl, Boolean delayWhileIdle,
|
||||
Map<String, String> data, List<String> registrationIds,
|
||||
String priority)
|
||||
{
|
||||
this.collapseKey = collapseKey;
|
||||
this.ttl = ttl;
|
||||
this.delayWhileIdle = delayWhileIdle;
|
||||
this.data = data;
|
||||
this.registrationIds = registrationIds;
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
public String serialize() throws JsonProcessingException {
|
||||
GcmRequestEntity requestEntity = new GcmRequestEntity(collapseKey, ttl, delayWhileIdle,
|
||||
data, registrationIds, priority);
|
||||
|
||||
return objectMapper.writeValueAsString(requestEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new Message using a Builder.
|
||||
* @return A new Builder.
|
||||
*/
|
||||
public static Builder newBuilder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private String collapseKey = null;
|
||||
private Long ttl = null;
|
||||
private Boolean delayWhileIdle = null;
|
||||
private Map<String, String> data = null;
|
||||
private List<String> registrationIds = new LinkedList<>();
|
||||
private String priority = null;
|
||||
|
||||
private Builder() {}
|
||||
|
||||
/**
|
||||
* @param collapseKey The GCM collapse key to use (optional).
|
||||
* @return The Builder.
|
||||
*/
|
||||
public Builder withCollapseKey(String collapseKey) {
|
||||
this.collapseKey = collapseKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param seconds The TTL (in seconds) for this message (optional).
|
||||
* @return The Builder.
|
||||
*/
|
||||
public Builder withTtl(long seconds) {
|
||||
this.ttl = seconds;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delayWhileIdle Set GCM delay_while_idle (optional).
|
||||
* @return The Builder.
|
||||
*/
|
||||
public Builder withDelayWhileIdle(boolean delayWhileIdle) {
|
||||
this.delayWhileIdle = delayWhileIdle;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a key in the GCM JSON payload delivered to the application (optional).
|
||||
* @param key The key to set.
|
||||
* @param value The value to set.
|
||||
* @return The Builder.
|
||||
*/
|
||||
public Builder withDataPart(String key, String value) {
|
||||
if (data == null) {
|
||||
data = new HashMap<>();
|
||||
}
|
||||
data.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the destination GCM registration ID (mandatory).
|
||||
* @param registrationId The destination GCM registration ID.
|
||||
* @return The Builder.
|
||||
*/
|
||||
public Builder withDestination(String registrationId) {
|
||||
this.registrationIds.clear();
|
||||
this.registrationIds.add(registrationId);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the GCM message priority (optional).
|
||||
*
|
||||
* @param priority Valid values are "normal" and "high."
|
||||
* On iOS, these correspond to APNs priority 5 and 10.
|
||||
* @return The Builder.
|
||||
*/
|
||||
public Builder withPriority(String priority) {
|
||||
this.priority = priority;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a message object.
|
||||
*
|
||||
* @return An immutable message object, as configured by this builder.
|
||||
*/
|
||||
public Message build() {
|
||||
if (registrationIds.isEmpty()) {
|
||||
throw new IllegalArgumentException("You must specify a destination!");
|
||||
}
|
||||
|
||||
return new Message(collapseKey, ttl, delayWhileIdle, data, registrationIds, priority);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.gcm.server;
|
||||
|
||||
/**
|
||||
* The result of a GCM send operation.
|
||||
*/
|
||||
public class Result {
|
||||
|
||||
private final String canonicalRegistrationId;
|
||||
private final String messageId;
|
||||
private final String error;
|
||||
|
||||
Result(String canonicalRegistrationId, String messageId, String error) {
|
||||
this.canonicalRegistrationId = canonicalRegistrationId;
|
||||
this.messageId = messageId;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the "canonical" GCM registration ID for this destination.
|
||||
* See GCM documentation for details.
|
||||
* @return The canonical GCM registration ID.
|
||||
*/
|
||||
public String getCanonicalRegistrationId() {
|
||||
return canonicalRegistrationId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If a "canonical" GCM registration ID is present in the response.
|
||||
*/
|
||||
public boolean hasCanonicalRegistrationId() {
|
||||
return canonicalRegistrationId != null && !canonicalRegistrationId.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The assigned GCM message ID, if successful.
|
||||
*/
|
||||
public String getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The raw error string, if present.
|
||||
*/
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If the send was a success.
|
||||
*/
|
||||
public boolean isSuccess() {
|
||||
return messageId != null && !messageId.isEmpty() && (error == null || error.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If the destination GCM registration ID is no longer registered.
|
||||
*/
|
||||
public boolean isUnregistered() {
|
||||
return "NotRegistered".equals(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If messages to this device are being throttled.
|
||||
*/
|
||||
public boolean isThrottled() {
|
||||
return "DeviceMessageRateExceeded".equals(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If the destination GCM registration ID is invalid.
|
||||
*/
|
||||
public boolean isInvalidRegistrationId() {
|
||||
return "InvalidRegistration".equals(error);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.gcm.server;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import io.github.resilience4j.retry.IntervalFunction;
|
||||
import io.github.resilience4j.retry.Retry;
|
||||
import io.github.resilience4j.retry.RetryConfig;
|
||||
import org.whispersystems.gcm.server.internal.GcmResponseEntity;
|
||||
import org.whispersystems.gcm.server.internal.GcmResponseListEntity;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse.BodyHandlers;
|
||||
import java.security.SecureRandom;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* The main interface to sending GCM messages. Thread safe.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class Sender {
|
||||
|
||||
private static final String PRODUCTION_URL = "https://fcm.googleapis.com/fcm/send";
|
||||
|
||||
private final String authorizationHeader;
|
||||
private final URI uri;
|
||||
private final Retry retry;
|
||||
private final ObjectMapper mapper;
|
||||
private final ScheduledExecutorService executorService;
|
||||
|
||||
private final HttpClient[] clients = new HttpClient[10];
|
||||
private final SecureRandom random = new SecureRandom();
|
||||
|
||||
/**
|
||||
* Construct a Sender instance.
|
||||
*
|
||||
* @param apiKey Your application's GCM API key.
|
||||
*/
|
||||
public Sender(String apiKey, ObjectMapper mapper) {
|
||||
this(apiKey, mapper, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a Sender instance with a specified retry count.
|
||||
*
|
||||
* @param apiKey Your application's GCM API key.
|
||||
* @param retryCount The number of retries to attempt on a network error or 500 response.
|
||||
*/
|
||||
public Sender(String apiKey, ObjectMapper mapper, int retryCount) {
|
||||
this(apiKey, mapper, retryCount, PRODUCTION_URL);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public Sender(String apiKey, ObjectMapper mapper, int retryCount, String url) {
|
||||
this.mapper = mapper;
|
||||
this.executorService = Executors.newSingleThreadScheduledExecutor();
|
||||
this.uri = URI.create(url);
|
||||
this.authorizationHeader = String.format("key=%s", apiKey);
|
||||
this.retry = Retry.of("fcm-sender", RetryConfig.custom()
|
||||
.maxAttempts(retryCount)
|
||||
.intervalFunction(IntervalFunction.ofExponentialRandomBackoff(Duration.ofMillis(100), 2.0))
|
||||
.retryOnException(this::isRetryableException)
|
||||
.build());
|
||||
|
||||
for (int i=0;i<clients.length;i++) {
|
||||
this.clients[i] = HttpClient.newBuilder()
|
||||
.version(HttpClient.Version.HTTP_2)
|
||||
.connectTimeout(Duration.ofSeconds(10))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isRetryableException(Throwable throwable) {
|
||||
while (throwable instanceof CompletionException) {
|
||||
throwable = throwable.getCause();
|
||||
}
|
||||
|
||||
return throwable instanceof ServerFailedException ||
|
||||
throwable instanceof TimeoutException ||
|
||||
throwable instanceof IOException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously send a message.
|
||||
*
|
||||
* @param message The message to send.
|
||||
* @return A future.
|
||||
*/
|
||||
public CompletableFuture<Result> send(Message message) {
|
||||
try {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(uri)
|
||||
.header("Authorization", authorizationHeader)
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(message.serialize()))
|
||||
.timeout(Duration.ofSeconds(10))
|
||||
.build();
|
||||
|
||||
return retry.executeCompletionStage(executorService,
|
||||
() -> getClient().sendAsync(request, BodyHandlers.ofByteArray())
|
||||
.thenApply(response -> {
|
||||
switch (response.statusCode()) {
|
||||
case 400: throw new CompletionException(new InvalidRequestException());
|
||||
case 401: throw new CompletionException(new AuthenticationFailedException());
|
||||
case 204:
|
||||
case 200: return response.body();
|
||||
default: throw new CompletionException(new ServerFailedException("Bad status: " + response.statusCode()));
|
||||
}
|
||||
})
|
||||
.thenApply(responseBytes -> {
|
||||
try {
|
||||
List<GcmResponseEntity> responseList = mapper.readValue(responseBytes, GcmResponseListEntity.class).getResults();
|
||||
|
||||
if (responseList == null || responseList.size() == 0) {
|
||||
throw new CompletionException(new IOException("Empty response list!"));
|
||||
}
|
||||
|
||||
GcmResponseEntity responseEntity = responseList.get(0);
|
||||
|
||||
return new Result(responseEntity.getCanonicalRegistrationId(),
|
||||
responseEntity.getMessageId(),
|
||||
responseEntity.getError());
|
||||
} catch (IOException e) {
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
})).toCompletableFuture();
|
||||
} catch (JsonProcessingException e) {
|
||||
return CompletableFuture.failedFuture(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Retry getRetry() {
|
||||
return retry;
|
||||
}
|
||||
|
||||
private HttpClient getClient() {
|
||||
return clients[random.nextInt(clients.length)];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.gcm.server;
|
||||
|
||||
public class ServerFailedException extends Exception {
|
||||
public ServerFailedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ServerFailedException(Exception e) {
|
||||
super(e);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.gcm.server.internal;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class GcmRequestEntity {
|
||||
|
||||
@JsonProperty(value = "collapse_key")
|
||||
private String collapseKey;
|
||||
|
||||
@JsonProperty(value = "time_to_live")
|
||||
private Long ttl;
|
||||
|
||||
@JsonProperty(value = "delay_while_idle")
|
||||
private Boolean delayWhileIdle;
|
||||
|
||||
@JsonProperty(value = "data")
|
||||
private Map<String, String> data;
|
||||
|
||||
@JsonProperty(value = "registration_ids")
|
||||
private List<String> registrationIds;
|
||||
|
||||
@JsonProperty
|
||||
private String priority;
|
||||
|
||||
public GcmRequestEntity(String collapseKey, Long ttl, Boolean delayWhileIdle,
|
||||
Map<String, String> data, List<String> registrationIds,
|
||||
String priority)
|
||||
{
|
||||
this.collapseKey = collapseKey;
|
||||
this.ttl = ttl;
|
||||
this.delayWhileIdle = delayWhileIdle;
|
||||
this.data = data;
|
||||
this.registrationIds = registrationIds;
|
||||
this.priority = priority;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.gcm.server.internal;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class GcmResponseEntity {
|
||||
|
||||
@JsonProperty(value = "message_id")
|
||||
private String messageId;
|
||||
|
||||
@JsonProperty(value = "registration_id")
|
||||
private String canonicalRegistrationId;
|
||||
|
||||
@JsonProperty
|
||||
private String error;
|
||||
|
||||
public String getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
public String getCanonicalRegistrationId() {
|
||||
return canonicalRegistrationId;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.gcm.server.internal;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GcmResponseListEntity {
|
||||
|
||||
@JsonProperty
|
||||
private List<GcmResponseEntity> results;
|
||||
|
||||
public List<GcmResponseEntity> getResults() {
|
||||
return results;
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.gcm.server;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.whispersystems.gcm.server.util.JsonHelpers.jsonFixture;
|
||||
|
||||
public class MessageTest {
|
||||
|
||||
@Test
|
||||
public void testMinimal() throws IOException {
|
||||
Message message = Message.newBuilder()
|
||||
.withDestination("1")
|
||||
.build();
|
||||
|
||||
assertEquals(message.serialize(), jsonFixture("fixtures/message-minimal.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComplete() throws IOException {
|
||||
Message message = Message.newBuilder()
|
||||
.withDestination("1")
|
||||
.withCollapseKey("collapse")
|
||||
.withDelayWhileIdle(true)
|
||||
.withTtl(10)
|
||||
.withPriority("high")
|
||||
.build();
|
||||
|
||||
assertEquals(message.serialize(), jsonFixture("fixtures/message-complete.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithData() throws IOException {
|
||||
Message message = Message.newBuilder()
|
||||
.withDestination("2")
|
||||
.withDataPart("key1", "value1")
|
||||
.withDataPart("key2", "value2")
|
||||
.build();
|
||||
|
||||
assertEquals(message.serialize(), jsonFixture("fixtures/message-data.json"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.gcm.server;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.any;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.anyRequestedFor;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.ok;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
|
||||
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.whispersystems.gcm.server.util.FixtureHelpers.fixture;
|
||||
import static org.whispersystems.gcm.server.util.JsonHelpers.jsonFixture;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.github.tomakehurst.wiremock.client.CountMatchingStrategy;
|
||||
import com.github.tomakehurst.wiremock.junit.WireMockRule;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
public class SenderTest {
|
||||
|
||||
@Rule
|
||||
public WireMockRule wireMock = new WireMockRule(options().dynamicPort().dynamicHttpsPort());
|
||||
|
||||
private static final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
static {
|
||||
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
|
||||
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
|
||||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccess() throws InterruptedException, ExecutionException, TimeoutException, IOException {
|
||||
wireMock.stubFor(any(anyUrl())
|
||||
.willReturn(aResponse()
|
||||
.withStatus(200)
|
||||
.withBody(fixture("fixtures/response-success.json"))));
|
||||
|
||||
|
||||
Sender sender = new Sender("foobarbaz", mapper, 10, "http://localhost:" + wireMock.port() + "/gcm/send");
|
||||
CompletableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build());
|
||||
|
||||
Result result = future.get(10, TimeUnit.SECONDS);
|
||||
|
||||
assertTrue(result.isSuccess());
|
||||
assertFalse(result.isThrottled());
|
||||
assertFalse(result.isUnregistered());
|
||||
assertEquals(result.getMessageId(), "1:08");
|
||||
assertNull(result.getError());
|
||||
assertNull(result.getCanonicalRegistrationId());
|
||||
|
||||
verify(1, postRequestedFor(urlEqualTo("/gcm/send"))
|
||||
.withHeader("Authorization", equalTo("key=foobarbaz"))
|
||||
.withHeader("Content-Type", equalTo("application/json"))
|
||||
.withRequestBody(equalTo(jsonFixture("fixtures/message-minimal.json"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadApiKey() throws InterruptedException, TimeoutException {
|
||||
wireMock.stubFor(any(anyUrl())
|
||||
.willReturn(aResponse()
|
||||
.withStatus(401)));
|
||||
|
||||
Sender sender = new Sender("foobar", mapper, 10, "http://localhost:" + wireMock.port() + "/gcm/send");
|
||||
CompletableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build());
|
||||
|
||||
try {
|
||||
future.get(10, TimeUnit.SECONDS);
|
||||
throw new AssertionError();
|
||||
} catch (ExecutionException ee) {
|
||||
assertTrue(ee.getCause() instanceof AuthenticationFailedException);
|
||||
}
|
||||
|
||||
verify(1, anyRequestedFor(anyUrl()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadRequest() throws TimeoutException, InterruptedException {
|
||||
wireMock.stubFor(any(anyUrl())
|
||||
.willReturn(aResponse()
|
||||
.withStatus(400)));
|
||||
|
||||
Sender sender = new Sender("foobarbaz", mapper, 10, "http://localhost:" + wireMock.port() + "/gcm/send");
|
||||
CompletableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build());
|
||||
|
||||
try {
|
||||
future.get(10, TimeUnit.SECONDS);
|
||||
throw new AssertionError();
|
||||
} catch (ExecutionException e) {
|
||||
assertTrue(e.getCause() instanceof InvalidRequestException);
|
||||
}
|
||||
|
||||
verify(1, anyRequestedFor(anyUrl()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerError() throws TimeoutException, InterruptedException {
|
||||
wireMock.stubFor(any(anyUrl())
|
||||
.willReturn(aResponse()
|
||||
.withStatus(503)));
|
||||
|
||||
Sender sender = new Sender("foobarbaz", mapper, 3, "http://localhost:" + wireMock.port() + "/gcm/send");
|
||||
CompletableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build());
|
||||
|
||||
try {
|
||||
future.get(10, TimeUnit.SECONDS);
|
||||
throw new AssertionError();
|
||||
} catch (ExecutionException ee) {
|
||||
assertTrue(ee.getCause() instanceof ServerFailedException);
|
||||
}
|
||||
|
||||
verify(3, anyRequestedFor(anyUrl()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerErrorRecovery() throws InterruptedException, ExecutionException, TimeoutException {
|
||||
|
||||
wireMock.stubFor(any(anyUrl()).willReturn(aResponse().withStatus(503)));
|
||||
|
||||
Sender sender = new Sender("foobarbaz", mapper, 4, "http://localhost:" + wireMock.port() + "/gcm/send");
|
||||
CompletableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build());
|
||||
|
||||
// up to three failures can happen, with 100ms exponential backoff
|
||||
// if we end up using the fourth, and finaly try, it would be after ~700 ms
|
||||
CompletableFuture.delayedExecutor(300, TimeUnit.MILLISECONDS).execute(() ->
|
||||
wireMock.stubFor(any(anyUrl())
|
||||
.willReturn(aResponse()
|
||||
.withStatus(200)
|
||||
.withBody(fixture("fixtures/response-success.json"))))
|
||||
);
|
||||
|
||||
Result result = future.get(10, TimeUnit.SECONDS);
|
||||
|
||||
verify(new CountMatchingStrategy(CountMatchingStrategy.GREATER_THAN, 1), anyRequestedFor(anyUrl()));
|
||||
assertTrue(result.isSuccess());
|
||||
assertFalse(result.isThrottled());
|
||||
assertFalse(result.isUnregistered());
|
||||
assertEquals(result.getMessageId(), "1:08");
|
||||
assertNull(result.getError());
|
||||
assertNull(result.getCanonicalRegistrationId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNetworkError() throws TimeoutException, InterruptedException {
|
||||
|
||||
wireMock.stubFor(any(anyUrl())
|
||||
.willReturn(ok()));
|
||||
|
||||
Sender sender = new Sender("foobarbaz", mapper ,2, "http://localhost:" + wireMock.port() + "/gcm/send");
|
||||
|
||||
wireMock.stop();
|
||||
|
||||
CompletableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build());
|
||||
|
||||
try {
|
||||
future.get(10, TimeUnit.SECONDS);
|
||||
} catch (ExecutionException e) {
|
||||
assertTrue(e.getCause() instanceof IOException);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotRegistered() throws InterruptedException, ExecutionException, TimeoutException {
|
||||
|
||||
wireMock.stubFor(any(anyUrl()).willReturn(aResponse().withStatus(200)
|
||||
.withBody(fixture("fixtures/response-not-registered.json"))));
|
||||
|
||||
Sender sender = new Sender("foobarbaz", mapper,2, "http://localhost:" + wireMock.port() + "/gcm/send");
|
||||
CompletableFuture<Result> future = sender.send(Message.newBuilder()
|
||||
.withDestination("2")
|
||||
.withDataPart("message", "new message!")
|
||||
.build());
|
||||
|
||||
Result result = future.get(10, TimeUnit.SECONDS);
|
||||
|
||||
assertFalse(result.isSuccess());
|
||||
assertTrue(result.isUnregistered());
|
||||
assertFalse(result.isThrottled());
|
||||
assertEquals(result.getError(), "NotRegistered");
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.gcm.server;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.post;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.whispersystems.gcm.server.util.FixtureHelpers.fixture;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
|
||||
import com.github.tomakehurst.wiremock.junit.WireMockRule;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
public class SimultaneousSenderTest {
|
||||
|
||||
@Rule
|
||||
public WireMockRule wireMock = new WireMockRule(WireMockConfiguration.options().dynamicPort().dynamicHttpsPort());
|
||||
|
||||
private static final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
static {
|
||||
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
|
||||
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
|
||||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimultaneousSuccess() throws TimeoutException, InterruptedException, ExecutionException, JsonProcessingException {
|
||||
stubFor(post(urlPathEqualTo("/gcm/send"))
|
||||
.willReturn(aResponse()
|
||||
.withStatus(200)
|
||||
.withBody(fixture("fixtures/response-success.json"))));
|
||||
|
||||
Sender sender = new Sender("foobarbaz", mapper, 2, "http://localhost:" + wireMock.port() + "/gcm/send");
|
||||
List<CompletableFuture<Result>> results = new LinkedList<>();
|
||||
|
||||
for (int i=0;i<1000;i++) {
|
||||
results.add(sender.send(Message.newBuilder().withDestination("1").build()));
|
||||
}
|
||||
|
||||
for (CompletableFuture<Result> future : results) {
|
||||
Result result = future.get(60, TimeUnit.SECONDS);
|
||||
|
||||
if (!result.isSuccess()) {
|
||||
throw new AssertionError(result.getError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testSimultaneousFailure() throws TimeoutException, InterruptedException {
|
||||
stubFor(post(urlPathEqualTo("/gcm/send"))
|
||||
.willReturn(aResponse()
|
||||
.withStatus(503)));
|
||||
|
||||
Sender sender = new Sender("foobarbaz", mapper, 2, "http://localhost:" + wireMock.port() + "/gcm/send");
|
||||
List<CompletableFuture<Result>> futures = new LinkedList<>();
|
||||
|
||||
for (int i=0;i<1000;i++) {
|
||||
futures.add(sender.send(Message.newBuilder().withDestination("1").build()));
|
||||
}
|
||||
|
||||
for (CompletableFuture<Result> future : futures) {
|
||||
try {
|
||||
Result result = future.get(60, TimeUnit.SECONDS);
|
||||
} catch (ExecutionException e) {
|
||||
assertTrue(e.getCause().toString(), e.getCause() instanceof ServerFailedException);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.gcm.server.util;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.io.Resources;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* A set of helper method for fixture files.
|
||||
*/
|
||||
public class FixtureHelpers {
|
||||
private FixtureHelpers() { /* singleton */ }
|
||||
|
||||
/**
|
||||
* Reads the given fixture file from the classpath (e. g. {@code src/test/resources})
|
||||
* and returns its contents as a UTF-8 string.
|
||||
*
|
||||
* @param filename the filename of the fixture file
|
||||
* @return the contents of {@code src/test/resources/{filename}}
|
||||
* @throws IllegalArgumentException if an I/O error occurs.
|
||||
*/
|
||||
public static String fixture(String filename) {
|
||||
return fixture(filename, Charsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the given fixture file from the classpath (e. g. {@code src/test/resources})
|
||||
* and returns its contents as a string.
|
||||
*
|
||||
* @param filename the filename of the fixture file
|
||||
* @param charset the character set of {@code filename}
|
||||
* @return the contents of {@code src/test/resources/{filename}}
|
||||
* @throws IllegalArgumentException if an I/O error occurs.
|
||||
*/
|
||||
private static String fixture(String filename, Charset charset) {
|
||||
try {
|
||||
return Resources.toString(Resources.getResource(filename), charset).trim();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.gcm.server.util;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.whispersystems.gcm.server.util.FixtureHelpers.fixture;
|
||||
|
||||
public class JsonHelpers {
|
||||
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
public static String asJson(Object object) throws JsonProcessingException {
|
||||
return objectMapper.writeValueAsString(object);
|
||||
}
|
||||
|
||||
public static <T> T fromJson(String value, Class<T> clazz) throws IOException {
|
||||
return objectMapper.readValue(value, clazz);
|
||||
}
|
||||
|
||||
public static String jsonFixture(String filename) throws IOException {
|
||||
return objectMapper.writeValueAsString(objectMapper.readValue(fixture(filename), JsonNode.class));
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"priority" : "high",
|
||||
"collapse_key" : "collapse",
|
||||
"time_to_live" : 10,
|
||||
"delay_while_idle" : true,
|
||||
"registration_ids" : ["1"]
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"data" : {
|
||||
"key1" : "value1",
|
||||
"key2" : "value2"
|
||||
},
|
||||
"registration_ids" : ["2"]
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"registration_ids" : ["1"]
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{ "multicast_id": 216,
|
||||
"success": 0,
|
||||
"failure": 1,
|
||||
"canonical_ids": 0,
|
||||
"results": [
|
||||
{ "error": "NotRegistered"}
|
||||
]
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{ "multicast_id": 108,
|
||||
"success": 1,
|
||||
"failure": 0,
|
||||
"canonical_ids": 0,
|
||||
"results": [
|
||||
{ "message_id": "1:08" }
|
||||
]
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
<configuration>
|
||||
<!-- Turning down the wiremock logging -->
|
||||
<logger name="com.github.tomakehurst.wiremock" level="WARN"/>
|
||||
<logger name="wiremock.org" level="ERROR"/>
|
||||
<logger name="WireMock" level="WARN"/>
|
||||
<!-- wiremock has per endpoint servlet logging -->
|
||||
<logger name="/" level="WARN"/>
|
||||
</configuration>
|
||||
2
integration-tests/.gitignore
vendored
Normal file
2
integration-tests/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.libs
|
||||
src/main/resources/config.yml
|
||||
62
integration-tests/pom.xml
Normal file
62
integration-tests/pom.xml
Normal file
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>TextSecureServer</artifactId>
|
||||
<groupId>org.whispersystems.textsecure</groupId>
|
||||
<version>JGITVER</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>integration-tests</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.whispersystems.textsecure</groupId>
|
||||
<artifactId>service</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>dynamodb</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.1.2</version>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>**</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<version>3.1.2</version>
|
||||
<configuration>
|
||||
<additionalClasspathElements>
|
||||
<additionalClasspathElement>${project.basedir}/.libs/software.amazon.awssdk-sso.jar</additionalClasspathElement>
|
||||
</additionalClasspathElements>
|
||||
<includes>
|
||||
<include>**/*.java</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.google.cloud.tools</groupId>
|
||||
<artifactId>jib-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<!-- we don't want jib to execute on this module -->
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.integration;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
import org.signal.libsignal.protocol.IdentityKey;
|
||||
import org.signal.libsignal.protocol.ecc.Curve;
|
||||
import org.signal.libsignal.protocol.ecc.ECPublicKey;
|
||||
|
||||
public final class Codecs {
|
||||
|
||||
private Codecs() {
|
||||
// utility class
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface CheckedFunction<T, R> {
|
||||
R apply(T t) throws Exception;
|
||||
}
|
||||
|
||||
public static class Base64BasedSerializer<T> extends JsonSerializer<T> {
|
||||
|
||||
private final CheckedFunction<T, byte[]> mapper;
|
||||
|
||||
public Base64BasedSerializer(final CheckedFunction<T, byte[]> mapper) {
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(final T value, final JsonGenerator gen, final SerializerProvider serializers) throws IOException {
|
||||
try {
|
||||
gen.writeString(Base64.getEncoder().withoutPadding().encodeToString(mapper.apply(value)));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Base64BasedDeserializer<T> extends JsonDeserializer<T> {
|
||||
|
||||
private final CheckedFunction<byte[], T> mapper;
|
||||
|
||||
public Base64BasedDeserializer(final CheckedFunction<byte[], T> mapper) {
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException {
|
||||
try {
|
||||
return mapper.apply(Base64.getDecoder().decode(p.getValueAsString()));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ByteArraySerializer extends Base64BasedSerializer<byte[]> {
|
||||
public ByteArraySerializer() {
|
||||
super(bytes -> bytes);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ByteArrayDeserializer extends Base64BasedDeserializer<byte[]> {
|
||||
public ByteArrayDeserializer() {
|
||||
super(bytes -> bytes);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ECPublicKeySerializer extends Base64BasedSerializer<ECPublicKey> {
|
||||
public ECPublicKeySerializer() {
|
||||
super(ECPublicKey::serialize);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ECPublicKeyDeserializer extends Base64BasedDeserializer<ECPublicKey> {
|
||||
public ECPublicKeyDeserializer() {
|
||||
super(bytes -> Curve.decodePoint(bytes, 0));
|
||||
}
|
||||
}
|
||||
|
||||
public static class IdentityKeySerializer extends Base64BasedSerializer<IdentityKey> {
|
||||
public IdentityKeySerializer() {
|
||||
super(IdentityKey::serialize);
|
||||
}
|
||||
}
|
||||
|
||||
public static class IdentityKeyDeserializer extends Base64BasedDeserializer<IdentityKey> {
|
||||
public IdentityKeyDeserializer() {
|
||||
super(bytes -> new IdentityKey(bytes, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.integration;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.signal.integration.config.Config;
|
||||
import org.whispersystems.textsecuregcm.registration.VerificationSession;
|
||||
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswords;
|
||||
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.VerificationSessionManager;
|
||||
import org.whispersystems.textsecuregcm.storage.VerificationSessions;
|
||||
import org.whispersystems.textsecuregcm.util.DynamoDbFromConfig;
|
||||
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
|
||||
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
|
||||
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
|
||||
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
|
||||
|
||||
public class IntegrationTools {
|
||||
|
||||
private final RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager;
|
||||
|
||||
private final VerificationSessionManager verificationSessionManager;
|
||||
|
||||
|
||||
public static IntegrationTools create(final Config config) {
|
||||
final AwsCredentialsProvider credentialsProvider = DefaultCredentialsProvider.builder().build();
|
||||
|
||||
final DynamoDbAsyncClient dynamoDbAsyncClient = DynamoDbFromConfig.asyncClient(
|
||||
config.dynamoDbClientConfiguration(),
|
||||
credentialsProvider);
|
||||
|
||||
final DynamoDbClient dynamoDbClient = DynamoDbFromConfig.client(
|
||||
config.dynamoDbClientConfiguration(),
|
||||
credentialsProvider);
|
||||
|
||||
final RegistrationRecoveryPasswords registrationRecoveryPasswords = new RegistrationRecoveryPasswords(
|
||||
config.dynamoDbTables().registrationRecovery(), Duration.ofDays(1), dynamoDbClient, dynamoDbAsyncClient);
|
||||
|
||||
final VerificationSessions verificationSessions = new VerificationSessions(
|
||||
dynamoDbAsyncClient, config.dynamoDbTables().verificationSessions(), Clock.systemUTC());
|
||||
|
||||
return new IntegrationTools(
|
||||
new RegistrationRecoveryPasswordsManager(registrationRecoveryPasswords),
|
||||
new VerificationSessionManager(verificationSessions)
|
||||
);
|
||||
}
|
||||
|
||||
private IntegrationTools(
|
||||
final RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager,
|
||||
final VerificationSessionManager verificationSessionManager) {
|
||||
this.registrationRecoveryPasswordsManager = registrationRecoveryPasswordsManager;
|
||||
this.verificationSessionManager = verificationSessionManager;
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> populateRecoveryPassword(final String e164, final byte[] password) {
|
||||
return registrationRecoveryPasswordsManager.storeForCurrentNumber(e164, password);
|
||||
}
|
||||
|
||||
public CompletableFuture<Optional<String>> peekVerificationSessionPushChallenge(final String sessionId) {
|
||||
return verificationSessionManager.findForId(sessionId)
|
||||
.thenApply(maybeSession -> maybeSession.map(VerificationSession::pushChallenge));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.integration;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.google.common.io.Resources;
|
||||
import com.google.common.net.HttpHeaders;
|
||||
import java.io.IOException;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executors;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.signal.integration.config.Config;
|
||||
import org.signal.libsignal.protocol.IdentityKey;
|
||||
import org.signal.libsignal.protocol.ecc.Curve;
|
||||
import org.signal.libsignal.protocol.ecc.ECKeyPair;
|
||||
import org.signal.libsignal.protocol.ecc.ECPublicKey;
|
||||
import org.signal.libsignal.protocol.kem.KEMKeyPair;
|
||||
import org.signal.libsignal.protocol.kem.KEMKeyType;
|
||||
import org.signal.libsignal.protocol.kem.KEMPublicKey;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
|
||||
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
|
||||
import org.whispersystems.textsecuregcm.entities.AccountIdentityResponse;
|
||||
import org.whispersystems.textsecuregcm.entities.ECSignedPreKey;
|
||||
import org.whispersystems.textsecuregcm.entities.KEMSignedPreKey;
|
||||
import org.whispersystems.textsecuregcm.entities.RegistrationRequest;
|
||||
import org.whispersystems.textsecuregcm.http.FaultTolerantHttpClient;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.util.HeaderUtils;
|
||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||
|
||||
public final class Operations {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
private static final Config CONFIG = loadConfigFromClasspath("config.yml");
|
||||
|
||||
private static final IntegrationTools INTEGRATION_TOOLS = IntegrationTools.create(CONFIG);
|
||||
|
||||
private static final String USER_AGENT = "integration-test";
|
||||
|
||||
private static final FaultTolerantHttpClient CLIENT = buildClient();
|
||||
|
||||
|
||||
private Operations() {
|
||||
// utility class
|
||||
}
|
||||
|
||||
public static TestUser newRegisteredUser(final String number) {
|
||||
final byte[] registrationPassword = randomBytes(32);
|
||||
final String accountPassword = Base64.getEncoder().encodeToString(randomBytes(32));
|
||||
|
||||
final TestUser user = TestUser.create(number, accountPassword, registrationPassword);
|
||||
final AccountAttributes accountAttributes = user.accountAttributes();
|
||||
|
||||
INTEGRATION_TOOLS.populateRecoveryPassword(number, registrationPassword).join();
|
||||
|
||||
final ECKeyPair aciIdentityKeyPair = Curve.generateKeyPair();
|
||||
final ECKeyPair pniIdentityKeyPair = Curve.generateKeyPair();
|
||||
|
||||
// register account
|
||||
final RegistrationRequest registrationRequest = new RegistrationRequest(null,
|
||||
registrationPassword,
|
||||
accountAttributes,
|
||||
true,
|
||||
new IdentityKey(aciIdentityKeyPair.getPublicKey()),
|
||||
new IdentityKey(pniIdentityKeyPair.getPublicKey()),
|
||||
generateSignedECPreKey(1, aciIdentityKeyPair),
|
||||
generateSignedECPreKey(2, pniIdentityKeyPair),
|
||||
generateSignedKEMPreKey(3, aciIdentityKeyPair),
|
||||
generateSignedKEMPreKey(4, pniIdentityKeyPair),
|
||||
Optional.empty(),
|
||||
Optional.empty());
|
||||
|
||||
final AccountIdentityResponse registrationResponse = apiPost("/v1/registration", registrationRequest)
|
||||
.authorized(number, accountPassword)
|
||||
.executeExpectSuccess(AccountIdentityResponse.class);
|
||||
|
||||
user.setAciUuid(registrationResponse.uuid());
|
||||
user.setPniUuid(registrationResponse.pni());
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
public record PrescribedVerificationNumber(String number, String verificationCode) {}
|
||||
public static PrescribedVerificationNumber prescribedVerificationNumber() {
|
||||
return new PrescribedVerificationNumber(
|
||||
CONFIG.prescribedRegistrationNumber(),
|
||||
CONFIG.prescribedRegistrationCode());
|
||||
}
|
||||
|
||||
public static void deleteUser(final TestUser user) {
|
||||
apiDelete("/v1/accounts/me").authorized(user).executeExpectSuccess();
|
||||
}
|
||||
|
||||
public static String peekVerificationSessionPushChallenge(final String sessionId) {
|
||||
return INTEGRATION_TOOLS.peekVerificationSessionPushChallenge(sessionId).join()
|
||||
.orElseThrow(() -> new RuntimeException("push challenge not found for the verification session"));
|
||||
}
|
||||
|
||||
public static <T> T sendEmptyRequestAuthenticated(
|
||||
final String endpoint,
|
||||
final String method,
|
||||
final String username,
|
||||
final String password,
|
||||
final Class<T> outputType) {
|
||||
try {
|
||||
final HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(serverUri(endpoint, Collections.emptyList()))
|
||||
.method(method, HttpRequest.BodyPublishers.noBody())
|
||||
.header(HttpHeaders.AUTHORIZATION, HeaderUtils.basicAuthHeader(username, password))
|
||||
.header(HttpHeaders.CONTENT_TYPE, "application/json")
|
||||
.build();
|
||||
return CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8))
|
||||
.whenComplete((response, error) -> {
|
||||
if (error != null) {
|
||||
logger.error("request error", error);
|
||||
error.printStackTrace();
|
||||
} else {
|
||||
logger.info("response: {}", response.statusCode());
|
||||
System.out.println("response: " + response.statusCode() + ", " + response.body());
|
||||
}
|
||||
})
|
||||
.thenApply(response -> {
|
||||
try {
|
||||
return outputType.equals(Void.class)
|
||||
? null
|
||||
: SystemMapper.jsonMapper().readValue(response.body(), outputType);
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
})
|
||||
.get();
|
||||
} catch (final Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] randomBytes(int numBytes) {
|
||||
final byte[] bytes = new byte[numBytes];
|
||||
new SecureRandom().nextBytes(bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static RequestBuilder apiGet(final String endpoint) {
|
||||
return new RequestBuilder(HttpRequest.newBuilder().GET(), endpoint);
|
||||
}
|
||||
|
||||
public static RequestBuilder apiDelete(final String endpoint) {
|
||||
return new RequestBuilder(HttpRequest.newBuilder().DELETE(), endpoint);
|
||||
}
|
||||
|
||||
public static <R> RequestBuilder apiPost(final String endpoint, final R input) {
|
||||
return RequestBuilder.withJsonBody(endpoint, "POST", input);
|
||||
}
|
||||
|
||||
public static <R> RequestBuilder apiPut(final String endpoint, final R input) {
|
||||
return RequestBuilder.withJsonBody(endpoint, "PUT", input);
|
||||
}
|
||||
|
||||
public static <R> RequestBuilder apiPatch(final String endpoint, final R input) {
|
||||
return RequestBuilder.withJsonBody(endpoint, "PATCH", input);
|
||||
}
|
||||
|
||||
private static URI serverUri(final String endpoint, final List<String> queryParams) {
|
||||
final String query = queryParams.isEmpty()
|
||||
? StringUtils.EMPTY
|
||||
: "?" + String.join("&", queryParams);
|
||||
return URI.create("https://" + CONFIG.domain() + endpoint + query);
|
||||
}
|
||||
|
||||
public static class RequestBuilder {
|
||||
|
||||
private final HttpRequest.Builder builder;
|
||||
|
||||
private final String endpoint;
|
||||
|
||||
private final List<String> queryParams = new ArrayList<>();
|
||||
|
||||
|
||||
private RequestBuilder(final HttpRequest.Builder builder, final String endpoint) {
|
||||
this.builder = builder;
|
||||
this.endpoint = endpoint;
|
||||
}
|
||||
|
||||
private static <R> RequestBuilder withJsonBody(final String endpoint, final String method, final R input) {
|
||||
try {
|
||||
final byte[] body = SystemMapper.jsonMapper().writeValueAsBytes(input);
|
||||
return new RequestBuilder(HttpRequest.newBuilder()
|
||||
.header(HttpHeaders.CONTENT_TYPE, "application/json")
|
||||
.method(method, HttpRequest.BodyPublishers.ofByteArray(body)), endpoint);
|
||||
} catch (final JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public RequestBuilder authorized(final TestUser user) {
|
||||
return authorized(user, Device.PRIMARY_ID);
|
||||
}
|
||||
|
||||
public RequestBuilder authorized(final TestUser user, final byte deviceId) {
|
||||
final String username = "%s.%d".formatted(user.aciUuid().toString(), deviceId);
|
||||
return authorized(username, user.accountPassword());
|
||||
}
|
||||
|
||||
public RequestBuilder authorized(final String username, final String password) {
|
||||
builder.header(HttpHeaders.AUTHORIZATION, HeaderUtils.basicAuthHeader(username, password));
|
||||
return this;
|
||||
}
|
||||
|
||||
public RequestBuilder queryParam(final String key, final String value) {
|
||||
queryParams.add("%s=%s".formatted(key, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
public RequestBuilder header(final String name, final String value) {
|
||||
builder.header(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Pair<Integer, Void> execute() {
|
||||
return execute(Void.class);
|
||||
}
|
||||
|
||||
public Pair<Integer, Void> executeExpectSuccess() {
|
||||
final Pair<Integer, Void> execute = execute();
|
||||
Validate.isTrue(
|
||||
execute.getLeft() >= 200 && execute.getLeft() < 300,
|
||||
"Unexpected response code: %d",
|
||||
execute.getLeft());
|
||||
return execute;
|
||||
}
|
||||
|
||||
public <T> T executeExpectSuccess(final Class<T> expectedType) {
|
||||
final Pair<Integer, T> execute = execute(expectedType);
|
||||
return requireNonNull(execute.getRight());
|
||||
}
|
||||
|
||||
public void executeExpectStatusCode(final int expectedStatusCode) {
|
||||
final Pair<Integer, Void> execute = execute(Void.class);
|
||||
Validate.isTrue(
|
||||
execute.getLeft() == expectedStatusCode,
|
||||
"Unexpected response code: %d",
|
||||
execute.getLeft()
|
||||
);
|
||||
}
|
||||
|
||||
public <T> Pair<Integer, T> execute(final Class<T> expectedType) {
|
||||
builder.uri(serverUri(endpoint, queryParams))
|
||||
.header(HttpHeaders.USER_AGENT, USER_AGENT);
|
||||
return CLIENT.sendAsync(builder.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8))
|
||||
.whenComplete((response, error) -> {
|
||||
if (error != null) {
|
||||
logger.error("request error", error);
|
||||
error.printStackTrace();
|
||||
}
|
||||
})
|
||||
.thenApply(response -> {
|
||||
try {
|
||||
final T result = expectedType.equals(Void.class)
|
||||
? null
|
||||
: SystemMapper.jsonMapper().readValue(response.body(), expectedType);
|
||||
return Pair.of(response.statusCode(), result);
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
})
|
||||
.join();
|
||||
}
|
||||
}
|
||||
|
||||
private static FaultTolerantHttpClient buildClient() {
|
||||
try {
|
||||
return FaultTolerantHttpClient.newBuilder()
|
||||
.withName("integration-test")
|
||||
.withExecutor(Executors.newFixedThreadPool(16))
|
||||
.withRetryExecutor(Executors.newSingleThreadScheduledExecutor())
|
||||
.withCircuitBreaker(new CircuitBreakerConfiguration())
|
||||
.withTrustedServerCertificates(CONFIG.rootCert())
|
||||
.build();
|
||||
} catch (final CertificateException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Config loadConfigFromClasspath(final String filename) {
|
||||
try {
|
||||
final URL configFileUrl = Resources.getResource(filename);
|
||||
return SystemMapper.yamlMapper().readValue(Resources.toByteArray(configFileUrl), Config.class);
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static ECSignedPreKey generateSignedECPreKey(long id, final ECKeyPair identityKeyPair) {
|
||||
final ECPublicKey pubKey = Curve.generateKeyPair().getPublicKey();
|
||||
final byte[] sig = identityKeyPair.getPrivateKey().calculateSignature(pubKey.serialize());
|
||||
return new ECSignedPreKey(id, pubKey, sig);
|
||||
}
|
||||
|
||||
private static KEMSignedPreKey generateSignedKEMPreKey(long id, final ECKeyPair identityKeyPair) {
|
||||
final KEMPublicKey pubKey = KEMKeyPair.generate(KEMKeyType.KYBER_1024).getPublicKey();
|
||||
final byte[] sig = identityKeyPair.getPrivateKey().calculateSignature(pubKey.serialize());
|
||||
return new KEMSignedPreKey(id, pubKey, sig);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.integration;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.signal.libsignal.protocol.IdentityKeyPair;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.signal.libsignal.protocol.ecc.Curve;
|
||||
import org.signal.libsignal.protocol.ecc.ECKeyPair;
|
||||
import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
|
||||
|
||||
public class TestDevice {
|
||||
|
||||
private final byte deviceId;
|
||||
|
||||
private final Map<Integer, Pair<IdentityKeyPair, SignedPreKeyRecord>> signedPreKeys = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
public static TestDevice create(
|
||||
final byte deviceId,
|
||||
final IdentityKeyPair aciIdentityKeyPair,
|
||||
final IdentityKeyPair pniIdentityKeyPair) {
|
||||
final TestDevice device = new TestDevice(deviceId);
|
||||
device.addSignedPreKey(aciIdentityKeyPair);
|
||||
device.addSignedPreKey(pniIdentityKeyPair);
|
||||
return device;
|
||||
}
|
||||
|
||||
public TestDevice(final byte deviceId) {
|
||||
this.deviceId = deviceId;
|
||||
}
|
||||
|
||||
public byte deviceId() {
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
public SignedPreKeyRecord latestSignedPreKey(final IdentityKeyPair identity) {
|
||||
final int id = signedPreKeys.entrySet()
|
||||
.stream()
|
||||
.filter(p -> p.getValue().getLeft().equals(identity))
|
||||
.mapToInt(Map.Entry::getKey)
|
||||
.max()
|
||||
.orElseThrow();
|
||||
return signedPreKeys.get(id).getRight();
|
||||
}
|
||||
|
||||
public SignedPreKeyRecord addSignedPreKey(final IdentityKeyPair identity) {
|
||||
try {
|
||||
final int nextId = signedPreKeys.keySet().stream().mapToInt(k -> k + 1).max().orElse(0);
|
||||
final ECKeyPair keyPair = Curve.generateKeyPair();
|
||||
final byte[] signature = Curve.calculateSignature(identity.getPrivateKey(), keyPair.getPublicKey().serialize());
|
||||
final SignedPreKeyRecord signedPreKeyRecord = new SignedPreKeyRecord(nextId, System.currentTimeMillis(), keyPair, signature);
|
||||
signedPreKeys.put(nextId, Pair.of(identity, signedPreKeyRecord));
|
||||
return signedPreKeyRecord;
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.integration;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import java.security.SecureRandom;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import org.signal.libsignal.protocol.IdentityKey;
|
||||
import org.signal.libsignal.protocol.IdentityKeyPair;
|
||||
import org.signal.libsignal.protocol.ecc.ECPublicKey;
|
||||
import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
|
||||
import org.signal.libsignal.protocol.util.KeyHelper;
|
||||
import org.whispersystems.textsecuregcm.auth.UnidentifiedAccessUtil;
|
||||
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
|
||||
public class TestUser {
|
||||
|
||||
private final int registrationId;
|
||||
|
||||
private final int pniRegistrationId;
|
||||
|
||||
private final IdentityKeyPair aciIdentityKey;
|
||||
|
||||
private final Map<Byte, TestDevice> devices = new ConcurrentHashMap<>();
|
||||
|
||||
private final byte[] unidentifiedAccessKey;
|
||||
|
||||
private String phoneNumber;
|
||||
|
||||
private IdentityKeyPair pniIdentityKey;
|
||||
|
||||
private String accountPassword;
|
||||
|
||||
private byte[] registrationPassword;
|
||||
|
||||
private UUID aciUuid;
|
||||
|
||||
private UUID pniUuid;
|
||||
|
||||
|
||||
public static TestUser create(final String phoneNumber, final String accountPassword, final byte[] registrationPassword) {
|
||||
// ACI identity key pair
|
||||
final IdentityKeyPair aciIdentityKey = IdentityKeyPair.generate();
|
||||
// PNI identity key pair
|
||||
final IdentityKeyPair pniIdentityKey = IdentityKeyPair.generate();
|
||||
// registration id
|
||||
final int registrationId = KeyHelper.generateRegistrationId(false);
|
||||
final int pniRegistrationId = KeyHelper.generateRegistrationId(false);
|
||||
// uak
|
||||
final byte[] unidentifiedAccessKey = new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH];
|
||||
new SecureRandom().nextBytes(unidentifiedAccessKey);
|
||||
|
||||
return new TestUser(
|
||||
registrationId,
|
||||
pniRegistrationId,
|
||||
aciIdentityKey,
|
||||
phoneNumber,
|
||||
pniIdentityKey,
|
||||
unidentifiedAccessKey,
|
||||
accountPassword,
|
||||
registrationPassword);
|
||||
}
|
||||
|
||||
public TestUser(
|
||||
final int registrationId,
|
||||
final int pniRegistrationId,
|
||||
final IdentityKeyPair aciIdentityKey,
|
||||
final String phoneNumber,
|
||||
final IdentityKeyPair pniIdentityKey,
|
||||
final byte[] unidentifiedAccessKey,
|
||||
final String accountPassword,
|
||||
final byte[] registrationPassword) {
|
||||
this.registrationId = registrationId;
|
||||
this.pniRegistrationId = pniRegistrationId;
|
||||
this.aciIdentityKey = aciIdentityKey;
|
||||
this.phoneNumber = phoneNumber;
|
||||
this.pniIdentityKey = pniIdentityKey;
|
||||
this.unidentifiedAccessKey = unidentifiedAccessKey;
|
||||
this.accountPassword = accountPassword;
|
||||
this.registrationPassword = registrationPassword;
|
||||
devices.put(Device.PRIMARY_ID, TestDevice.create(Device.PRIMARY_ID, aciIdentityKey, pniIdentityKey));
|
||||
}
|
||||
|
||||
public int registrationId() {
|
||||
return registrationId;
|
||||
}
|
||||
|
||||
public IdentityKeyPair aciIdentityKey() {
|
||||
return aciIdentityKey;
|
||||
}
|
||||
|
||||
public String phoneNumber() {
|
||||
return phoneNumber;
|
||||
}
|
||||
|
||||
public IdentityKeyPair pniIdentityKey() {
|
||||
return pniIdentityKey;
|
||||
}
|
||||
|
||||
public String accountPassword() {
|
||||
return accountPassword;
|
||||
}
|
||||
|
||||
public byte[] registrationPassword() {
|
||||
return registrationPassword;
|
||||
}
|
||||
|
||||
public UUID aciUuid() {
|
||||
return aciUuid;
|
||||
}
|
||||
|
||||
public UUID pniUuid() {
|
||||
return pniUuid;
|
||||
}
|
||||
|
||||
public AccountAttributes accountAttributes() {
|
||||
return new AccountAttributes(true, registrationId, pniRegistrationId, "".getBytes(StandardCharsets.UTF_8), "", true, new Device.DeviceCapabilities(false, false, false))
|
||||
.withUnidentifiedAccessKey(unidentifiedAccessKey)
|
||||
.withRecoveryPassword(registrationPassword);
|
||||
}
|
||||
|
||||
public void setAciUuid(final UUID aciUuid) {
|
||||
this.aciUuid = aciUuid;
|
||||
}
|
||||
|
||||
public void setPniUuid(final UUID pniUuid) {
|
||||
this.pniUuid = pniUuid;
|
||||
}
|
||||
|
||||
public void setPhoneNumber(final String phoneNumber) {
|
||||
this.phoneNumber = phoneNumber;
|
||||
}
|
||||
|
||||
public void setPniIdentityKey(final IdentityKeyPair pniIdentityKey) {
|
||||
this.pniIdentityKey = pniIdentityKey;
|
||||
}
|
||||
|
||||
public void setAccountPassword(final String accountPassword) {
|
||||
this.accountPassword = accountPassword;
|
||||
}
|
||||
|
||||
public void setRegistrationPassword(final byte[] registrationPassword) {
|
||||
this.registrationPassword = registrationPassword;
|
||||
}
|
||||
|
||||
public PreKeySetPublicView preKeys(final byte deviceId, final boolean pni) {
|
||||
final IdentityKeyPair identity = pni
|
||||
? pniIdentityKey
|
||||
: aciIdentityKey;
|
||||
final TestDevice device = requireNonNull(devices.get(deviceId));
|
||||
final SignedPreKeyRecord signedPreKeyRecord = device.latestSignedPreKey(identity);
|
||||
return new PreKeySetPublicView(
|
||||
Collections.emptyList(),
|
||||
identity.getPublicKey(),
|
||||
new SignedPreKeyPublicView(
|
||||
signedPreKeyRecord.getId(),
|
||||
signedPreKeyRecord.getKeyPair().getPublicKey(),
|
||||
signedPreKeyRecord.getSignature()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public record SignedPreKeyPublicView(
|
||||
int keyId,
|
||||
@JsonSerialize(using = Codecs.ECPublicKeySerializer.class)
|
||||
@JsonDeserialize(using = Codecs.ECPublicKeyDeserializer.class)
|
||||
ECPublicKey publicKey,
|
||||
@JsonSerialize(using = Codecs.ByteArraySerializer.class)
|
||||
@JsonDeserialize(using = Codecs.ByteArrayDeserializer.class)
|
||||
byte[] signature) {
|
||||
}
|
||||
|
||||
public record PreKeySetPublicView(
|
||||
List<String> preKeys,
|
||||
@JsonSerialize(using = Codecs.IdentityKeySerializer.class)
|
||||
@JsonDeserialize(using = Codecs.IdentityKeyDeserializer.class)
|
||||
IdentityKey identityKey,
|
||||
SignedPreKeyPublicView signedPreKey) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.integration.config;
|
||||
|
||||
import org.whispersystems.textsecuregcm.configuration.DynamoDbClientConfiguration;
|
||||
|
||||
public record Config(String domain,
|
||||
String rootCert,
|
||||
DynamoDbClientConfiguration dynamoDbClientConfiguration,
|
||||
DynamoDbTables dynamoDbTables,
|
||||
String prescribedRegistrationNumber,
|
||||
String prescribedRegistrationCode) {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.integration.config;
|
||||
|
||||
public record DynamoDbTables(String registrationRecovery,
|
||||
String verificationSessions) {
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.integration;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.signal.libsignal.usernames.BaseUsernameException;
|
||||
import org.signal.libsignal.usernames.Username;
|
||||
import org.whispersystems.textsecuregcm.entities.AccountIdentifierResponse;
|
||||
import org.whispersystems.textsecuregcm.entities.AccountIdentityResponse;
|
||||
import org.whispersystems.textsecuregcm.entities.ConfirmUsernameHashRequest;
|
||||
import org.whispersystems.textsecuregcm.entities.ReserveUsernameHashRequest;
|
||||
import org.whispersystems.textsecuregcm.entities.ReserveUsernameHashResponse;
|
||||
import org.whispersystems.textsecuregcm.entities.UsernameHashResponse;
|
||||
import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier;
|
||||
|
||||
public class AccountTest {
|
||||
|
||||
@Test
|
||||
public void testCreateAccount() throws Exception {
|
||||
final TestUser user = Operations.newRegisteredUser("+19995550101");
|
||||
try {
|
||||
final Pair<Integer, AccountIdentityResponse> execute = Operations.apiGet("/v1/accounts/whoami")
|
||||
.authorized(user)
|
||||
.execute(AccountIdentityResponse.class);
|
||||
assertEquals(HttpStatus.SC_OK, execute.getLeft());
|
||||
} finally {
|
||||
Operations.deleteUser(user);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateAccountAtomic() throws Exception {
|
||||
final TestUser user = Operations.newRegisteredUser("+19995550201");
|
||||
try {
|
||||
final Pair<Integer, AccountIdentityResponse> execute = Operations.apiGet("/v1/accounts/whoami")
|
||||
.authorized(user)
|
||||
.execute(AccountIdentityResponse.class);
|
||||
assertEquals(HttpStatus.SC_OK, execute.getLeft());
|
||||
} finally {
|
||||
Operations.deleteUser(user);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsernameOperations() throws Exception {
|
||||
final TestUser user = Operations.newRegisteredUser("+19995550102");
|
||||
try {
|
||||
verifyFullUsernameLifecycle(user);
|
||||
// no do it again to check changing usernames
|
||||
verifyFullUsernameLifecycle(user);
|
||||
} finally {
|
||||
Operations.deleteUser(user);
|
||||
}
|
||||
}
|
||||
|
||||
private static void verifyFullUsernameLifecycle(final TestUser user) throws BaseUsernameException {
|
||||
final String preferred = "test";
|
||||
final List<Username> candidates = Username.candidatesFrom(preferred, preferred.length(), preferred.length() + 1);
|
||||
|
||||
// reserve a username
|
||||
final ReserveUsernameHashRequest reserveUsernameHashRequest = new ReserveUsernameHashRequest(
|
||||
candidates.stream().map(Username::getHash).toList());
|
||||
// try unauthorized
|
||||
Operations
|
||||
.apiPut("/v1/accounts/username_hash/reserve", reserveUsernameHashRequest)
|
||||
.executeExpectStatusCode(HttpStatus.SC_UNAUTHORIZED);
|
||||
|
||||
final ReserveUsernameHashResponse reserveUsernameHashResponse = Operations
|
||||
.apiPut("/v1/accounts/username_hash/reserve", reserveUsernameHashRequest)
|
||||
.authorized(user)
|
||||
.executeExpectSuccess(ReserveUsernameHashResponse.class);
|
||||
|
||||
// find which one is the reserved username
|
||||
final byte[] reservedHash = reserveUsernameHashResponse.usernameHash();
|
||||
final Username reservedUsername = candidates.stream()
|
||||
.filter(u -> Arrays.equals(u.getHash(), reservedHash))
|
||||
.findAny()
|
||||
.orElseThrow();
|
||||
|
||||
// confirm a username
|
||||
final ConfirmUsernameHashRequest confirmUsernameHashRequest = new ConfirmUsernameHashRequest(
|
||||
reservedUsername.getHash(),
|
||||
reservedUsername.generateProof(),
|
||||
"cluck cluck i'm a parrot".getBytes()
|
||||
);
|
||||
// try unauthorized
|
||||
Operations
|
||||
.apiPut("/v1/accounts/username_hash/confirm", confirmUsernameHashRequest)
|
||||
.executeExpectStatusCode(HttpStatus.SC_UNAUTHORIZED);
|
||||
Operations
|
||||
.apiPut("/v1/accounts/username_hash/confirm", confirmUsernameHashRequest)
|
||||
.authorized(user)
|
||||
.executeExpectSuccess(UsernameHashResponse.class);
|
||||
|
||||
|
||||
// lookup username
|
||||
final AccountIdentifierResponse accountIdentifierResponse = Operations
|
||||
.apiGet("/v1/accounts/username_hash/" + Base64.getUrlEncoder().encodeToString(reservedHash))
|
||||
.executeExpectSuccess(AccountIdentifierResponse.class);
|
||||
assertEquals(new AciServiceIdentifier(user.aciUuid()), accountIdentifierResponse.uuid());
|
||||
// try authorized
|
||||
Operations
|
||||
.apiGet("/v1/accounts/username_hash/" + Base64.getUrlEncoder().encodeToString(reservedHash))
|
||||
.authorized(user)
|
||||
.executeExpectStatusCode(HttpStatus.SC_BAD_REQUEST);
|
||||
|
||||
// delete username
|
||||
Operations
|
||||
.apiDelete("/v1/accounts/username_hash")
|
||||
.authorized(user)
|
||||
.executeExpectSuccess();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.integration;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.whispersystems.textsecuregcm.entities.IncomingMessage;
|
||||
import org.whispersystems.textsecuregcm.entities.IncomingMessageList;
|
||||
import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntityList;
|
||||
import org.whispersystems.textsecuregcm.entities.SendMessageResponse;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
|
||||
public class MessagingTest {
|
||||
|
||||
@Test
|
||||
public void testSendMessageUnsealed() {
|
||||
final TestUser userA = Operations.newRegisteredUser("+19995550102");
|
||||
final TestUser userB = Operations.newRegisteredUser("+19995550103");
|
||||
|
||||
try {
|
||||
final byte[] expectedContent = "Hello, World!".getBytes(StandardCharsets.UTF_8);
|
||||
final String contentBase64 = Base64.getEncoder().encodeToString(expectedContent);
|
||||
final IncomingMessage message = new IncomingMessage(1, Device.PRIMARY_ID, userB.registrationId(), contentBase64);
|
||||
final IncomingMessageList messages = new IncomingMessageList(List.of(message), false, true, System.currentTimeMillis());
|
||||
|
||||
final Pair<Integer, SendMessageResponse> sendMessage = Operations
|
||||
.apiPut("/v1/messages/%s".formatted(userB.aciUuid().toString()), messages)
|
||||
.authorized(userA)
|
||||
.execute(SendMessageResponse.class);
|
||||
|
||||
final Pair<Integer, OutgoingMessageEntityList> receiveMessages = Operations.apiGet("/v1/messages")
|
||||
.authorized(userB)
|
||||
.execute(OutgoingMessageEntityList.class);
|
||||
|
||||
final byte[] actualContent = receiveMessages.getRight().messages().get(0).content();
|
||||
assertArrayEquals(expectedContent, actualContent);
|
||||
} finally {
|
||||
Operations.deleteUser(userA);
|
||||
Operations.deleteUser(userB);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.integration;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.whispersystems.textsecuregcm.entities.CreateVerificationSessionRequest;
|
||||
import org.whispersystems.textsecuregcm.entities.SubmitVerificationCodeRequest;
|
||||
import org.whispersystems.textsecuregcm.entities.UpdateVerificationSessionRequest;
|
||||
import org.whispersystems.textsecuregcm.entities.VerificationCodeRequest;
|
||||
import org.whispersystems.textsecuregcm.entities.VerificationSessionResponse;
|
||||
|
||||
public class RegistrationTest {
|
||||
|
||||
@Test
|
||||
public void testRegistration() throws Exception {
|
||||
final UpdateVerificationSessionRequest originalRequest = new UpdateVerificationSessionRequest(
|
||||
"test", UpdateVerificationSessionRequest.PushTokenType.FCM, null, null, null, null);
|
||||
|
||||
final Operations.PrescribedVerificationNumber params = Operations.prescribedVerificationNumber();
|
||||
final CreateVerificationSessionRequest input = new CreateVerificationSessionRequest(params.number(),
|
||||
originalRequest);
|
||||
|
||||
final VerificationSessionResponse verificationSessionResponse = Operations
|
||||
.apiPost("/v1/verification/session", input)
|
||||
.executeExpectSuccess(VerificationSessionResponse.class);
|
||||
|
||||
final String sessionId = verificationSessionResponse.id();
|
||||
final String pushChallenge = Operations.peekVerificationSessionPushChallenge(sessionId);
|
||||
|
||||
// supply push challenge
|
||||
final UpdateVerificationSessionRequest updatedRequest = new UpdateVerificationSessionRequest(
|
||||
"test", UpdateVerificationSessionRequest.PushTokenType.FCM, pushChallenge, null, null, null);
|
||||
final VerificationSessionResponse pushChallengeSupplied = Operations
|
||||
.apiPatch("/v1/verification/session/%s".formatted(sessionId), updatedRequest)
|
||||
.executeExpectSuccess(VerificationSessionResponse.class);
|
||||
|
||||
Assertions.assertTrue(pushChallengeSupplied.allowedToRequestCode());
|
||||
|
||||
// request code
|
||||
final VerificationCodeRequest verificationCodeRequest = new VerificationCodeRequest(
|
||||
VerificationCodeRequest.Transport.SMS, "android-ng");
|
||||
|
||||
final VerificationSessionResponse codeRequested = Operations
|
||||
.apiPost("/v1/verification/session/%s/code".formatted(sessionId), verificationCodeRequest)
|
||||
.executeExpectSuccess(VerificationSessionResponse.class);
|
||||
|
||||
// verify code
|
||||
final SubmitVerificationCodeRequest submitVerificationCodeRequest = new SubmitVerificationCodeRequest(
|
||||
params.verificationCode());
|
||||
final VerificationSessionResponse codeVerified = Operations
|
||||
.apiPut("/v1/verification/session/%s/code".formatted(sessionId), submitVerificationCodeRequest)
|
||||
.executeExpectSuccess(VerificationSessionResponse.class);
|
||||
}
|
||||
}
|
||||
308
mvnw
vendored
Executable file
308
mvnw
vendored
Executable file
@@ -0,0 +1,308 @@
|
||||
#!/bin/sh
|
||||
# ----------------------------------------------------------------------------
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you 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
|
||||
#
|
||||
# https://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.
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Apache Maven Wrapper startup batch script, version 3.2.0
|
||||
#
|
||||
# Required ENV vars:
|
||||
# ------------------
|
||||
# JAVA_HOME - location of a JDK home dir
|
||||
#
|
||||
# Optional ENV vars
|
||||
# -----------------
|
||||
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
# e.g. to debug Maven itself, use
|
||||
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
if [ -z "$MAVEN_SKIP_RC" ] ; then
|
||||
|
||||
if [ -f /usr/local/etc/mavenrc ] ; then
|
||||
. /usr/local/etc/mavenrc
|
||||
fi
|
||||
|
||||
if [ -f /etc/mavenrc ] ; then
|
||||
. /etc/mavenrc
|
||||
fi
|
||||
|
||||
if [ -f "$HOME/.mavenrc" ] ; then
|
||||
. "$HOME/.mavenrc"
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
# OS specific support. $var _must_ be set to either true or false.
|
||||
cygwin=false;
|
||||
darwin=false;
|
||||
mingw=false
|
||||
case "$(uname)" in
|
||||
CYGWIN*) cygwin=true ;;
|
||||
MINGW*) mingw=true;;
|
||||
Darwin*) darwin=true
|
||||
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
|
||||
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
if [ -x "/usr/libexec/java_home" ]; then
|
||||
JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
|
||||
else
|
||||
JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then
|
||||
if [ -r /etc/gentoo-release ] ; then
|
||||
JAVA_HOME=$(java-config --jre-home)
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
|
||||
fi
|
||||
|
||||
# For Mingw, ensure paths are in UNIX format before anything is touched
|
||||
if $mingw ; then
|
||||
[ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
|
||||
JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
javaExecutable="$(which javac)"
|
||||
if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
|
||||
# readlink(1) is not available as standard on Solaris 10.
|
||||
readLink=$(which readlink)
|
||||
if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
|
||||
if $darwin ; then
|
||||
javaHome="$(dirname "\"$javaExecutable\"")"
|
||||
javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
|
||||
else
|
||||
javaExecutable="$(readlink -f "\"$javaExecutable\"")"
|
||||
fi
|
||||
javaHome="$(dirname "\"$javaExecutable\"")"
|
||||
javaHome=$(expr "$javaHome" : '\(.*\)/bin')
|
||||
JAVA_HOME="$javaHome"
|
||||
export JAVA_HOME
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$JAVACMD" ] ; then
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
else
|
||||
JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
echo "Error: JAVA_HOME is not defined correctly." >&2
|
||||
echo " We cannot execute $JAVACMD" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then
|
||||
echo "Warning: JAVA_HOME environment variable is not set."
|
||||
fi
|
||||
|
||||
# traverses directory structure from process work directory to filesystem root
|
||||
# first directory with .mvn subdirectory is considered project base directory
|
||||
find_maven_basedir() {
|
||||
if [ -z "$1" ]
|
||||
then
|
||||
echo "Path not specified to find_maven_basedir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
basedir="$1"
|
||||
wdir="$1"
|
||||
while [ "$wdir" != '/' ] ; do
|
||||
if [ -d "$wdir"/.mvn ] ; then
|
||||
basedir=$wdir
|
||||
break
|
||||
fi
|
||||
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
|
||||
if [ -d "${wdir}" ]; then
|
||||
wdir=$(cd "$wdir/.." || exit 1; pwd)
|
||||
fi
|
||||
# end of workaround
|
||||
done
|
||||
printf '%s' "$(cd "$basedir" || exit 1; pwd)"
|
||||
}
|
||||
|
||||
# concatenates all lines of a file
|
||||
concat_lines() {
|
||||
if [ -f "$1" ]; then
|
||||
# Remove \r in case we run on Windows within Git Bash
|
||||
# and check out the repository with auto CRLF management
|
||||
# enabled. Otherwise, we may read lines that are delimited with
|
||||
# \r\n and produce $'-Xarg\r' rather than -Xarg due to word
|
||||
# splitting rules.
|
||||
tr -s '\r\n' ' ' < "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
log() {
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
printf '%s\n' "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
|
||||
if [ -z "$BASE_DIR" ]; then
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
|
||||
log "$MAVEN_PROJECTBASEDIR"
|
||||
|
||||
##########################################################################################
|
||||
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
# This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||
##########################################################################################
|
||||
wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
|
||||
if [ -r "$wrapperJarPath" ]; then
|
||||
log "Found $wrapperJarPath"
|
||||
else
|
||||
log "Couldn't find $wrapperJarPath, downloading it ..."
|
||||
|
||||
if [ -n "$MVNW_REPOURL" ]; then
|
||||
wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
|
||||
else
|
||||
wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
|
||||
fi
|
||||
while IFS="=" read -r key value; do
|
||||
# Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
|
||||
safeValue=$(echo "$value" | tr -d '\r')
|
||||
case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
|
||||
esac
|
||||
done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
|
||||
log "Downloading from: $wrapperUrl"
|
||||
|
||||
if $cygwin; then
|
||||
wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
|
||||
fi
|
||||
|
||||
if command -v wget > /dev/null; then
|
||||
log "Found wget ... using wget"
|
||||
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
|
||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||
wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
|
||||
else
|
||||
wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
|
||||
fi
|
||||
elif command -v curl > /dev/null; then
|
||||
log "Found curl ... using curl"
|
||||
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
|
||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||
curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
|
||||
else
|
||||
curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
|
||||
fi
|
||||
else
|
||||
log "Falling back to using Java to download"
|
||||
javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
|
||||
javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
|
||||
# For Cygwin, switch paths to Windows format before running javac
|
||||
if $cygwin; then
|
||||
javaSource=$(cygpath --path --windows "$javaSource")
|
||||
javaClass=$(cygpath --path --windows "$javaClass")
|
||||
fi
|
||||
if [ -e "$javaSource" ]; then
|
||||
if [ ! -e "$javaClass" ]; then
|
||||
log " - Compiling MavenWrapperDownloader.java ..."
|
||||
("$JAVA_HOME/bin/javac" "$javaSource")
|
||||
fi
|
||||
if [ -e "$javaClass" ]; then
|
||||
log " - Running MavenWrapperDownloader.java ..."
|
||||
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
##########################################################################################
|
||||
# End of extension
|
||||
##########################################################################################
|
||||
|
||||
# If specified, validate the SHA-256 sum of the Maven wrapper jar file
|
||||
wrapperSha256Sum=""
|
||||
while IFS="=" read -r key value; do
|
||||
case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
|
||||
esac
|
||||
done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
|
||||
if [ -n "$wrapperSha256Sum" ]; then
|
||||
wrapperSha256Result=false
|
||||
if command -v sha256sum > /dev/null; then
|
||||
if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
|
||||
wrapperSha256Result=true
|
||||
fi
|
||||
elif command -v shasum > /dev/null; then
|
||||
if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
|
||||
wrapperSha256Result=true
|
||||
fi
|
||||
else
|
||||
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
|
||||
echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
|
||||
exit 1
|
||||
fi
|
||||
if [ $wrapperSha256Result = false ]; then
|
||||
echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
|
||||
echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
|
||||
echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin; then
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
|
||||
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
|
||||
MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
|
||||
fi
|
||||
|
||||
# Provide a "standardized" way to retrieve the CLI args that will
|
||||
# work with both Windows and non-Windows executions.
|
||||
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
|
||||
export MAVEN_CMD_LINE_ARGS
|
||||
|
||||
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
# shellcheck disable=SC2086 # safe args
|
||||
exec "$JAVACMD" \
|
||||
$MAVEN_OPTS \
|
||||
$MAVEN_DEBUG_OPTS \
|
||||
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
|
||||
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
|
||||
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
|
||||
205
mvnw.cmd
vendored
Normal file
205
mvnw.cmd
vendored
Normal file
@@ -0,0 +1,205 @@
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||
@REM or more contributor license agreements. See the NOTICE file
|
||||
@REM distributed with this work for additional information
|
||||
@REM regarding copyright ownership. The ASF licenses this file
|
||||
@REM to you under the Apache License, Version 2.0 (the
|
||||
@REM "License"); you may not use this file except in compliance
|
||||
@REM with the License. You may obtain a copy of the License at
|
||||
@REM
|
||||
@REM http://www.apache.org/licenses/LICENSE-2.0
|
||||
@REM
|
||||
@REM Unless required by applicable law or agreed to in writing,
|
||||
@REM software distributed under the License is distributed on an
|
||||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
@REM KIND, either express or implied. See the License for the
|
||||
@REM specific language governing permissions and limitations
|
||||
@REM under the License.
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Apache Maven Wrapper startup batch script, version 3.2.0
|
||||
@REM
|
||||
@REM Required ENV vars:
|
||||
@REM JAVA_HOME - location of a JDK home dir
|
||||
@REM
|
||||
@REM Optional ENV vars
|
||||
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
|
||||
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
|
||||
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
@REM e.g. to debug Maven itself, use
|
||||
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
|
||||
@echo off
|
||||
@REM set title of command window
|
||||
title %0
|
||||
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
|
||||
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
|
||||
|
||||
@REM set %HOME% to equivalent of $HOME
|
||||
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
|
||||
|
||||
@REM Execute a user defined script before this one
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
|
||||
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
|
||||
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
|
||||
:skipRcPre
|
||||
|
||||
@setlocal
|
||||
|
||||
set ERROR_CODE=0
|
||||
|
||||
@REM To isolate internal variables from possible post scripts, we use another setlocal
|
||||
@setlocal
|
||||
|
||||
@REM ==== START VALIDATION ====
|
||||
if not "%JAVA_HOME%" == "" goto OkJHome
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME not found in your environment. >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
:OkJHome
|
||||
if exist "%JAVA_HOME%\bin\java.exe" goto init
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME is set to an invalid directory. >&2
|
||||
echo JAVA_HOME = "%JAVA_HOME%" >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
@REM ==== END VALIDATION ====
|
||||
|
||||
:init
|
||||
|
||||
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
|
||||
@REM Fallback to current working directory if not found.
|
||||
|
||||
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
|
||||
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
|
||||
|
||||
set EXEC_DIR=%CD%
|
||||
set WDIR=%EXEC_DIR%
|
||||
:findBaseDir
|
||||
IF EXIST "%WDIR%"\.mvn goto baseDirFound
|
||||
cd ..
|
||||
IF "%WDIR%"=="%CD%" goto baseDirNotFound
|
||||
set WDIR=%CD%
|
||||
goto findBaseDir
|
||||
|
||||
:baseDirFound
|
||||
set MAVEN_PROJECTBASEDIR=%WDIR%
|
||||
cd "%EXEC_DIR%"
|
||||
goto endDetectBaseDir
|
||||
|
||||
:baseDirNotFound
|
||||
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
|
||||
cd "%EXEC_DIR%"
|
||||
|
||||
:endDetectBaseDir
|
||||
|
||||
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
|
||||
|
||||
@setlocal EnableExtensions EnableDelayedExpansion
|
||||
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
|
||||
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
|
||||
|
||||
:endReadAdditionalConfig
|
||||
|
||||
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
|
||||
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
|
||||
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
|
||||
|
||||
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
|
||||
IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
|
||||
)
|
||||
|
||||
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||
if exist %WRAPPER_JAR% (
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Found %WRAPPER_JAR%
|
||||
)
|
||||
) else (
|
||||
if not "%MVNW_REPOURL%" == "" (
|
||||
SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
|
||||
)
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Couldn't find %WRAPPER_JAR%, downloading it ...
|
||||
echo Downloading from: %WRAPPER_URL%
|
||||
)
|
||||
|
||||
powershell -Command "&{"^
|
||||
"$webclient = new-object System.Net.WebClient;"^
|
||||
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
|
||||
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
|
||||
"}"^
|
||||
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
|
||||
"}"
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Finished downloading %WRAPPER_JAR%
|
||||
)
|
||||
)
|
||||
@REM End of extension
|
||||
|
||||
@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
|
||||
SET WRAPPER_SHA_256_SUM=""
|
||||
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
|
||||
IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
|
||||
)
|
||||
IF NOT %WRAPPER_SHA_256_SUM%=="" (
|
||||
powershell -Command "&{"^
|
||||
"$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
|
||||
"If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
|
||||
" Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
|
||||
" Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
|
||||
" Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
|
||||
" exit 1;"^
|
||||
"}"^
|
||||
"}"
|
||||
if ERRORLEVEL 1 goto error
|
||||
)
|
||||
|
||||
@REM Provide a "standardized" way to retrieve the CLI args that will
|
||||
@REM work with both Windows and non-Windows executions.
|
||||
set MAVEN_CMD_LINE_ARGS=%*
|
||||
|
||||
%MAVEN_JAVA_EXE% ^
|
||||
%JVM_CONFIG_MAVEN_PROPS% ^
|
||||
%MAVEN_OPTS% ^
|
||||
%MAVEN_DEBUG_OPTS% ^
|
||||
-classpath %WRAPPER_JAR% ^
|
||||
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
|
||||
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
|
||||
if ERRORLEVEL 1 goto error
|
||||
goto end
|
||||
|
||||
:error
|
||||
set ERROR_CODE=1
|
||||
|
||||
:end
|
||||
@endlocal & set ERROR_CODE=%ERROR_CODE%
|
||||
|
||||
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
|
||||
@REM check for post script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
|
||||
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
|
||||
:skipRcPost
|
||||
|
||||
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
|
||||
if "%MAVEN_BATCH_PAUSE%"=="on" pause
|
||||
|
||||
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
|
||||
|
||||
cmd /C exit /B %ERROR_CODE%
|
||||
289
pom.xml
289
pom.xml
@@ -14,47 +14,68 @@
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>dynamodb-local-oregon</id>
|
||||
<name>DynamoDB Local Release Repository</name>
|
||||
<url>https://s3-us-west-2.amazonaws.com/dynamodb-local/release</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>ossrh-snapshots</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
|
||||
<modules>
|
||||
<module>redis-dispatch</module>
|
||||
<module>websocket-resources</module>
|
||||
<module>gcm-sender-async</module>
|
||||
<module>api-doc</module>
|
||||
<module>integration-tests</module>
|
||||
<module>service</module>
|
||||
<module>websocket-resources</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<aws.sdk.version>1.11.939</aws.sdk.version>
|
||||
<aws.sdk2.version>2.16.66</aws.sdk2.version>
|
||||
<commons-codec.version>1.15</commons-codec.version>
|
||||
<commons-csv.version>1.8</commons-csv.version>
|
||||
<commons-io.version>2.9.0</commons-io.version>
|
||||
<dropwizard.version>2.0.22</dropwizard.version>
|
||||
<aws.sdk2.version>2.23.8</aws.sdk2.version>
|
||||
<braintree.version>3.27.0</braintree.version>
|
||||
<commons-csv.version>1.10.0</commons-csv.version>
|
||||
<commons-io.version>2.14.0</commons-io.version>
|
||||
<dropwizard.version>3.0.4</dropwizard.version>
|
||||
<dropwizard-metrics-datadog.version>1.1.13</dropwizard-metrics-datadog.version>
|
||||
<gson.version>2.8.8</gson.version>
|
||||
<guava.version>30.1.1-jre</guava.version>
|
||||
<google-cloud-libraries.version>26.25.0</google-cloud-libraries.version>
|
||||
<grpc.version>1.58.0</grpc.version> <!-- should be kept in sync with the value from Google libraries-bom -->
|
||||
<gson.version>2.10.1</gson.version>
|
||||
<!-- several libraries (AWS, Google Cloud) use Apache http components transitively, and we need to align them -->
|
||||
<httpcore.version>4.4.16</httpcore.version>
|
||||
<httpclient.version>4.5.14</httpclient.version>
|
||||
<jackson.version>2.16.0</jackson.version>
|
||||
<jaxb.version>2.3.1</jaxb.version>
|
||||
<jedis.version>2.9.0</jedis.version>
|
||||
<lettuce.version>6.0.4.RELEASE</lettuce.version>
|
||||
<libphonenumber.version>8.12.33</libphonenumber.version>
|
||||
<logstash.logback.version>6.6</logstash.logback.version>
|
||||
<micrometer.version>1.5.3</micrometer.version>
|
||||
<mockito.version>3.11.1</mockito.version>
|
||||
<netty.version>4.1.65.Final</netty.version>
|
||||
<netty.tcnative-boringssl-static.version>2.0.39.Final</netty.tcnative-boringssl-static.version>
|
||||
<opentest4j.version>1.2.0</opentest4j.version>
|
||||
<postgresql.version>9.4-1201-jdbc41</postgresql.version>
|
||||
<protobuf.version>3.17.1</protobuf.version>
|
||||
<pushy.version>0.15.0</pushy.version>
|
||||
<resilience4j.version>1.5.0</resilience4j.version>
|
||||
<junit-pioneer.version>2.1.0</junit-pioneer.version>
|
||||
<jsr305.version>3.0.2</jsr305.version>
|
||||
<kotlin.version>1.9.10</kotlin.version>
|
||||
<kotlinx-serialization.version>1.5.1</kotlinx-serialization.version>
|
||||
<lettuce.version>6.2.6.RELEASE</lettuce.version>
|
||||
<libphonenumber.version>8.13.23</libphonenumber.version>
|
||||
<logstash.logback.version>7.3</logstash.logback.version>
|
||||
<log4j-bom.version>2.21.0</log4j-bom.version>
|
||||
<luajava.version>3.4.0</luajava.version>
|
||||
<micrometer.version>1.10.10</micrometer.version>
|
||||
<netty.version>4.1.96.Final</netty.version>
|
||||
<opentest4j.version>1.3.0</opentest4j.version>
|
||||
<protobuf.version>3.24.3</protobuf.version> <!-- should be kept in sync with the value from Google libraries-bom -->
|
||||
<pushy.version>0.15.2</pushy.version>
|
||||
<reactive.grpc.version>1.2.4</reactive.grpc.version>
|
||||
<reactor-bom.version>2022.0.12</reactor-bom.version> <!-- 3.5.x, see https://github.com/reactor/reactor#bom-versioning-scheme -->
|
||||
<resilience4j.version>1.7.0</resilience4j.version>
|
||||
<semver4j.version>3.1.0</semver4j.version>
|
||||
<slf4j.version>1.7.30</slf4j.version>
|
||||
<stripe.version>20.79.0</stripe.version>
|
||||
<slf4j.version>2.0.9</slf4j.version>
|
||||
<stripe.version>23.10.0</stripe.version>
|
||||
<swagger.version>2.2.17</swagger.version>
|
||||
<vavr.version>0.10.4</vavr.version>
|
||||
|
||||
<!-- 21.0.1_12-jre-jammy -->
|
||||
<docker.image.sha256>2d00f6910282a7a20ae7747b8f5e2371f7d55f06daed6bf60a323fcc7eaa3da8</docker.image.sha256>
|
||||
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
@@ -65,6 +86,13 @@
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson</groupId>
|
||||
<artifactId>jackson-bom</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-dependencies</artifactId>
|
||||
@@ -72,6 +100,13 @@
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<!-- Needed for gRPC with Java 9+ -->
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat</groupId>
|
||||
<artifactId>annotations-api</artifactId>
|
||||
<version>6.0.53</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-bom</artifactId>
|
||||
@@ -79,13 +114,6 @@
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.amazonaws</groupId>
|
||||
<artifactId>aws-java-sdk-bom</artifactId>
|
||||
<version>${aws.sdk.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>bom</artifactId>
|
||||
@@ -96,10 +124,15 @@
|
||||
<dependency>
|
||||
<groupId>com.google.cloud</groupId>
|
||||
<artifactId>libraries-bom</artifactId>
|
||||
<version>20.9.0</version>
|
||||
<version>${google-cloud-libraries.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.salesforce.servicelibs</groupId>
|
||||
<artifactId>reactor-grpc-stub</artifactId>
|
||||
<version>${reactive.grpc.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.resilience4j</groupId>
|
||||
<artifactId>resilience4j-bom</artifactId>
|
||||
@@ -114,7 +147,20 @@
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-bom</artifactId>
|
||||
<version>${reactor-bom.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-bom</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.eatthepath</groupId>
|
||||
<artifactId>pushy</artifactId>
|
||||
@@ -125,11 +171,6 @@
|
||||
<artifactId>pushy-dropwizard-metrics-listener</artifactId>
|
||||
<version>${pushy.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>${guava.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.protobuf</groupId>
|
||||
<artifactId>protobuf-java</artifactId>
|
||||
@@ -145,11 +186,6 @@
|
||||
<artifactId>semver4j</artifactId>
|
||||
<version>${semver4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
<version>${commons-codec.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
@@ -161,10 +197,9 @@
|
||||
<version>${lettuce.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-tcnative-boringssl-static</artifactId>
|
||||
<version>${netty.tcnative-boringssl-static.version}</version>
|
||||
<scope>runtime</scope>
|
||||
<groupId>io.vavr</groupId>
|
||||
<artifactId>vavr</artifactId>
|
||||
<version>${vavr.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.xml.bind</groupId>
|
||||
@@ -192,30 +227,12 @@
|
||||
<version>${jaxb.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-inline</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.opentest4j</groupId>
|
||||
<artifactId>opentest4j</artifactId>
|
||||
<version>${opentest4j.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>${postgresql.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
@@ -227,11 +244,6 @@
|
||||
<version>${slf4j.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
<version>${jedis.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
@@ -240,7 +252,7 @@
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm</artifactId>
|
||||
<version>9.2</version>
|
||||
<version>9.5</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -248,11 +260,54 @@
|
||||
<artifactId>stripe-java</artifactId>
|
||||
<version>${stripe.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.braintreepayments.gateway</groupId>
|
||||
<artifactId>braintree-java</artifactId>
|
||||
<version>${braintree.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.findbugs</groupId>
|
||||
<artifactId>jsr305</artifactId>
|
||||
<version>${jsr305.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>${gson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.signal</groupId>
|
||||
<artifactId>embedded-redis</artifactId>
|
||||
<version>0.8.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.signal</groupId>
|
||||
<artifactId>libsignal-server</artifactId>
|
||||
<version>0.39.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.signal.forks</groupId>
|
||||
<artifactId>noise-java</artifactId>
|
||||
<version>0.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-bom</artifactId>
|
||||
<version>${log4j-bom.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpcore</artifactId>
|
||||
<version>${httpcore.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>${httpclient.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
@@ -264,9 +319,14 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.tomakehurst</groupId>
|
||||
<artifactId>wiremock-jre8</artifactId>
|
||||
<version>2.31.0</version>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>aws-crt-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.wiremock</groupId>
|
||||
<!-- use standalone until Dropwizard 4 + jakarta.* -->
|
||||
<artifactId>wiremock-standalone</artifactId>
|
||||
<version>3.3.1</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
@@ -282,7 +342,6 @@
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -291,8 +350,14 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit-pioneer</groupId>
|
||||
<artifactId>junit-pioneer</artifactId>
|
||||
<version>${junit-pioneer.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -300,22 +365,22 @@
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>include-abusive-message-filter</id>
|
||||
<id>include-spam-filter</id>
|
||||
<activation>
|
||||
<file>
|
||||
<exists>abusive-message-filter/pom.xml</exists>
|
||||
<exists>spam-filter/pom.xml</exists>
|
||||
</file>
|
||||
</activation>
|
||||
<modules>
|
||||
<module>abusive-message-filter</module>
|
||||
<module>spam-filter</module>
|
||||
</modules>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>exclude-abusive-message-filter</id>
|
||||
<id>exclude-spam-filter</id>
|
||||
<activation>
|
||||
<file>
|
||||
<missing>abusive-message-filter/pom.xml</missing>
|
||||
<missing>spam-filter/pom.xml</missing>
|
||||
</file>
|
||||
</activation>
|
||||
</profile>
|
||||
@@ -329,6 +394,15 @@
|
||||
<version>1.7.0</version>
|
||||
</extension>
|
||||
</extensions>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.google.cloud.tools</groupId>
|
||||
<artifactId>jib-maven-plugin</artifactId>
|
||||
<version>3.4.0</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
@@ -336,14 +410,28 @@
|
||||
<artifactId>protobuf-maven-plugin</artifactId>
|
||||
<version>0.6.1</version>
|
||||
<configuration>
|
||||
<protocArtifact>com.google.protobuf:protoc:3.18.0:exe:${os.detected.classifier}</protocArtifact>
|
||||
<checkStaleness>true</checkStaleness>
|
||||
<checkStaleness>false</checkStaleness>
|
||||
<protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
|
||||
<pluginId>grpc-java</pluginId>
|
||||
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
|
||||
|
||||
<protocPlugins>
|
||||
<protocPlugin>
|
||||
<id>reactor-grpc</id>
|
||||
<groupId>com.salesforce.servicelibs</groupId>
|
||||
<artifactId>reactor-grpc</artifactId>
|
||||
<version>${reactive.grpc.version}</version>
|
||||
<mainClass>com.salesforce.reactorgrpc.ReactorGrpcGenerator</mainClass>
|
||||
</protocPlugin>
|
||||
</protocPlugins>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
<goal>compile-custom</goal>
|
||||
<goal>test-compile</goal>
|
||||
<goal>test-compile-custom</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
@@ -352,17 +440,16 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<version>3.11.0</version>
|
||||
<configuration>
|
||||
<source>11</source>
|
||||
<target>11</target>
|
||||
<release>21</release>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
@@ -375,7 +462,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>3.1.2</version>
|
||||
<version>3.3.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy</id>
|
||||
@@ -395,7 +482,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.0.0-M5</version>
|
||||
<version>3.1.2</version>
|
||||
<configuration>
|
||||
<systemProperties>
|
||||
<property>
|
||||
@@ -409,7 +496,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-enforcer-plugin</artifactId>
|
||||
<version>3.0.0-M3</version>
|
||||
<version>3.3.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
@@ -419,7 +506,7 @@
|
||||
<rules>
|
||||
<dependencyConvergence/>
|
||||
<requireMavenVersion>
|
||||
<version>3.0.0</version>
|
||||
<version>3.8.6</version>
|
||||
</requireMavenVersion>
|
||||
</rules>
|
||||
</configuration>
|
||||
@@ -430,7 +517,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-install-plugin</artifactId>
|
||||
<version>3.0.0-M1</version>
|
||||
<version>3.1.1</version>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
@@ -439,7 +526,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<version>3.0.0-M1</version>
|
||||
<version>3.1.1</version>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>TextSecureServer</artifactId>
|
||||
<groupId>org.whispersystems.textsecure</groupId>
|
||||
<version>JGITVER</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>redis-dispatch</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -1,11 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.dispatch;
|
||||
|
||||
public interface DispatchChannel {
|
||||
void onDispatchMessage(String channel, byte[] message);
|
||||
void onDispatchSubscribed(String channel);
|
||||
void onDispatchUnsubscribed(String channel);
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.dispatch;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.dispatch.io.RedisPubSubConnectionFactory;
|
||||
import org.whispersystems.dispatch.redis.PubSubConnection;
|
||||
import org.whispersystems.dispatch.redis.PubSubReply;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
public class DispatchManager extends Thread {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DispatchManager.class);
|
||||
private final Executor executor = Executors.newCachedThreadPool();
|
||||
private final Map<String, DispatchChannel> subscriptions = new ConcurrentHashMap<>();
|
||||
|
||||
private final Optional<DispatchChannel> deadLetterChannel;
|
||||
private final RedisPubSubConnectionFactory redisPubSubConnectionFactory;
|
||||
|
||||
private PubSubConnection pubSubConnection;
|
||||
private volatile boolean running;
|
||||
|
||||
public DispatchManager(RedisPubSubConnectionFactory redisPubSubConnectionFactory,
|
||||
Optional<DispatchChannel> deadLetterChannel)
|
||||
{
|
||||
this.redisPubSubConnectionFactory = redisPubSubConnectionFactory;
|
||||
this.deadLetterChannel = deadLetterChannel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
this.pubSubConnection = redisPubSubConnectionFactory.connect();
|
||||
this.running = true;
|
||||
super.start();
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
this.running = false;
|
||||
this.pubSubConnection.close();
|
||||
}
|
||||
|
||||
public synchronized void subscribe(String name, DispatchChannel dispatchChannel) {
|
||||
Optional<DispatchChannel> previous = Optional.ofNullable(subscriptions.get(name));
|
||||
subscriptions.put(name, dispatchChannel);
|
||||
|
||||
try {
|
||||
pubSubConnection.subscribe(name);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Subscription error", e);
|
||||
}
|
||||
|
||||
previous.ifPresent(channel -> dispatchUnsubscription(name, channel));
|
||||
}
|
||||
|
||||
public synchronized void unsubscribe(String name, DispatchChannel channel) {
|
||||
Optional<DispatchChannel> subscription = Optional.ofNullable(subscriptions.get(name));
|
||||
|
||||
if (subscription.isPresent() && subscription.get() == channel) {
|
||||
subscriptions.remove(name);
|
||||
|
||||
try {
|
||||
pubSubConnection.unsubscribe(name);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Unsubscribe error", e);
|
||||
}
|
||||
|
||||
dispatchUnsubscription(name, subscription.get());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasSubscription(String name) {
|
||||
return subscriptions.containsKey(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (running) {
|
||||
try {
|
||||
PubSubReply reply = pubSubConnection.read();
|
||||
|
||||
switch (reply.getType()) {
|
||||
case UNSUBSCRIBE: break;
|
||||
case SUBSCRIBE: dispatchSubscribe(reply); break;
|
||||
case MESSAGE: dispatchMessage(reply); break;
|
||||
default: throw new AssertionError("Unknown pubsub reply type! " + reply.getType());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("***** PubSub Connection Error *****", e);
|
||||
if (running) {
|
||||
this.pubSubConnection.close();
|
||||
this.pubSubConnection = redisPubSubConnectionFactory.connect();
|
||||
resubscribeAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.warn("DispatchManager Shutting Down...");
|
||||
}
|
||||
|
||||
private void dispatchSubscribe(final PubSubReply reply) {
|
||||
Optional<DispatchChannel> subscription = Optional.ofNullable(subscriptions.get(reply.getChannel()));
|
||||
|
||||
if (subscription.isPresent()) {
|
||||
dispatchSubscription(reply.getChannel(), subscription.get());
|
||||
} else {
|
||||
logger.info("Received subscribe event for non-existing channel: " + reply.getChannel());
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchMessage(PubSubReply reply) {
|
||||
Optional<DispatchChannel> subscription = Optional.ofNullable(subscriptions.get(reply.getChannel()));
|
||||
|
||||
if (subscription.isPresent()) {
|
||||
dispatchMessage(reply.getChannel(), subscription.get(), reply.getContent().get());
|
||||
} else if (deadLetterChannel.isPresent()) {
|
||||
dispatchMessage(reply.getChannel(), deadLetterChannel.get(), reply.getContent().get());
|
||||
} else {
|
||||
logger.warn("Received message for non-existing channel, with no dead letter handler: " + reply.getChannel());
|
||||
}
|
||||
}
|
||||
|
||||
private void resubscribeAll() {
|
||||
new Thread(() -> {
|
||||
synchronized (DispatchManager.this) {
|
||||
try {
|
||||
for (String name : subscriptions.keySet()) {
|
||||
pubSubConnection.subscribe(name);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("***** RESUBSCRIPTION ERROR *****", e);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void dispatchMessage(final String name, final DispatchChannel channel, final byte[] message) {
|
||||
executor.execute(() -> channel.onDispatchMessage(name, message));
|
||||
}
|
||||
|
||||
private void dispatchSubscription(final String name, final DispatchChannel channel) {
|
||||
executor.execute(() -> channel.onDispatchSubscribed(name));
|
||||
}
|
||||
|
||||
private void dispatchUnsubscription(final String name, final DispatchChannel channel) {
|
||||
executor.execute(() -> channel.onDispatchUnsubscribed(name));
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.dispatch.io;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class RedisInputStream {
|
||||
|
||||
private static final byte CR = 0x0D;
|
||||
private static final byte LF = 0x0A;
|
||||
|
||||
private final InputStream inputStream;
|
||||
|
||||
public RedisInputStream(InputStream inputStream) {
|
||||
this.inputStream = inputStream;
|
||||
}
|
||||
|
||||
public String readLine() throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
boolean foundCr = false;
|
||||
|
||||
while (true) {
|
||||
int character = inputStream.read();
|
||||
|
||||
if (character == -1) {
|
||||
throw new IOException("Stream closed!");
|
||||
}
|
||||
|
||||
baos.write(character);
|
||||
|
||||
if (foundCr && character == LF) break;
|
||||
else if (character == CR) foundCr = true;
|
||||
else if (foundCr) foundCr = false;
|
||||
}
|
||||
|
||||
byte[] data = baos.toByteArray();
|
||||
return new String(data, 0, data.length-2);
|
||||
}
|
||||
|
||||
public byte[] readFully(int size) throws IOException {
|
||||
byte[] result = new byte[size];
|
||||
int offset = 0;
|
||||
int remaining = result.length;
|
||||
|
||||
while (remaining > 0) {
|
||||
int read = inputStream.read(result, offset, remaining);
|
||||
|
||||
if (read < 0) {
|
||||
throw new IOException("Stream closed!");
|
||||
}
|
||||
|
||||
offset += read;
|
||||
remaining -= read;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
inputStream.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.dispatch.io;
|
||||
|
||||
import org.whispersystems.dispatch.redis.PubSubConnection;
|
||||
|
||||
public interface RedisPubSubConnectionFactory {
|
||||
|
||||
PubSubConnection connect();
|
||||
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.dispatch.redis;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.dispatch.io.RedisInputStream;
|
||||
import org.whispersystems.dispatch.redis.protocol.ArrayReplyHeader;
|
||||
import org.whispersystems.dispatch.redis.protocol.IntReply;
|
||||
import org.whispersystems.dispatch.redis.protocol.StringReplyHeader;
|
||||
import org.whispersystems.dispatch.util.Util;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class PubSubConnection {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(PubSubConnection.class);
|
||||
|
||||
private static final byte[] UNSUBSCRIBE_TYPE = {'u', 'n', 's', 'u', 'b', 's', 'c', 'r', 'i', 'b', 'e' };
|
||||
private static final byte[] SUBSCRIBE_TYPE = {'s', 'u', 'b', 's', 'c', 'r', 'i', 'b', 'e' };
|
||||
private static final byte[] MESSAGE_TYPE = {'m', 'e', 's', 's', 'a', 'g', 'e' };
|
||||
|
||||
private static final byte[] SUBSCRIBE_COMMAND = {'S', 'U', 'B', 'S', 'C', 'R', 'I', 'B', 'E', ' ' };
|
||||
private static final byte[] UNSUBSCRIBE_COMMAND = {'U', 'N', 'S', 'U', 'B', 'S', 'C', 'R', 'I', 'B', 'E', ' '};
|
||||
private static final byte[] CRLF = {'\r', '\n' };
|
||||
|
||||
private final OutputStream outputStream;
|
||||
private final RedisInputStream inputStream;
|
||||
private final Socket socket;
|
||||
private final AtomicBoolean closed;
|
||||
|
||||
public PubSubConnection(Socket socket) throws IOException {
|
||||
this.socket = socket;
|
||||
this.outputStream = socket.getOutputStream();
|
||||
this.inputStream = new RedisInputStream(new BufferedInputStream(socket.getInputStream()));
|
||||
this.closed = new AtomicBoolean(false);
|
||||
}
|
||||
|
||||
public void subscribe(String channelName) throws IOException {
|
||||
if (closed.get()) throw new IOException("Connection closed!");
|
||||
|
||||
byte[] command = Util.combine(SUBSCRIBE_COMMAND, channelName.getBytes(), CRLF);
|
||||
outputStream.write(command);
|
||||
}
|
||||
|
||||
public void unsubscribe(String channelName) throws IOException {
|
||||
if (closed.get()) throw new IOException("Connection closed!");
|
||||
|
||||
byte[] command = Util.combine(UNSUBSCRIBE_COMMAND, channelName.getBytes(), CRLF);
|
||||
outputStream.write(command);
|
||||
}
|
||||
|
||||
public PubSubReply read() throws IOException {
|
||||
if (closed.get()) throw new IOException("Connection closed!");
|
||||
|
||||
ArrayReplyHeader replyHeader = new ArrayReplyHeader(inputStream.readLine());
|
||||
|
||||
if (replyHeader.getElementCount() != 3) {
|
||||
throw new IOException("Received array reply header with strange count: " + replyHeader.getElementCount());
|
||||
}
|
||||
|
||||
StringReplyHeader replyTypeHeader = new StringReplyHeader(inputStream.readLine());
|
||||
byte[] replyType = inputStream.readFully(replyTypeHeader.getStringLength());
|
||||
inputStream.readLine();
|
||||
|
||||
if (Arrays.equals(SUBSCRIBE_TYPE, replyType)) return readSubscribeReply();
|
||||
else if (Arrays.equals(UNSUBSCRIBE_TYPE, replyType)) return readUnsubscribeReply();
|
||||
else if (Arrays.equals(MESSAGE_TYPE, replyType)) return readMessageReply();
|
||||
else throw new IOException("Unknown reply type: " + new String(replyType));
|
||||
}
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
this.closed.set(true);
|
||||
this.inputStream.close();
|
||||
this.outputStream.close();
|
||||
this.socket.close();
|
||||
} catch (IOException e) {
|
||||
logger.warn("Exception while closing", e);
|
||||
}
|
||||
}
|
||||
|
||||
private PubSubReply readMessageReply() throws IOException {
|
||||
StringReplyHeader channelNameHeader = new StringReplyHeader(inputStream.readLine());
|
||||
byte[] channelName = inputStream.readFully(channelNameHeader.getStringLength());
|
||||
inputStream.readLine();
|
||||
|
||||
StringReplyHeader messageHeader = new StringReplyHeader(inputStream.readLine());
|
||||
byte[] message = inputStream.readFully(messageHeader.getStringLength());
|
||||
inputStream.readLine();
|
||||
|
||||
return new PubSubReply(PubSubReply.Type.MESSAGE, new String(channelName), Optional.of(message));
|
||||
}
|
||||
|
||||
private PubSubReply readUnsubscribeReply() throws IOException {
|
||||
String channelName = readSubscriptionReply();
|
||||
return new PubSubReply(PubSubReply.Type.UNSUBSCRIBE, channelName, Optional.empty());
|
||||
}
|
||||
|
||||
private PubSubReply readSubscribeReply() throws IOException {
|
||||
String channelName = readSubscriptionReply();
|
||||
return new PubSubReply(PubSubReply.Type.SUBSCRIBE, channelName, Optional.empty());
|
||||
}
|
||||
|
||||
private String readSubscriptionReply() throws IOException {
|
||||
StringReplyHeader channelNameHeader = new StringReplyHeader(inputStream.readLine());
|
||||
byte[] channelName = inputStream.readFully(channelNameHeader.getStringLength());
|
||||
inputStream.readLine();
|
||||
|
||||
IntReply subscriptionCount = new IntReply(inputStream.readLine());
|
||||
|
||||
return new String(channelName);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.dispatch.redis;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
public class PubSubReply {
|
||||
|
||||
public enum Type {
|
||||
MESSAGE,
|
||||
SUBSCRIBE,
|
||||
UNSUBSCRIBE
|
||||
}
|
||||
|
||||
private final Type type;
|
||||
private final String channel;
|
||||
private final Optional<byte[]> content;
|
||||
|
||||
public PubSubReply(Type type, String channel, Optional<byte[]> content) {
|
||||
this.type = type;
|
||||
this.channel = channel;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public Optional<byte[]> getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.dispatch.redis.protocol;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ArrayReplyHeader {
|
||||
|
||||
private final int elementCount;
|
||||
|
||||
public ArrayReplyHeader(String header) throws IOException {
|
||||
if (header == null || header.length() < 2 || header.charAt(0) != '*') {
|
||||
throw new IOException("Invalid array reply header: " + header);
|
||||
}
|
||||
|
||||
try {
|
||||
this.elementCount = Integer.parseInt(header.substring(1));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public int getElementCount() {
|
||||
return elementCount;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.dispatch.redis.protocol;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class IntReply {
|
||||
|
||||
private final int value;
|
||||
|
||||
public IntReply(String reply) throws IOException {
|
||||
if (reply == null || reply.length() < 2 || reply.charAt(0) != ':') {
|
||||
throw new IOException("Invalid int reply: " + reply);
|
||||
}
|
||||
|
||||
try {
|
||||
this.value = Integer.parseInt(reply.substring(1));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.dispatch.redis.protocol;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class StringReplyHeader {
|
||||
|
||||
private final int stringLength;
|
||||
|
||||
public StringReplyHeader(String header) throws IOException {
|
||||
if (header == null || header.length() < 2 || header.charAt(0) != '$') {
|
||||
throw new IOException("Invalid string reply header: " + header);
|
||||
}
|
||||
|
||||
try {
|
||||
this.stringLength = Integer.parseInt(header.substring(1));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public int getStringLength() {
|
||||
return stringLength;
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.dispatch.util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class Util {
|
||||
|
||||
public static byte[] combine(byte[]... elements) {
|
||||
try {
|
||||
int sum = 0;
|
||||
|
||||
for (byte[] element : elements) {
|
||||
sum += element.length;
|
||||
}
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(sum);
|
||||
|
||||
for (byte[] element : elements) {
|
||||
baos.write(element);
|
||||
}
|
||||
|
||||
return baos.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void sleep(long millis) {
|
||||
try {
|
||||
Thread.sleep(millis);
|
||||
} catch (InterruptedException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.dispatch;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExternalResource;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.whispersystems.dispatch.io.RedisPubSubConnectionFactory;
|
||||
import org.whispersystems.dispatch.redis.PubSubConnection;
|
||||
import org.whispersystems.dispatch.redis.PubSubReply;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.timeout;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class DispatchManagerTest {
|
||||
|
||||
private PubSubConnection pubSubConnection;
|
||||
private RedisPubSubConnectionFactory socketFactory;
|
||||
private DispatchManager dispatchManager;
|
||||
private PubSubReplyInputStream pubSubReplyInputStream;
|
||||
|
||||
@Rule
|
||||
public ExternalResource resource = new ExternalResource() {
|
||||
@Override
|
||||
protected void before() throws Throwable {
|
||||
pubSubConnection = mock(PubSubConnection.class );
|
||||
socketFactory = mock(RedisPubSubConnectionFactory.class);
|
||||
pubSubReplyInputStream = new PubSubReplyInputStream();
|
||||
|
||||
when(socketFactory.connect()).thenReturn(pubSubConnection);
|
||||
when(pubSubConnection.read()).thenAnswer(new Answer<PubSubReply>() {
|
||||
@Override
|
||||
public PubSubReply answer(InvocationOnMock invocationOnMock) throws Throwable {
|
||||
return pubSubReplyInputStream.read();
|
||||
}
|
||||
});
|
||||
|
||||
dispatchManager = new DispatchManager(socketFactory, Optional.empty());
|
||||
dispatchManager.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void after() {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@Test
|
||||
public void testConnect() {
|
||||
verify(socketFactory).connect();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscribe() throws IOException {
|
||||
DispatchChannel dispatchChannel = mock(DispatchChannel.class);
|
||||
dispatchManager.subscribe("foo", dispatchChannel);
|
||||
pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.SUBSCRIBE, "foo", Optional.empty()));
|
||||
|
||||
verify(dispatchChannel, timeout(1000)).onDispatchSubscribed(eq("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscribeUnsubscribe() throws IOException {
|
||||
DispatchChannel dispatchChannel = mock(DispatchChannel.class);
|
||||
dispatchManager.subscribe("foo", dispatchChannel);
|
||||
dispatchManager.unsubscribe("foo", dispatchChannel);
|
||||
|
||||
pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.SUBSCRIBE, "foo", Optional.empty()));
|
||||
pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.UNSUBSCRIBE, "foo", Optional.empty()));
|
||||
|
||||
verify(dispatchChannel, timeout(1000)).onDispatchUnsubscribed(eq("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessages() throws IOException {
|
||||
DispatchChannel fooChannel = mock(DispatchChannel.class);
|
||||
DispatchChannel barChannel = mock(DispatchChannel.class);
|
||||
|
||||
dispatchManager.subscribe("foo", fooChannel);
|
||||
dispatchManager.subscribe("bar", barChannel);
|
||||
|
||||
pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.SUBSCRIBE, "foo", Optional.empty()));
|
||||
pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.SUBSCRIBE, "bar", Optional.empty()));
|
||||
|
||||
verify(fooChannel, timeout(1000)).onDispatchSubscribed(eq("foo"));
|
||||
verify(barChannel, timeout(1000)).onDispatchSubscribed(eq("bar"));
|
||||
|
||||
pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.MESSAGE, "foo", Optional.of("hello".getBytes())));
|
||||
pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.MESSAGE, "bar", Optional.of("there".getBytes())));
|
||||
|
||||
ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class);
|
||||
verify(fooChannel, timeout(1000)).onDispatchMessage(eq("foo"), captor.capture());
|
||||
|
||||
assertArrayEquals("hello".getBytes(), captor.getValue());
|
||||
|
||||
verify(barChannel, timeout(1000)).onDispatchMessage(eq("bar"), captor.capture());
|
||||
|
||||
assertArrayEquals("there".getBytes(), captor.getValue());
|
||||
}
|
||||
|
||||
private static class PubSubReplyInputStream {
|
||||
|
||||
private final List<PubSubReply> pubSubReplyList = new LinkedList<>();
|
||||
|
||||
public synchronized PubSubReply read() {
|
||||
try {
|
||||
while (pubSubReplyList.isEmpty()) wait();
|
||||
return pubSubReplyList.remove(0);
|
||||
} catch (InterruptedException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void write(PubSubReply pubSubReply) {
|
||||
pubSubReplyList.add(pubSubReply);
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,265 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.dispatch.redis;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.security.SecureRandom;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
public class PubSubConnectionTest {
|
||||
|
||||
private static final String REPLY = "*3\r\n" +
|
||||
"$9\r\n" +
|
||||
"subscribe\r\n" +
|
||||
"$5\r\n" +
|
||||
"abcde\r\n" +
|
||||
":1\r\n" +
|
||||
"*3\r\n" +
|
||||
"$9\r\n" +
|
||||
"subscribe\r\n" +
|
||||
"$5\r\n" +
|
||||
"fghij\r\n" +
|
||||
":2\r\n" +
|
||||
"*3\r\n" +
|
||||
"$9\r\n" +
|
||||
"subscribe\r\n" +
|
||||
"$5\r\n" +
|
||||
"klmno\r\n" +
|
||||
":2\r\n" +
|
||||
"*3\r\n" +
|
||||
"$7\r\n" +
|
||||
"message\r\n" +
|
||||
"$5\r\n" +
|
||||
"abcde\r\n" +
|
||||
"$10\r\n" +
|
||||
"1234567890\r\n" +
|
||||
"*3\r\n" +
|
||||
"$7\r\n" +
|
||||
"message\r\n" +
|
||||
"$5\r\n" +
|
||||
"klmno\r\n" +
|
||||
"$10\r\n" +
|
||||
"0987654321\r\n";
|
||||
|
||||
|
||||
@Test
|
||||
public void testSubscribe() throws IOException {
|
||||
// ByteChannel byteChannel = mock(ByteChannel.class);
|
||||
OutputStream outputStream = mock(OutputStream.class);
|
||||
Socket socket = mock(Socket.class );
|
||||
when(socket.getOutputStream()).thenReturn(outputStream);
|
||||
PubSubConnection connection = new PubSubConnection(socket);
|
||||
|
||||
connection.subscribe("foobar");
|
||||
|
||||
ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class);
|
||||
verify(outputStream).write(captor.capture());
|
||||
|
||||
assertArrayEquals(captor.getValue(), "SUBSCRIBE foobar\r\n".getBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsubscribe() throws IOException {
|
||||
OutputStream outputStream = mock(OutputStream.class);
|
||||
Socket socket = mock(Socket.class );
|
||||
when(socket.getOutputStream()).thenReturn(outputStream);
|
||||
PubSubConnection connection = new PubSubConnection(socket);
|
||||
|
||||
connection.unsubscribe("bazbar");
|
||||
|
||||
ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class);
|
||||
verify(outputStream).write(captor.capture());
|
||||
|
||||
assertArrayEquals(captor.getValue(), "UNSUBSCRIBE bazbar\r\n".getBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTricklyResponse() throws Exception {
|
||||
InputStream inputStream = mockInputStreamFor(new TrickleInputStream(REPLY.getBytes()));
|
||||
OutputStream outputStream = mock(OutputStream.class);
|
||||
Socket socket = mock(Socket.class );
|
||||
when(socket.getOutputStream()).thenReturn(outputStream);
|
||||
when(socket.getInputStream()).thenReturn(inputStream);
|
||||
|
||||
PubSubConnection pubSubConnection = new PubSubConnection(socket);
|
||||
readResponses(pubSubConnection);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFullResponse() throws Exception {
|
||||
InputStream inputStream = mockInputStreamFor(new FullInputStream(REPLY.getBytes()));
|
||||
OutputStream outputStream = mock(OutputStream.class);
|
||||
Socket socket = mock(Socket.class );
|
||||
when(socket.getOutputStream()).thenReturn(outputStream);
|
||||
when(socket.getInputStream()).thenReturn(inputStream);
|
||||
|
||||
PubSubConnection pubSubConnection = new PubSubConnection(socket);
|
||||
readResponses(pubSubConnection);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRandomLengthResponse() throws Exception {
|
||||
InputStream inputStream = mockInputStreamFor(new RandomInputStream(REPLY.getBytes()));
|
||||
OutputStream outputStream = mock(OutputStream.class);
|
||||
Socket socket = mock(Socket.class );
|
||||
when(socket.getOutputStream()).thenReturn(outputStream);
|
||||
when(socket.getInputStream()).thenReturn(inputStream);
|
||||
|
||||
PubSubConnection pubSubConnection = new PubSubConnection(socket);
|
||||
readResponses(pubSubConnection);
|
||||
}
|
||||
|
||||
private InputStream mockInputStreamFor(final MockInputStream stub) throws IOException {
|
||||
InputStream result = mock(InputStream.class);
|
||||
|
||||
when(result.read()).thenAnswer(new Answer<Integer>() {
|
||||
@Override
|
||||
public Integer answer(InvocationOnMock invocationOnMock) throws Throwable {
|
||||
return stub.read();
|
||||
}
|
||||
});
|
||||
|
||||
when(result.read(any(byte[].class))).thenAnswer(new Answer<Integer>() {
|
||||
@Override
|
||||
public Integer answer(InvocationOnMock invocationOnMock) throws Throwable {
|
||||
byte[] buffer = (byte[])invocationOnMock.getArguments()[0];
|
||||
return stub.read(buffer, 0, buffer.length);
|
||||
}
|
||||
});
|
||||
|
||||
when(result.read(any(byte[].class), anyInt(), anyInt())).thenAnswer(new Answer<Integer>() {
|
||||
@Override
|
||||
public Integer answer(InvocationOnMock invocationOnMock) throws Throwable {
|
||||
byte[] buffer = (byte[]) invocationOnMock.getArguments()[0];
|
||||
int offset = (int) invocationOnMock.getArguments()[1];
|
||||
int length = (int) invocationOnMock.getArguments()[2];
|
||||
|
||||
return stub.read(buffer, offset, length);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void readResponses(PubSubConnection pubSubConnection) throws Exception {
|
||||
PubSubReply reply = pubSubConnection.read();
|
||||
|
||||
assertEquals(reply.getType(), PubSubReply.Type.SUBSCRIBE);
|
||||
assertEquals(reply.getChannel(), "abcde");
|
||||
assertFalse(reply.getContent().isPresent());
|
||||
|
||||
reply = pubSubConnection.read();
|
||||
|
||||
assertEquals(reply.getType(), PubSubReply.Type.SUBSCRIBE);
|
||||
assertEquals(reply.getChannel(), "fghij");
|
||||
assertFalse(reply.getContent().isPresent());
|
||||
|
||||
reply = pubSubConnection.read();
|
||||
|
||||
assertEquals(reply.getType(), PubSubReply.Type.SUBSCRIBE);
|
||||
assertEquals(reply.getChannel(), "klmno");
|
||||
assertFalse(reply.getContent().isPresent());
|
||||
|
||||
reply = pubSubConnection.read();
|
||||
|
||||
assertEquals(reply.getType(), PubSubReply.Type.MESSAGE);
|
||||
assertEquals(reply.getChannel(), "abcde");
|
||||
assertArrayEquals(reply.getContent().get(), "1234567890".getBytes());
|
||||
|
||||
reply = pubSubConnection.read();
|
||||
|
||||
assertEquals(reply.getType(), PubSubReply.Type.MESSAGE);
|
||||
assertEquals(reply.getChannel(), "klmno");
|
||||
assertArrayEquals(reply.getContent().get(), "0987654321".getBytes());
|
||||
}
|
||||
|
||||
private interface MockInputStream {
|
||||
public int read();
|
||||
public int read(byte[] input, int offset, int length);
|
||||
}
|
||||
|
||||
private static class TrickleInputStream implements MockInputStream {
|
||||
|
||||
private final byte[] data;
|
||||
private int index = 0;
|
||||
|
||||
private TrickleInputStream(byte[] data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public int read() {
|
||||
return data[index++];
|
||||
}
|
||||
|
||||
public int read(byte[] input, int offset, int length) {
|
||||
input[offset] = data[index++];
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class FullInputStream implements MockInputStream {
|
||||
|
||||
private final byte[] data;
|
||||
private int index = 0;
|
||||
|
||||
private FullInputStream(byte[] data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public int read() {
|
||||
return data[index++];
|
||||
}
|
||||
|
||||
public int read(byte[] input, int offset, int length) {
|
||||
int amount = Math.min(data.length - index, length);
|
||||
System.arraycopy(data, index, input, offset, amount);
|
||||
index += length;
|
||||
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
|
||||
private static class RandomInputStream implements MockInputStream {
|
||||
private final byte[] data;
|
||||
private int index = 0;
|
||||
|
||||
private RandomInputStream(byte[] data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public int read() {
|
||||
return data[index++];
|
||||
}
|
||||
|
||||
public int read(byte[] input, int offset, int length) {
|
||||
int maxCopy = Math.min(data.length - index, length);
|
||||
int randomCopy = new SecureRandom().nextInt(maxCopy) + 1;
|
||||
int copyAmount = Math.min(maxCopy, randomCopy);
|
||||
|
||||
System.arraycopy(data, index, input, offset, copyAmount);
|
||||
index += copyAmount;
|
||||
|
||||
return copyAmount;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.dispatch.redis.protocol;
|
||||
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class ArrayReplyHeaderTest {
|
||||
|
||||
|
||||
@Test(expected = IOException.class)
|
||||
public void testNull() throws IOException {
|
||||
new ArrayReplyHeader(null);
|
||||
}
|
||||
|
||||
@Test(expected = IOException.class)
|
||||
public void testBadPrefix() throws IOException {
|
||||
new ArrayReplyHeader(":3");
|
||||
}
|
||||
|
||||
@Test(expected = IOException.class)
|
||||
public void testEmpty() throws IOException {
|
||||
new ArrayReplyHeader("");
|
||||
}
|
||||
|
||||
@Test(expected = IOException.class)
|
||||
public void testTruncated() throws IOException {
|
||||
new ArrayReplyHeader("*");
|
||||
}
|
||||
|
||||
@Test(expected = IOException.class)
|
||||
public void testBadNumber() throws IOException {
|
||||
new ArrayReplyHeader("*ABC");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValid() throws IOException {
|
||||
assertEquals(4, new ArrayReplyHeader("*4").getElementCount());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.dispatch.redis.protocol;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class IntReplyHeaderTest {
|
||||
|
||||
@Test(expected = IOException.class)
|
||||
public void testNull() throws IOException {
|
||||
new IntReply(null);
|
||||
}
|
||||
|
||||
@Test(expected = IOException.class)
|
||||
public void testEmpty() throws IOException {
|
||||
new IntReply("");
|
||||
}
|
||||
|
||||
@Test(expected = IOException.class)
|
||||
public void testBadNumber() throws IOException {
|
||||
new IntReply(":A");
|
||||
}
|
||||
|
||||
@Test(expected = IOException.class)
|
||||
public void testBadFormat() throws IOException {
|
||||
new IntReply("*");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValid() throws IOException {
|
||||
assertEquals(23, new IntReply(":23").getValue());
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.dispatch.redis.protocol;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class StringReplyHeaderTest {
|
||||
|
||||
@Test
|
||||
public void testNull() {
|
||||
try {
|
||||
new StringReplyHeader(null);
|
||||
throw new AssertionError();
|
||||
} catch (IOException e) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadNumber() {
|
||||
try {
|
||||
new StringReplyHeader("$100A");
|
||||
throw new AssertionError();
|
||||
} catch (IOException e) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadPrefix() {
|
||||
try {
|
||||
new StringReplyHeader("*");
|
||||
throw new AssertionError();
|
||||
} catch (IOException e) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValid() throws IOException {
|
||||
assertEquals(1000, new StringReplyHeader("$1000").getStringLength());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
97
service/config/sample-secrets-bundle.yml
Normal file
97
service/config/sample-secrets-bundle.yml
Normal file
@@ -0,0 +1,97 @@
|
||||
datadog.apiKey: unset
|
||||
|
||||
stripe.apiKey: unset
|
||||
stripe.idempotencyKeyGenerator: abcdefg12345678= # base64 for creating request idempotency hash
|
||||
|
||||
braintree.privateKey: unset
|
||||
|
||||
directoryV2.client.userAuthenticationTokenSharedSecret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with CDS to generate auth tokens for Signal users
|
||||
directoryV2.client.userIdTokenSharedSecret: bbcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with CDS to generate auth identity tokens for Signal users
|
||||
|
||||
svr2.userAuthenticationTokenSharedSecret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with SVR2 to generate auth tokens for Signal users
|
||||
svr2.userIdTokenSharedSecret: bbcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with SVR2 to generate auth identity tokens for Signal users
|
||||
|
||||
svr3.userAuthenticationTokenSharedSecret: cbcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with SVR3 to generate auth tokens for Signal users
|
||||
svr3.userIdTokenSharedSecret: dbcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with SVR3 to generate auth identity tokens for Signal users
|
||||
|
||||
tus.userAuthenticationTokenSharedSecret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG=
|
||||
|
||||
awsAttachments.accessKey: test
|
||||
awsAttachments.accessSecret: test
|
||||
|
||||
gcpAttachments.rsaSigningKey: |
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
AAAAAAAA
|
||||
-----END PRIVATE KEY-----
|
||||
|
||||
apn.teamId: team-id
|
||||
apn.keyId: key-id
|
||||
apn.signingKey: |
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
AAAAAAAA
|
||||
-----END PRIVATE KEY-----
|
||||
|
||||
fcm.credentials: |
|
||||
{ "json": true }
|
||||
|
||||
cdn.accessKey: test # AWS Access Key ID
|
||||
cdn.accessSecret: test # AWS Access Secret
|
||||
|
||||
cdn3StorageManager.clientSecret: test
|
||||
|
||||
unidentifiedDelivery.certificate: ABCD1234
|
||||
unidentifiedDelivery.privateKey: ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789AAAAAAA
|
||||
|
||||
hCaptcha.apiKey: unset
|
||||
|
||||
storageService.userAuthenticationTokenSharedSecret: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
||||
|
||||
zkConfig-libsignal-0.37.serverSecret: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzAA==
|
||||
|
||||
genericZkConfig.serverSecret: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzAA==
|
||||
callingZkConfig.serverSecret: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzAA==
|
||||
backupsZkConfig.serverSecret: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzAA==
|
||||
|
||||
paymentsService.userAuthenticationTokenSharedSecret: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= # base64-encoded 32-byte secret shared with MobileCoin services used to generate auth tokens for Signal users
|
||||
paymentsService.fixerApiKey: unset
|
||||
paymentsService.coinMarketCapApiKey: unset
|
||||
|
||||
artService.userAuthenticationTokenSharedSecret: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= # base64-encoded 32-byte secret not shared with any external service, but used in ArtController
|
||||
artService.userAuthenticationTokenUserIdSecret: AAAAAAAAAAA= # base64-encoded secret to obscure user phone numbers from Sticker Creator
|
||||
|
||||
currentReportingKey.secret: AAAAAAAAAAA=
|
||||
currentReportingKey.salt: AAAAAAAAAAA=
|
||||
|
||||
turn.secret: AAAAAAAAAAA=
|
||||
|
||||
linkDevice.secret: AAAAAAAAAAA=
|
||||
|
||||
tlsKeyStore.password: unset
|
||||
@@ -1,296 +1,474 @@
|
||||
# Example, relatively minimal, configuration that passes validation (see `io.dropwizard.cli.CheckCommand`)
|
||||
#
|
||||
# `unset` values will need to be set to work properly.
|
||||
# Most other values are technically valid for a local/demonstration environment, but are probably not production-ready.
|
||||
|
||||
logging:
|
||||
level: INFO
|
||||
appenders:
|
||||
- type: console
|
||||
threshold: ALL
|
||||
timeZone: UTC
|
||||
target: stdout
|
||||
- type: logstashtcpsocket
|
||||
destination: example.com:10516
|
||||
apiKey: secret://datadog.apiKey
|
||||
environment: staging
|
||||
|
||||
metrics:
|
||||
reporters:
|
||||
- type: signal-datadog
|
||||
frequency: 10 seconds
|
||||
tags:
|
||||
- "env:staging"
|
||||
- "service:chat"
|
||||
udpTransport:
|
||||
statsdHost: localhost
|
||||
port: 8125
|
||||
excludesAttributes:
|
||||
- m1_rate
|
||||
- m5_rate
|
||||
- m15_rate
|
||||
- mean_rate
|
||||
- stddev
|
||||
useRegexFilters: true
|
||||
excludes:
|
||||
- ^.+\.total$
|
||||
- ^.+\.request\.filtering$
|
||||
- ^.+\.response\.filtering$
|
||||
- ^executor\..+$
|
||||
- ^lettuce\..+$
|
||||
reportOnStop: true
|
||||
|
||||
tlsKeyStore:
|
||||
password: secret://tlsKeyStore.password
|
||||
|
||||
stripe:
|
||||
apiKey:
|
||||
idempotencyKeyGenerator:
|
||||
apiKey: secret://stripe.apiKey
|
||||
idempotencyKeyGenerator: secret://stripe.idempotencyKeyGenerator
|
||||
boostDescription: >
|
||||
Example
|
||||
supportedCurrenciesByPaymentMethod:
|
||||
CARD:
|
||||
- usd
|
||||
- eur
|
||||
SEPA_DEBIT:
|
||||
- eur
|
||||
|
||||
braintree:
|
||||
merchantId: unset
|
||||
publicKey: unset
|
||||
privateKey: secret://braintree.privateKey
|
||||
environment: unset
|
||||
graphqlUrl: unset
|
||||
merchantAccounts:
|
||||
# ISO 4217 currency code and its corresponding sub-merchant account
|
||||
'xts': unset
|
||||
supportedCurrenciesByPaymentMethod:
|
||||
PAYPAL:
|
||||
- usd
|
||||
|
||||
dynamoDbClientConfiguration:
|
||||
region: # AWS Region
|
||||
region: us-west-2 # AWS Region
|
||||
|
||||
dynamoDbTables:
|
||||
accounts:
|
||||
tableName: Example_Accounts
|
||||
phoneNumberTableName: Example_Accounts_PhoneNumbers
|
||||
phoneNumberIdentifierTableName: Example_Accounts_PhoneNumberIdentifiers
|
||||
usernamesTableName: Example_Accounts_Usernames
|
||||
backups:
|
||||
tableName: Example_Backups
|
||||
clientReleases:
|
||||
tableName: Example_ClientReleases
|
||||
deletedAccounts:
|
||||
tableName: Example_DeletedAccounts
|
||||
deletedAccountsLock:
|
||||
tableName: Example_DeletedAccountsLock
|
||||
issuedReceipts:
|
||||
tableName: # DDB Table Name
|
||||
expiration: # Duration of time until rows expire
|
||||
generator: # random binary sequence
|
||||
tableName: Example_IssuedReceipts
|
||||
expiration: P30D # Duration of time until rows expire
|
||||
generator: abcdefg12345678= # random base64-encoded binary sequence
|
||||
ecKeys:
|
||||
tableName: Example_Keys
|
||||
ecSignedPreKeys:
|
||||
tableName: Example_EC_Signed_Pre_Keys
|
||||
pqKeys:
|
||||
tableName: Example_PQ_Keys
|
||||
pqLastResortKeys:
|
||||
tableName: Example_PQ_Last_Resort_Keys
|
||||
messages:
|
||||
tableName: Example_Messages
|
||||
expiration: P30D # Duration of time until rows expire
|
||||
onetimeDonations:
|
||||
tableName: Example_OnetimeDonations
|
||||
expiration: P90D
|
||||
phoneNumberIdentifiers:
|
||||
tableName: Example_PhoneNumberIdentifiers
|
||||
profiles:
|
||||
tableName: Example_Profiles
|
||||
pushChallenge:
|
||||
tableName: Example_PushChallenge
|
||||
redeemedReceipts:
|
||||
tableName: # DDB Table Name
|
||||
expiration: # Duration of time until rows expire
|
||||
tableName: Example_RedeemedReceipts
|
||||
expiration: P30D # Duration of time until rows expire
|
||||
registrationRecovery:
|
||||
tableName: Example_RegistrationRecovery
|
||||
expiration: P300D # Duration of time until rows expire
|
||||
remoteConfig:
|
||||
tableName: Example_RemoteConfig
|
||||
reportMessage:
|
||||
tableName: Example_ReportMessage
|
||||
subscriptions:
|
||||
tableName: # DDB Table Name
|
||||
|
||||
twilio: # Twilio gateway configuration
|
||||
accountId:
|
||||
accountToken:
|
||||
nanpaMessagingServiceSid: # Twilio SID for the messaging service to use for NANPA.
|
||||
messagingServiceSid: # Twilio SID for the message service to use for non-NANPA.
|
||||
verifyServiceSid: # Twilio SID for a Verify service
|
||||
localDomain: # Domain Twilio can connect back to for calls. Should be domain of your service.
|
||||
defaultClientVerificationTexts:
|
||||
ios: # Text to use for the verification message on iOS. Will be passed to String.format with the verification code as argument 1.
|
||||
androidNg: # Text to use for the verification message on android-ng client types. Will be passed to String.format with the verification code as argument 1.
|
||||
android202001: # Text to use for the verification message on android-2020-01 client types. Will be passed to String.format with the verification code as argument 1.
|
||||
android202103: # Text to use for the verification message on android-2021-03 client types. Will be passed to String.format with the verification code as argument 1.
|
||||
generic: # Text to use when the client type is unrecognized. Will be passed to String.format with the verification code as argument 1.
|
||||
regionalClientVerificationTexts: # Map of country codes to custom texts
|
||||
999: # example country code
|
||||
ios:
|
||||
# … all keys from defaultClientVerificationTexts are required
|
||||
androidAppHash: # Hash appended to Android
|
||||
verifyServiceFriendlyName: # Service name used in template. Requires Twilio account rep to enable
|
||||
|
||||
push:
|
||||
queueSize: # Size of push pending queue
|
||||
|
||||
turn: # TURN server configuration
|
||||
secret: # TURN server secret
|
||||
uris:
|
||||
- stun:yourdomain:80
|
||||
- stun:yourdomain.com:443
|
||||
- turn:yourdomain:443?transport=udp
|
||||
- turn:etc.com:80?transport=udp
|
||||
tableName: Example_Subscriptions
|
||||
clientPublicKeys:
|
||||
tableName: Example_ClientPublicKeys
|
||||
verificationSessions:
|
||||
tableName: Example_VerificationSessions
|
||||
|
||||
cacheCluster: # Redis server configuration for cache cluster
|
||||
urls:
|
||||
- redis://redis.example.com:6379/
|
||||
configurationUri: redis://redis.example.com:6379/
|
||||
|
||||
clientPresenceCluster: # Redis server configuration for client presence cluster
|
||||
urls:
|
||||
- redis://redis.example.com:6379/
|
||||
configurationUri: redis://redis.example.com:6379/
|
||||
|
||||
pubsub: # Redis server configuration for pubsub cluster
|
||||
url: redis://redis.example.com:6379/
|
||||
replicaUrls:
|
||||
- redis://redis.example.com:6379/
|
||||
uri: redis://redis.example.com:6379/
|
||||
|
||||
pushSchedulerCluster: # Redis server configuration for push scheduler cluster
|
||||
urls:
|
||||
- redis://redis.example.com:6379/
|
||||
configurationUri: redis://redis.example.com:6379/
|
||||
|
||||
rateLimitersCluster: # Redis server configuration for rate limiters cluster
|
||||
urls:
|
||||
- redis://redis.example.com:6379/
|
||||
|
||||
directory:
|
||||
client: # Configuration for interfacing with Contact Discovery Service cluster
|
||||
userAuthenticationTokenSharedSecret: # hex-encoded secret shared with CDS used to generate auth tokens for Signal users
|
||||
userAuthenticationTokenUserIdSecret: # hex-encoded secret shared among Signal-Servers to obscure user phone numbers from CDS
|
||||
sqs:
|
||||
accessKey: # AWS SQS accessKey
|
||||
accessSecret: # AWS SQS accessSecret
|
||||
queueUrls: # AWS SQS queue urls
|
||||
- https://sqs.example.com/directory.fifo
|
||||
server: # One or more CDS servers
|
||||
- replicationName: # CDS replication name
|
||||
replicationUrl: # CDS replication endpoint base url
|
||||
replicationPassword: # CDS replication endpoint password
|
||||
replicationCaCertificate: # CDS replication endpoint TLS certificate trust root
|
||||
configurationUri: redis://redis.example.com:6379/
|
||||
|
||||
directoryV2:
|
||||
client: # Configuration for interfacing with Contact Discovery Service v2 cluster
|
||||
userAuthenticationTokenSharedSecret: # base64-encoded secret shared with CDS to generate auth tokens for Signal users
|
||||
userAuthenticationTokenSharedSecret: secret://directoryV2.client.userAuthenticationTokenSharedSecret
|
||||
userIdTokenSharedSecret: secret://directoryV2.client.userIdTokenSharedSecret
|
||||
|
||||
svr2:
|
||||
uri: svr2.example.com
|
||||
userAuthenticationTokenSharedSecret: secret://svr2.userAuthenticationTokenSharedSecret
|
||||
userIdTokenSharedSecret: secret://svr2.userIdTokenSharedSecret
|
||||
svrCaCertificates:
|
||||
- |
|
||||
-----BEGIN CERTIFICATE-----
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
AAAAAAAAAAAAAAAAAAAA
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
svr3:
|
||||
uri: svr3.example.com
|
||||
userAuthenticationTokenSharedSecret: secret://svr3.userAuthenticationTokenSharedSecret
|
||||
userIdTokenSharedSecret: secret://svr3.userIdTokenSharedSecret
|
||||
svrCaCertificates:
|
||||
- |
|
||||
-----BEGIN CERTIFICATE-----
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
AAAAAAAAAAAAAAAAAAAA
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
|
||||
messageCache: # Redis server configuration for message store cache
|
||||
persistDelayMinutes:
|
||||
|
||||
persistDelayMinutes: 1
|
||||
cluster:
|
||||
urls:
|
||||
- redis://redis.example.com:6379/
|
||||
configurationUri: redis://redis.example.com:6379/
|
||||
|
||||
metricsCluster:
|
||||
urls:
|
||||
- redis://redis.example.com:6379/
|
||||
|
||||
messageDynamoDb: # DynamoDB table configuration
|
||||
region:
|
||||
tableName:
|
||||
|
||||
keysDynamoDb: # DynamoDB table configuration
|
||||
region:
|
||||
tableName:
|
||||
|
||||
accountsDynamoDb: # DynamoDB table configuration
|
||||
region:
|
||||
tableName:
|
||||
phoneNumberTableName:
|
||||
|
||||
deletedAccountsDynamoDb: # DynamoDb table configuration
|
||||
region:
|
||||
tableName:
|
||||
needsReconciliationIndexName:
|
||||
|
||||
deletedAccountsLockDynamoDb: # DynamoDb table configuration
|
||||
region:
|
||||
tableName:
|
||||
|
||||
redeemedReceiptsDynamoDb: # DynamoDB table configuration
|
||||
region:
|
||||
tableName:
|
||||
expirationTime: # ISO8601 Duration
|
||||
|
||||
migrationDeletedAccountsDynamoDb: # DynamoDB table configuration
|
||||
region:
|
||||
tableName:
|
||||
|
||||
migrationRetryAccountsDynamoDb: # DynamoDB table configuration
|
||||
region:
|
||||
tableName:
|
||||
|
||||
pendingAccountsDynamoDb: # DynamoDB table configuration
|
||||
region:
|
||||
tableName:
|
||||
|
||||
pendingDevicesDynamoDb: # DynamoDB table configuration
|
||||
region:
|
||||
tableName:
|
||||
|
||||
pushChallengeDynamoDb: # DynamoDB table configuration
|
||||
region:
|
||||
tableName:
|
||||
|
||||
reportMessageDynamoDb: # DynamoDB table configuration
|
||||
region:
|
||||
tableName:
|
||||
configurationUri: redis://redis.example.com:6379/
|
||||
|
||||
awsAttachments: # AWS S3 configuration
|
||||
accessKey:
|
||||
accessSecret:
|
||||
bucket:
|
||||
region:
|
||||
accessKey: secret://awsAttachments.accessKey
|
||||
accessSecret: secret://awsAttachments.accessSecret
|
||||
bucket: aws-attachments
|
||||
region: us-west-2
|
||||
|
||||
gcpAttachments: # GCP Storage configuration
|
||||
domain:
|
||||
email:
|
||||
maxSizeInBytes:
|
||||
domain: example.com
|
||||
email: user@example.cocm
|
||||
maxSizeInBytes: 1024
|
||||
pathPrefix:
|
||||
rsaSigningKey:
|
||||
rsaSigningKey: secret://gcpAttachments.rsaSigningKey
|
||||
|
||||
abuseDatabase: # Postgresql database configuration
|
||||
driverClass: org.postgresql.Driver
|
||||
user:
|
||||
password:
|
||||
url:
|
||||
|
||||
accountsDatabase: # Postgresql database configuration
|
||||
driverClass: org.postgresql.Driver
|
||||
user:
|
||||
password:
|
||||
url:
|
||||
|
||||
accountDatabaseCrawler:
|
||||
chunkSize: # accounts per run
|
||||
chunkIntervalMs: # time per run
|
||||
|
||||
dynamoDbMigrationCrawler:
|
||||
chunkSize: # accounts per run
|
||||
chunkIntervalMs: # time per run
|
||||
tus:
|
||||
uploadUri: https://example.org/upload
|
||||
userAuthenticationTokenSharedSecret: secret://tus.userAuthenticationTokenSharedSecret
|
||||
|
||||
apn: # Apple Push Notifications configuration
|
||||
sandbox: true
|
||||
bundleId:
|
||||
keyId:
|
||||
teamId:
|
||||
signingKey:
|
||||
bundleId: com.example.textsecuregcm
|
||||
keyId: secret://apn.keyId
|
||||
teamId: secret://apn.teamId
|
||||
signingKey: secret://apn.signingKey
|
||||
|
||||
gcm: # GCM Configuration
|
||||
senderId:
|
||||
apiKey:
|
||||
fcm: # FCM configuration
|
||||
credentials: secret://fcm.credentials
|
||||
|
||||
cdn:
|
||||
accessKey: # AWS Access Key ID
|
||||
accessSecret: # AWS Access Secret
|
||||
bucket: # S3 Bucket name
|
||||
region: # AWS region
|
||||
accessKey: secret://cdn.accessKey
|
||||
accessSecret: secret://cdn.accessSecret
|
||||
bucket: cdn # S3 Bucket name
|
||||
region: us-west-2 # AWS region
|
||||
|
||||
datadog:
|
||||
apiKey:
|
||||
environment:
|
||||
clientCdn:
|
||||
attachmentUrls:
|
||||
2: https://cdn2.example.com/attachments/
|
||||
caCertificates:
|
||||
- |
|
||||
-----BEGIN CERTIFICATE-----
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
AAAAAAAAAAAAAAAAAAAA
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
cdn3StorageManager:
|
||||
baseUri: https://storage-manager.example.com
|
||||
clientId: example
|
||||
clientSecret: secret://cdn3StorageManager.clientSecret
|
||||
|
||||
dogstatsd:
|
||||
environment: dev
|
||||
host: 127.0.0.1
|
||||
|
||||
unidentifiedDelivery:
|
||||
certificate:
|
||||
privateKey:
|
||||
expiresDays:
|
||||
|
||||
voiceVerification:
|
||||
url: https://cdn-ca.signal.org/verification/
|
||||
locales:
|
||||
- en
|
||||
certificate: secret://unidentifiedDelivery.certificate
|
||||
privateKey: secret://unidentifiedDelivery.privateKey
|
||||
expiresDays: 7
|
||||
|
||||
recaptcha:
|
||||
secret:
|
||||
projectPath: projects/example
|
||||
credentialConfigurationJson: "{ }" # service account configuration for backend authentication
|
||||
|
||||
recaptchaV2:
|
||||
siteKey:
|
||||
scoreFloor:
|
||||
projectPath:
|
||||
credentialConfigurationJson:
|
||||
hCaptcha:
|
||||
apiKey: secret://hCaptcha.apiKey
|
||||
|
||||
shortCode:
|
||||
baseUrl: https://example.com/shortcodes/
|
||||
|
||||
storageService:
|
||||
uri:
|
||||
userAuthenticationTokenSharedSecret:
|
||||
storageCaCertificate:
|
||||
|
||||
backupService:
|
||||
uri:
|
||||
userAuthenticationTokenSharedSecret:
|
||||
backupCaCertificate:
|
||||
uri: storage.example.com
|
||||
userAuthenticationTokenSharedSecret: secret://storageService.userAuthenticationTokenSharedSecret
|
||||
storageCaCertificates:
|
||||
- |
|
||||
-----BEGIN CERTIFICATE-----
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
AAAAAAAAAAAAAAAAAAAA
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
zkConfig:
|
||||
serverPublic:
|
||||
serverSecret:
|
||||
enabled:
|
||||
serverPublic: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
serverSecret: secret://zkConfig-libsignal-0.37.serverSecret
|
||||
|
||||
callingZkConfig:
|
||||
serverSecret: secret://callingZkConfig.serverSecret
|
||||
|
||||
backupsZkConfig:
|
||||
serverSecret: secret://backupsZkConfig.serverSecret
|
||||
|
||||
appConfig:
|
||||
application:
|
||||
environment:
|
||||
configuration:
|
||||
application: example
|
||||
environment: example
|
||||
configuration: example
|
||||
|
||||
remoteConfig:
|
||||
authorizedTokens:
|
||||
- # 1st authorized token
|
||||
- # 2nd authorized token
|
||||
- # ...
|
||||
- # Nth authorized token
|
||||
globalConfig: # keys and values that are given to clients on GET /v1/config
|
||||
|
||||
EXAMPLE_KEY: VALUE
|
||||
|
||||
paymentsService:
|
||||
userAuthenticationTokenSharedSecret: # hex-encoded 32-byte secret shared with MobileCoin services used to generate auth tokens for Signal users
|
||||
fixerApiKey:
|
||||
userAuthenticationTokenSharedSecret: secret://paymentsService.userAuthenticationTokenSharedSecret
|
||||
fixerApiKey: secret://paymentsService.fixerApiKey
|
||||
coinMarketCapApiKey: secret://paymentsService.coinMarketCapApiKey
|
||||
coinMarketCapCurrencyIds:
|
||||
MOB: 7878
|
||||
paymentCurrencies:
|
||||
-
|
||||
# list of symbols for supported currencies
|
||||
- MOB
|
||||
|
||||
torExitNodeList:
|
||||
s3Region:
|
||||
s3Bucket:
|
||||
objectKey:
|
||||
maxSize:
|
||||
|
||||
asnTable:
|
||||
s3Region:
|
||||
s3Bucket:
|
||||
objectKey:
|
||||
maxSize:
|
||||
|
||||
donation:
|
||||
uri: # value
|
||||
supportedCurrencies:
|
||||
- # 1st supported currency
|
||||
- # 2nd supported currency
|
||||
- # ...
|
||||
- # Nth supported currency
|
||||
circuitBreaker:
|
||||
failureRateThreshold: # value
|
||||
ringBufferSizeInHalfOpenState: # value
|
||||
ringBufferSizeInClosedState: # value
|
||||
waitDurationInOpenStateInSeconds: # value
|
||||
retry:
|
||||
maxAttempts: # value
|
||||
waitDuration: # value
|
||||
artService:
|
||||
userAuthenticationTokenSharedSecret: secret://artService.userAuthenticationTokenSharedSecret
|
||||
userAuthenticationTokenUserIdSecret: secret://artService.userAuthenticationTokenUserIdSecret
|
||||
|
||||
badges:
|
||||
badges:
|
||||
- id: TEST
|
||||
imageUrl: https://example.com/test-badge
|
||||
category: other
|
||||
sprites: # exactly 6
|
||||
- sprite-1.png
|
||||
- sprite-2.png
|
||||
- sprite-3.png
|
||||
- sprite-4.png
|
||||
- sprite-5.png
|
||||
- sprite-6.png
|
||||
svg: example.svg
|
||||
svgs:
|
||||
- light: example-light.svg
|
||||
dark: example-dark.svg
|
||||
badgeIdsEnabledForAll:
|
||||
- TEST
|
||||
receiptLevels:
|
||||
'1': TEST
|
||||
|
||||
subscription: # configuration for Stripe subscriptions
|
||||
badgeExpiration: P30D
|
||||
badgeGracePeriod: P15D
|
||||
levels:
|
||||
500:
|
||||
badge: EXAMPLE
|
||||
prices:
|
||||
# list of ISO 4217 currency codes and amounts for the given badge level
|
||||
xts:
|
||||
amount: '10'
|
||||
processorIds:
|
||||
STRIPE: price_example # stripe Price ID
|
||||
BRAINTREE: plan_example # braintree Plan ID
|
||||
|
||||
oneTimeDonations:
|
||||
sepaMaximumEuros: '10000'
|
||||
boost:
|
||||
level: 1
|
||||
expiration: P90D
|
||||
badge: EXAMPLE
|
||||
gift:
|
||||
level: 10
|
||||
expiration: P90D
|
||||
badge: EXAMPLE
|
||||
currencies:
|
||||
# ISO 4217 currency codes and amounts in those currencies
|
||||
xts:
|
||||
minimum: '0.5'
|
||||
gift: '2'
|
||||
boosts:
|
||||
- '1'
|
||||
- '2'
|
||||
- '4'
|
||||
- '8'
|
||||
- '20'
|
||||
- '40'
|
||||
|
||||
registrationService:
|
||||
host: registration.example.com
|
||||
port: 443
|
||||
credentialConfigurationJson: |
|
||||
{
|
||||
"example": "example"
|
||||
}
|
||||
identityTokenAudience: https://registration.example.com
|
||||
registrationCaCertificate: | # Registration service TLS certificate trust root
|
||||
-----BEGIN CERTIFICATE-----
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
AAAAAAAAAAAAAAAAAAAA
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
turn:
|
||||
secret: secret://turn.secret
|
||||
|
||||
linkDevice:
|
||||
secret: secret://linkDevice.secret
|
||||
|
||||
maxmindCityDatabase:
|
||||
s3Region: a-region
|
||||
s3Bucket: a-bucket
|
||||
objectKey: an-object.tar.gz
|
||||
maxSize: 32777216
|
||||
|
||||
callingTurnDnsRecords:
|
||||
s3Region: a-region
|
||||
s3Bucket: a-bucket
|
||||
objectKey: an-object.tar.gz
|
||||
maxSize: 32777216
|
||||
|
||||
callingTurnPerformanceTable:
|
||||
s3Region: a-region
|
||||
s3Bucket: a-bucket
|
||||
objectKey: an-object.tar.gz
|
||||
maxSize: 32777216
|
||||
|
||||
callingTurnManualTable:
|
||||
s3Region: a-region
|
||||
s3Bucket: a-bucket
|
||||
objectKey: an-object.tar.gz
|
||||
maxSize: 32777216
|
||||
|
||||
386
service/pom.xml
386
service/pom.xml
@@ -10,20 +10,25 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>service</artifactId>
|
||||
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>ossrh-snapshots</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
<properties>
|
||||
<firebase-admin.version>9.2.0</firebase-admin.version>
|
||||
<java-uuid-generator.version>4.3.0</java-uuid-generator.version>
|
||||
<sqlite4java.version>1.0.392</sqlite4java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-jaxrs2</artifactId>
|
||||
<version>${swagger.version}</version>
|
||||
<exclusions>
|
||||
<!-- org.yaml:snakeyaml is causing a dependency convergence error -->
|
||||
<exclusion>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
@@ -37,40 +42,25 @@
|
||||
<artifactId>jakarta.ws.rs-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.whispersystems.textsecure</groupId>
|
||||
<artifactId>redis-dispatch</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.whispersystems.textsecure</groupId>
|
||||
<artifactId>websocket-resources</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.whispersystems.textsecure</groupId>
|
||||
<artifactId>gcm-sender-async</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.signal</groupId>
|
||||
<artifactId>zkgroup-java</artifactId>
|
||||
<version>0.8.2</version>
|
||||
<artifactId>libsignal-server</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.whispersystems</groupId>
|
||||
<artifactId>curve25519-java</artifactId>
|
||||
<version>0.5.0</version>
|
||||
<groupId>org.signal.forks</groupId>
|
||||
<artifactId>noise-java</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-jdbi3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-auth</artifactId>
|
||||
@@ -81,7 +71,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-db</artifactId>
|
||||
<artifactId>dropwizard-http2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard</groupId>
|
||||
@@ -142,24 +132,10 @@
|
||||
<artifactId>logstash-logback-encoder</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jdbi</groupId>
|
||||
<artifactId>jdbi3-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.liquibase</groupId>
|
||||
<artifactId>liquibase-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.dropwizard.metrics</groupId>
|
||||
<artifactId>metrics-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard.metrics</groupId>
|
||||
<artifactId>metrics-jdbi3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard.metrics</groupId>
|
||||
<artifactId>metrics-healthchecks</artifactId>
|
||||
@@ -191,26 +167,64 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>party.iroiro.luajava</groupId>
|
||||
<artifactId>luajava</artifactId>
|
||||
<version>${luajava.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>party.iroiro.luajava</groupId>
|
||||
<artifactId>lua51</artifactId>
|
||||
<version>${luajava.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>party.iroiro.luajava</groupId>
|
||||
<artifactId>lua51-platform</artifactId>
|
||||
<version>${luajava.version}</version>
|
||||
<classifier>natives-desktop</classifier>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-api</artifactId>
|
||||
<artifactId>websocket-jetty-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlets</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-jetty-client</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-csv</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-csv</artifactId>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
<version>1.26.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.maxmind.geoip2</groupId>
|
||||
<artifactId>geoip2</artifactId>
|
||||
<version>4.2.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.firebase</groupId>
|
||||
<artifactId>firebase-admin</artifactId>
|
||||
<version>${firebase-admin.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -226,6 +240,29 @@
|
||||
<groupId>io.github.resilience4j</groupId>
|
||||
<artifactId>resilience4j-retry</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.resilience4j</groupId>
|
||||
<artifactId>resilience4j-reactor</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-netty</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-protobuf</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-stub</artifactId>
|
||||
</dependency>
|
||||
<!-- Needed for gRPC with Java 9+ -->
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat</groupId>
|
||||
<artifactId>annotations-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
@@ -233,7 +270,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-registry-datadog</artifactId>
|
||||
<artifactId>micrometer-registry-statsd</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.coursera</groupId>
|
||||
@@ -264,6 +301,19 @@
|
||||
<artifactId>jackson-jaxrs-json-provider</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.salesforce.servicelibs</groupId>
|
||||
<artifactId>reactor-grpc-stub</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>apache-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>netty-nio-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>sts</artifactId>
|
||||
@@ -272,10 +322,6 @@
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>s3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>sqs</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>dynamodb</artifactId>
|
||||
@@ -285,17 +331,13 @@
|
||||
<artifactId>appconfig</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.amazonaws</groupId>
|
||||
<artifactId>aws-java-sdk-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.amazonaws</groupId>
|
||||
<artifactId>aws-java-sdk-s3</artifactId>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>appconfigdata</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.amazonaws</groupId>
|
||||
<artifactId>dynamodb-lock-client</artifactId>
|
||||
<version>1.1.0</version>
|
||||
<version>1.2.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>commons-logging</groupId>
|
||||
@@ -304,22 +346,11 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.lettuce</groupId>
|
||||
<artifactId>lettuce-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.eatthepath</groupId>
|
||||
<artifactId>pushy</artifactId>
|
||||
@@ -329,12 +360,6 @@
|
||||
<artifactId>pushy-dropwizard-metrics-listener</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-tcnative-boringssl-static</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.vdurmont</groupId>
|
||||
<artifactId>semver4j</artifactId>
|
||||
@@ -374,74 +399,70 @@
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.opentable.components</groupId>
|
||||
<artifactId>otj-pg-embedded</artifactId>
|
||||
<version>0.13.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.almworks.sqlite4java</groupId>
|
||||
<artifactId>sqlite4java</artifactId>
|
||||
<version>1.0.392</version>
|
||||
<version>${sqlite4java.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
<version>3.3.16.RELEASE</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core-micrometer</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.vavr</groupId>
|
||||
<artifactId>vavr</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-params</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-test</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.signal</groupId>
|
||||
<artifactId>embedded-redis</artifactId>
|
||||
<version>0.8.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.uuid</groupId>
|
||||
<artifactId>java-uuid-generator</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>${java-uuid-generator.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.amazonaws</groupId>
|
||||
<artifactId>DynamoDBLocal</artifactId>
|
||||
<version>1.16.0</version>
|
||||
<version>1.23.0</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.antlr</groupId>
|
||||
<artifactId>antlr4-runtime</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.ganadist.sqlite4java</groupId>
|
||||
<artifactId>libsqlite4java-osx-aarch64</artifactId>
|
||||
<version>${sqlite4java.version}</version>
|
||||
<type>dylib</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.auth</groupId>
|
||||
<artifactId>google-auth-library-oauth2-http</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -455,22 +476,34 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>pl.pragmatists</groupId>
|
||||
<artifactId>JUnitParams</artifactId>
|
||||
<version>1.1.1</version>
|
||||
<scope>test</scope>
|
||||
<groupId>com.braintreepayments.gateway</groupId>
|
||||
<artifactId>braintree-java</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.apollographql.apollo3</groupId>
|
||||
<artifactId>apollo-api-jvm</artifactId>
|
||||
<version>3.8.2</version>
|
||||
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.jetbrains</groupId>
|
||||
<artifactId>annotations</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>exclude-abusive-message-filter</id>
|
||||
<id>exclude-spam-filter</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.4</version>
|
||||
<version>3.5.1</version>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>true</createDependencyReducedPom>
|
||||
<filters>
|
||||
@@ -505,7 +538,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<version>3.6.0</version>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>assembly.xml</descriptor>
|
||||
@@ -525,7 +558,7 @@
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>properties-maven-plugin</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<version>1.2.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>read-deploy-configuration</id>
|
||||
@@ -541,24 +574,64 @@
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.signal</groupId>
|
||||
<artifactId>s3-upload-maven-plugin</artifactId>
|
||||
<version>1.6-SNAPSHOT</version>
|
||||
<configuration>
|
||||
<source>${project.build.directory}/${project.build.finalName}-bin.tar.gz</source>
|
||||
<bucketName>${deploy.bucketName}</bucketName>
|
||||
<region>${deploy.bucketRegion}</region>
|
||||
<destination>${project.build.finalName}-bin.tar.gz</destination>
|
||||
</configuration>
|
||||
<groupId>com.google.cloud.tools</groupId>
|
||||
<artifactId>jib-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>deploy-to-s3</id>
|
||||
<phase>deploy</phase>
|
||||
<goals>
|
||||
<goal>s3-upload</goal>
|
||||
<goal>build</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<from>
|
||||
<image>eclipse-temurin@sha256:${docker.image.sha256}</image>
|
||||
</from>
|
||||
<to>
|
||||
<image>${docker.repo}:${project.version}</image>
|
||||
</to>
|
||||
<container>
|
||||
<mainClass>org.whispersystems.textsecuregcm.WhisperServerService</mainClass>
|
||||
<jvmFlags>
|
||||
<jvmFlag>-server</jvmFlag>
|
||||
<jvmFlag>-Djava.awt.headless=true</jvmFlag>
|
||||
<jvmFlag>-Djdk.nio.maxCachedBufferSize=262144</jvmFlag>
|
||||
<jvmFlag>-Dlog4j2.formatMsgNoLookups=true</jvmFlag>
|
||||
<jvmFlag>-XX:MaxRAMPercentage=75</jvmFlag>
|
||||
<jvmFlag>-XX:+HeapDumpOnOutOfMemoryError</jvmFlag>
|
||||
<jvmFlag>-XX:HeapDumpPath=/tmp/heapdump.bin</jvmFlag>
|
||||
</jvmFlags>
|
||||
<ports>
|
||||
<port>8080</port>
|
||||
</ports>
|
||||
<creationTime>USE_CURRENT_TIMESTAMP</creationTime>
|
||||
</container>
|
||||
<extraDirectories>
|
||||
<paths>
|
||||
<path>
|
||||
<from>${project.basedir}/config</from>
|
||||
<includes>*.yml</includes>
|
||||
<into>/usr/share/signal/</into>
|
||||
</path>
|
||||
</paths>
|
||||
</extraDirectories>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>include-spam-filter</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.google.cloud.tools</groupId>
|
||||
<artifactId>jib-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<!-- we don't want jib to execute on this module -->
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
@@ -582,6 +655,16 @@
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.1.2</version>
|
||||
<configuration>
|
||||
<!-- work around PATCH not being a supported method on HttpUrlConnection -->
|
||||
<argLine>--add-opens=java.base/java.net=ALL-UNNAMED</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
@@ -593,6 +676,53 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>check-all-service-config</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>java</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<mainClass>org.whispersystems.textsecuregcm.CheckServiceConfigurations</mainClass>
|
||||
<classpathScope>test</classpathScope>
|
||||
<arguments>
|
||||
<argument>${project.basedir}/config</argument>
|
||||
</arguments>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>com.github.aoudiamoncef</groupId>
|
||||
<artifactId>apollo-client-maven-plugin</artifactId>
|
||||
<version>5.0.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>generate</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<services>
|
||||
<braintree>
|
||||
<compilationUnit>
|
||||
<name>braintree</name>
|
||||
<compilerParams>
|
||||
<schemaPackageName>com.braintree.graphql.client</schemaPackageName>
|
||||
</compilerParams>
|
||||
</compilationUnit>
|
||||
</braintree>
|
||||
</services>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# https://graphql.braintreepayments.com/reference/#Mutation--chargePaymentMethod
|
||||
mutation ChargePayPalOneTimePayment($input: ChargePaymentMethodInput!) {
|
||||
chargePaymentMethod(input: $input) {
|
||||
transaction {
|
||||
id,
|
||||
status
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
mutation CreatePayPalBillingAgreement($input: CreatePayPalBillingAgreementInput!) {
|
||||
createPayPalBillingAgreement(input: $input) {
|
||||
approvalUrl,
|
||||
billingAgreementToken
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# https://graphql.braintreepayments.com/reference/#Mutation--createPayPalOneTimePayment
|
||||
mutation CreatePayPalOneTimePayment($input: CreatePayPalOneTimePaymentInput!) {
|
||||
createPayPalOneTimePayment(input: $input) {
|
||||
approvalUrl,
|
||||
paymentId
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
mutation TokenizePayPalBillingAgreement($input: TokenizePayPalBillingAgreementInput!) {
|
||||
tokenizePayPalBillingAgreement(input: $input) {
|
||||
paymentMethod {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
# https://graphql.braintreepayments.com/reference/#Mutation--tokenizePayPalOneTimePayment
|
||||
mutation TokenizePayPalOneTimePayment($input: TokenizePayPalOneTimePaymentInput!) {
|
||||
tokenizePayPalOneTimePayment(input: $input) {
|
||||
paymentMethod {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
mutation VaultPaymentMethod($input: VaultPaymentMethodInput!) {
|
||||
vaultPaymentMethod(input: $input) {
|
||||
paymentMethod {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
35093
service/src/main/graphql/braintree/schema.json
Normal file
35093
service/src/main/graphql/braintree/schema.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,71 +1,85 @@
|
||||
/*
|
||||
* Copyright 2013-2021 Signal Messenger, LLC
|
||||
* Copyright 2013 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.dropwizard.Configuration;
|
||||
import io.dropwizard.client.JerseyClientConfiguration;
|
||||
import io.dropwizard.core.Configuration;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import org.whispersystems.textsecuregcm.configuration.AbusiveMessageFilterConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.AccountDatabaseCrawlerConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.AccountsDatabaseConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.AccountsDynamoDbConfiguration;
|
||||
import org.whispersystems.textsecuregcm.attachments.TusConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.ApnConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.AppConfigConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.ArtServiceConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.AwsAttachmentsConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.BadgesConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.BoostConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.BraintreeConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.Cdn3StorageManagerConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.CdnConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.DatabaseConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.DatadogConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.DeletedAccountsDynamoDbConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.DirectoryConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.ClientCdnConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.ClientReleaseConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.DirectoryV2Configuration;
|
||||
import org.whispersystems.textsecuregcm.configuration.DonationConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.DogstatsdConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.DynamoDbClientConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.DynamoDbConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.DynamoDbTables;
|
||||
import org.whispersystems.textsecuregcm.configuration.GcmConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.FcmConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.GcpAttachmentsConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.GenericZkConfig;
|
||||
import org.whispersystems.textsecuregcm.configuration.HCaptchaConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.LinkDeviceSecretConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.MaxDeviceConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.MessageByteLimitCardinalityEstimatorConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.MessageCacheConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.MessageDynamoDbConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.MonitoredS3ObjectConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.OneTimeDonationConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.PaymentsServiceConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.PushConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.RateLimitsConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.RecaptchaConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.RecaptchaV2Configuration;
|
||||
import org.whispersystems.textsecuregcm.configuration.RedisClusterConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.RedisConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.RegistrationServiceConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.RemoteConfigConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.ReportMessageConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.SecureBackupServiceConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.SecureValueRecovery2Configuration;
|
||||
import org.whispersystems.textsecuregcm.configuration.SecureValueRecovery3Configuration;
|
||||
import org.whispersystems.textsecuregcm.configuration.ShortCodeExpanderConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.SpamFilterConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.StripeConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.SubscriptionConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.TestDeviceConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.TurnConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.TwilioConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.TlsKeyStoreConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.TurnSecretConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.UnidentifiedDeliveryConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.VoiceVerificationConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.VirtualThreadConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.ZkConfig;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiterConfig;
|
||||
import org.whispersystems.websocket.configuration.WebSocketConfiguration;
|
||||
|
||||
/** @noinspection MismatchedQueryAndUpdateOfCollection, WeakerAccess */
|
||||
public class WhisperServerConfiguration extends Configuration {
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private TlsKeyStoreConfiguration tlsKeyStore;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private StripeConfiguration stripe;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private BraintreeConfiguration braintree;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
@@ -76,16 +90,6 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
@JsonProperty
|
||||
private DynamoDbTables dynamoDbTables;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private TwilioConfiguration twilio;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private PushConfiguration push;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
@@ -104,7 +108,17 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private DatadogConfiguration datadog;
|
||||
private ClientCdnConfiguration clientCdn;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private Cdn3StorageManagerConfiguration cdn3StorageManager;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private DogstatsdConfiguration dogstatsd = new DogstatsdConfiguration();
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@@ -121,11 +135,6 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
@JsonProperty
|
||||
private RedisClusterConfiguration metricsCluster;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private DirectoryConfiguration directory;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
@@ -134,7 +143,11 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private AccountDatabaseCrawlerConfiguration accountDatabaseCrawler;
|
||||
private SecureValueRecovery2Configuration svr2;
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private SecureValueRecovery3Configuration svr3;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@@ -159,62 +172,7 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private MessageDynamoDbConfiguration messageDynamoDb;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private DynamoDbConfiguration keysDynamoDb;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private AccountsDynamoDbConfiguration accountsDynamoDb;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private DynamoDbConfiguration phoneNumberIdentifiersDynamoDb;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private DeletedAccountsDynamoDbConfiguration deletedAccountsDynamoDb;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private DynamoDbConfiguration deletedAccountsLockDynamoDb;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private DynamoDbConfiguration pushChallengeDynamoDb;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private DynamoDbConfiguration reportMessageDynamoDb;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private DynamoDbConfiguration pendingAccountsDynamoDb;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private DynamoDbConfiguration pendingDevicesDynamoDb;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private DatabaseConfiguration abuseDatabase;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private List<TestDeviceConfiguration> testDevices = new LinkedList<>();
|
||||
private Set<String> testDevices = new HashSet<>();
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@@ -224,17 +182,7 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private AccountsDatabaseConfiguration accountsDatabase;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private RateLimitsConfiguration limits = new RateLimitsConfiguration();
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private JerseyClientConfiguration httpClient = new JerseyClientConfiguration();
|
||||
private Map<String, RateLimiterConfig> limits = new HashMap<>();
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@@ -244,12 +192,7 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private TurnConfiguration turn;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private GcmConfiguration gcm;
|
||||
private FcmConfiguration fcm;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@@ -261,11 +204,6 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
@JsonProperty
|
||||
private UnidentifiedDeliveryConfiguration unidentifiedDelivery;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private VoiceVerificationConfiguration voiceVerification;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
@@ -274,28 +212,43 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private RecaptchaV2Configuration recaptchaV2;
|
||||
private HCaptchaConfiguration hCaptcha;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private ShortCodeExpanderConfiguration shortCode;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private SecureStorageServiceConfiguration storageService;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private SecureBackupServiceConfiguration backupService;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private PaymentsServiceConfiguration paymentsService;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private ArtServiceConfiguration artService;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private ZkConfig zkConfig;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private GenericZkConfig callingZkConfig;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private GenericZkConfig backupsZkConfig;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
@@ -306,11 +259,6 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
@JsonProperty
|
||||
private AppConfigConfiguration appConfig;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private DonationConfiguration donation;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
@@ -324,7 +272,7 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
@Valid
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
private BoostConfiguration boost;
|
||||
private OneTimeDonationConfiguration oneTimeDonations;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@@ -333,14 +281,76 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private AbusiveMessageFilterConfiguration abusiveMessageFilter;
|
||||
private SpamFilterConfiguration spamFilterConfiguration;
|
||||
|
||||
private Map<String, String> transparentDataIndex = new HashMap<>();
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private RegistrationServiceConfiguration registrationService;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private TurnSecretConfiguration turn;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private TusConfiguration tus;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private ClientReleaseConfiguration clientRelease = new ClientReleaseConfiguration(Duration.ofHours(4));
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private MessageByteLimitCardinalityEstimatorConfiguration messageByteLimitCardinalityEstimator = new MessageByteLimitCardinalityEstimatorConfiguration(Duration.ofDays(1));
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private LinkDeviceSecretConfiguration linkDevice;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private VirtualThreadConfiguration virtualThreadConfiguration = new VirtualThreadConfiguration(Duration.ofMillis(1));
|
||||
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private MonitoredS3ObjectConfiguration maxmindCityDatabase;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private MonitoredS3ObjectConfiguration callingTurnDnsRecords;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private MonitoredS3ObjectConfiguration callingTurnPerformanceTable;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private MonitoredS3ObjectConfiguration callingTurnManualTable;
|
||||
|
||||
public TlsKeyStoreConfiguration getTlsKeyStoreConfiguration() {
|
||||
return tlsKeyStore;
|
||||
}
|
||||
|
||||
public StripeConfiguration getStripe() {
|
||||
return stripe;
|
||||
}
|
||||
|
||||
public BraintreeConfiguration getBraintree() {
|
||||
return braintree;
|
||||
}
|
||||
|
||||
public DynamoDbClientConfiguration getDynamoDbClientConfiguration() {
|
||||
return dynamoDbClientConfiguration;
|
||||
}
|
||||
@@ -353,30 +363,18 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
return recaptcha;
|
||||
}
|
||||
|
||||
public RecaptchaV2Configuration getRecaptchaV2Configuration() {
|
||||
return recaptchaV2;
|
||||
public HCaptchaConfiguration getHCaptchaConfiguration() {
|
||||
return hCaptcha;
|
||||
}
|
||||
|
||||
public VoiceVerificationConfiguration getVoiceVerificationConfiguration() {
|
||||
return voiceVerification;
|
||||
public ShortCodeExpanderConfiguration getShortCodeRetrieverConfiguration() {
|
||||
return shortCode;
|
||||
}
|
||||
|
||||
public WebSocketConfiguration getWebSocketConfiguration() {
|
||||
return webSocket;
|
||||
}
|
||||
|
||||
public TwilioConfiguration getTwilioConfiguration() {
|
||||
return twilio;
|
||||
}
|
||||
|
||||
public PushConfiguration getPushConfiguration() {
|
||||
return push;
|
||||
}
|
||||
|
||||
public JerseyClientConfiguration getJerseyClientConfiguration() {
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
public AwsAttachmentsConfiguration getAwsAttachmentsConfiguration() {
|
||||
return awsAttachments;
|
||||
}
|
||||
@@ -397,8 +395,12 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
return metricsCluster;
|
||||
}
|
||||
|
||||
public DirectoryConfiguration getDirectoryConfiguration() {
|
||||
return directory;
|
||||
|
||||
public SecureValueRecovery2Configuration getSvr2Configuration() {
|
||||
return svr2;
|
||||
}
|
||||
public SecureValueRecovery3Configuration getSvr3Configuration() {
|
||||
return svr3;
|
||||
}
|
||||
|
||||
public DirectoryV2Configuration getDirectoryV2Configuration() {
|
||||
@@ -409,10 +411,6 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
return storageService;
|
||||
}
|
||||
|
||||
public AccountDatabaseCrawlerConfiguration getAccountDatabaseCrawlerConfiguration() {
|
||||
return accountDatabaseCrawler;
|
||||
}
|
||||
|
||||
public MessageCacheConfiguration getMessageCacheConfiguration() {
|
||||
return messageCache;
|
||||
}
|
||||
@@ -429,48 +427,12 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
return rateLimitersCluster;
|
||||
}
|
||||
|
||||
public MessageDynamoDbConfiguration getMessageDynamoDbConfiguration() {
|
||||
return messageDynamoDb;
|
||||
}
|
||||
|
||||
public DynamoDbConfiguration getKeysDynamoDbConfiguration() {
|
||||
return keysDynamoDb;
|
||||
}
|
||||
|
||||
public AccountsDynamoDbConfiguration getAccountsDynamoDbConfiguration() {
|
||||
return accountsDynamoDb;
|
||||
}
|
||||
|
||||
public DynamoDbConfiguration getPhoneNumberIdentifiersDynamoDbConfiguration() {
|
||||
return phoneNumberIdentifiersDynamoDb;
|
||||
}
|
||||
|
||||
public DeletedAccountsDynamoDbConfiguration getDeletedAccountsDynamoDbConfiguration() {
|
||||
return deletedAccountsDynamoDb;
|
||||
}
|
||||
|
||||
public DynamoDbConfiguration getDeletedAccountsLockDynamoDbConfiguration() {
|
||||
return deletedAccountsLockDynamoDb;
|
||||
}
|
||||
|
||||
public DatabaseConfiguration getAbuseDatabaseConfiguration() {
|
||||
return abuseDatabase;
|
||||
}
|
||||
|
||||
public AccountsDatabaseConfiguration getAccountsDatabaseConfiguration() {
|
||||
return accountsDatabase;
|
||||
}
|
||||
|
||||
public RateLimitsConfiguration getLimitsConfiguration() {
|
||||
public Map<String, RateLimiterConfig> getLimitsConfiguration() {
|
||||
return limits;
|
||||
}
|
||||
|
||||
public TurnConfiguration getTurnConfiguration() {
|
||||
return turn;
|
||||
}
|
||||
|
||||
public GcmConfiguration getGcmConfiguration() {
|
||||
return gcm;
|
||||
public FcmConfiguration getFcmConfiguration() {
|
||||
return fcm;
|
||||
}
|
||||
|
||||
public ApnConfiguration getApnConfiguration() {
|
||||
@@ -481,23 +443,24 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
return cdn;
|
||||
}
|
||||
|
||||
public DatadogConfiguration getDatadogConfiguration() {
|
||||
return datadog;
|
||||
public ClientCdnConfiguration getClientCdnConfiguration() {
|
||||
return clientCdn;
|
||||
}
|
||||
|
||||
public Cdn3StorageManagerConfiguration getCdn3StorageManagerConfiguration() {
|
||||
return cdn3StorageManager;
|
||||
}
|
||||
|
||||
public DogstatsdConfiguration getDatadogConfiguration() {
|
||||
return dogstatsd;
|
||||
}
|
||||
|
||||
public UnidentifiedDeliveryConfiguration getDeliveryCertificate() {
|
||||
return unidentifiedDelivery;
|
||||
}
|
||||
|
||||
public Map<String, Integer> getTestDevices() {
|
||||
Map<String, Integer> results = new HashMap<>();
|
||||
|
||||
for (TestDeviceConfiguration testDeviceConfiguration : testDevices) {
|
||||
results.put(testDeviceConfiguration.getNumber(),
|
||||
testDeviceConfiguration.getCode());
|
||||
}
|
||||
|
||||
return results;
|
||||
public Set<String> getTestDevices() {
|
||||
return testDevices;
|
||||
}
|
||||
|
||||
public Map<String, Integer> getMaxDevices() {
|
||||
@@ -511,22 +474,26 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
return results;
|
||||
}
|
||||
|
||||
public Map<String, String> getTransparentDataIndex() {
|
||||
return transparentDataIndex;
|
||||
}
|
||||
|
||||
public SecureBackupServiceConfiguration getSecureBackupServiceConfiguration() {
|
||||
return backupService;
|
||||
}
|
||||
|
||||
public PaymentsServiceConfiguration getPaymentsServiceConfiguration() {
|
||||
return paymentsService;
|
||||
}
|
||||
|
||||
public ArtServiceConfiguration getArtServiceConfiguration() {
|
||||
return artService;
|
||||
}
|
||||
|
||||
public ZkConfig getZkConfig() {
|
||||
return zkConfig;
|
||||
}
|
||||
|
||||
public GenericZkConfig getCallingZkConfig() {
|
||||
return callingZkConfig;
|
||||
}
|
||||
|
||||
public GenericZkConfig getBackupsZkConfig() {
|
||||
return backupsZkConfig;
|
||||
}
|
||||
|
||||
public RemoteConfigConfiguration getRemoteConfigConfiguration() {
|
||||
return remoteConfig;
|
||||
}
|
||||
@@ -535,26 +502,6 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
return appConfig;
|
||||
}
|
||||
|
||||
public DynamoDbConfiguration getPushChallengeDynamoDbConfiguration() {
|
||||
return pushChallengeDynamoDb;
|
||||
}
|
||||
|
||||
public DynamoDbConfiguration getReportMessageDynamoDbConfiguration() {
|
||||
return reportMessageDynamoDb;
|
||||
}
|
||||
|
||||
public DynamoDbConfiguration getPendingAccountsDynamoDbConfiguration() {
|
||||
return pendingAccountsDynamoDb;
|
||||
}
|
||||
|
||||
public DynamoDbConfiguration getPendingDevicesDynamoDbConfiguration() {
|
||||
return pendingDevicesDynamoDb;
|
||||
}
|
||||
|
||||
public DonationConfiguration getDonationConfiguration() {
|
||||
return donation;
|
||||
}
|
||||
|
||||
public BadgesConfiguration getBadges() {
|
||||
return badges;
|
||||
}
|
||||
@@ -563,15 +510,59 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
return subscription;
|
||||
}
|
||||
|
||||
public BoostConfiguration getBoost() {
|
||||
return boost;
|
||||
public OneTimeDonationConfiguration getOneTimeDonations() {
|
||||
return oneTimeDonations;
|
||||
}
|
||||
|
||||
public ReportMessageConfiguration getReportMessageConfiguration() {
|
||||
return reportMessage;
|
||||
}
|
||||
|
||||
public AbusiveMessageFilterConfiguration getAbusiveMessageFilterConfiguration() {
|
||||
return abusiveMessageFilter;
|
||||
public SpamFilterConfiguration getSpamFilterConfiguration() {
|
||||
return spamFilterConfiguration;
|
||||
}
|
||||
|
||||
public RegistrationServiceConfiguration getRegistrationServiceConfiguration() {
|
||||
return registrationService;
|
||||
}
|
||||
|
||||
public TurnSecretConfiguration getTurnSecretConfiguration() {
|
||||
return turn;
|
||||
}
|
||||
|
||||
public TusConfiguration getTus() {
|
||||
return tus;
|
||||
}
|
||||
|
||||
public ClientReleaseConfiguration getClientReleaseConfiguration() {
|
||||
return clientRelease;
|
||||
}
|
||||
|
||||
public MessageByteLimitCardinalityEstimatorConfiguration getMessageByteLimitCardinalityEstimator() {
|
||||
return messageByteLimitCardinalityEstimator;
|
||||
}
|
||||
|
||||
public LinkDeviceSecretConfiguration getLinkDeviceSecretConfiguration() {
|
||||
return linkDevice;
|
||||
}
|
||||
|
||||
public VirtualThreadConfiguration getVirtualThreadConfiguration() {
|
||||
return virtualThreadConfiguration;
|
||||
}
|
||||
|
||||
public MonitoredS3ObjectConfiguration getMaxmindCityDatabase() {
|
||||
return maxmindCityDatabase;
|
||||
}
|
||||
|
||||
public MonitoredS3ObjectConfiguration getCallingTurnDnsRecords() {
|
||||
return callingTurnDnsRecords;
|
||||
}
|
||||
|
||||
public MonitoredS3ObjectConfiguration getCallingTurnPerformanceTable() {
|
||||
return callingTurnPerformanceTable;
|
||||
}
|
||||
|
||||
public MonitoredS3ObjectConfiguration getCallingTurnManualTable() {
|
||||
return callingTurnManualTable;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.abuse;
|
||||
|
||||
import io.dropwizard.lifecycle.Managed;
|
||||
import javax.ws.rs.container.ContainerRequestFilter;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* An abusive message filter is a {@link ContainerRequestFilter} that filters requests to message-sending endpoints to
|
||||
* detect and respond to patterns of abusive behavior.
|
||||
* <p/>
|
||||
* Abusive message filters are managed components that are generally loaded dynamically via a
|
||||
* {@link java.util.ServiceLoader}. Their {@link #configure(String)} method will be called prior to be adding to the
|
||||
* server's pool of {@link Managed} objects.
|
||||
* <p/>
|
||||
* Abusive message filters must be annotated with {@link FilterAbusiveMessages}, a name binding annotation that
|
||||
* restricts the endpoints to which the filter may apply.
|
||||
*/
|
||||
public interface AbusiveMessageFilter extends ContainerRequestFilter, Managed {
|
||||
|
||||
/**
|
||||
* Configures this abusive message filter. This method will be called before the filter is added to the server's pool
|
||||
* of managed objects and before the server processes any requests.
|
||||
*
|
||||
* @param environmentName the name of the environment in which this filter is running (e.g. "staging" or "production")
|
||||
* @throws IOException if the filter could not read its configuration source for any reason
|
||||
*/
|
||||
void configure(String environmentName) throws IOException;
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.abuse;
|
||||
|
||||
import javax.ws.rs.NameBinding;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* A name-binding annotation that associates {@link AbusiveMessageFilter}s with resource methods.
|
||||
*/
|
||||
@NameBinding
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
public @interface FilterAbusiveMessages {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.attachments;
|
||||
import java.util.Map;
|
||||
|
||||
public interface AttachmentGenerator {
|
||||
|
||||
record Descriptor(Map<String, String> headers, String signedUploadLocation) {}
|
||||
|
||||
Descriptor generateAttachment(final String key);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.attachments;
|
||||
|
||||
import org.whispersystems.textsecuregcm.gcp.CanonicalRequest;
|
||||
import org.whispersystems.textsecuregcm.gcp.CanonicalRequestGenerator;
|
||||
import org.whispersystems.textsecuregcm.gcp.CanonicalRequestSigner;
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
public class GcsAttachmentGenerator implements AttachmentGenerator {
|
||||
@Nonnull
|
||||
private final CanonicalRequestGenerator canonicalRequestGenerator;
|
||||
|
||||
@Nonnull
|
||||
private final CanonicalRequestSigner canonicalRequestSigner;
|
||||
|
||||
public GcsAttachmentGenerator(@Nonnull String domain, @Nonnull String email,
|
||||
int maxSizeInBytes, @Nonnull String pathPrefix, @Nonnull String rsaSigningKey)
|
||||
throws IOException, InvalidKeyException, InvalidKeySpecException {
|
||||
this.canonicalRequestGenerator = new CanonicalRequestGenerator(domain, email, maxSizeInBytes, pathPrefix);
|
||||
this.canonicalRequestSigner = new CanonicalRequestSigner(rsaSigningKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Descriptor generateAttachment(final String key) {
|
||||
final ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);
|
||||
final CanonicalRequest canonicalRequest = canonicalRequestGenerator.createFor(key, now);
|
||||
return new Descriptor(getHeaderMap(canonicalRequest), getSignedUploadLocation(canonicalRequest));
|
||||
}
|
||||
|
||||
private String getSignedUploadLocation(@Nonnull CanonicalRequest canonicalRequest) {
|
||||
return "https://" + canonicalRequest.getDomain() + canonicalRequest.getResourcePath()
|
||||
+ '?' + canonicalRequest.getCanonicalQuery()
|
||||
+ "&X-Goog-Signature=" + canonicalRequestSigner.sign(canonicalRequest);
|
||||
}
|
||||
|
||||
private static Map<String, String> getHeaderMap(@Nonnull CanonicalRequest canonicalRequest) {
|
||||
return Map.of(
|
||||
"host", canonicalRequest.getDomain(),
|
||||
"x-goog-content-length-range", "1," + canonicalRequest.getMaxSizeInBytes(),
|
||||
"x-goog-resumable", "start");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.attachments;
|
||||
|
||||
import org.apache.http.HttpHeaders;
|
||||
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
|
||||
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
|
||||
import org.whispersystems.textsecuregcm.util.HeaderUtils;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Clock;
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
|
||||
public class TusAttachmentGenerator implements AttachmentGenerator {
|
||||
|
||||
private static final String ATTACHMENTS = "attachments";
|
||||
|
||||
final ExternalServiceCredentialsGenerator credentialsGenerator;
|
||||
final String tusUri;
|
||||
|
||||
public TusAttachmentGenerator(final TusConfiguration cfg) {
|
||||
this.tusUri = cfg.uploadUri();
|
||||
this.credentialsGenerator = credentialsGenerator(Clock.systemUTC(), cfg);
|
||||
}
|
||||
|
||||
private static ExternalServiceCredentialsGenerator credentialsGenerator(final Clock clock, final TusConfiguration cfg) {
|
||||
return ExternalServiceCredentialsGenerator
|
||||
.builder(cfg.userAuthenticationTokenSharedSecret())
|
||||
.prependUsername(false)
|
||||
.withClock(clock)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Descriptor generateAttachment(final String key) {
|
||||
final ExternalServiceCredentials credentials = credentialsGenerator.generateFor(ATTACHMENTS + "/" + key);
|
||||
final String b64Key = Base64.getEncoder().encodeToString(key.getBytes(StandardCharsets.UTF_8));
|
||||
final Map<String, String> headers = Map.of(
|
||||
HttpHeaders.AUTHORIZATION, HeaderUtils.basicAuthHeader(credentials),
|
||||
"Upload-Metadata", String.format("filename %s", b64Key)
|
||||
);
|
||||
return new Descriptor(headers, tusUri + "/" + ATTACHMENTS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.attachments;
|
||||
|
||||
import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
|
||||
import org.whispersystems.textsecuregcm.util.ExactlySize;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
public record TusConfiguration(
|
||||
@ExactlySize(32) SecretBytes userAuthenticationTokenSharedSecret,
|
||||
@NotEmpty String uploadUri
|
||||
){}
|
||||
@@ -1,38 +1,153 @@
|
||||
/*
|
||||
* Copyright 2013-2021 Signal Messenger, LLC
|
||||
* Copyright 2013 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import io.dropwizard.auth.Authenticator;
|
||||
import io.dropwizard.auth.basic.BasicCredentials;
|
||||
import java.util.Optional;
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import static com.codahale.metrics.MetricRegistry.name;
|
||||
|
||||
public class AccountAuthenticator extends BaseAccountAuthenticator implements
|
||||
Authenticator<BasicCredentials, AuthenticatedAccount> {
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import io.dropwizard.auth.Authenticator;
|
||||
import io.dropwizard.auth.basic.BasicCredentials;
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import io.micrometer.core.instrument.Tags;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.util.Pair;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
private static final String AUTHENTICATION_COUNTER_NAME = name(AccountAuthenticator.class, "authenticate");
|
||||
public class AccountAuthenticator implements Authenticator<BasicCredentials, AuthenticatedAccount> {
|
||||
|
||||
private static final String LEGACY_NAME_PREFIX = "org.whispersystems.textsecuregcm.auth.BaseAccountAuthenticator";
|
||||
|
||||
private static final String AUTHENTICATION_COUNTER_NAME = name(LEGACY_NAME_PREFIX, "authentication");
|
||||
private static final String AUTHENTICATION_SUCCEEDED_TAG_NAME = "succeeded";
|
||||
private static final String AUTHENTICATION_FAILURE_REASON_TAG_NAME = "reason";
|
||||
|
||||
private static final String DAYS_SINCE_LAST_SEEN_DISTRIBUTION_NAME = name(LEGACY_NAME_PREFIX, "daysSinceLastSeen");
|
||||
private static final String IS_PRIMARY_DEVICE_TAG = "isPrimary";
|
||||
|
||||
@VisibleForTesting
|
||||
static final char DEVICE_ID_SEPARATOR = '.';
|
||||
|
||||
private final AccountsManager accountsManager;
|
||||
private final Clock clock;
|
||||
|
||||
public AccountAuthenticator(AccountsManager accountsManager) {
|
||||
super(accountsManager);
|
||||
this(accountsManager, Clock.systemUTC());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public AccountAuthenticator(AccountsManager accountsManager, Clock clock) {
|
||||
this.accountsManager = accountsManager;
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
static Pair<String, Byte> getIdentifierAndDeviceId(final String basicUsername) {
|
||||
final String identifier;
|
||||
final byte deviceId;
|
||||
|
||||
final int deviceIdSeparatorIndex = basicUsername.indexOf(DEVICE_ID_SEPARATOR);
|
||||
|
||||
if (deviceIdSeparatorIndex == -1) {
|
||||
identifier = basicUsername;
|
||||
deviceId = Device.PRIMARY_ID;
|
||||
} else {
|
||||
identifier = basicUsername.substring(0, deviceIdSeparatorIndex);
|
||||
deviceId = Byte.parseByte(basicUsername.substring(deviceIdSeparatorIndex + 1));
|
||||
}
|
||||
|
||||
return new Pair<>(identifier, deviceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<AuthenticatedAccount> authenticate(BasicCredentials basicCredentials) {
|
||||
final Optional<AuthenticatedAccount> maybeAuthenticatedAccount = super.authenticate(basicCredentials, true);
|
||||
boolean succeeded = false;
|
||||
String failureReason = null;
|
||||
|
||||
// TODO Remove after announcement groups have launched
|
||||
maybeAuthenticatedAccount.ifPresent(authenticatedAccount ->
|
||||
Metrics.counter(AUTHENTICATION_COUNTER_NAME,
|
||||
"supportsAnnouncementGroups",
|
||||
String.valueOf(authenticatedAccount.getAccount().isAnnouncementGroupSupported()))
|
||||
.increment());
|
||||
try {
|
||||
final UUID accountUuid;
|
||||
final byte deviceId;
|
||||
{
|
||||
final Pair<String, Byte> identifierAndDeviceId = getIdentifierAndDeviceId(basicCredentials.getUsername());
|
||||
|
||||
return maybeAuthenticatedAccount;
|
||||
accountUuid = UUID.fromString(identifierAndDeviceId.first());
|
||||
deviceId = identifierAndDeviceId.second();
|
||||
}
|
||||
|
||||
Optional<Account> account = accountsManager.getByAccountIdentifier(accountUuid);
|
||||
|
||||
if (account.isEmpty()) {
|
||||
failureReason = "noSuchAccount";
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Optional<Device> device = account.get().getDevice(deviceId);
|
||||
|
||||
if (device.isEmpty()) {
|
||||
failureReason = "noSuchDevice";
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
SaltedTokenHash deviceSaltedTokenHash = device.get().getAuthTokenHash();
|
||||
if (deviceSaltedTokenHash.verify(basicCredentials.getPassword())) {
|
||||
succeeded = true;
|
||||
Account authenticatedAccount = updateLastSeen(account.get(), device.get());
|
||||
if (deviceSaltedTokenHash.getVersion() != SaltedTokenHash.CURRENT_VERSION) {
|
||||
authenticatedAccount = accountsManager.updateDeviceAuthentication(
|
||||
authenticatedAccount,
|
||||
device.get(),
|
||||
SaltedTokenHash.generateFor(basicCredentials.getPassword())); // new credentials have current version
|
||||
}
|
||||
return Optional.of(new AuthenticatedAccount(authenticatedAccount, device.get()));
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
} catch (IllegalArgumentException | InvalidAuthorizationHeaderException iae) {
|
||||
failureReason = "invalidHeader";
|
||||
return Optional.empty();
|
||||
} finally {
|
||||
Tags tags = Tags.of(
|
||||
AUTHENTICATION_SUCCEEDED_TAG_NAME, String.valueOf(succeeded));
|
||||
|
||||
if (StringUtils.isNotBlank(failureReason)) {
|
||||
tags = tags.and(AUTHENTICATION_FAILURE_REASON_TAG_NAME, failureReason);
|
||||
}
|
||||
|
||||
Metrics.counter(AUTHENTICATION_COUNTER_NAME, tags).increment();
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public Account updateLastSeen(Account account, Device device) {
|
||||
// compute a non-negative integer between 0 and 86400.
|
||||
long n = Util.ensureNonNegativeLong(account.getUuid().getLeastSignificantBits());
|
||||
final long lastSeenOffsetSeconds = n % ChronoUnit.DAYS.getDuration().toSeconds();
|
||||
|
||||
// produce a truncated timestamp which is either today at UTC midnight
|
||||
// or yesterday at UTC midnight, based on per-user randomized offset used.
|
||||
final long todayInMillisWithOffset = Util.todayInMillisGivenOffsetFromNow(clock,
|
||||
Duration.ofSeconds(lastSeenOffsetSeconds).negated());
|
||||
|
||||
// only update the device's last seen time when it falls behind the truncated timestamp.
|
||||
// this ensures a few things:
|
||||
// (1) each account will only update last-seen at most once per day
|
||||
// (2) these updates will occur throughout the day rather than all occurring at UTC midnight.
|
||||
if (device.getLastSeen() < todayInMillisWithOffset) {
|
||||
Metrics.summary(DAYS_SINCE_LAST_SEEN_DISTRIBUTION_NAME, IS_PRIMARY_DEVICE_TAG, String.valueOf(device.isPrimary()))
|
||||
.record(Duration.ofMillis(todayInMillisWithOffset - device.getLastSeen()).toDays());
|
||||
|
||||
return accountsManager.updateDeviceLastSeen(account, device, Util.todayInMillis(clock));
|
||||
}
|
||||
|
||||
return account;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -31,7 +30,6 @@ import org.whispersystems.textsecuregcm.util.Pair;
|
||||
* {@link io.dropwizard.auth.Auth} object with a current device list.
|
||||
*
|
||||
* @see AuthenticatedAccount
|
||||
* @see DisabledPermittedAuthenticatedAccount
|
||||
*/
|
||||
public class AuthEnablementRefreshRequirementProvider implements WebsocketRefreshRequirementProvider {
|
||||
|
||||
@@ -46,10 +44,6 @@ public class AuthEnablementRefreshRequirementProvider implements WebsocketRefres
|
||||
this.accountsManager = accountsManager;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static Map<Long, Boolean> buildDevicesEnabledMap(final Account account) {
|
||||
return account.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::isEnabled));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequestFiltered(final RequestEvent requestEvent) {
|
||||
@@ -61,40 +55,46 @@ public class AuthEnablementRefreshRequirementProvider implements WebsocketRefres
|
||||
setAccount(requestEvent.getContainerRequest(), account));
|
||||
}
|
||||
}
|
||||
|
||||
public static void setAccount(final ContainerRequest containerRequest, final Account account) {
|
||||
containerRequest.setProperty(ACCOUNT_UUID, account.getUuid());
|
||||
containerRequest.setProperty(DEVICES_ENABLED, buildDevicesEnabledMap(account));
|
||||
setAccount(containerRequest, ContainerRequestUtil.AccountInfo.fromAccount(account));
|
||||
}
|
||||
|
||||
private static void setAccount(final ContainerRequest containerRequest, final ContainerRequestUtil.AccountInfo info) {
|
||||
containerRequest.setProperty(ACCOUNT_UUID, info.accountId());
|
||||
containerRequest.setProperty(DEVICES_ENABLED, info.devicesEnabled());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pair<UUID, Long>> handleRequestFinished(final RequestEvent requestEvent) {
|
||||
public List<Pair<UUID, Byte>> handleRequestFinished(final RequestEvent requestEvent) {
|
||||
// Now that the request is finished, check whether `isEnabled` changed for any of the devices. If the value did
|
||||
// change or if a devices was added or removed, all devices must disconnect and reauthenticate.
|
||||
if (requestEvent.getContainerRequest().getProperty(DEVICES_ENABLED) != null) {
|
||||
|
||||
@SuppressWarnings("unchecked") final Map<Long, Boolean> initialDevicesEnabled =
|
||||
(Map<Long, Boolean>) requestEvent.getContainerRequest().getProperty(DEVICES_ENABLED);
|
||||
@SuppressWarnings("unchecked") final Map<Byte, Boolean> initialDevicesEnabled =
|
||||
(Map<Byte, Boolean>) requestEvent.getContainerRequest().getProperty(DEVICES_ENABLED);
|
||||
|
||||
return accountsManager.getByAccountIdentifier((UUID) requestEvent.getContainerRequest().getProperty(ACCOUNT_UUID)).map(account -> {
|
||||
final Set<Long> deviceIdsToDisplace;
|
||||
final Map<Long, Boolean> currentDevicesEnabled = buildDevicesEnabledMap(account);
|
||||
return accountsManager.getByAccountIdentifier((UUID) requestEvent.getContainerRequest().getProperty(ACCOUNT_UUID))
|
||||
.map(ContainerRequestUtil.AccountInfo::fromAccount)
|
||||
.map(account -> {
|
||||
final Set<Byte> deviceIdsToDisplace;
|
||||
final Map<Byte, Boolean> currentDevicesEnabled = account.devicesEnabled();
|
||||
|
||||
if (!initialDevicesEnabled.equals(currentDevicesEnabled)) {
|
||||
deviceIdsToDisplace = new HashSet<>(initialDevicesEnabled.keySet());
|
||||
deviceIdsToDisplace.addAll(currentDevicesEnabled.keySet());
|
||||
} else {
|
||||
deviceIdsToDisplace = Collections.emptySet();
|
||||
}
|
||||
if (!initialDevicesEnabled.equals(currentDevicesEnabled)) {
|
||||
deviceIdsToDisplace = new HashSet<>(initialDevicesEnabled.keySet());
|
||||
deviceIdsToDisplace.addAll(currentDevicesEnabled.keySet());
|
||||
} else {
|
||||
deviceIdsToDisplace = Collections.emptySet();
|
||||
}
|
||||
|
||||
return deviceIdsToDisplace.stream()
|
||||
.map(deviceId -> new Pair<>(account.getUuid(), deviceId))
|
||||
.collect(Collectors.toList());
|
||||
}).orElseGet(() -> {
|
||||
logger.error("Request had account, but it is no longer present");
|
||||
return Collections.emptyList();
|
||||
});
|
||||
} else
|
||||
return deviceIdsToDisplace.stream()
|
||||
.map(deviceId -> new Pair<>(account.accountId(), deviceId))
|
||||
.collect(Collectors.toList());
|
||||
}).orElseGet(() -> {
|
||||
logger.error("Request had account, but it is no longer present");
|
||||
return Collections.emptyList();
|
||||
});
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,24 +10,24 @@ import java.util.function.Supplier;
|
||||
import javax.security.auth.Subject;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.util.Pair;
|
||||
|
||||
public class AuthenticatedAccount implements Principal, AccountAndAuthenticatedDeviceHolder {
|
||||
private final Account account;
|
||||
private final Device device;
|
||||
|
||||
private final Supplier<Pair<Account, Device>> accountAndDevice;
|
||||
|
||||
public AuthenticatedAccount(final Supplier<Pair<Account, Device>> accountAndDevice) {
|
||||
this.accountAndDevice = accountAndDevice;
|
||||
public AuthenticatedAccount(final Account account, final Device device) {
|
||||
this.account = account;
|
||||
this.device = device;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Account getAccount() {
|
||||
return accountAndDevice.get().first();
|
||||
return account;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Device getAuthenticatedDevice() {
|
||||
return accountAndDevice.get().second();
|
||||
return device;
|
||||
}
|
||||
|
||||
// Principal implementation
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import org.whispersystems.textsecuregcm.backup.BackupTier;
|
||||
|
||||
public record AuthenticatedBackupUser(byte[] backupId, BackupTier backupTier) {}
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class AuthenticationCredentials {
|
||||
|
||||
private final String hashedAuthenticationToken;
|
||||
private final String salt;
|
||||
|
||||
public AuthenticationCredentials(String hashedAuthenticationToken, String salt) {
|
||||
this.hashedAuthenticationToken = hashedAuthenticationToken;
|
||||
this.salt = salt;
|
||||
}
|
||||
|
||||
public AuthenticationCredentials(String authenticationToken) {
|
||||
this.salt = String.valueOf(Math.abs(new SecureRandom().nextInt()));
|
||||
this.hashedAuthenticationToken = getHashedValue(salt, authenticationToken);
|
||||
}
|
||||
|
||||
public String getHashedAuthenticationToken() {
|
||||
return hashedAuthenticationToken;
|
||||
}
|
||||
|
||||
public String getSalt() {
|
||||
return salt;
|
||||
}
|
||||
|
||||
public boolean verify(String authenticationToken) {
|
||||
String theirValue = getHashedValue(salt, authenticationToken);
|
||||
return MessageDigest.isEqual(theirValue.getBytes(StandardCharsets.UTF_8), this.hashedAuthenticationToken.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private static String getHashedValue(String salt, String token) {
|
||||
try {
|
||||
return new String(Hex.encodeHex(MessageDigest.getInstance("SHA1").digest((salt + token).getBytes(StandardCharsets.UTF_8))));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import static com.codahale.metrics.MetricRegistry.name;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import io.dropwizard.auth.basic.BasicCredentials;
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import io.micrometer.core.instrument.Tags;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.storage.RefreshingAccountAndDeviceSupplier;
|
||||
import org.whispersystems.textsecuregcm.util.Pair;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
public class BaseAccountAuthenticator {
|
||||
|
||||
private static final String AUTHENTICATION_COUNTER_NAME = name(BaseAccountAuthenticator.class, "authentication");
|
||||
private static final String AUTHENTICATION_SUCCEEDED_TAG_NAME = "succeeded";
|
||||
private static final String AUTHENTICATION_FAILURE_REASON_TAG_NAME = "reason";
|
||||
private static final String AUTHENTICATION_ENABLED_REQUIRED_TAG_NAME = "enabledRequired";
|
||||
|
||||
private static final String DAYS_SINCE_LAST_SEEN_DISTRIBUTION_NAME = name(BaseAccountAuthenticator.class, "daysSinceLastSeen");
|
||||
private static final String IS_PRIMARY_DEVICE_TAG = "isPrimary";
|
||||
|
||||
private final AccountsManager accountsManager;
|
||||
private final Clock clock;
|
||||
|
||||
public BaseAccountAuthenticator(AccountsManager accountsManager) {
|
||||
this(accountsManager, Clock.systemUTC());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public BaseAccountAuthenticator(AccountsManager accountsManager, Clock clock) {
|
||||
this.accountsManager = accountsManager;
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
static Pair<String, Long> getIdentifierAndDeviceId(final String basicUsername) {
|
||||
final String identifier;
|
||||
final long deviceId;
|
||||
|
||||
final int deviceIdSeparatorIndex = basicUsername.indexOf('.');
|
||||
|
||||
if (deviceIdSeparatorIndex == -1) {
|
||||
identifier = basicUsername;
|
||||
deviceId = Device.MASTER_ID;
|
||||
} else {
|
||||
identifier = basicUsername.substring(0, deviceIdSeparatorIndex);
|
||||
deviceId = Long.parseLong(basicUsername.substring(deviceIdSeparatorIndex + 1));
|
||||
}
|
||||
|
||||
return new Pair<>(identifier, deviceId);
|
||||
}
|
||||
|
||||
public Optional<AuthenticatedAccount> authenticate(BasicCredentials basicCredentials, boolean enabledRequired) {
|
||||
boolean succeeded = false;
|
||||
String failureReason = null;
|
||||
|
||||
try {
|
||||
final UUID accountUuid;
|
||||
final long deviceId;
|
||||
{
|
||||
final Pair<String, Long> identifierAndDeviceId = getIdentifierAndDeviceId(basicCredentials.getUsername());
|
||||
|
||||
accountUuid = UUID.fromString(identifierAndDeviceId.first());
|
||||
deviceId = identifierAndDeviceId.second();
|
||||
}
|
||||
|
||||
Optional<Account> account = accountsManager.getByAccountIdentifier(accountUuid);
|
||||
|
||||
if (account.isEmpty()) {
|
||||
failureReason = "noSuchAccount";
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Optional<Device> device = account.get().getDevice(deviceId);
|
||||
|
||||
if (device.isEmpty()) {
|
||||
failureReason = "noSuchDevice";
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (enabledRequired) {
|
||||
if (!device.get().isEnabled()) {
|
||||
failureReason = "deviceDisabled";
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (!account.get().isEnabled()) {
|
||||
failureReason = "accountDisabled";
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
if (device.get().getAuthenticationCredentials().verify(basicCredentials.getPassword())) {
|
||||
succeeded = true;
|
||||
final Account authenticatedAccount = updateLastSeen(account.get(), device.get());
|
||||
return Optional.of(new AuthenticatedAccount(
|
||||
new RefreshingAccountAndDeviceSupplier(authenticatedAccount, device.get().getId(), accountsManager)));
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
} catch (IllegalArgumentException | InvalidAuthorizationHeaderException iae) {
|
||||
failureReason = "invalidHeader";
|
||||
return Optional.empty();
|
||||
} finally {
|
||||
Tags tags = Tags.of(
|
||||
AUTHENTICATION_SUCCEEDED_TAG_NAME, String.valueOf(succeeded),
|
||||
AUTHENTICATION_ENABLED_REQUIRED_TAG_NAME, String.valueOf(enabledRequired));
|
||||
|
||||
if (StringUtils.isNotBlank(failureReason)) {
|
||||
tags = tags.and(AUTHENTICATION_FAILURE_REASON_TAG_NAME, failureReason);
|
||||
}
|
||||
|
||||
Metrics.counter(AUTHENTICATION_COUNTER_NAME, tags).increment();
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public Account updateLastSeen(Account account, Device device) {
|
||||
final long lastSeenOffsetSeconds = Math.abs(account.getUuid().getLeastSignificantBits()) % ChronoUnit.DAYS.getDuration().toSeconds();
|
||||
final long todayInMillisWithOffset = Util.todayInMillisGivenOffsetFromNow(clock, Duration.ofSeconds(lastSeenOffsetSeconds).negated());
|
||||
|
||||
if (device.getLastSeen() < todayInMillisWithOffset) {
|
||||
Metrics.summary(DAYS_SINCE_LAST_SEEN_DISTRIBUTION_NAME, IS_PRIMARY_DEVICE_TAG, String.valueOf(device.isMaster()))
|
||||
.record(Duration.ofMillis(todayInMillisWithOffset - device.getLastSeen()).toDays());
|
||||
|
||||
return accountsManager.updateDeviceLastSeen(account, device, Util.todayInMillis(clock));
|
||||
}
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,18 +5,16 @@
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.UUID;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.util.Pair;
|
||||
|
||||
public class BasicAuthorizationHeader {
|
||||
|
||||
private final String username;
|
||||
private final long deviceId;
|
||||
private final byte deviceId;
|
||||
private final String password;
|
||||
|
||||
private BasicAuthorizationHeader(final String username, final long deviceId, final String password) {
|
||||
private BasicAuthorizationHeader(final String username, final byte deviceId, final String password) {
|
||||
this.username = username;
|
||||
this.deviceId = deviceId;
|
||||
this.password = password;
|
||||
@@ -61,10 +59,10 @@ public class BasicAuthorizationHeader {
|
||||
final String usernameComponent = credentials.substring(0, credentialSeparatorIndex);
|
||||
|
||||
final String username;
|
||||
final long deviceId;
|
||||
final byte deviceId;
|
||||
{
|
||||
final Pair<String, Long> identifierAndDeviceId =
|
||||
BaseAccountAuthenticator.getIdentifierAndDeviceId(usernameComponent);
|
||||
final Pair<String, Byte> identifierAndDeviceId =
|
||||
AccountAuthenticator.getIdentifierAndDeviceId(usernameComponent);
|
||||
|
||||
username = identifierAndDeviceId.first();
|
||||
deviceId = identifierAndDeviceId.second();
|
||||
|
||||
@@ -7,18 +7,16 @@ package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import org.whispersystems.textsecuregcm.crypto.Curve;
|
||||
import org.whispersystems.textsecuregcm.crypto.ECPrivateKey;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.signal.libsignal.protocol.ecc.Curve;
|
||||
import org.signal.libsignal.protocol.ecc.ECPrivateKey;
|
||||
import org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate;
|
||||
import org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate;
|
||||
import org.whispersystems.textsecuregcm.identity.IdentityType;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.util.Base64;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class CertificateGenerator {
|
||||
|
||||
private final ECPrivateKey privateKey;
|
||||
@@ -35,18 +33,23 @@ public class CertificateGenerator {
|
||||
|
||||
public byte[] createFor(Account account, Device device, boolean includeE164) throws InvalidKeyException {
|
||||
SenderCertificate.Certificate.Builder builder = SenderCertificate.Certificate.newBuilder()
|
||||
.setSenderDevice(Math.toIntExact(device.getId()))
|
||||
.setExpires(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(expiresDays))
|
||||
.setIdentityKey(ByteString.copyFrom(Base64.getDecoder().decode(account.getIdentityKey())))
|
||||
.setSigner(serverCertificate)
|
||||
.setSenderUuid(account.getUuid().toString());
|
||||
.setSenderDevice(Math.toIntExact(device.getId()))
|
||||
.setExpires(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(expiresDays))
|
||||
.setIdentityKey(ByteString.copyFrom(account.getIdentityKey(IdentityType.ACI).serialize()))
|
||||
.setSigner(serverCertificate)
|
||||
.setSenderUuid(account.getUuid().toString());
|
||||
|
||||
if (includeE164) {
|
||||
builder.setSender(account.getNumber());
|
||||
}
|
||||
|
||||
byte[] certificate = builder.build().toByteArray();
|
||||
byte[] signature = Curve.calculateSignature(privateKey, certificate);
|
||||
byte[] signature;
|
||||
try {
|
||||
signature = Curve.calculateSignature(privateKey, certificate);
|
||||
} catch (org.signal.libsignal.protocol.InvalidKeyException e) {
|
||||
throw new InvalidKeyException(e);
|
||||
}
|
||||
|
||||
return SenderCertificate.newBuilder()
|
||||
.setCertificate(ByteString.copyFrom(certificate))
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Indicates that an endpoint changes the phone number and PNI keys associated with an account, and that
|
||||
* any websockets associated with the account may need to be refreshed after a call to that endpoint.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ChangesPhoneNumber {
|
||||
}
|
||||
@@ -16,7 +16,7 @@ public class CombinedUnidentifiedSenderAccessKeys {
|
||||
public CombinedUnidentifiedSenderAccessKeys(String header) {
|
||||
try {
|
||||
this.combinedUnidentifiedSenderAccessKeys = Base64.getDecoder().decode(header);
|
||||
if (this.combinedUnidentifiedSenderAccessKeys == null || this.combinedUnidentifiedSenderAccessKeys.length != 16) {
|
||||
if (this.combinedUnidentifiedSenderAccessKeys == null || this.combinedUnidentifiedSenderAccessKeys.length != UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH) {
|
||||
throw new WebApplicationException("Invalid combined unidentified sender access keys", Status.UNAUTHORIZED);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user