mirror of
https://github.com/signalapp/Signal-Server.git
synced 2025-12-05 01:10:13 +00:00
Compare commits
1607 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
735573e61b | ||
|
|
c545cff1b3 | ||
|
|
cbd9681e3e | ||
|
|
ca876e40ca | ||
|
|
76f5a71727 | ||
|
|
117de2382d | ||
|
|
25e7036451 | ||
|
|
3131bd3dd9 | ||
|
|
1cf9397bbd | ||
|
|
c97be15e79 | ||
|
|
164fc40990 | ||
|
|
6456af6284 | ||
|
|
81212cc13a | ||
|
|
6f0750790c | ||
|
|
3e61b5c49d | ||
|
|
50c4df4f45 | ||
|
|
1eb946f5fe | ||
|
|
7bd402b48d | ||
|
|
90444d5b91 | ||
|
|
5ee093f87c | ||
|
|
623743286c | ||
|
|
67067f1d2d | ||
|
|
07f9bb112e | ||
|
|
417d48c452 | ||
|
|
358412c78a | ||
|
|
215621a9b0 | ||
|
|
c3f53c4dd9 | ||
|
|
01514f83a0 | ||
|
|
c10b64c367 | ||
|
|
722055c8b5 | ||
|
|
680e501f83 | ||
|
|
f13f7a5ff4 | ||
|
|
5290656c3b | ||
|
|
93fbb87741 | ||
|
|
ce76c5c117 | ||
|
|
e663e1b0a6 | ||
|
|
20cdd09171 | ||
|
|
f98dd80941 | ||
|
|
f84736bd32 | ||
|
|
9995f271c8 | ||
|
|
cf59d849b0 | ||
|
|
ee3b91e4fb | ||
|
|
77f134ddca | ||
|
|
8913192b7e | ||
|
|
94ac3f6cc8 | ||
|
|
b89de860d3 | ||
|
|
f8c623074b | ||
|
|
1160af9522 | ||
|
|
3056ea8cbc | ||
|
|
28e3b23e8c | ||
|
|
fbaf4a09e2 | ||
|
|
cfa8cbedc1 | ||
|
|
be4c46e674 | ||
|
|
bacf524ae6 | ||
|
|
aa65d34c36 | ||
|
|
0cd3640f13 | ||
|
|
c595d9415c | ||
|
|
1a604d8c79 | ||
|
|
f76e6705c0 | ||
|
|
10cd60738a | ||
|
|
89470ff536 | ||
|
|
79b8202452 | ||
|
|
d252e579f4 | ||
|
|
30b2c2b5ad | ||
|
|
282f39141e | ||
|
|
85e4de6933 | ||
|
|
0b993098a8 | ||
|
|
1880773fb9 | ||
|
|
00c9023e74 | ||
|
|
d59eabd9d7 | ||
|
|
2a3ea13c9e | ||
|
|
6906336dfb | ||
|
|
514b94a5cb | ||
|
|
df01be2dca | ||
|
|
10c6f885fd | ||
|
|
224e6dac31 | ||
|
|
3b1eb3a9db | ||
|
|
e320626c6e | ||
|
|
03dac2bf7e | ||
|
|
730303567f | ||
|
|
57ff9f86f5 | ||
|
|
bfd2c32d4e | ||
|
|
e9a3d52d7f | ||
|
|
ac7eb88194 | ||
|
|
d45154f2aa | ||
|
|
760462f8fb | ||
|
|
1999bd2bcb | ||
|
|
46110d4d65 | ||
|
|
5752853bba | ||
|
|
02d06af3fc | ||
|
|
09e0934eaf | ||
|
|
b100f09205 | ||
|
|
670b69df24 | ||
|
|
03a531e1b0 | ||
|
|
13ecbe7e53 | ||
|
|
17047513c3 | ||
|
|
7bd7d0e84e | ||
|
|
4571042814 | ||
|
|
9cb89b42bf | ||
|
|
9a4453c414 | ||
|
|
5fa22bc073 | ||
|
|
bf32b766a5 | ||
|
|
f0a8b5a54a | ||
|
|
8e68e0e037 | ||
|
|
b81b811400 | ||
|
|
b41f97233e | ||
|
|
350de1c759 | ||
|
|
055e8d80a1 | ||
|
|
dfb8a419e7 | ||
|
|
030a791d69 | ||
|
|
cf495ef7cf | ||
|
|
8fdbcbef44 | ||
|
|
30c9968928 | ||
|
|
f357ad098f | ||
|
|
1a8c40c02a | ||
|
|
20677d4be1 | ||
|
|
c448c37cc9 | ||
|
|
f117d9ff4d | ||
|
|
2dbd7ffc75 | ||
|
|
fac4538f6f | ||
|
|
01e526af25 | ||
|
|
7e805d1592 | ||
|
|
c63bebb3e7 | ||
|
|
0e6cfb460d | ||
|
|
cd6b2512e1 | ||
|
|
4f6b132449 | ||
|
|
b7c611a466 | ||
|
|
0163242c8a | ||
|
|
7fa17e33e9 | ||
|
|
e4dbb8efe7 | ||
|
|
89256fb5b3 | ||
|
|
59e401f41e | ||
|
|
4b42dd1db3 | ||
|
|
6196856a7c | ||
|
|
0e8d4f9a61 | ||
|
|
97d2d97ee7 | ||
|
|
62315f423c | ||
|
|
5ee56b022c | ||
|
|
6c37b658ac | ||
|
|
1f53900345 | ||
|
|
deece33a0d | ||
|
|
13053da97f | ||
|
|
4c019aef15 | ||
|
|
bab5e5769b | ||
|
|
f68390e96f | ||
|
|
76cbf734ad | ||
|
|
17ba630014 | ||
|
|
7057476048 | ||
|
|
3121867f72 | ||
|
|
435410b004 | ||
|
|
f190462879 | ||
|
|
7c0ff67625 | ||
|
|
ac72c8b2de | ||
|
|
20208ae528 | ||
|
|
6c6f073bc2 | ||
|
|
0663fe30df | ||
|
|
2c0a75586b | ||
|
|
b6cb23cbb5 | ||
|
|
ee555285ed | ||
|
|
b75456acf3 | ||
|
|
be6d6351b9 | ||
|
|
abafa2ccac | ||
|
|
53e6f419b6 | ||
|
|
b75dec40ac | ||
|
|
0f4f775ee2 | ||
|
|
5974328d9c | ||
|
|
a472774734 | ||
|
|
166d203e8e | ||
|
|
3b3764535c | ||
|
|
f2a1a65a45 | ||
|
|
b7c56108ca | ||
|
|
52478e7de0 | ||
|
|
ae9fd090de | ||
|
|
59bbd0c43c | ||
|
|
f6c9b2b6e7 | ||
|
|
0c0e33bc0e | ||
|
|
4d33ba48cc | ||
|
|
18fb23f27c | ||
|
|
92c25a8373 | ||
|
|
14f5271c20 | ||
|
|
37bda0b035 | ||
|
|
675785a4fd | ||
|
|
0572951c8a | ||
|
|
7d766ee39e | ||
|
|
1f24c913a6 | ||
|
|
2a8806ec2e | ||
|
|
ffcabe6fc4 | ||
|
|
365ad3a4f8 | ||
|
|
2cb788ceb7 | ||
|
|
257fef9734 | ||
|
|
37e0730d2a | ||
|
|
dea359ef91 | ||
|
|
64c9648dd8 | ||
|
|
6dfd13118d | ||
|
|
2f6105f9bc | ||
|
|
5c23f62cec | ||
|
|
ab4e94edab | ||
|
|
9589b7758c | ||
|
|
681cdf8eff | ||
|
|
ad6c271f9d | ||
|
|
c8414a63fb | ||
|
|
c10d9603ad | ||
|
|
91bd061110 | ||
|
|
83aa59f4dd | ||
|
|
3745a0b81d | ||
|
|
e2b093abce | ||
|
|
7e29ed1cc7 | ||
|
|
5965f0fd22 | ||
|
|
c3c46f2f74 | ||
|
|
a816aa0186 | ||
|
|
a7bad20eae | ||
|
|
089b6b1644 | ||
|
|
7509520883 | ||
|
|
9778775046 | ||
|
|
e5ae0572c5 | ||
|
|
63dac3bd9f | ||
|
|
19295eef46 | ||
|
|
0bc1369e04 | ||
|
|
ca2f7d2eed | ||
|
|
3ea535a412 | ||
|
|
a288b9df8e | ||
|
|
8b6012f8a8 | ||
|
|
1e5d7582da | ||
|
|
ad838b4827 | ||
|
|
25f603efc9 | ||
|
|
152c927929 | ||
|
|
b5bd16c6a9 | ||
|
|
14bfa83bb8 | ||
|
|
5dc8086968 | ||
|
|
7118340f12 | ||
|
|
efe7f2e4c1 | ||
|
|
fb2fc2335a | ||
|
|
345e116699 | ||
|
|
e50a1c0646 | ||
|
|
a6fd1aa06c | ||
|
|
3cdc58200a | ||
|
|
933dd81d82 | ||
|
|
a1434524a4 | ||
|
|
cdc6afefe2 | ||
|
|
738ec2a38e | ||
|
|
07886a9722 | ||
|
|
fde1b49729 | ||
|
|
58210141f4 | ||
|
|
e1f35102aa | ||
|
|
af2a8548c3 | ||
|
|
1faedd3870 | ||
|
|
f57a4171ba | ||
|
|
df9dc82de5 | ||
|
|
0573f09285 | ||
|
|
eb6fe11da1 | ||
|
|
823025f3b3 | ||
|
|
0ee3f0a5b5 | ||
|
|
6bff564129 | ||
|
|
7dabc92447 | ||
|
|
78bbe8855b | ||
|
|
5354104128 | ||
|
|
a5118e4daa | ||
|
|
b5ade5dc12 | ||
|
|
fff8c72f42 | ||
|
|
06ca5f14fc | ||
|
|
8c9d871268 | ||
|
|
5951ead1b6 | ||
|
|
4a0a0e10d2 | ||
|
|
7266eeee7a | ||
|
|
5839ce3e1a | ||
|
|
f85c6bf828 | ||
|
|
9af9e21e05 | ||
|
|
6d16ad2763 | ||
|
|
447fba1594 | ||
|
|
93f845610d | ||
|
|
aa8525385a | ||
|
|
ec783133c1 | ||
|
|
f630bddb19 | ||
|
|
71f0aab2c6 | ||
|
|
ae8de67271 | ||
|
|
6142998b87 | ||
|
|
142376f360 | ||
|
|
ae329e735f | ||
|
|
2dbab70c8c | ||
|
|
47916ecb0f | ||
|
|
635f669a32 | ||
|
|
5f49772ca6 | ||
|
|
6332552346 | ||
|
|
4fb7afcf7b | ||
|
|
ff448950ed | ||
|
|
d6319aeb92 | ||
|
|
8fc6f9c442 | ||
|
|
fdcf317963 | ||
|
|
e9ea79cc8e | ||
|
|
ad32555cc9 | ||
|
|
be8a1acca9 | ||
|
|
477615fc66 | ||
|
|
e0ed8fa0b8 | ||
|
|
dcbf285fae | ||
|
|
ceda459942 | ||
|
|
28fe44aea4 | ||
|
|
71510a8199 | ||
|
|
9cd121c8f6 | ||
|
|
03f14475ff | ||
|
|
2f105ed0a4 | ||
|
|
b4350ec77b | ||
|
|
0fa6eb4e31 | ||
|
|
704d54dd01 | ||
|
|
bee9b61831 | ||
|
|
9c6ce08db0 | ||
|
|
6c0de89de8 | ||
|
|
aa99e202b4 | ||
|
|
04728ea4bc | ||
|
|
6865cdfce3 | ||
|
|
d09b36b1d5 | ||
|
|
a5dd4f5fac | ||
|
|
9936b2967e | ||
|
|
0971613ac0 | ||
|
|
98f9bc3fc1 | ||
|
|
f5f2da11d1 | ||
|
|
f7d855c59e | ||
|
|
b6dba2cbe9 | ||
|
|
2fe743649d | ||
|
|
a015237fd2 | ||
|
|
e1f4deaacc | ||
|
|
1dceee3fa0 | ||
|
|
3a17a7c98f | ||
|
|
3298db8683 | ||
|
|
d4d9403829 | ||
|
|
426e6923ac | ||
|
|
b413f665d8 | ||
|
|
5e1a572bd8 | ||
|
|
3036a149bb | ||
|
|
0dcb4b645c | ||
|
|
d71082b491 | ||
|
|
fc4c8d6054 | ||
|
|
1a27c7eabc | ||
|
|
b2e9602aba | ||
|
|
408b959441 | ||
|
|
35fc98a188 | ||
|
|
92f6a79e1f | ||
|
|
5a9c8e304c | ||
|
|
8f94ed68a3 | ||
|
|
a4cd30451c | ||
|
|
ce1a4b94cb | ||
|
|
92a0deffcf | ||
|
|
97b6f6028b | ||
|
|
99e300a640 | ||
|
|
611e8c39ee | ||
|
|
af55287dee | ||
|
|
01f1c263a6 | ||
|
|
24ea6a9f1d | ||
|
|
46c800b8b7 | ||
|
|
f10be893ce | ||
|
|
c606c1664f | ||
|
|
90a938fe2b | ||
|
|
225932b4c9 | ||
|
|
6b850b9894 | ||
|
|
d8ef796a46 | ||
|
|
943a5d1036 | ||
|
|
e600e9c583 | ||
|
|
b25da8ceaa | ||
|
|
f7388f6492 | ||
|
|
10cdb7387d | ||
|
|
dd436dd1dd | ||
|
|
13b84635b5 | ||
|
|
144d1ea280 | ||
|
|
27534d408f | ||
|
|
b80a2921aa | ||
|
|
0a23ce870a | ||
|
|
ba1e100b42 | ||
|
|
2bc237468d | ||
|
|
c355ef8d53 | ||
|
|
3052d88164 | ||
|
|
1feb23ba99 | ||
|
|
767f650e6f | ||
|
|
59a0fd0799 | ||
|
|
00b5cfcf17 | ||
|
|
f7217944e7 | ||
|
|
9e342f253d | ||
|
|
20c48b6bb2 | ||
|
|
572004d37a | ||
|
|
4f9e7bb572 | ||
|
|
df9b692a32 | ||
|
|
0a322d5a9f | ||
|
|
59eb6d10c1 | ||
|
|
affb219d72 | ||
|
|
a57ce1dd17 | ||
|
|
4e7ace3b48 | ||
|
|
b100b3c36b | ||
|
|
b64b27e5ea | ||
|
|
81c1ba6eef | ||
|
|
46b981bb2f | ||
|
|
93ae4d1ee6 | ||
|
|
9c53d818f4 | ||
|
|
efb2a1d913 | ||
|
|
e5a2c1ab10 | ||
|
|
550c0c7625 | ||
|
|
0abc269a3e | ||
|
|
6b3cbe7882 | ||
|
|
67ed035b36 | ||
|
|
ca25105f13 | ||
|
|
ad30786f4a | ||
|
|
ff0bdcd0c2 | ||
|
|
2e01da5ec1 | ||
|
|
8fb37a0024 | ||
|
|
9412a7424c | ||
|
|
e440eb1733 | ||
|
|
f8cbb4f386 | ||
|
|
86ccaa52a5 | ||
|
|
db14d15953 | ||
|
|
cc3e5d23e4 | ||
|
|
b70d076324 | ||
|
|
cac86d1f77 | ||
|
|
22f7bb822f | ||
|
|
1b53f10091 | ||
|
|
2d697ac8db | ||
|
|
bac268a21c | ||
|
|
321e6e6679 | ||
|
|
e028700175 | ||
|
|
63a673cf1d | ||
|
|
22ef058cb6 | ||
|
|
adcdb19c88 | ||
|
|
d35fa8e8e1 | ||
|
|
9ee6419bc0 | ||
|
|
6af7bfb536 | ||
|
|
3bf0188e7f | ||
|
|
91fc0fd623 | ||
|
|
f936ec0236 | ||
|
|
d2fcf68381 | ||
|
|
a4d0c17efd | ||
|
|
ff1a721d5b | ||
|
|
c870a1bbd5 | ||
|
|
ebf332a8c9 | ||
|
|
b2d335e0da | ||
|
|
85d1fff18f | ||
|
|
2839a95198 | ||
|
|
6bb106c2cb | ||
|
|
e551fd2c1b | ||
|
|
34a11c2338 | ||
|
|
0de3a400eb | ||
|
|
e524ff965d | ||
|
|
7ba689aaeb | ||
|
|
c228e125c3 | ||
|
|
92fde83b3a | ||
|
|
3a268aef50 | ||
|
|
9486dcf6b0 | ||
|
|
f673bd8d7b | ||
|
|
299b680013 | ||
|
|
b1160af896 | ||
|
|
81e8352391 | ||
|
|
1a627d6a87 | ||
|
|
d5f00db9ea | ||
|
|
00a3e562dc | ||
|
|
36aca49fc3 | ||
|
|
0628c9161c | ||
|
|
9b28672e19 | ||
|
|
903ffef42c | ||
|
|
d764058a04 | ||
|
|
0aafe38496 | ||
|
|
d86d565b3f | ||
|
|
e7745db36e | ||
|
|
66d3e1b551 | ||
|
|
474b879b16 | ||
|
|
2f5d6e16a6 | ||
|
|
0a23b57ff8 | ||
|
|
251e1b51c5 | ||
|
|
68150b640e | ||
|
|
217d270457 | ||
|
|
143b6f0df1 | ||
|
|
81684b921e | ||
|
|
2cc6c959a5 | ||
|
|
fb9aa672c9 | ||
|
|
325e65db7f | ||
|
|
603e2b173d | ||
|
|
103b49ec45 | ||
|
|
6c78d7544f | ||
|
|
4d5fbec5a5 | ||
|
|
7cf50a15d0 | ||
|
|
adbc4e9fec | ||
|
|
4815434dd7 | ||
|
|
44f20e7ad6 | ||
|
|
b25e50bdae | ||
|
|
4aab388eff | ||
|
|
604287244f | ||
|
|
47646a4aa0 | ||
|
|
70ca5e2aef | ||
|
|
4a4a721e90 | ||
|
|
52078f7762 | ||
|
|
36377e59cb | ||
|
|
a4062b338e | ||
|
|
ec223ac2ed | ||
|
|
5587b7d469 | ||
|
|
b00577fda4 | ||
|
|
26870d134f | ||
|
|
fb2baad7cc | ||
|
|
0431a2abb1 | ||
|
|
4bae8d4cfb | ||
|
|
c2db2d3cbd | ||
|
|
05d9ec673e | ||
|
|
ae566dca98 | ||
|
|
1732cf9243 | ||
|
|
ab62c19de9 | ||
|
|
96d3a69479 | ||
|
|
2f7bb3499d | ||
|
|
8523bb1ad8 | ||
|
|
e266e1ce40 | ||
|
|
169c3d5a0f | ||
|
|
9cffbe3d49 | ||
|
|
6090439289 | ||
|
|
e6da54d9b8 | ||
|
|
0a843dc086 | ||
|
|
7b3ed2dcbf | ||
|
|
42ed6c3ded | ||
|
|
23ca011ac1 | ||
|
|
d82b3dc429 | ||
|
|
e391793c58 | ||
|
|
236cef4b56 | ||
|
|
45687513bf | ||
|
|
019ffdaf12 | ||
|
|
1a57d4fe11 | ||
|
|
3081f22e70 | ||
|
|
df847431eb | ||
|
|
5b2f1eee65 | ||
|
|
99f488d48f | ||
|
|
05929871c9 | ||
|
|
74b3daa70a | ||
|
|
5e30b0499a | ||
|
|
32bf742709 | ||
|
|
9535f399f2 | ||
|
|
85c7347899 | ||
|
|
4579d26a53 | ||
|
|
128605ab33 | ||
|
|
4dbc908619 | ||
|
|
3a84775912 | ||
|
|
290a82e61c | ||
|
|
adac7d7fb2 | ||
|
|
679fd9d60f | ||
|
|
52320ebb91 | ||
|
|
b4aa17bfbe | ||
|
|
3f8b7ec327 | ||
|
|
eab1f503a5 | ||
|
|
2bcc90a9eb | ||
|
|
a9d0aa136d | ||
|
|
bc7f2677b1 | ||
|
|
691ab3080d | ||
|
|
c5147e0c68 | ||
|
|
e9b0829860 | ||
|
|
95428ab8b0 | ||
|
|
8a595ed77a | ||
|
|
f0ce003765 | ||
|
|
775d56fe52 | ||
|
|
ac2ff29288 | ||
|
|
81cfa5891c | ||
|
|
8e1975efe4 | ||
|
|
39c09733d3 | ||
|
|
da16dfd528 | ||
|
|
e1c397993d | ||
|
|
96cbdd5c37 | ||
|
|
58ca4baf71 | ||
|
|
5245b68689 | ||
|
|
2b6811cb1b | ||
|
|
f0a8aa06bc | ||
|
|
c82496b972 | ||
|
|
c31348ea9a | ||
|
|
75d903b164 | ||
|
|
c885540749 | ||
|
|
7dd40fd2d4 | ||
|
|
bb087caddc | ||
|
|
899b54c082 | ||
|
|
5e3f8b9c2e | ||
|
|
1ccfe928f7 | ||
|
|
3016269268 | ||
|
|
952cfae4e6 | ||
|
|
df7f209ebc | ||
|
|
b09eb63e1e | ||
|
|
d464721397 | ||
|
|
551a85c1e6 | ||
|
|
f3f4bd33e5 | ||
|
|
2686761608 | ||
|
|
02a2c3224f | ||
|
|
8ec1dda9ba | ||
|
|
0308532523 | ||
|
|
10b3af2947 | ||
|
|
1f34569ddc | ||
|
|
158bfe4816 | ||
|
|
fb0941bbe9 | ||
|
|
16eefe333f | ||
|
|
65e585e122 | ||
|
|
2ba36ee04c | ||
|
|
fc05529574 | ||
|
|
010770904f | ||
|
|
07d24f487a | ||
|
|
0960e4caa4 | ||
|
|
811acdb7f5 | ||
|
|
2fce5c4d5d | ||
|
|
a7266364d1 | ||
|
|
e83b41dc01 | ||
|
|
76665dd56e | ||
|
|
2d42b478ba | ||
|
|
5797e8aeec | ||
|
|
885fa6beae | ||
|
|
65cdd5fcbe | ||
|
|
73da4844ee | ||
|
|
4302e19aba | ||
|
|
0c6f05f34a | ||
|
|
385123fd40 | ||
|
|
8040c285cd | ||
|
|
bf1ee61bf0 | ||
|
|
ada454f56f | ||
|
|
57d2ef8740 | ||
|
|
a97e0982e3 | ||
|
|
eaa2060d84 | ||
|
|
3e02c574e7 | ||
|
|
e873d55cd3 | ||
|
|
c7230ccbb0 | ||
|
|
fc71ced660 | ||
|
|
6041a9d094 | ||
|
|
599cd766e1 | ||
|
|
84e02099a2 | ||
|
|
e64c8007c0 | ||
|
|
9339823e84 | ||
|
|
1ab52cfce3 | ||
|
|
e6d4620af1 | ||
|
|
656e6db846 | ||
|
|
9ed16478f4 | ||
|
|
30474e3a2b | ||
|
|
460bd98f1b | ||
|
|
a553eba574 | ||
|
|
61f515670c | ||
|
|
789af0f8a6 | ||
|
|
86fae58c96 | ||
|
|
03ae741505 | ||
|
|
c54d3abe47 | ||
|
|
906cd975d1 | ||
|
|
6fe511eb50 | ||
|
|
839f34ec4e | ||
|
|
17d18b22c7 | ||
|
|
66a04ed730 | ||
|
|
7e14a0bc30 | ||
|
|
4f2e06407b | ||
|
|
77de0f86dc | ||
|
|
f79c998f95 | ||
|
|
3b4bc9163a | ||
|
|
e146135bd1 | ||
|
|
e9e18afb4a | ||
|
|
62c31eb202 | ||
|
|
8016e84bc7 | ||
|
|
1eacee85ae | ||
|
|
5986145282 | ||
|
|
5756be7d36 | ||
|
|
b134a69a28 | ||
|
|
83f9eacac4 | ||
|
|
baab6b951b | ||
|
|
b041fbe3ec | ||
|
|
1b7b6d4b7e | ||
|
|
903a1bec91 | ||
|
|
15c7d9b0f1 | ||
|
|
ebc3a251b7 | ||
|
|
a567f4a6de | ||
|
|
4dc49604b6 | ||
|
|
c660daf4c2 | ||
|
|
fadcf62166 | ||
|
|
c02b255766 | ||
|
|
89788fa665 | ||
|
|
a052e2ee8f | ||
|
|
158e5004b7 | ||
|
|
6f9ff3be37 | ||
|
|
f766c57743 | ||
|
|
8f53152c3e | ||
|
|
7bbc88d716 | ||
|
|
68256d2343 | ||
|
|
1a0c70acc2 | ||
|
|
f88c440c48 | ||
|
|
cfa56ba6d4 | ||
|
|
37e6297fb2 | ||
|
|
2c6b646d87 | ||
|
|
e7572094b5 | ||
|
|
ddd5e0e889 | ||
|
|
5e34823a49 | ||
|
|
fdef21a871 | ||
|
|
d40cff8a99 | ||
|
|
8927e45ded | ||
|
|
a602f73ed0 | ||
|
|
1a93df92d4 | ||
|
|
12fe28d8ab | ||
|
|
06754d6158 | ||
|
|
8f9ec07ac3 | ||
|
|
1d5087374e | ||
|
|
8356264fe0 | ||
|
|
18ecd748dd | ||
|
|
6061d0603a | ||
|
|
e324f27655 | ||
|
|
afd645fb11 | ||
|
|
e48d37ccab | ||
|
|
5b42593fbb | ||
|
|
25f3c6a548 | ||
|
|
5c04f2634a | ||
|
|
ad01610d1e | ||
|
|
697c380cd1 | ||
|
|
ce89bf3c77 | ||
|
|
81e8143a43 | ||
|
|
39c4117409 | ||
|
|
8409986ef5 | ||
|
|
2b50367d7f | ||
|
|
cd4b85b0b5 | ||
|
|
dcec02412d | ||
|
|
1dcc491fec | ||
|
|
c6419a9c61 | ||
|
|
d715f86713 | ||
|
|
5221828705 | ||
|
|
6aa4acd3db | ||
|
|
15936c29c1 | ||
|
|
41689a2d82 | ||
|
|
1f71d19004 | ||
|
|
8b70c69a0d | ||
|
|
dfe80a30dc | ||
|
|
82a7f2dc2d | ||
|
|
ce026e7ad0 | ||
|
|
58e3122dab | ||
|
|
3b55b2d1b2 | ||
|
|
e3a7164fe1 | ||
|
|
2326e61de5 | ||
|
|
32b18c9509 | ||
|
|
acf52ad8a3 | ||
|
|
08dd493f98 | ||
|
|
4188cc2949 | ||
|
|
07bbe7dfb2 | ||
|
|
0aa1b80e3e | ||
|
|
5ac390281e | ||
|
|
ac465c5a18 | ||
|
|
745cd9f501 | ||
|
|
1ef3546822 | ||
|
|
b9df028bfb | ||
|
|
e74ad2b555 | ||
|
|
71c0056c66 | ||
|
|
56b27ea785 | ||
|
|
7e8974683c | ||
|
|
2d75f59d33 | ||
|
|
a709a3bcc0 | ||
|
|
34bf5112e0 | ||
|
|
bfe18d1d28 | ||
|
|
6a76afc20d | ||
|
|
9c469c2f96 | ||
|
|
e68a1dee33 | ||
|
|
2ab42f3dd6 | ||
|
|
af34b43a8d | ||
|
|
0f71cc7864 | ||
|
|
115ca7b789 | ||
|
|
df90de3a5f | ||
|
|
42ea7a9814 | ||
|
|
809750b995 | ||
|
|
c683cbdb2d | ||
|
|
d243b73678 | ||
|
|
b9abd2f9a5 | ||
|
|
dc28d063aa | ||
|
|
34cb661c35 | ||
|
|
bb6045c1d0 | ||
|
|
f1a74b5939 | ||
|
|
6fb9038af1 | ||
|
|
27f721a1f5 | ||
|
|
5717dc294e | ||
|
|
ae0f8df11b | ||
|
|
20bbdf22c7 | ||
|
|
77460ba502 | ||
|
|
72c6a4289e | ||
|
|
f8235da4d8 | ||
|
|
29973d7a72 | ||
|
|
8d3316ccd6 | ||
|
|
2c29f831e8 | ||
|
|
9457325119 | ||
|
|
189f8afcc9 | ||
|
|
f3a34990ab | ||
|
|
9699b67510 | ||
|
|
d60633a46c | ||
|
|
ae2df33ce6 | ||
|
|
0fcf28e7e7 | ||
|
|
5fad8f74b1 | ||
|
|
e35e34d2e0 | ||
|
|
8943144b2b | ||
|
|
aa6acc6673 | ||
|
|
31a215d4d6 | ||
|
|
9be6af8481 | ||
|
|
30948de13d | ||
|
|
b97158bf7b | ||
|
|
b14a8ff2fd | ||
|
|
6646be8d94 | ||
|
|
647a2aea64 | ||
|
|
035693aa30 | ||
|
|
58e58ce51c | ||
|
|
7aff72fc7c | ||
|
|
4b7e48d3ec | ||
|
|
91086d004c | ||
|
|
0e074d3a5a | ||
|
|
5d86b8893c | ||
|
|
ea00224e7f | ||
|
|
38293efe75 | ||
|
|
3286c5e174 | ||
|
|
e0f8a28f38 | ||
|
|
bf1b00b163 | ||
|
|
2678b9003a | ||
|
|
4fa3a136ad | ||
|
|
178a6bd66e | ||
|
|
e7d3ee3bc8 | ||
|
|
57e1339230 | ||
|
|
97c9a9b0b0 | ||
|
|
4144423227 | ||
|
|
4d03514142 | ||
|
|
0bc5566976 | ||
|
|
99550b79ab | ||
|
|
925567add5 | ||
|
|
ad97731d46 | ||
|
|
40684a93a2 | ||
|
|
f3b644ceb8 | ||
|
|
901ba6e87f | ||
|
|
6e9b70a8d6 | ||
|
|
76389bd584 | ||
|
|
7bf8650d59 | ||
|
|
7cb24dd96d | ||
|
|
dee040318a | ||
|
|
b93c5a9daa | ||
|
|
baf563e46d | ||
|
|
42910ebe14 | ||
|
|
e10246f10b | ||
|
|
f524219d68 | ||
|
|
a9dfd88671 | ||
|
|
61b338f464 | ||
|
|
beac73b6c8 | ||
|
|
f9f93c77e2 | ||
|
|
6fc1b4c6c0 | ||
|
|
639898ec07 | ||
|
|
3d3790fdbc | ||
|
|
69c8968cb0 | ||
|
|
229caea5fd | ||
|
|
aa25fc7901 | ||
|
|
4aba493ee2 | ||
|
|
dd7a080e2d | ||
|
|
b9cfac5934 | ||
|
|
f8e97fcc32 | ||
|
|
d2a26d6d48 | ||
|
|
7f8f2641f6 | ||
|
|
022dbb606f | ||
|
|
fea72b190d | ||
|
|
bce4351b4f | ||
|
|
eea073f882 | ||
|
|
0352d413e3 | ||
|
|
56bf98d68a | ||
|
|
fc1d88f5bb | ||
|
|
2b109db1b1 | ||
|
|
acbe410e0b | ||
|
|
89bafea61f | ||
|
|
07b7e05caa | ||
|
|
ec072fd639 | ||
|
|
b874c1a8a8 | ||
|
|
67b03076d7 | ||
|
|
33a0c4a9ae | ||
|
|
e25914c3d3 | ||
|
|
4cc5999f05 | ||
|
|
403aa5fd3e | ||
|
|
08f203c0c2 | ||
|
|
0fbf31ec98 | ||
|
|
db9b7ca447 | ||
|
|
fc9fa2614d | ||
|
|
eecc71c77f | ||
|
|
d5f69aec10 | ||
|
|
5f898a9071 | ||
|
|
a08f21336a | ||
|
|
215125de26 | ||
|
|
dfa94eac41 | ||
|
|
5de72a74f5 | ||
|
|
a44f0a719e | ||
|
|
247d869a5c | ||
|
|
b9b6e1818f | ||
|
|
a7968ccc3c | ||
|
|
b7e0e5a356 | ||
|
|
e3aecb2aa9 | ||
|
|
2b879ab471 | ||
|
|
116ab83b95 | ||
|
|
f24ae0fc2c | ||
|
|
c5d0d4acd0 | ||
|
|
062bf737c2 | ||
|
|
06190286ec | ||
|
|
3bca856e87 | ||
|
|
b4437d9cfd | ||
|
|
b3a778b89a | ||
|
|
dcb11f7606 | ||
|
|
933ce42d5a | ||
|
|
6c1ba957bd | ||
|
|
e021286eee | ||
|
|
0ee7a66033 | ||
|
|
42c797ee97 | ||
|
|
c03699fc5b | ||
|
|
b585c6676d | ||
|
|
f5ddb0f1f8 | ||
|
|
ef97f9e738 | ||
|
|
26a03b55de | ||
|
|
e9b0100b06 | ||
|
|
b93a16abae | ||
|
|
ff2783d434 | ||
|
|
664df55525 | ||
|
|
66f93148a7 | ||
|
|
25a5a8db68 | ||
|
|
a68d91b54c | ||
|
|
e48afc9fdf | ||
|
|
acdefb394c | ||
|
|
88ec3a5751 | ||
|
|
e3af0a13da | ||
|
|
734dc2e37a | ||
|
|
06b97b91e0 | ||
|
|
6aecd8d44a | ||
|
|
8a4ac3ea10 | ||
|
|
0ca123f4bc | ||
|
|
bbf5e1fa78 | ||
|
|
7454e55693 | ||
|
|
c745fe7778 | ||
|
|
6adcebb247 | ||
|
|
4c1844e46a | ||
|
|
38f9b8f3dd | ||
|
|
c2e72c7641 | ||
|
|
7faf143a97 | ||
|
|
e53a7f65b8 | ||
|
|
21eb9df85f | ||
|
|
17cfd4924c | ||
|
|
a0bebca1e6 | ||
|
|
75cbfa2898 | ||
|
|
58a8ed1588 | ||
|
|
e032f8df59 | ||
|
|
b16e37d80a | ||
|
|
c17cc07b73 | ||
|
|
6f767a72a7 | ||
|
|
11196436e9 | ||
|
|
9afc433db4 | ||
|
|
f701e3d834 | ||
|
|
4c623ca3c5 | ||
|
|
0671f05c05 | ||
|
|
0713da7393 | ||
|
|
d980b8cfdc | ||
|
|
05955d0483 | ||
|
|
28c765bd9a | ||
|
|
8287317be7 | ||
|
|
08cc67d7c5 | ||
|
|
ec858b2d4c | ||
|
|
47ece983d2 | ||
|
|
52310b5dd9 | ||
|
|
c2a4a2778e | ||
|
|
1db5977e80 | ||
|
|
1b5dc0e434 | ||
|
|
251364d8be | ||
|
|
f07f02d866 | ||
|
|
1388103919 | ||
|
|
fe1054d58a | ||
|
|
ba6ac778fc | ||
|
|
228ffcbfce | ||
|
|
f18ab9e5cc | ||
|
|
06c82ee87d | ||
|
|
9ba5ee8043 | ||
|
|
eede4e50ca | ||
|
|
1e7b6f78ca | ||
|
|
aa10f63d9f | ||
|
|
5b984d924f | ||
|
|
a25af36e32 | ||
|
|
eb8b5e5c01 | ||
|
|
817f057927 | ||
|
|
a13c44d81a | ||
|
|
45ad8f8ffb | ||
|
|
7da9e88c0b | ||
|
|
674e63cd3e | ||
|
|
1c73c91133 | ||
|
|
b1d11d4f69 | ||
|
|
001a9310c3 | ||
|
|
4cea9023f2 | ||
|
|
8ffadfa1f1 | ||
|
|
39e0b8e40e | ||
|
|
50d7929e76 | ||
|
|
10840b22c5 | ||
|
|
acfbab5915 | ||
|
|
9b00f65798 | ||
|
|
48c324fe86 | ||
|
|
0c495e7e72 | ||
|
|
50ccfee201 | ||
|
|
f39a5f6e68 | ||
|
|
f1f2efc4f8 | ||
|
|
fa739c9594 | ||
|
|
95f0ce1816 | ||
|
|
3432529f9c | ||
|
|
a32c8fabed | ||
|
|
6a11501184 | ||
|
|
7ca228d466 | ||
|
|
09a00f7d42 | ||
|
|
eac4cd15e3 | ||
|
|
c03fd4645d | ||
|
|
911ddbe1c8 | ||
|
|
b76c7a4824 | ||
|
|
1408ac77f9 | ||
|
|
c641abc7cd | ||
|
|
7e97d10ae1 | ||
|
|
56b134facd | ||
|
|
41286650cc | ||
|
|
2aca007a59 | ||
|
|
39f5c00f7e | ||
|
|
7eab431e5d | ||
|
|
678b15e759 | ||
|
|
3c8e7c6c10 | ||
|
|
bb7433ab40 | ||
|
|
4f64513c83 | ||
|
|
4ee47b6b1b | ||
|
|
350f5ccb3c | ||
|
|
0d3f94860b | ||
|
|
3d7489563d | ||
|
|
ac1153c7cf | ||
|
|
d4c4220299 | ||
|
|
3b1672a4a7 | ||
|
|
009f81a9a6 | ||
|
|
69285f28ad | ||
|
|
3593df0e73 | ||
|
|
8b10b1dc62 | ||
|
|
0db2a81e4e | ||
|
|
9fe64008c2 | ||
|
|
077c259d5b | ||
|
|
879bd62468 | ||
|
|
e5746c19cf | ||
|
|
29814d7458 | ||
|
|
e399f9e851 | ||
|
|
e4e20c2d25 | ||
|
|
08a70664f4 | ||
|
|
9d77f8dcd2 | ||
|
|
1d76c644cb | ||
|
|
75fc35ee4b | ||
|
|
ba3102d667 | ||
|
|
a94fc22659 | ||
|
|
8a9fed64f2 | ||
|
|
4468b5a2e4 | ||
|
|
71c7e30548 | ||
|
|
940bd55079 | ||
|
|
f2aa40c772 | ||
|
|
886db1a2c3 | ||
|
|
c8979940a8 | ||
|
|
b4c06db031 | ||
|
|
82486a873a | ||
|
|
99760ba6a0 | ||
|
|
2b987e6e93 | ||
|
|
523134f24b | ||
|
|
99c228dd6d | ||
|
|
10f80f9a4f | ||
|
|
44d38a00d4 | ||
|
|
c623f70caa | ||
|
|
62a10047ca | ||
|
|
f16b783378 | ||
|
|
95c55a8ab3 | ||
|
|
a8c932ffe4 | ||
|
|
be4b75932b | ||
|
|
04d7f3a5dc | ||
|
|
eddfacd0f4 | ||
|
|
cba3c20d5c | ||
|
|
507783ed8d | ||
|
|
06c98ed229 | ||
|
|
69742839c0 | ||
|
|
bb52049bf4 | ||
|
|
20b5f0e681 | ||
|
|
3803b8f284 | ||
|
|
e3daf743f2 | ||
|
|
ae5da74bb1 | ||
|
|
05f37ec2bc | ||
|
|
cf78047830 | ||
|
|
284428a45a | ||
|
|
79f2efdfd9 | ||
|
|
07822b371f | ||
|
|
7a3a385569 | ||
|
|
0f8cb7ea6d | ||
|
|
fa7ae376e0 | ||
|
|
8c223056fe | ||
|
|
e57f78cf90 | ||
|
|
ebd79d388b | ||
|
|
10724fee04 | ||
|
|
0d46f85ead | ||
|
|
4727ba3b51 | ||
|
|
eb15a3d849 | ||
|
|
4d09bae09b | ||
|
|
56ad177d4a | ||
|
|
11902dec3c | ||
|
|
4fdbe9b9ff | ||
|
|
18037bb484 | ||
|
|
a6e7e30177 | ||
|
|
288285f22b | ||
|
|
5b69ff7e94 | ||
|
|
fa2d838e60 | ||
|
|
bc0c6be4c5 | ||
|
|
ef767728ac | ||
|
|
f56d219882 | ||
|
|
32afccb16d | ||
|
|
b4f528039f | ||
|
|
d6b470ffbe | ||
|
|
3c6b418ca8 | ||
|
|
105a38a7db | ||
|
|
e6f25b9c5e | ||
|
|
6e0b956e61 | ||
|
|
86af14ad71 | ||
|
|
a029768d24 | ||
|
|
e0b85131bd | ||
|
|
4d9c9206cf | ||
|
|
92ca8862e1 | ||
|
|
b77bc28a79 | ||
|
|
b5eca401c6 | ||
|
|
a88e6ec534 | ||
|
|
35116f9229 | ||
|
|
fe66a59618 | ||
|
|
a1f90cd39b | ||
|
|
743975db52 | ||
|
|
45dc7459b8 | ||
|
|
ff3056332e | ||
|
|
6877b663f1 | ||
|
|
3c69f81a10 | ||
|
|
d316d57e5d | ||
|
|
92eddf8eb6 | ||
|
|
109a5b4748 | ||
|
|
0c81556b90 | ||
|
|
7e4b572699 | ||
|
|
d72828b3f4 | ||
|
|
9220f4d829 | ||
|
|
66917cd2c0 | ||
|
|
d0d375aeb7 | ||
|
|
b41dde777e | ||
|
|
d3dcd39f61 | ||
|
|
4121cae1d6 | ||
|
|
341138b731 | ||
|
|
527c3996ae | ||
|
|
305b4148bd | ||
|
|
07c22ed5bc | ||
|
|
c2fba6b1cf | ||
|
|
da87059041 | ||
|
|
0e300df68c | ||
|
|
6a9c4cf8cc | ||
|
|
1bebd5488a | ||
|
|
bb354e4941 | ||
|
|
0b5053d49a | ||
|
|
b3a1e6d0a5 | ||
|
|
457459671c | ||
|
|
20f09e6c6e | ||
|
|
944e1d9698 | ||
|
|
e8f795763b | ||
|
|
1d11683ce8 | ||
|
|
cca4258887 | ||
|
|
9999321400 | ||
|
|
3de3fc00ce | ||
|
|
2debb32098 | ||
|
|
e49d7b4ec2 | ||
|
|
c009a56825 | ||
|
|
c75dada340 | ||
|
|
890b0ac301 | ||
|
|
77142eb2df | ||
|
|
1185fad75c | ||
|
|
7463652345 | ||
|
|
d63309ae51 | ||
|
|
40bac000ab | ||
|
|
dac9cee7ca | ||
|
|
ef4c0c529a | ||
|
|
9a4986f189 | ||
|
|
afa674e2ea | ||
|
|
8277c74c5b | ||
|
|
b3c615576e | ||
|
|
6610a29422 | ||
|
|
67b0f14be6 | ||
|
|
15bc5b5b5d | ||
|
|
57d594acb0 | ||
|
|
e4aa761098 | ||
|
|
40aa685aba | ||
|
|
ed766484d2 | ||
|
|
a5f844bc7d | ||
|
|
97bd9b6381 | ||
|
|
ca8f2f5734 | ||
|
|
bcaaf2bb13 | ||
|
|
23128b2e53 | ||
|
|
5fc0e4a071 | ||
|
|
d7d143b97f | ||
|
|
4fe25da30b | ||
|
|
74c8a199f7 | ||
|
|
5f4a2ec4e7 | ||
|
|
56f451b30f | ||
|
|
1683c8e963 | ||
|
|
3091a93a52 | ||
|
|
f18d310348 | ||
|
|
228bdf74a4 | ||
|
|
51a1977243 | ||
|
|
e201344ccd | ||
|
|
f630ccb134 | ||
|
|
3a100702e8 | ||
|
|
fa0745e226 | ||
|
|
e5a89946f6 | ||
|
|
276ba2cd8e | ||
|
|
05a55f4a43 | ||
|
|
37a4e8a4aa | ||
|
|
3776292278 | ||
|
|
a9bba9be2b | ||
|
|
92ee0a5227 | ||
|
|
052fd35c72 | ||
|
|
5e3357d062 | ||
|
|
9e2a55edc2 | ||
|
|
29d8efd26e | ||
|
|
4b8608906a | ||
|
|
dbfe4fd5ac | ||
|
|
266f1c3a49 | ||
|
|
6ce686ab9c | ||
|
|
ea38645493 | ||
|
|
a929aaca04 | ||
|
|
5090c07846 | ||
|
|
8eb6fc8343 | ||
|
|
7da7bec241 | ||
|
|
a69789d572 | ||
|
|
172bc81dd2 | ||
|
|
65234a5a9a | ||
|
|
495481725a | ||
|
|
88353e8748 | ||
|
|
939c46fafd | ||
|
|
3145be12c0 | ||
|
|
4cfb599165 | ||
|
|
30e834744d | ||
|
|
c95a0c86b3 | ||
|
|
2daabd000f | ||
|
|
b97fd17146 | ||
|
|
5987330e59 | ||
|
|
5903475f4a | ||
|
|
0c3dc3dea2 | ||
|
|
c2f2146872 | ||
|
|
05087a833c | ||
|
|
c6eb306691 | ||
|
|
a3545ce551 | ||
|
|
5a6aef7a1d | ||
|
|
585bbf3987 | ||
|
|
df9bd21f55 | ||
|
|
feb7cd7bbf | ||
|
|
79c05c37dd | ||
|
|
1dd3766c5f | ||
|
|
54a41b4f0a | ||
|
|
dc691daf54 | ||
|
|
768b52e517 | ||
|
|
bb0d26e116 | ||
|
|
deef167cb2 | ||
|
|
c5767a280e | ||
|
|
ce5f73a5a6 | ||
|
|
fedfc66403 | ||
|
|
27042dae4d | ||
|
|
0877c4cb29 | ||
|
|
098ea0f405 | ||
|
|
de86376724 | ||
|
|
7fc4d8a172 | ||
|
|
ab276a6a61 | ||
|
|
7e026a7072 | ||
|
|
8513b6fbd5 | ||
|
|
ee6785eff9 | ||
|
|
a341a20e2c | ||
|
|
fefadaebfa | ||
|
|
777d77db53 | ||
|
|
8d72515a30 | ||
|
|
7a262eac12 | ||
|
|
1cd0abf415 | ||
|
|
10575d80ad | ||
|
|
15cf010e44 | ||
|
|
e26e383bd7 | ||
|
|
6652f96349 | ||
|
|
3b2eacfc8e | ||
|
|
3a4cdfd7ca | ||
|
|
2a665f9d92 | ||
|
|
cdea1d5545 | ||
|
|
a577c2d859 | ||
|
|
8f74e83d83 | ||
|
|
4e9bcd0d1f | ||
|
|
bc29495dd0 | ||
|
|
5df598fd56 | ||
|
|
8ddc688c13 | ||
|
|
49dad3099a | ||
|
|
90ecc5c13b | ||
|
|
9b31e4f385 | ||
|
|
9923a07c25 | ||
|
|
35d6bfb3a8 | ||
|
|
aa4a567160 | ||
|
|
dc3ca6db4f | ||
|
|
86389a5fb3 | ||
|
|
d7140eac35 | ||
|
|
d2b81cd359 | ||
|
|
18bab4aa7d | ||
|
|
c0bbebd532 | ||
|
|
acbc2fd490 | ||
|
|
d765d11c3e | ||
|
|
79ab85c632 | ||
|
|
dea68f3cf5 | ||
|
|
50ea267664 | ||
|
|
789f11a5c4 | ||
|
|
322548f078 | ||
|
|
8ea805e4e3 | ||
|
|
3d7e4766f7 | ||
|
|
5c2166a019 | ||
|
|
f5aec1c894 | ||
|
|
b8fb8a52f1 | ||
|
|
9d32300612 | ||
|
|
51c3257df9 | ||
|
|
35180b41bc | ||
|
|
3200ba0ed0 | ||
|
|
8e742ceb91 | ||
|
|
02deea85e6 | ||
|
|
13ea678e5e | ||
|
|
2e98c16f05 | ||
|
|
ca6aa5213c | ||
|
|
54f25358eb | ||
|
|
6fce69bbac | ||
|
|
5ceb18414a | ||
|
|
716150cfd2 | ||
|
|
e10baa915d | ||
|
|
ef6ff68b0b | ||
|
|
b87a6a9fec | ||
|
|
2efe8ae0cf | ||
|
|
8b2f46f0ba | ||
|
|
3c41d4b3a4 | ||
|
|
189d95f4fa | ||
|
|
28939e7405 | ||
|
|
84be8cc045 | ||
|
|
2a7e2be675 | ||
|
|
a1057ef764 | ||
|
|
f79a0a8603 | ||
|
|
6b84f54611 | ||
|
|
1bd66297e2 | ||
|
|
ea08f39f6e | ||
|
|
818c5a9cf5 | ||
|
|
074fd14849 | ||
|
|
a783859ab2 | ||
|
|
4b84a5ec15 | ||
|
|
905db1e8ff | ||
|
|
934d7e0f02 | ||
|
|
3b9a76c1f2 | ||
|
|
dabd294eaf | ||
|
|
3d2f8a7ddb | ||
|
|
507d457900 | ||
|
|
875be1f028 | ||
|
|
71267ec333 | ||
|
|
3aed470a87 | ||
|
|
356b0ae659 | ||
|
|
d8e142d454 | ||
|
|
dd6c5292fd | ||
|
|
571c7a8069 | ||
|
|
5dbde869df | ||
|
|
432943d6ee | ||
|
|
014a821d05 | ||
|
|
d8d98e289a | ||
|
|
53a65ea810 | ||
|
|
fc0ac45f21 | ||
|
|
c2d8c9a662 | ||
|
|
2ad2a95cc6 | ||
|
|
47a8329cd0 | ||
|
|
0087f328d6 | ||
|
|
9250d90e57 | ||
|
|
2dfe9eea94 | ||
|
|
5b28594189 | ||
|
|
33c88ec9e4 | ||
|
|
6d4bb5dcbc | ||
|
|
f2d0f1e51e | ||
|
|
b6d3e76568 | ||
|
|
1515793109 | ||
|
|
26bd15ec28 | ||
|
|
b78dd69fd6 | ||
|
|
7277e30443 | ||
|
|
683c37aca1 | ||
|
|
856e5eca4c | ||
|
|
3c9963065d | ||
|
|
ad9886284b | ||
|
|
7b18ce41a1 | ||
|
|
0fb46ed60b | ||
|
|
a9875dff13 | ||
|
|
9e6427d406 | ||
|
|
92322288ca | ||
|
|
19a4c7253a | ||
|
|
8eed2329bc | ||
|
|
e07c521288 | ||
|
|
917eaa50fb | ||
|
|
9b274cb243 | ||
|
|
93cbdadff3 | ||
|
|
9f5e213402 | ||
|
|
7b60ae26fc | ||
|
|
26b552a12e | ||
|
|
4e8ca603fe | ||
|
|
8cbeecd347 | ||
|
|
ef25503d58 | ||
|
|
5c4c00bd88 | ||
|
|
52d1a103aa | ||
|
|
48888be408 | ||
|
|
8b5106adc7 | ||
|
|
6a80ce878f | ||
|
|
13a75adba9 | ||
|
|
804d4320d7 | ||
|
|
76bf89dda3 | ||
|
|
950bc05d62 | ||
|
|
f9acd6a66b | ||
|
|
d3023a0068 | ||
|
|
d7df99e960 | ||
|
|
6b8478dbe9 | ||
|
|
a297d03db5 | ||
|
|
9d3d9d1390 | ||
|
|
761ac95085 | ||
|
|
d95ca5f9e4 | ||
|
|
c410348278 | ||
|
|
d8a758211f | ||
|
|
33e60f2527 | ||
|
|
fb705eee23 | ||
|
|
635e16e934 | ||
|
|
1deb3ae67f | ||
|
|
16ff40f420 | ||
|
|
a8b5cb23fe | ||
|
|
82f88d04ad | ||
|
|
0e1091e0ea | ||
|
|
7b48f10cc9 | ||
|
|
0be34b1135 | ||
|
|
fb5e0242d0 | ||
|
|
d376035557 | ||
|
|
747b2dc7c5 | ||
|
|
fac2f1bee3 | ||
|
|
a211f6aed9 | ||
|
|
0bc494245d | ||
|
|
b31a88043e | ||
|
|
85509c6d8b | ||
|
|
2dd131cf79 | ||
|
|
51990d0b33 | ||
|
|
00a49afc30 | ||
|
|
faa0630851 | ||
|
|
aac3fc68fc | ||
|
|
9c08b96b50 | ||
|
|
15ddde1df4 | ||
|
|
f2a9de3ba8 | ||
|
|
fd725206e2 | ||
|
|
6368b9383a | ||
|
|
2b8a11b001 | ||
|
|
c9e0339a30 | ||
|
|
8d11595290 | ||
|
|
2fe9f3effa | ||
|
|
ae122ff8a2 | ||
|
|
8b941ddd33 | ||
|
|
2902ea6689 | ||
|
|
5ccbf355bd | ||
|
|
62d8f635b0 | ||
|
|
4c3aae63d3 | ||
|
|
8f94aa0c0d | ||
|
|
0370306bb6 | ||
|
|
c9176efe6f | ||
|
|
a3fd08b7ef | ||
|
|
83a9e36ef1 | ||
|
|
9668decc84 | ||
|
|
328bb47d44 | ||
|
|
c74e0b9eab | ||
|
|
20dc32413f | ||
|
|
d4e618893c | ||
|
|
8d0d934249 | ||
|
|
ef2441ad82 | ||
|
|
bb7859138c | ||
|
|
ebc4570941 | ||
|
|
d04baed38b | ||
|
|
001c81f797 | ||
|
|
3327bf4788 | ||
|
|
e0b480e232 | ||
|
|
b328d85230 | ||
|
|
3afaa5c1e6 | ||
|
|
f2c8699823 | ||
|
|
4c11315a3c | ||
|
|
0e3a347d6b | ||
|
|
dc723fadaa | ||
|
|
6396958a31 | ||
|
|
1fe57e4841 | ||
|
|
3885ae6337 | ||
|
|
39e3366b3b | ||
|
|
a5ffd47935 | ||
|
|
18a96a445b | ||
|
|
de366b976e | ||
|
|
8f6aff3a7e | ||
|
|
fb411b20cc | ||
|
|
52ce7d6935 | ||
|
|
75ee398633 | ||
|
|
53bdd946d6 | ||
|
|
79f36664ef | ||
|
|
83078a48ab | ||
|
|
6f67a812dc | ||
|
|
6ad705b40e | ||
|
|
4cb43415a1 | ||
|
|
bbb09b558c | ||
|
|
6363be81e0 | ||
|
|
c6810d7460 | ||
|
|
4c1e7e7c2f | ||
|
|
931081752a | ||
|
|
424e98e67e | ||
|
|
7cfa93f5f8 | ||
|
|
fd8e8d1475 | ||
|
|
7ed5eb22ec | ||
|
|
fa1c275904 | ||
|
|
558c72bbb7 | ||
|
|
37976455bc | ||
|
|
db6ee8f687 | ||
|
|
53e7ffa311 | ||
|
|
e0f7ff325a | ||
|
|
1fcd1e33c5 | ||
|
|
843b16c1f0 | ||
|
|
a58f3f0fe3 | ||
|
|
e69e395b25 | ||
|
|
456164fc24 | ||
|
|
9b7f61a09d | ||
|
|
2de9adb7ae | ||
|
|
c7e0cc1158 | ||
|
|
e79861c30a | ||
|
|
407f596b61 | ||
|
|
41d30fc8dc | ||
|
|
2e429c5b35 | ||
|
|
28afe3470b | ||
|
|
f623b24196 | ||
|
|
2016c17894 | ||
|
|
2d28077010 | ||
|
|
7011f3c3c7 | ||
|
|
1403dbd5dd | ||
|
|
6ef3845a34 | ||
|
|
de2f0914f0 | ||
|
|
080ae0985f | ||
|
|
887f49760f | ||
|
|
be77f2291b | ||
|
|
b585b849a1 | ||
|
|
0c94e3d994 | ||
|
|
4a93658d0f | ||
|
|
6da19c6254 | ||
|
|
289058be81 | ||
|
|
864675ecde | ||
|
|
c79d7e3e30 | ||
|
|
549cc6f492 | ||
|
|
aa84ab66af | ||
|
|
1fef812c67 | ||
|
|
9170f74887 | ||
|
|
c9bd700d31 | ||
|
|
0928e4c035 | ||
|
|
75aec0a8d4 | ||
|
|
1f5ee36a6b | ||
|
|
45a0b74b89 | ||
|
|
f7132bdbbc | ||
|
|
d2dbff173a | ||
|
|
79f83babb3 | ||
|
|
715181f830 | ||
|
|
5c1c80dad3 | ||
|
|
32c0712715 | ||
|
|
fa4e492d1c | ||
|
|
4711fa2a9a | ||
|
|
08291502eb | ||
|
|
1f0acd0622 | ||
|
|
e88b732715 | ||
|
|
dafda85c36 | ||
|
|
8441fa9687 | ||
|
|
77800dfb01 | ||
|
|
41d15b738b | ||
|
|
aa2a5ff929 | ||
|
|
56d3c1e73f | ||
|
|
f401f9a674 | ||
|
|
30933d792b | ||
|
|
905717977e | ||
|
|
b802994809 | ||
|
|
ac96f906b3 | ||
|
|
cc395e914f | ||
|
|
f8063f8faf | ||
|
|
958ada9110 | ||
|
|
3452ea29b8 | ||
|
|
675b6f4b5e | ||
|
|
4fab67b0f5 | ||
|
|
8a2131416d | ||
|
|
2525304215 | ||
|
|
fdb35d4f77 | ||
|
|
222c7ea641 | ||
|
|
8f2722263f | ||
|
|
fd662e3401 | ||
|
|
bc65461ecb | ||
|
|
30017371df | ||
|
|
b944b86bf8 | ||
|
|
6ba8352fa6 | ||
|
|
aadf76692e | ||
|
|
c9a1386a55 | ||
|
|
4eb88a3e02 | ||
|
|
160c0bfe14 | ||
|
|
4cd098af1d | ||
|
|
362abd618f | ||
|
|
69de9f6684 | ||
|
|
2aa379bf21 | ||
|
|
820a2f1a63 | ||
|
|
6fac7614f5 | ||
|
|
b724ea8d3b | ||
|
|
06f80c320d | ||
|
|
d9de015eab | ||
|
|
dd36c861ba | ||
|
|
b34e46af93 | ||
|
|
405802c492 | ||
|
|
e15f3c9d2b | ||
|
|
885af064c9 | ||
|
|
40529dc41f | ||
|
|
2452f6ef8a | ||
|
|
4c543e6f06 | ||
|
|
bc5fd5d441 | ||
|
|
7a33cef27e | ||
|
|
b433b9c879 | ||
|
|
5d169c523f | ||
|
|
98d277368f | ||
|
|
3bd58bf25e | ||
|
|
ba05e577ae | ||
|
|
4206f6af45 | ||
|
|
0c5da1cc47 | ||
|
|
d9bd1c679e | ||
|
|
437eb8de37 | ||
|
|
f14c181840 | ||
|
|
d46c9fb157 | ||
|
|
6913e4dfd2 | ||
|
|
aea3f299a0 | ||
|
|
5667476780 | ||
|
|
b263f47826 | ||
|
|
21723d6313 | ||
|
|
a63cdc76b0 | ||
|
|
129e372613 | ||
|
|
53de38fc06 |
1396
.editorconfig
Normal file
1396
.editorconfig
Normal file
File diff suppressed because it is too large
Load Diff
4
.github/FUNDING.yml
vendored
Normal file
4
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# Copyright 2021 Signal Messenger, LLC
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
custom: https://signal.org/donate/
|
||||
24
.github/workflows/test.yml
vendored
Normal file
24
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: Service CI
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: 11
|
||||
- name: Cache local Maven repository
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-maven-
|
||||
- name: Build with Maven
|
||||
run: mvn -e -B verify
|
||||
14
.gitignore
vendored
14
.gitignore
vendored
@@ -7,3 +7,17 @@ run.sh
|
||||
local.yml
|
||||
config/production.yml
|
||||
config/federated.yml
|
||||
config/staging.yml
|
||||
config/testing.yml
|
||||
config/deploy.properties
|
||||
/service/config/production.yml
|
||||
/service/config/federated.yml
|
||||
/service/config/staging.yml
|
||||
/service/config/testing.yml
|
||||
/service/config/deploy.properties
|
||||
/service/dependency-reduced-pom.xml
|
||||
.opsmanage
|
||||
put.sh
|
||||
deployer-staging.properties
|
||||
deployer-production.properties
|
||||
deployer.log
|
||||
|
||||
9
.mvn/extensions.xml
Normal file
9
.mvn/extensions.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<extensions xmlns="http://maven.apache.org/EXTENSIONS/1.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/EXTENSIONS/1.0.0 http://maven.apache.org/xsd/core-extensions-1.0.0.xsd">
|
||||
<extension>
|
||||
<groupId>fr.brouillard.oss</groupId>
|
||||
<artifactId>jgitver-maven-plugin</artifactId>
|
||||
<version>1.7.1</version>
|
||||
</extension>
|
||||
</extensions>
|
||||
14
.mvn/jgitver.config.xml
Normal file
14
.mvn/jgitver.config.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<configuration xmlns="http://jgitver.github.io/maven/configuration/1.1.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://jgitver.github.io/maven/configuration/1.1.0 https://jgitver.github.io/maven/configuration/jgitver-configuration-v1_1_0.xsd">
|
||||
<useDirty>true</useDirty>
|
||||
<useDefaultBranchingPolicy>false</useDefaultBranchingPolicy>
|
||||
<branchPolicies>
|
||||
<branchPolicy>
|
||||
<pattern>(.*)</pattern>
|
||||
<transformations>
|
||||
<transformation>IGNORE</transformation>
|
||||
</transformations>
|
||||
</branchPolicy>
|
||||
</branchPolicies>
|
||||
</configuration>
|
||||
617
LICENSE
Normal file
617
LICENSE
Normal file
@@ -0,0 +1,617 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
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
|
||||
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
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
2
Procfile
2
Procfile
@@ -1,2 +0,0 @@
|
||||
web: java $JAVA_OPTS -Ddw.http.port=$PORT -Ddw.http.adminPort=$PORT -Ddw.federation.name=$FEDERATION_NAME -Ddw.federation.herokuPeers="$FEDERATED_PEERS" -Ddw.twilio.accountId=$TWILIO_ACCOUNT_SID -Ddw.twilio.accountToken=$TWILIO_ACCOUNT_TOKEN -Ddw.twilio.number=$TWILIO_NUMBER -Ddw.nexmo.apiKey=$NEXMO_KEY -Ddw.nexmo.apiSecret=$NEXMO_SECRET -Ddw.nexmo.number=$NEXMO_NUMBER -Ddw.gcm.apiKey=$GCM_KEY -Ddw.apn.certificate="$APN_CERTIFICATE" -Ddw.apn.key="$APN_KEY" -Ddw.s3.accessKey=$AWS_ACCESS_KEY -Ddw.s3.accessSecret=$AWS_SECRET_KEY -Ddw.s3.attachmentsBucket=$AWS_ATTACHMENTS_BUCKET -Ddw.memcache.servers=$MEMCACHIER_SERVERS -Ddw.memcache.user=$MEMCACHIER_USERNAME -Ddw.memcache.password=$MEMCACHIER_PASSWORD -Ddw.redis.url=$REDIS_URL -Ddw.database.driverClass=org.postgresql.Driver -Ddw.database.user=`echo $DATABASE_URL | awk -F'://' {'print $2'} | awk -F':' {'print $1'}` -Ddw.database.password=`echo $DATABASE_URL | awk -F'://' {'print $2'} | awk -F':' {'print $2'} | awk -F'@' {'print $1'}` -Ddw.database.url=jdbc:postgresql://`echo $DATABASE_URL | awk -F'@' {'print $2'}` -jar target/TextSecure-MGCM-1.0-SNAPSHOT.jar server
|
||||
dir: java $JAVA_OPTS -Ddw.http.port=$PORT -Ddw.http.adminPort=$PORT -Ddw.federation.name=$FEDERATION_NAME -Ddw.federation.herokuPeers="$FEDERATED_PEERS" -Ddw.twilio.accountId=$TWILIO_ACCOUNT_SID -Ddw.twilio.accountToken=$TWILIO_ACCOUNT_TOKEN -Ddw.twilio.number=$TWILIO_NUMBER -Ddw.nexmo.apiKey=$NEXMO_KEY -Ddw.nexmo.apiSecret=$NEXMO_SECRET -Ddw.nexmo.number=$NEXMO_NUMBER -Ddw.gcm.apiKey=$GCM_KEY -Ddw.apn.certificate="$APN_CERTIFICATE" -Ddw.apn.key="$APN_KEY" -Ddw.s3.accessKey=$AWS_ACCESS_KEY -Ddw.s3.accessSecret=$AWS_SECRET_KEY -Ddw.s3.attachmentsBucket=$AWS_ATTACHMENTS_BUCKET -Ddw.memcache.servers=$MEMCACHIER_SERVERS -Ddw.memcache.user=$MEMCACHIER_USERNAME -Ddw.memcache.password=$MEMCACHIER_PASSWORD -Ddw.redis.url=$REDIS_URL -Ddw.database.driverClass=org.postgresql.Driver -Ddw.database.user=`echo $DATABASE_URL | awk -F'://' {'print $2'} | awk -F':' {'print $1'}` -Ddw.database.password=`echo $DATABASE_URL | awk -F'://' {'print $2'} | awk -F':' {'print $2'} | awk -F'@' {'print $1'}` -Ddw.database.url=jdbc:postgresql://`echo $DATABASE_URL | awk -F'@' {'print $2'}` -jar target/TextSecure-MGCM-1.0-SNAPSHOT.jar directory
|
||||
35
README.md
35
README.md
@@ -1,39 +1,12 @@
|
||||
TextSecure-Server
|
||||
Signal-Server
|
||||
=================
|
||||
|
||||
The server that handles message routing for the
|
||||
[TextSecure](https://github.com/whispersystems/TextSecure/) data channel. Communication
|
||||
is handled by a REST API and Push messaging (both GCM and APN).
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Looking for protocol documentation? Check out the wiki!
|
||||
|
||||
https://github.com/WhisperSystems/TextSecure-Server/wiki/API-Protocol
|
||||
|
||||
|
||||
Bug tracker
|
||||
-----------
|
||||
|
||||
Have a bug? Please create an issue here on GitHub!
|
||||
|
||||
https://github.com/WhisperSystems/TextSecure-Server/issues
|
||||
|
||||
|
||||
Mailing list
|
||||
------------
|
||||
|
||||
Have a question? Ask on our mailing list!
|
||||
|
||||
whispersystems@lists.riseup.net
|
||||
|
||||
https://lists.riseup.net/www/info/whispersystems
|
||||
|
||||
Current BitHub Payment Per Commit:
|
||||
=================
|
||||

|
||||
Looking for protocol documentation? Check out the website!
|
||||
|
||||
https://signal.org/docs/
|
||||
|
||||
Cryptography Notice
|
||||
------------
|
||||
@@ -48,6 +21,6 @@ The form and manner of this distribution makes it eligible for export under the
|
||||
License
|
||||
---------------------
|
||||
|
||||
Copyright 2013 Open Whisper Systems
|
||||
Copyright 2013-2021 Signal Messenger, LLC
|
||||
|
||||
Licensed under the AGPLv3: https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
twilio:
|
||||
accountId:
|
||||
accountToken:
|
||||
number:
|
||||
localDomain: # The domain Twilio can call back to.
|
||||
international: # Boolean specifying Twilio for international delivery
|
||||
|
||||
# Optional. If specified, Nexmo will be used for non-US SMS and
|
||||
# voice verification if twilio.international is false. Otherwise,
|
||||
# Nexmo, if specified, Nexmo will only be used as a fallback
|
||||
# for failed Twilio deliveries.
|
||||
nexmo:
|
||||
apiKey:
|
||||
apiSecret:
|
||||
number:
|
||||
|
||||
gcm:
|
||||
apiKey:
|
||||
|
||||
# Optional. Only if iOS clients are supported.
|
||||
apn:
|
||||
# In PEM format.
|
||||
certificate:
|
||||
|
||||
# In PEM format.
|
||||
key:
|
||||
|
||||
s3:
|
||||
accessKey:
|
||||
accessSecret:
|
||||
|
||||
# Name of the S3 bucket (needs to have been created)
|
||||
# for attachments to go. Should be configured with
|
||||
# correct permissions.
|
||||
attachmentsBucket:
|
||||
|
||||
memcache:
|
||||
servers:
|
||||
user:
|
||||
password:
|
||||
|
||||
redis:
|
||||
url:
|
||||
|
||||
federation:
|
||||
name:
|
||||
peers:
|
||||
-
|
||||
name: somepeer
|
||||
url: https://foo.com
|
||||
authenticationToken: foo
|
||||
certificate: in pem format
|
||||
|
||||
# Optional address of graphite server to report metrics
|
||||
graphite:
|
||||
host:
|
||||
port:
|
||||
|
||||
http:
|
||||
shutdownGracePeriod: 0s
|
||||
|
||||
database:
|
||||
# the name of your JDBC driver
|
||||
driverClass: org.postgresql.Driver
|
||||
|
||||
# the username
|
||||
user:
|
||||
|
||||
# the password
|
||||
password:
|
||||
|
||||
# the JDBC URL
|
||||
url: jdbc:postgresql://somehost:somport/somedb
|
||||
|
||||
# any properties specific to your JDBC driver:
|
||||
properties:
|
||||
charSet: UTF-8
|
||||
|
||||
# the maximum amount of time to wait on an empty pool before throwing an exception
|
||||
maxWaitForConnection: 1s
|
||||
|
||||
# the SQL query to run when validating a connection's liveness
|
||||
validationQuery: "/* MyService Health Check */ SELECT 1"
|
||||
|
||||
# the minimum number of connections to keep open
|
||||
minSize: 8
|
||||
|
||||
# the maximum number of connections to keep open
|
||||
maxSize: 32
|
||||
|
||||
# whether or not idle connections should be validated
|
||||
checkConnectionWhileIdle: false
|
||||
|
||||
# how long a connection must be held before it can be validated
|
||||
checkConnectionHealthWhenIdleFor: 10s
|
||||
|
||||
# the maximum lifetime of an idle connection
|
||||
closeConnectionIfIdleFor: 1 minute
|
||||
52
gcm-sender-async/pom.xml
Normal file
52
gcm-sender-async/pom.xml
Normal file
@@ -0,0 +1,52 @@
|
||||
<?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>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.gcm.server;
|
||||
|
||||
|
||||
public class AuthenticationFailedException extends Exception {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.gcm.server;
|
||||
|
||||
|
||||
public class InvalidRequestException extends Exception {
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* 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)];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* 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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"priority" : "high",
|
||||
"collapse_key" : "collapse",
|
||||
"time_to_live" : 10,
|
||||
"delay_while_idle" : true,
|
||||
"registration_ids" : ["1"]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"data" : {
|
||||
"key1" : "value1",
|
||||
"key2" : "value2"
|
||||
},
|
||||
"registration_ids" : ["2"]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"registration_ids" : ["1"]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{ "multicast_id": 216,
|
||||
"success": 0,
|
||||
"failure": 1,
|
||||
"canonical_ids": 0,
|
||||
"results": [
|
||||
{ "error": "NotRegistered"}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{ "multicast_id": 108,
|
||||
"success": 1,
|
||||
"failure": 0,
|
||||
"canonical_ids": 0,
|
||||
"results": [
|
||||
{ "message_id": "1:08" }
|
||||
]
|
||||
}
|
||||
8
gcm-sender-async/src/test/resources/logback-test.xml
Normal file
8
gcm-sender-async/src/test/resources/logback-test.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<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>
|
||||
589
pom.xml
589
pom.xml
@@ -1,224 +1,391 @@
|
||||
<?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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<prerequisites>
|
||||
<maven>3.0.0</maven>
|
||||
</prerequisites>
|
||||
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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<groupId>org.whispersystems.textsecure</groupId>
|
||||
<artifactId>TextSecureServer</artifactId>
|
||||
<version>0.4</version>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>central</id>
|
||||
<name>Central Repository</name>
|
||||
<url>https://repo.maven.apache.org/maven2</url>
|
||||
<snapshots>
|
||||
<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>
|
||||
|
||||
<modules>
|
||||
<module>redis-dispatch</module>
|
||||
<module>websocket-resources</module>
|
||||
<module>gcm-sender-async</module>
|
||||
<module>service</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>
|
||||
<guava.version>30.1.1-jre</guava.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.23</libphonenumber.version>
|
||||
<logstash.logback.version>6.6</logstash.logback.version>
|
||||
<micrometer.version>1.5.3</micrometer.version>
|
||||
<mockito.version>2.25.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.14.2</pushy.version>
|
||||
<resilience4j.version>1.5.0</resilience4j.version>
|
||||
<semver4j.version>3.1.0</semver4j.version>
|
||||
<slf4j.version>1.7.30</slf4j.version>
|
||||
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<groupId>org.whispersystems.textsecure</groupId>
|
||||
<artifactId>TextSecureServer</artifactId>
|
||||
<version>JGITVER</version>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk16</artifactId>
|
||||
<version>140</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.yammer.dropwizard</groupId>
|
||||
<artifactId>dropwizard-core</artifactId>
|
||||
<version>0.6.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.yammer.metrics</groupId>
|
||||
<artifactId>metrics-graphite</artifactId>
|
||||
<version>2.2.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.android.gcm</groupId>
|
||||
<artifactId>gcm-server</artifactId>
|
||||
<version>1.0.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.2.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.spy</groupId>
|
||||
<artifactId>spymemcached</artifactId>
|
||||
<version>2.10.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.notnoop.apns</groupId>
|
||||
<artifactId>apns</artifactId>
|
||||
<version>0.2.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-dependencies</artifactId>
|
||||
<version>${dropwizard.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-bom</artifactId>
|
||||
<version>${netty.version}</version>
|
||||
<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>
|
||||
<version>${aws.sdk2.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.resilience4j</groupId>
|
||||
<artifactId>resilience4j-bom</artifactId>
|
||||
<version>${resilience4j.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-bom</artifactId>
|
||||
<version>${micrometer.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.amazonaws</groupId>
|
||||
<artifactId>aws-java-sdk</artifactId>
|
||||
<version>1.4.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.protobuf</groupId>
|
||||
<artifactId>protobuf-java</artifactId>
|
||||
<version>2.4.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
<version>2.2.1</version>
|
||||
<type>jar</type>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.yammer.dropwizard</groupId>
|
||||
<artifactId>dropwizard-jdbi</artifactId>
|
||||
<version>0.6.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.yammer.dropwizard</groupId>
|
||||
<artifactId>dropwizard-auth</artifactId>
|
||||
<version>0.6.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.yammer.dropwizard</groupId>
|
||||
<artifactId>dropwizard-client</artifactId>
|
||||
<version>0.6.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.yammer.dropwizard</groupId>
|
||||
<artifactId>dropwizard-migrations</artifactId>
|
||||
<version>0.6.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.yammer.dropwizard</groupId>
|
||||
<artifactId>dropwizard-testing</artifactId>
|
||||
<version>0.6.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twilio.sdk</groupId>
|
||||
<artifactId>twilio-java-sdk</artifactId>
|
||||
<version>3.4.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>9.1-901.jdbc4</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.sun.jersey</groupId>
|
||||
<artifactId>jersey-json</artifactId>
|
||||
<version>1.17.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-websocket</artifactId>
|
||||
<version>8.1.14.v20131031</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.coursera</groupId>
|
||||
<artifactId>metrics-datadog</artifactId>
|
||||
<version>0.1.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.eatthepath</groupId>
|
||||
<artifactId>pushy</artifactId>
|
||||
<version>${pushy.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.eatthepath</groupId>
|
||||
<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>
|
||||
<version>${protobuf.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.googlecode.libphonenumber</groupId>
|
||||
<artifactId>libphonenumber</artifactId>
|
||||
<version>${libphonenumber.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.vdurmont</groupId>
|
||||
<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>
|
||||
<version>${commons-io.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.lettuce</groupId>
|
||||
<artifactId>lettuce-core</artifactId>
|
||||
<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>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.xml.bind</groupId>
|
||||
<artifactId>jaxb-api</artifactId>
|
||||
<version>${jaxb.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.logstash.logback</groupId>
|
||||
<artifactId>logstash-logback-encoder</artifactId>
|
||||
<version>${logstash.logback.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-csv</artifactId>
|
||||
<version>${commons-csv.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jaxb</groupId>
|
||||
<artifactId>jaxb-runtime</artifactId>
|
||||
<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>
|
||||
<version>${slf4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-nop</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
<version>${jedis.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>2.2.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>1.6</version>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>true</createDependencyReducedPom>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>org.whispersystems.textsecuregcm.WhisperServerService</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
<version>1.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.tomakehurst</groupId>
|
||||
<artifactId>wiremock-jre8</artifactId>
|
||||
<version>2.27.2</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-core</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>javax.xml.bind</groupId>
|
||||
<artifactId>jaxb-api</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>assembly.xml</descriptor>
|
||||
</descriptors>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-assembly</id> <!-- this is used for inheritance merges -->
|
||||
<phase>package</phase> <!-- bind to the packaging phase -->
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</dependencies>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
<build>
|
||||
<extensions>
|
||||
<extension>
|
||||
<groupId>kr.motd.maven</groupId>
|
||||
<artifactId>os-maven-plugin</artifactId>
|
||||
<version>1.7.0</version>
|
||||
</extension>
|
||||
</extensions>
|
||||
<plugins>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>gcm-server-repository</id>
|
||||
<url>https://raw.github.com/whispersystems/maven/master/gcm-server/releases/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<plugin>
|
||||
<groupId>org.xolstice.maven.plugins</groupId>
|
||||
<artifactId>protobuf-maven-plugin</artifactId>
|
||||
<version>0.6.1</version>
|
||||
<configuration>
|
||||
<protocArtifact>com.google.protobuf:protoc:3.17.2:exe:${os.detected.classifier}</protocArtifact>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
<goal>test-compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>11</source>
|
||||
<target>11</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>3.1.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy</id>
|
||||
<phase>test-compile</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<includeScope>test</includeScope>
|
||||
<includeTypes>so,dll,dylib</includeTypes>
|
||||
<outputDirectory>${project.build.directory}/lib</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.0.0-M5</version>
|
||||
<configuration>
|
||||
<systemProperties>
|
||||
<property>
|
||||
<name>sqlite4java.library.path</name>
|
||||
<value>${project.build.directory}/lib</value>
|
||||
</property>
|
||||
</systemProperties>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-enforcer-plugin</artifactId>
|
||||
<version>3.0.0-M3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>enforce</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<rules>
|
||||
<dependencyConvergence/>
|
||||
<requireMavenVersion>
|
||||
<version>3.0.0</version>
|
||||
</requireMavenVersion>
|
||||
</rules>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-install-plugin</artifactId>
|
||||
<version>3.0.0-M1</version>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<version>3.0.0-M1</version>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
|
||||
all:
|
||||
protoc --java_out=../src/main/java/ OutgoingMessageSignal.proto
|
||||
@@ -1,30 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package textsecure;
|
||||
|
||||
option java_package = "org.whispersystems.textsecuregcm.entities";
|
||||
option java_outer_classname = "MessageProtos";
|
||||
|
||||
message OutgoingMessageSignal {
|
||||
optional uint32 type = 1;
|
||||
optional string source = 2;
|
||||
optional uint32 sourceDevice = 7;
|
||||
optional string relay = 3;
|
||||
// repeated string destinations = 4;
|
||||
optional uint64 timestamp = 5;
|
||||
optional bytes message = 6;
|
||||
}
|
||||
20
redis-dispatch/pom.xml
Normal file
20
redis-dispatch/pom.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.dispatch;
|
||||
|
||||
public interface DispatchChannel {
|
||||
public void onDispatchMessage(String channel, byte[] message);
|
||||
public void onDispatchSubscribed(String channel);
|
||||
public void onDispatchUnsubscribed(String channel);
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
if (previous.isPresent()) {
|
||||
dispatchUnsubscription(name, previous.get());
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
@Override
|
||||
public void run() {
|
||||
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(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
channel.onDispatchMessage(name, message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void dispatchSubscription(final String name, final DispatchChannel channel) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
channel.onDispatchSubscribed(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void dispatchUnsubscription(final String name, final DispatchChannel channel) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
channel.onDispatchUnsubscribed(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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 boas = new ByteArrayOutputStream();
|
||||
|
||||
boolean foundCr = false;
|
||||
|
||||
while (true) {
|
||||
int character = inputStream.read();
|
||||
|
||||
if (character == -1) {
|
||||
throw new IOException("Stream closed!");
|
||||
}
|
||||
|
||||
boas.write(character);
|
||||
|
||||
if (foundCr && character == LF) break;
|
||||
else if (character == CR) foundCr = true;
|
||||
else if (foundCr) foundCr = false;
|
||||
}
|
||||
|
||||
byte[] data = boas.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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* 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 {
|
||||
|
||||
public PubSubConnection connect();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
|
||||
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
|
||||
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0 http://maven.apache.org/xsd/assembly-2.1.0.xsd">
|
||||
<id>bin</id>
|
||||
<includeBaseDirectory>false</includeBaseDirectory>
|
||||
<formats>
|
||||
@@ -18,8 +18,8 @@
|
||||
<directory>${project.build.directory}</directory>
|
||||
<outputDirectory>/</outputDirectory>
|
||||
<includes>
|
||||
<include>${project.name}-${project.version}.jar</include>
|
||||
<include>${parent.artifactId}-${project.version}.jar</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
||||
</assembly>
|
||||
139
service/config/sample.yml
Normal file
139
service/config/sample.yml
Normal file
@@ -0,0 +1,139 @@
|
||||
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
|
||||
|
||||
redphone:
|
||||
authKey: # Deprecated
|
||||
|
||||
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
|
||||
|
||||
cacheCluster: # Redis server configuration for cache 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
|
||||
queueUrl: # AWS SQS queue url
|
||||
server:
|
||||
replicationUrl: # CDS replication endpoint base url
|
||||
replicationPassword: # CDS replication endpoint password
|
||||
replicationCaCertificate: # CDS replication endpoint TLS certificate trust root
|
||||
reconciliationChunkSize: # CDS reconciliation chunk size
|
||||
reconciliationChunkIntervalMs: # CDS reconciliation chunk interval, in milliseconds
|
||||
|
||||
messageCache: # Redis server configuration for message store cache
|
||||
persistDelayMinutes:
|
||||
|
||||
cluster:
|
||||
urls:
|
||||
- redis://redis.example.com:6379/
|
||||
|
||||
messageStore: # Postgresql database configuration for message store
|
||||
driverClass: org.postgresql.Driver
|
||||
user:
|
||||
password:
|
||||
url:
|
||||
|
||||
metricsCluster:
|
||||
urls:
|
||||
- redis://redis.example.com:6379/
|
||||
|
||||
awsAttachments: # AWS S3 configuration
|
||||
accessKey:
|
||||
accessSecret:
|
||||
bucket:
|
||||
region:
|
||||
|
||||
gcpAttachments: # GCP Storage configuration
|
||||
domain:
|
||||
email:
|
||||
maxSizeInBytes:
|
||||
pathPrefix:
|
||||
rsaSigningKey:
|
||||
|
||||
profiles: # AWS S3 configuration
|
||||
accessKey:
|
||||
accessSecret:
|
||||
bucket:
|
||||
region:
|
||||
|
||||
database: # Postgresql database configuration
|
||||
driverClass: org.postgresql.Driver
|
||||
user:
|
||||
password:
|
||||
url:
|
||||
|
||||
apn: # Apple Push Notifications configuration
|
||||
sandbox: true
|
||||
bundleId:
|
||||
keyId:
|
||||
teamId:
|
||||
signingKey:
|
||||
|
||||
gcm: # GCM Configuration
|
||||
senderId:
|
||||
apiKey:
|
||||
|
||||
micrometer: # Micrometer metrics config
|
||||
- name: "example"
|
||||
- uri: "https://metrics.example.com/"
|
||||
- apiKey:
|
||||
- accountId:
|
||||
|
||||
remoteConfig:
|
||||
authorizedTokens:
|
||||
- # 1st authorized token
|
||||
- # 2nd authorized token
|
||||
- # ...
|
||||
- # Nth authorized token
|
||||
globalConfig: # keys and values that are given to clients on GET /v1/config
|
||||
|
||||
paymentService:
|
||||
userAuthenticationTokenSharedSecret: # hex-encoded 32-byte secret shared with MobileCoin services used to generate auth tokens for Signal users
|
||||
|
||||
donation:
|
||||
uri: # value
|
||||
apiKey: # 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
|
||||
540
service/pom.xml
Normal file
540
service/pom.xml
Normal file
@@ -0,0 +1,540 @@
|
||||
<?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>service</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.validation</groupId>
|
||||
<artifactId>jakarta.validation-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.ws.rs</groupId>
|
||||
<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.7.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.whispersystems</groupId>
|
||||
<artifactId>curve25519-java</artifactId>
|
||||
<version>0.5.0</version>
|
||||
</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>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-db</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-logging</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-metrics</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-util</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-servlets</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-lifecycle</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-jersey</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-jetty</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-migrations</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-access</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.logstash.logback</groupId>
|
||||
<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>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard.metrics</groupId>
|
||||
<artifactId>metrics-annotation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.core</groupId>
|
||||
<artifactId>jersey-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.core</groupId>
|
||||
<artifactId>jersey-server</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.core</groupId>
|
||||
<artifactId>jersey-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jaxb</groupId>
|
||||
<artifactId>jaxb-runtime</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-testing</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlets</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-csv</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.code.findbugs</groupId>
|
||||
<artifactId>jsr305</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.github.resilience4j</groupId>
|
||||
<artifactId>resilience4j-circuitbreaker</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.resilience4j</groupId>
|
||||
<artifactId>resilience4j-retry</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-registry-wavefront</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-registry-datadog</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.fasterxml.jackson.dataformat</groupId>
|
||||
<artifactId>jackson-dataformat-yaml</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.jaxrs</groupId>
|
||||
<artifactId>jackson-jaxrs-json-provider</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>s3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>dynamodb</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>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.amazonaws</groupId>
|
||||
<artifactId>aws-java-sdk-sqs</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.amazonaws</groupId>
|
||||
<artifactId>aws-java-sdk-appconfig</artifactId>
|
||||
</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>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.eatthepath</groupId>
|
||||
<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>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.protobuf</groupId>
|
||||
<artifactId>protobuf-java</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.googlecode.libphonenumber</groupId>
|
||||
<artifactId>libphonenumber</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.sourceforge.argparse4j</groupId>
|
||||
<artifactId>argparse4j</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.test-framework</groupId>
|
||||
<artifactId>jersey-test-framework-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
|
||||
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<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>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
<version>3.3.16.RELEASE</version>
|
||||
</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>
|
||||
</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>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.amazonaws</groupId>
|
||||
<artifactId>DynamoDBLocal</artifactId>
|
||||
<version>1.13.6</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.antlr</groupId>
|
||||
<artifactId>antlr4-runtime</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>pl.pragmatists</groupId>
|
||||
<artifactId>JUnitParams</artifactId>
|
||||
<version>1.1.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
<build>
|
||||
<finalName>${project.parent.artifactId}-${project.version}</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.4</version>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>true</createDependencyReducedPom>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>org.whispersystems.textsecuregcm.WhisperServerService</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>assembly.xml</descriptor>
|
||||
</descriptors>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-assembly</id> <!-- this is used for inheritance merges -->
|
||||
<phase>package</phase> <!-- bind to the packaging phase -->
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>properties-maven-plugin</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>read-deploy-configuration</id>
|
||||
<phase>deploy</phase>
|
||||
<goals>
|
||||
<goal>read-project-properties</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<files>${project.basedir}/config/deploy.properties</files>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>com.bazaarvoice.maven.plugins</groupId>
|
||||
<artifactId>s3-upload-maven-plugin</artifactId>
|
||||
<version>1.5</version>
|
||||
<configuration>
|
||||
<source>${project.build.directory}/${project.build.finalName}-bin.tar.gz</source>
|
||||
<bucketName>${deploy.bucketName}</bucketName>
|
||||
<destination>${project.build.finalName}-bin.tar.gz</destination>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>deploy-to-s3</id>
|
||||
<phase>deploy</phase>
|
||||
<goals>
|
||||
<goal>s3-upload</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>templating-maven-plugin</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>filter-src</id>
|
||||
<goals>
|
||||
<goal>filter-sources</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright 2013-2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm;
|
||||
|
||||
public class WhisperServerVersion {
|
||||
|
||||
private static final String VERSION = "${project.version}";
|
||||
|
||||
public static String getServerVersion() {
|
||||
return VERSION;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,479 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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 java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import org.whispersystems.textsecuregcm.configuration.AccountDatabaseCrawlerConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.AccountsDatabaseConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.AccountsDynamoDbConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.ApnConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.AppConfigConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.AwsAttachmentsConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.CdnConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.DatabaseConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.DatadogConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.DirectoryConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.DonationConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.DynamoDbConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.GcmConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.GcpAttachmentsConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.MaxDeviceConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.MessageCacheConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.MessageDynamoDbConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.WavefrontConfiguration;
|
||||
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.RedisClusterConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.RedisConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.RemoteConfigConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.SecureBackupServiceConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.TestDeviceConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.MonitoredS3ObjectConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.TurnConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.TwilioConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.UnidentifiedDeliveryConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.VoiceVerificationConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.ZkConfig;
|
||||
import org.whispersystems.websocket.configuration.WebSocketConfiguration;
|
||||
|
||||
/** @noinspection MismatchedQueryAndUpdateOfCollection, WeakerAccess */
|
||||
public class WhisperServerConfiguration extends Configuration {
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private TwilioConfiguration twilio;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private PushConfiguration push;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private AwsAttachmentsConfiguration awsAttachments;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private GcpAttachmentsConfiguration gcpAttachments;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private CdnConfiguration cdn;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private WavefrontConfiguration wavefront;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private DatadogConfiguration datadog;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private RedisClusterConfiguration cacheCluster;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private RedisConfiguration pubsub;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private RedisClusterConfiguration metricsCluster;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private DirectoryConfiguration directory;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private AccountDatabaseCrawlerConfiguration accountDatabaseCrawler;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private RedisClusterConfiguration pushSchedulerCluster;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private RedisClusterConfiguration rateLimitersCluster;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private MessageCacheConfiguration messageCache;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private RedisClusterConfiguration clientPresenceCluster;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private MessageDynamoDbConfiguration messageDynamoDb;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private DynamoDbConfiguration keysDynamoDb;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private AccountsDynamoDbConfiguration accountsDynamoDb;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private DynamoDbConfiguration migrationDeletedAccountsDynamoDb;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private DynamoDbConfiguration migrationRetryAccountsDynamoDb;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private DynamoDbConfiguration pushChallengeDynamoDb;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private DynamoDbConfiguration reportMessageDynamoDb;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private DatabaseConfiguration abuseDatabase;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private List<TestDeviceConfiguration> testDevices = new LinkedList<>();
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private List<MaxDeviceConfiguration> maxDevices = new LinkedList<>();
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private AccountsDatabaseConfiguration accountsDatabase;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private RateLimitsConfiguration limits = new RateLimitsConfiguration();
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private JerseyClientConfiguration httpClient = new JerseyClientConfiguration();
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private WebSocketConfiguration webSocket = new WebSocketConfiguration();
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private TurnConfiguration turn;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private GcmConfiguration gcm;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private ApnConfiguration apn;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private UnidentifiedDeliveryConfiguration unidentifiedDelivery;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private VoiceVerificationConfiguration voiceVerification;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private RecaptchaConfiguration recaptcha;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private SecureStorageServiceConfiguration storageService;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private SecureBackupServiceConfiguration backupService;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private PaymentsServiceConfiguration paymentsService;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private ZkConfig zkConfig;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private RemoteConfigConfiguration remoteConfig;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private AppConfigConfiguration appConfig;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private MonitoredS3ObjectConfiguration torExitNodeList;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private MonitoredS3ObjectConfiguration asnTable;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private DonationConfiguration donation;
|
||||
|
||||
private Map<String, String> transparentDataIndex = new HashMap<>();
|
||||
|
||||
public RecaptchaConfiguration getRecaptchaConfiguration() {
|
||||
return recaptcha;
|
||||
}
|
||||
|
||||
public VoiceVerificationConfiguration getVoiceVerificationConfiguration() {
|
||||
return voiceVerification;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public GcpAttachmentsConfiguration getGcpAttachmentsConfiguration() {
|
||||
return gcpAttachments;
|
||||
}
|
||||
|
||||
public RedisClusterConfiguration getCacheClusterConfiguration() {
|
||||
return cacheCluster;
|
||||
}
|
||||
|
||||
public RedisConfiguration getPubsubCacheConfiguration() {
|
||||
return pubsub;
|
||||
}
|
||||
|
||||
public RedisClusterConfiguration getMetricsClusterConfiguration() {
|
||||
return metricsCluster;
|
||||
}
|
||||
|
||||
public DirectoryConfiguration getDirectoryConfiguration() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
public SecureStorageServiceConfiguration getSecureStorageServiceConfiguration() {
|
||||
return storageService;
|
||||
}
|
||||
|
||||
public AccountDatabaseCrawlerConfiguration getAccountDatabaseCrawlerConfiguration() {
|
||||
return accountDatabaseCrawler;
|
||||
}
|
||||
|
||||
public MessageCacheConfiguration getMessageCacheConfiguration() {
|
||||
return messageCache;
|
||||
}
|
||||
|
||||
public RedisClusterConfiguration getClientPresenceClusterConfiguration() {
|
||||
return clientPresenceCluster;
|
||||
}
|
||||
|
||||
public RedisClusterConfiguration getPushSchedulerCluster() {
|
||||
return pushSchedulerCluster;
|
||||
}
|
||||
|
||||
public RedisClusterConfiguration getRateLimitersCluster() {
|
||||
return rateLimitersCluster;
|
||||
}
|
||||
|
||||
public MessageDynamoDbConfiguration getMessageDynamoDbConfiguration() {
|
||||
return messageDynamoDb;
|
||||
}
|
||||
|
||||
public DynamoDbConfiguration getKeysDynamoDbConfiguration() {
|
||||
return keysDynamoDb;
|
||||
}
|
||||
|
||||
public AccountsDynamoDbConfiguration getAccountsDynamoDbConfiguration() {
|
||||
return accountsDynamoDb;
|
||||
}
|
||||
|
||||
public DynamoDbConfiguration getMigrationDeletedAccountsDynamoDbConfiguration() {
|
||||
return migrationDeletedAccountsDynamoDb;
|
||||
}
|
||||
|
||||
public DynamoDbConfiguration getMigrationRetryAccountsDynamoDbConfiguration() {
|
||||
return migrationRetryAccountsDynamoDb;
|
||||
}
|
||||
|
||||
public DatabaseConfiguration getAbuseDatabaseConfiguration() {
|
||||
return abuseDatabase;
|
||||
}
|
||||
|
||||
public AccountsDatabaseConfiguration getAccountsDatabaseConfiguration() {
|
||||
return accountsDatabase;
|
||||
}
|
||||
|
||||
public RateLimitsConfiguration getLimitsConfiguration() {
|
||||
return limits;
|
||||
}
|
||||
|
||||
public TurnConfiguration getTurnConfiguration() {
|
||||
return turn;
|
||||
}
|
||||
|
||||
public GcmConfiguration getGcmConfiguration() {
|
||||
return gcm;
|
||||
}
|
||||
|
||||
public ApnConfiguration getApnConfiguration() {
|
||||
return apn;
|
||||
}
|
||||
|
||||
public CdnConfiguration getCdnConfiguration() {
|
||||
return cdn;
|
||||
}
|
||||
|
||||
public WavefrontConfiguration getWavefrontConfiguration() {
|
||||
return wavefront;
|
||||
}
|
||||
|
||||
public DatadogConfiguration getDatadogConfiguration() {
|
||||
return datadog;
|
||||
}
|
||||
|
||||
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 Map<String, Integer> getMaxDevices() {
|
||||
Map<String, Integer> results = new HashMap<>();
|
||||
|
||||
for (MaxDeviceConfiguration maxDeviceConfiguration : maxDevices) {
|
||||
results.put(maxDeviceConfiguration.getNumber(),
|
||||
maxDeviceConfiguration.getCount());
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public Map<String, String> getTransparentDataIndex() {
|
||||
return transparentDataIndex;
|
||||
}
|
||||
|
||||
public SecureBackupServiceConfiguration getSecureBackupServiceConfiguration() {
|
||||
return backupService;
|
||||
}
|
||||
|
||||
public PaymentsServiceConfiguration getPaymentsServiceConfiguration() {
|
||||
return paymentsService;
|
||||
}
|
||||
|
||||
public ZkConfig getZkConfig() {
|
||||
return zkConfig;
|
||||
}
|
||||
|
||||
public RemoteConfigConfiguration getRemoteConfigConfiguration() {
|
||||
return remoteConfig;
|
||||
}
|
||||
|
||||
public AppConfigConfiguration getAppConfig() {
|
||||
return appConfig;
|
||||
}
|
||||
|
||||
public DynamoDbConfiguration getPushChallengeDynamoDbConfiguration() {
|
||||
return pushChallengeDynamoDb;
|
||||
}
|
||||
|
||||
public DynamoDbConfiguration getReportMessageDynamoDbConfiguration() {
|
||||
return reportMessageDynamoDb;
|
||||
}
|
||||
|
||||
public MonitoredS3ObjectConfiguration getTorExitNodeListConfiguration() {
|
||||
return torExitNodeList;
|
||||
}
|
||||
|
||||
public MonitoredS3ObjectConfiguration getAsnTableConfiguration() {
|
||||
return asnTable;
|
||||
}
|
||||
|
||||
public DonationConfiguration getDonationConfiguration() {
|
||||
return donation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,652 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm;
|
||||
|
||||
import static com.codahale.metrics.MetricRegistry.name;
|
||||
|
||||
import com.amazonaws.ClientConfiguration;
|
||||
import com.amazonaws.auth.AWSCredentials;
|
||||
import com.amazonaws.auth.AWSCredentialsProvider;
|
||||
import com.amazonaws.auth.AWSStaticCredentialsProvider;
|
||||
import com.amazonaws.auth.BasicAWSCredentials;
|
||||
import com.amazonaws.auth.InstanceProfileCredentialsProvider;
|
||||
import com.amazonaws.services.s3.AmazonS3;
|
||||
import com.amazonaws.services.s3.AmazonS3Client;
|
||||
import com.codahale.metrics.SharedMetricRegistries;
|
||||
import com.codahale.metrics.jdbi3.strategies.DefaultNameStrategy;
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import io.dropwizard.Application;
|
||||
import io.dropwizard.auth.AuthFilter;
|
||||
import io.dropwizard.auth.PolymorphicAuthDynamicFeature;
|
||||
import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider;
|
||||
import io.dropwizard.auth.basic.BasicCredentialAuthFilter;
|
||||
import io.dropwizard.auth.basic.BasicCredentials;
|
||||
import io.dropwizard.db.DataSourceFactory;
|
||||
import io.dropwizard.db.PooledDataSourceFactory;
|
||||
import io.dropwizard.jdbi3.JdbiFactory;
|
||||
import io.dropwizard.setup.Bootstrap;
|
||||
import io.dropwizard.setup.Environment;
|
||||
import io.lettuce.core.resource.ClientResources;
|
||||
import io.micrometer.core.instrument.Clock;
|
||||
import io.micrometer.core.instrument.Meter;
|
||||
import io.micrometer.core.instrument.Meter.Id;
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import io.micrometer.core.instrument.config.MeterFilter;
|
||||
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
|
||||
import io.micrometer.datadog.DatadogConfig;
|
||||
import io.micrometer.datadog.DatadogMeterRegistry;
|
||||
import io.micrometer.wavefront.WavefrontConfig;
|
||||
import io.micrometer.wavefront.WavefrontMeterRegistry;
|
||||
import java.net.http.HttpClient;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.FilterRegistration;
|
||||
import javax.servlet.ServletRegistration;
|
||||
import org.eclipse.jetty.servlets.CrossOriginFilter;
|
||||
import org.jdbi.v3.core.Jdbi;
|
||||
import org.signal.zkgroup.ServerSecretParams;
|
||||
import org.signal.zkgroup.auth.ServerZkAuthOperations;
|
||||
import org.signal.zkgroup.profiles.ServerZkProfileOperations;
|
||||
import org.whispersystems.dispatch.DispatchManager;
|
||||
import org.whispersystems.textsecuregcm.auth.AccountAuthenticator;
|
||||
import org.whispersystems.textsecuregcm.auth.CertificateGenerator;
|
||||
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccount;
|
||||
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccountAuthenticator;
|
||||
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
|
||||
import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator;
|
||||
import org.whispersystems.textsecuregcm.configuration.DirectoryServerConfiguration;
|
||||
import org.whispersystems.textsecuregcm.controllers.AccountController;
|
||||
import org.whispersystems.textsecuregcm.controllers.AttachmentControllerV1;
|
||||
import org.whispersystems.textsecuregcm.controllers.AttachmentControllerV2;
|
||||
import org.whispersystems.textsecuregcm.controllers.AttachmentControllerV3;
|
||||
import org.whispersystems.textsecuregcm.controllers.CertificateController;
|
||||
import org.whispersystems.textsecuregcm.controllers.ChallengeController;
|
||||
import org.whispersystems.textsecuregcm.controllers.DeviceController;
|
||||
import org.whispersystems.textsecuregcm.controllers.DirectoryController;
|
||||
import org.whispersystems.textsecuregcm.controllers.DonationController;
|
||||
import org.whispersystems.textsecuregcm.controllers.KeepAliveController;
|
||||
import org.whispersystems.textsecuregcm.controllers.KeysController;
|
||||
import org.whispersystems.textsecuregcm.controllers.MessageController;
|
||||
import org.whispersystems.textsecuregcm.controllers.PaymentsController;
|
||||
import org.whispersystems.textsecuregcm.controllers.ProfileController;
|
||||
import org.whispersystems.textsecuregcm.controllers.ProvisioningController;
|
||||
import org.whispersystems.textsecuregcm.controllers.RemoteConfigController;
|
||||
import org.whispersystems.textsecuregcm.controllers.SecureBackupController;
|
||||
import org.whispersystems.textsecuregcm.controllers.SecureStorageController;
|
||||
import org.whispersystems.textsecuregcm.controllers.StickerController;
|
||||
import org.whispersystems.textsecuregcm.controllers.VoiceVerificationController;
|
||||
import org.whispersystems.textsecuregcm.currency.CurrencyConversionManager;
|
||||
import org.whispersystems.textsecuregcm.currency.FixerClient;
|
||||
import org.whispersystems.textsecuregcm.currency.FtxClient;
|
||||
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
|
||||
import org.whispersystems.textsecuregcm.filters.RemoteDeprecationFilter;
|
||||
import org.whispersystems.textsecuregcm.filters.TimestampResponseFilter;
|
||||
import org.whispersystems.textsecuregcm.limits.PreKeyRateLimiter;
|
||||
import org.whispersystems.textsecuregcm.limits.PushChallengeManager;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimitChallengeManager;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimitResetMetricsManager;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.limits.UnsealedSenderRateLimiter;
|
||||
import org.whispersystems.textsecuregcm.liquibase.NameableMigrationsBundle;
|
||||
import org.whispersystems.textsecuregcm.mappers.DeviceLimitExceededExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.mappers.IOExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.mappers.InvalidWebsocketAddressExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.mappers.RateLimitChallengeExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.mappers.RetryLaterExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.metrics.BufferPoolGauges;
|
||||
import org.whispersystems.textsecuregcm.metrics.CpuUsageGauge;
|
||||
import org.whispersystems.textsecuregcm.metrics.FileDescriptorGauge;
|
||||
import org.whispersystems.textsecuregcm.metrics.FreeMemoryGauge;
|
||||
import org.whispersystems.textsecuregcm.metrics.GarbageCollectionGauges;
|
||||
import org.whispersystems.textsecuregcm.metrics.MaxFileDescriptorGauge;
|
||||
import org.whispersystems.textsecuregcm.metrics.MetricsApplicationEventListener;
|
||||
import org.whispersystems.textsecuregcm.metrics.MetricsRequestEventListener;
|
||||
import org.whispersystems.textsecuregcm.metrics.NetworkReceivedGauge;
|
||||
import org.whispersystems.textsecuregcm.metrics.NetworkSentGauge;
|
||||
import org.whispersystems.textsecuregcm.metrics.NstatCounters;
|
||||
import org.whispersystems.textsecuregcm.metrics.OperatingSystemMemoryGauge;
|
||||
import org.whispersystems.textsecuregcm.metrics.PushLatencyManager;
|
||||
import org.whispersystems.textsecuregcm.metrics.TrafficSource;
|
||||
import org.whispersystems.textsecuregcm.providers.MultiRecipientMessageProvider;
|
||||
import org.whispersystems.textsecuregcm.providers.RedisClientFactory;
|
||||
import org.whispersystems.textsecuregcm.providers.RedisClusterHealthCheck;
|
||||
import org.whispersystems.textsecuregcm.push.APNSender;
|
||||
import org.whispersystems.textsecuregcm.push.ApnFallbackManager;
|
||||
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
|
||||
import org.whispersystems.textsecuregcm.push.GCMSender;
|
||||
import org.whispersystems.textsecuregcm.push.MessageSender;
|
||||
import org.whispersystems.textsecuregcm.push.ProvisioningManager;
|
||||
import org.whispersystems.textsecuregcm.push.ReceiptSender;
|
||||
import org.whispersystems.textsecuregcm.recaptcha.RecaptchaClient;
|
||||
import org.whispersystems.textsecuregcm.redis.ConnectionEventLogger;
|
||||
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
|
||||
import org.whispersystems.textsecuregcm.redis.ReplicatedJedisPool;
|
||||
import org.whispersystems.textsecuregcm.s3.PolicySigner;
|
||||
import org.whispersystems.textsecuregcm.s3.PostPolicyGenerator;
|
||||
import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient;
|
||||
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
|
||||
import org.whispersystems.textsecuregcm.sms.SmsSender;
|
||||
import org.whispersystems.textsecuregcm.sms.TwilioSmsSender;
|
||||
import org.whispersystems.textsecuregcm.sms.TwilioVerifyExperimentEnrollmentManager;
|
||||
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
|
||||
import org.whispersystems.textsecuregcm.storage.AbusiveHostRules;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountCleaner;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawler;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawlerCache;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawlerListener;
|
||||
import org.whispersystems.textsecuregcm.storage.Accounts;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsDynamoDb;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsDynamoDbMigrator;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.ActiveUserCounter;
|
||||
import org.whispersystems.textsecuregcm.storage.DirectoryReconciler;
|
||||
import org.whispersystems.textsecuregcm.storage.DirectoryReconciliationClient;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||
import org.whispersystems.textsecuregcm.storage.FaultTolerantDatabase;
|
||||
import org.whispersystems.textsecuregcm.storage.KeysDynamoDb;
|
||||
import org.whispersystems.textsecuregcm.storage.MessagePersister;
|
||||
import org.whispersystems.textsecuregcm.storage.MessagesCache;
|
||||
import org.whispersystems.textsecuregcm.storage.MessagesDynamoDb;
|
||||
import org.whispersystems.textsecuregcm.storage.MessagesManager;
|
||||
import org.whispersystems.textsecuregcm.storage.MigrationDeletedAccounts;
|
||||
import org.whispersystems.textsecuregcm.storage.MigrationRetryAccounts;
|
||||
import org.whispersystems.textsecuregcm.storage.PendingAccounts;
|
||||
import org.whispersystems.textsecuregcm.storage.PendingAccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.PendingDevices;
|
||||
import org.whispersystems.textsecuregcm.storage.PendingDevicesManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Profiles;
|
||||
import org.whispersystems.textsecuregcm.storage.ProfilesManager;
|
||||
import org.whispersystems.textsecuregcm.storage.PubSubManager;
|
||||
import org.whispersystems.textsecuregcm.storage.PushChallengeDynamoDb;
|
||||
import org.whispersystems.textsecuregcm.storage.PushFeedbackProcessor;
|
||||
import org.whispersystems.textsecuregcm.storage.RegistrationLockVersionCounter;
|
||||
import org.whispersystems.textsecuregcm.storage.RemoteConfigs;
|
||||
import org.whispersystems.textsecuregcm.storage.RemoteConfigsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.ReportMessageDynamoDb;
|
||||
import org.whispersystems.textsecuregcm.storage.ReportMessageManager;
|
||||
import org.whispersystems.textsecuregcm.storage.ReservedUsernames;
|
||||
import org.whispersystems.textsecuregcm.storage.Usernames;
|
||||
import org.whispersystems.textsecuregcm.storage.UsernamesManager;
|
||||
import org.whispersystems.textsecuregcm.util.AsnManager;
|
||||
import org.whispersystems.textsecuregcm.util.Constants;
|
||||
import org.whispersystems.textsecuregcm.util.DynamoDbFromConfig;
|
||||
import org.whispersystems.textsecuregcm.util.TorExitNodeManager;
|
||||
import org.whispersystems.textsecuregcm.websocket.AuthenticatedConnectListener;
|
||||
import org.whispersystems.textsecuregcm.websocket.DeadLetterHandler;
|
||||
import org.whispersystems.textsecuregcm.websocket.ProvisioningConnectListener;
|
||||
import org.whispersystems.textsecuregcm.websocket.WebSocketAccountAuthenticator;
|
||||
import org.whispersystems.textsecuregcm.workers.CertificateCommand;
|
||||
import org.whispersystems.textsecuregcm.workers.DeleteUserCommand;
|
||||
import org.whispersystems.textsecuregcm.workers.GetRedisCommandStatsCommand;
|
||||
import org.whispersystems.textsecuregcm.workers.GetRedisSlowlogCommand;
|
||||
import org.whispersystems.textsecuregcm.workers.ServerVersionCommand;
|
||||
import org.whispersystems.textsecuregcm.workers.SetCrawlerAccelerationTask;
|
||||
import org.whispersystems.textsecuregcm.workers.SetRequestLoggingEnabledTask;
|
||||
import org.whispersystems.textsecuregcm.workers.VacuumCommand;
|
||||
import org.whispersystems.textsecuregcm.workers.ZkParamsCommand;
|
||||
import org.whispersystems.websocket.WebSocketResourceProviderFactory;
|
||||
import org.whispersystems.websocket.setup.WebSocketEnvironment;
|
||||
import software.amazon.awssdk.core.client.config.ClientAsyncConfiguration;
|
||||
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
|
||||
import software.amazon.awssdk.core.client.config.SdkAdvancedAsyncClientOption;
|
||||
import software.amazon.awssdk.http.SdkHttpClient;
|
||||
import software.amazon.awssdk.regions.Region;
|
||||
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
|
||||
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
|
||||
import software.amazon.awssdk.services.dynamodb.DynamoDbClientBuilder;
|
||||
|
||||
public class WhisperServerService extends Application<WhisperServerConfiguration> {
|
||||
|
||||
@Override
|
||||
public void initialize(Bootstrap<WhisperServerConfiguration> bootstrap) {
|
||||
bootstrap.addCommand(new VacuumCommand());
|
||||
bootstrap.addCommand(new DeleteUserCommand());
|
||||
bootstrap.addCommand(new CertificateCommand());
|
||||
bootstrap.addCommand(new ZkParamsCommand());
|
||||
bootstrap.addCommand(new GetRedisSlowlogCommand());
|
||||
bootstrap.addCommand(new GetRedisCommandStatsCommand());
|
||||
bootstrap.addCommand(new ServerVersionCommand());
|
||||
|
||||
bootstrap.addBundle(new NameableMigrationsBundle<WhisperServerConfiguration>("accountdb", "accountsdb.xml") {
|
||||
@Override
|
||||
public DataSourceFactory getDataSourceFactory(WhisperServerConfiguration configuration) {
|
||||
return configuration.getAccountsDatabaseConfiguration();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
bootstrap.addBundle(new NameableMigrationsBundle<WhisperServerConfiguration>("abusedb", "abusedb.xml") {
|
||||
@Override
|
||||
public PooledDataSourceFactory getDataSourceFactory(WhisperServerConfiguration configuration) {
|
||||
return configuration.getAbuseDatabaseConfiguration();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "whisper-server";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(WhisperServerConfiguration config, Environment environment)
|
||||
throws Exception {
|
||||
|
||||
SharedMetricRegistries.add(Constants.METRICS_NAME, environment.metrics());
|
||||
|
||||
final DistributionStatisticConfig defaultDistributionStatisticConfig = DistributionStatisticConfig.builder()
|
||||
.percentiles(.75, .95, .99, .999)
|
||||
.build();
|
||||
|
||||
final WavefrontConfig wavefrontConfig = new WavefrontConfig() {
|
||||
@Override
|
||||
public String get(final String key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String uri() {
|
||||
return config.getWavefrontConfiguration().getUri();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int batchSize() {
|
||||
return config.getWavefrontConfiguration().getBatchSize();
|
||||
}
|
||||
};
|
||||
|
||||
Metrics.addRegistry(new WavefrontMeterRegistry(wavefrontConfig, Clock.SYSTEM) {
|
||||
@Override
|
||||
protected DistributionStatisticConfig defaultHistogramConfig() {
|
||||
return defaultDistributionStatisticConfig.merge(super.defaultHistogramConfig());
|
||||
}
|
||||
});
|
||||
|
||||
{
|
||||
final DatadogMeterRegistry datadogMeterRegistry = new DatadogMeterRegistry(new DatadogConfig() {
|
||||
@Override
|
||||
public String get(final String key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apiKey() {
|
||||
return config.getDatadogConfiguration().getApiKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration step() {
|
||||
return config.getDatadogConfiguration().getStep();
|
||||
}
|
||||
}, Clock.SYSTEM);
|
||||
|
||||
datadogMeterRegistry.config().meterFilter(new MeterFilter() {
|
||||
@Override
|
||||
public DistributionStatisticConfig configure(final Id id, final DistributionStatisticConfig config) {
|
||||
return defaultDistributionStatisticConfig.merge(config);
|
||||
}
|
||||
})
|
||||
.meterFilter(MeterFilter.denyNameStartsWith(MetricsRequestEventListener.REQUEST_COUNTER_NAME))
|
||||
.meterFilter(MeterFilter.denyNameStartsWith(MetricsRequestEventListener.ANDROID_REQUEST_COUNTER_NAME))
|
||||
.meterFilter(MeterFilter.denyNameStartsWith(MetricsRequestEventListener.DESKTOP_REQUEST_COUNTER_NAME))
|
||||
.meterFilter(MeterFilter.denyNameStartsWith(MetricsRequestEventListener.IOS_REQUEST_COUNTER_NAME));
|
||||
|
||||
Metrics.addRegistry(datadogMeterRegistry);
|
||||
}
|
||||
|
||||
environment.getObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
environment.getObjectMapper().setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
|
||||
environment.getObjectMapper().setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
|
||||
|
||||
JdbiFactory jdbiFactory = new JdbiFactory(DefaultNameStrategy.CHECK_EMPTY);
|
||||
Jdbi accountJdbi = jdbiFactory.build(environment, config.getAccountsDatabaseConfiguration(), "accountdb");
|
||||
Jdbi abuseJdbi = jdbiFactory.build(environment, config.getAbuseDatabaseConfiguration(), "abusedb" );
|
||||
|
||||
FaultTolerantDatabase accountDatabase = new FaultTolerantDatabase("accounts_database", accountJdbi, config.getAccountsDatabaseConfiguration().getCircuitBreakerConfiguration());
|
||||
FaultTolerantDatabase abuseDatabase = new FaultTolerantDatabase("abuse_database", abuseJdbi, config.getAbuseDatabaseConfiguration().getCircuitBreakerConfiguration());
|
||||
|
||||
DynamoDbClient messageDynamoDb = DynamoDbFromConfig.client(config.getMessageDynamoDbConfiguration(),
|
||||
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create());
|
||||
|
||||
DynamoDbClient preKeyDynamoDb = DynamoDbFromConfig.client(config.getKeysDynamoDbConfiguration(),
|
||||
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create());
|
||||
|
||||
DynamoDbClient accountsDynamoDbClient = DynamoDbFromConfig.client(config.getAccountsDynamoDbConfiguration(),
|
||||
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create());
|
||||
|
||||
// The thread pool core & max sizes are set via dynamic configuration within AccountsDynamoDb
|
||||
ThreadPoolExecutor accountsDynamoDbMigrationThreadPool = new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS,
|
||||
new LinkedBlockingDeque<>());
|
||||
|
||||
DynamoDbAsyncClient accountsDynamoDbAsyncClient = DynamoDbFromConfig.asyncClient(config.getAccountsDynamoDbConfiguration(),
|
||||
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create(),
|
||||
accountsDynamoDbMigrationThreadPool);
|
||||
|
||||
DynamoDbClient recentlyDeletedAccountsDynamoDb = DynamoDbFromConfig.client(config.getMigrationDeletedAccountsDynamoDbConfiguration(),
|
||||
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create());
|
||||
|
||||
DynamoDbClient pushChallengeDynamoDbClient = DynamoDbFromConfig.client(config.getPushChallengeDynamoDbConfiguration(),
|
||||
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create());
|
||||
|
||||
DynamoDbClient reportMessageDynamoDbClient = DynamoDbFromConfig.client(config.getReportMessageDynamoDbConfiguration(),
|
||||
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create());
|
||||
|
||||
DynamoDbClient migrationRetryAccountsDynamoDb = DynamoDbFromConfig.client(config.getMigrationRetryAccountsDynamoDbConfiguration(),
|
||||
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create());
|
||||
|
||||
MigrationDeletedAccounts migrationDeletedAccounts = new MigrationDeletedAccounts(recentlyDeletedAccountsDynamoDb, config.getMigrationDeletedAccountsDynamoDbConfiguration().getTableName());
|
||||
MigrationRetryAccounts migrationRetryAccounts = new MigrationRetryAccounts(migrationRetryAccountsDynamoDb, config.getMigrationRetryAccountsDynamoDbConfiguration().getTableName());
|
||||
|
||||
Accounts accounts = new Accounts(accountDatabase);
|
||||
AccountsDynamoDb accountsDynamoDb = new AccountsDynamoDb(accountsDynamoDbClient, accountsDynamoDbAsyncClient, accountsDynamoDbMigrationThreadPool, config.getAccountsDynamoDbConfiguration().getTableName(), config.getAccountsDynamoDbConfiguration().getPhoneNumberTableName(), migrationDeletedAccounts, migrationRetryAccounts);
|
||||
PendingAccounts pendingAccounts = new PendingAccounts(accountDatabase);
|
||||
PendingDevices pendingDevices = new PendingDevices (accountDatabase);
|
||||
Usernames usernames = new Usernames(accountDatabase);
|
||||
ReservedUsernames reservedUsernames = new ReservedUsernames(accountDatabase);
|
||||
Profiles profiles = new Profiles(accountDatabase);
|
||||
KeysDynamoDb keysDynamoDb = new KeysDynamoDb(preKeyDynamoDb, config.getKeysDynamoDbConfiguration().getTableName());
|
||||
MessagesDynamoDb messagesDynamoDb = new MessagesDynamoDb(messageDynamoDb, config.getMessageDynamoDbConfiguration().getTableName(), config.getMessageDynamoDbConfiguration().getTimeToLive());
|
||||
AbusiveHostRules abusiveHostRules = new AbusiveHostRules(abuseDatabase);
|
||||
RemoteConfigs remoteConfigs = new RemoteConfigs(accountDatabase);
|
||||
PushChallengeDynamoDb pushChallengeDynamoDb = new PushChallengeDynamoDb(pushChallengeDynamoDbClient, config.getPushChallengeDynamoDbConfiguration().getTableName());
|
||||
ReportMessageDynamoDb reportMessageDynamoDb = new ReportMessageDynamoDb(reportMessageDynamoDbClient, config.getReportMessageDynamoDbConfiguration().getTableName());
|
||||
|
||||
RedisClientFactory pubSubClientFactory = new RedisClientFactory("pubsub_cache", config.getPubsubCacheConfiguration().getUrl(), config.getPubsubCacheConfiguration().getReplicaUrls(), config.getPubsubCacheConfiguration().getCircuitBreakerConfiguration());
|
||||
ReplicatedJedisPool pubsubClient = pubSubClientFactory.getRedisClientPool();
|
||||
|
||||
ClientResources generalCacheClientResources = ClientResources.builder().build();
|
||||
ClientResources messageCacheClientResources = ClientResources.builder().build();
|
||||
ClientResources presenceClientResources = ClientResources.builder().build();
|
||||
ClientResources metricsCacheClientResources = ClientResources.builder().build();
|
||||
ClientResources pushSchedulerCacheClientResources = ClientResources.builder().ioThreadPoolSize(4).build();
|
||||
ClientResources rateLimitersCacheClientResources = ClientResources.builder().build();
|
||||
|
||||
ConnectionEventLogger.logConnectionEvents(generalCacheClientResources);
|
||||
ConnectionEventLogger.logConnectionEvents(messageCacheClientResources);
|
||||
ConnectionEventLogger.logConnectionEvents(presenceClientResources);
|
||||
ConnectionEventLogger.logConnectionEvents(metricsCacheClientResources);
|
||||
|
||||
FaultTolerantRedisCluster cacheCluster = new FaultTolerantRedisCluster("main_cache_cluster", config.getCacheClusterConfiguration(), generalCacheClientResources);
|
||||
FaultTolerantRedisCluster messagesCluster = new FaultTolerantRedisCluster("messages_cluster", config.getMessageCacheConfiguration().getRedisClusterConfiguration(), messageCacheClientResources);
|
||||
FaultTolerantRedisCluster clientPresenceCluster = new FaultTolerantRedisCluster("client_presence_cluster", config.getClientPresenceClusterConfiguration(), presenceClientResources);
|
||||
FaultTolerantRedisCluster metricsCluster = new FaultTolerantRedisCluster("metrics_cluster", config.getMetricsClusterConfiguration(), metricsCacheClientResources);
|
||||
FaultTolerantRedisCluster pushSchedulerCluster = new FaultTolerantRedisCluster("push_scheduler", config.getPushSchedulerCluster(), pushSchedulerCacheClientResources);
|
||||
FaultTolerantRedisCluster rateLimitersCluster = new FaultTolerantRedisCluster("rate_limiters", config.getRateLimitersCluster(), rateLimitersCacheClientResources);
|
||||
|
||||
BlockingQueue<Runnable> keyspaceNotificationDispatchQueue = new ArrayBlockingQueue<>(10_000);
|
||||
Metrics.gaugeCollectionSize(name(getClass(), "keyspaceNotificationDispatchQueueSize"), Collections.emptyList(), keyspaceNotificationDispatchQueue);
|
||||
|
||||
ScheduledExecutorService recurringJobExecutor = environment.lifecycle().scheduledExecutorService(name(getClass(), "recurringJob-%d")).threads(2).build();
|
||||
ScheduledExecutorService declinedMessageReceiptExecutor = environment.lifecycle().scheduledExecutorService(name(getClass(), "declined-receipt-%d")).threads(2).build();
|
||||
ScheduledExecutorService retrySchedulingExecutor = environment.lifecycle().scheduledExecutorService(name(getClass(), "retry-%d")).threads(2).build();
|
||||
ExecutorService keyspaceNotificationDispatchExecutor = environment.lifecycle().executorService(name(getClass(), "keyspaceNotification-%d")).maxThreads(16).workQueue(keyspaceNotificationDispatchQueue).build();
|
||||
ExecutorService apnSenderExecutor = environment.lifecycle().executorService(name(getClass(), "apnSender-%d")).maxThreads(1).minThreads(1).build();
|
||||
ExecutorService gcmSenderExecutor = environment.lifecycle().executorService(name(getClass(), "gcmSender-%d")).maxThreads(1).minThreads(1).build();
|
||||
ExecutorService backupServiceExecutor = environment.lifecycle().executorService(name(getClass(), "backupService-%d")).maxThreads(1).minThreads(1).build();
|
||||
ExecutorService storageServiceExecutor = environment.lifecycle().executorService(name(getClass(), "storageService-%d")).maxThreads(1).minThreads(1).build();
|
||||
ExecutorService donationExecutor = environment.lifecycle().executorService(name(getClass(), "donation-%d")).maxThreads(1).minThreads(1).build();
|
||||
|
||||
ExternalServiceCredentialGenerator directoryCredentialsGenerator = new ExternalServiceCredentialGenerator(config.getDirectoryConfiguration().getDirectoryClientConfiguration().getUserAuthenticationTokenSharedSecret(),
|
||||
config.getDirectoryConfiguration().getDirectoryClientConfiguration().getUserAuthenticationTokenUserIdSecret(),
|
||||
true);
|
||||
|
||||
DynamicConfigurationManager dynamicConfigurationManager = new DynamicConfigurationManager(config.getAppConfig().getApplication(), config.getAppConfig().getEnvironment(), config.getAppConfig().getConfigurationName());
|
||||
dynamicConfigurationManager.start();
|
||||
|
||||
ExperimentEnrollmentManager experimentEnrollmentManager = new ExperimentEnrollmentManager(dynamicConfigurationManager);
|
||||
|
||||
TwilioVerifyExperimentEnrollmentManager verifyExperimentEnrollmentManager = new TwilioVerifyExperimentEnrollmentManager(
|
||||
config.getVoiceVerificationConfiguration(), experimentEnrollmentManager);
|
||||
|
||||
ExternalServiceCredentialGenerator storageCredentialsGenerator = new ExternalServiceCredentialGenerator(config.getSecureStorageServiceConfiguration().getUserAuthenticationTokenSharedSecret(), new byte[0], false);
|
||||
ExternalServiceCredentialGenerator backupCredentialsGenerator = new ExternalServiceCredentialGenerator(config.getSecureBackupServiceConfiguration().getUserAuthenticationTokenSharedSecret(), new byte[0], false);
|
||||
ExternalServiceCredentialGenerator paymentsCredentialsGenerator = new ExternalServiceCredentialGenerator(config.getPaymentsServiceConfiguration().getUserAuthenticationTokenSharedSecret(), new byte[0], false);
|
||||
|
||||
SecureBackupClient secureBackupClient = new SecureBackupClient(backupCredentialsGenerator, backupServiceExecutor, config.getSecureBackupServiceConfiguration());
|
||||
SecureStorageClient secureStorageClient = new SecureStorageClient(storageCredentialsGenerator, storageServiceExecutor, config.getSecureStorageServiceConfiguration());
|
||||
ClientPresenceManager clientPresenceManager = new ClientPresenceManager(clientPresenceCluster, recurringJobExecutor, keyspaceNotificationDispatchExecutor);
|
||||
DirectoryQueue directoryQueue = new DirectoryQueue(config.getDirectoryConfiguration().getSqsConfiguration());
|
||||
PendingAccountsManager pendingAccountsManager = new PendingAccountsManager(pendingAccounts, cacheCluster);
|
||||
PendingDevicesManager pendingDevicesManager = new PendingDevicesManager(pendingDevices, cacheCluster);
|
||||
UsernamesManager usernamesManager = new UsernamesManager(usernames, reservedUsernames, cacheCluster);
|
||||
ProfilesManager profilesManager = new ProfilesManager(profiles, cacheCluster);
|
||||
MessagesCache messagesCache = new MessagesCache(messagesCluster, messagesCluster, keyspaceNotificationDispatchExecutor);
|
||||
PushLatencyManager pushLatencyManager = new PushLatencyManager(metricsCluster);
|
||||
ReportMessageManager reportMessageManager = new ReportMessageManager(reportMessageDynamoDb, Metrics.globalRegistry);
|
||||
MessagesManager messagesManager = new MessagesManager(messagesDynamoDb, messagesCache, pushLatencyManager, reportMessageManager);
|
||||
AccountsManager accountsManager = new AccountsManager(accounts, accountsDynamoDb, cacheCluster, directoryQueue, keysDynamoDb, messagesManager, usernamesManager, profilesManager, secureStorageClient, secureBackupClient, experimentEnrollmentManager, dynamicConfigurationManager);
|
||||
RemoteConfigsManager remoteConfigsManager = new RemoteConfigsManager(remoteConfigs);
|
||||
DeadLetterHandler deadLetterHandler = new DeadLetterHandler(accountsManager, messagesManager);
|
||||
DispatchManager dispatchManager = new DispatchManager(pubSubClientFactory, Optional.of(deadLetterHandler));
|
||||
PubSubManager pubSubManager = new PubSubManager(pubsubClient, dispatchManager);
|
||||
APNSender apnSender = new APNSender(apnSenderExecutor, accountsManager, config.getApnConfiguration());
|
||||
GCMSender gcmSender = new GCMSender(gcmSenderExecutor, accountsManager, config.getGcmConfiguration().getApiKey());
|
||||
RateLimiters rateLimiters = new RateLimiters(config.getLimitsConfiguration(), dynamicConfigurationManager, rateLimitersCluster);
|
||||
ProvisioningManager provisioningManager = new ProvisioningManager(pubSubManager);
|
||||
TorExitNodeManager torExitNodeManager = new TorExitNodeManager(recurringJobExecutor, config.getTorExitNodeListConfiguration());
|
||||
AsnManager asnManager = new AsnManager(recurringJobExecutor, config.getAsnTableConfiguration());
|
||||
|
||||
AccountAuthenticator accountAuthenticator = new AccountAuthenticator(accountsManager);
|
||||
DisabledPermittedAccountAuthenticator disabledPermittedAccountAuthenticator = new DisabledPermittedAccountAuthenticator(accountsManager);
|
||||
|
||||
RateLimitResetMetricsManager rateLimitResetMetricsManager = new RateLimitResetMetricsManager(metricsCluster, Metrics.globalRegistry);
|
||||
|
||||
UnsealedSenderRateLimiter unsealedSenderRateLimiter = new UnsealedSenderRateLimiter(rateLimiters, rateLimitersCluster, dynamicConfigurationManager, rateLimitResetMetricsManager);
|
||||
PreKeyRateLimiter preKeyRateLimiter = new PreKeyRateLimiter(rateLimiters, dynamicConfigurationManager, rateLimitResetMetricsManager);
|
||||
|
||||
ApnFallbackManager apnFallbackManager = new ApnFallbackManager(pushSchedulerCluster, apnSender, accountsManager);
|
||||
TwilioSmsSender twilioSmsSender = new TwilioSmsSender(config.getTwilioConfiguration(), dynamicConfigurationManager);
|
||||
SmsSender smsSender = new SmsSender(twilioSmsSender);
|
||||
MessageSender messageSender = new MessageSender(apnFallbackManager, clientPresenceManager, messagesManager, gcmSender, apnSender, pushLatencyManager);
|
||||
ReceiptSender receiptSender = new ReceiptSender(accountsManager, messageSender);
|
||||
TurnTokenGenerator turnTokenGenerator = new TurnTokenGenerator(config.getTurnConfiguration());
|
||||
RecaptchaClient recaptchaClient = new RecaptchaClient(config.getRecaptchaConfiguration().getSecret());
|
||||
PushChallengeManager pushChallengeManager = new PushChallengeManager(apnSender, gcmSender, pushChallengeDynamoDb);
|
||||
RateLimitChallengeManager rateLimitChallengeManager = new RateLimitChallengeManager(pushChallengeManager, recaptchaClient, preKeyRateLimiter, unsealedSenderRateLimiter, rateLimiters, dynamicConfigurationManager);
|
||||
|
||||
MessagePersister messagePersister = new MessagePersister(messagesCache, messagesManager, accountsManager, dynamicConfigurationManager, Duration.ofMinutes(config.getMessageCacheConfiguration().getPersistDelayMinutes()));
|
||||
|
||||
final List<AccountDatabaseCrawlerListener> accountDatabaseCrawlerListeners = new ArrayList<>();
|
||||
accountDatabaseCrawlerListeners.add(new PushFeedbackProcessor(accountsManager, directoryQueue));
|
||||
accountDatabaseCrawlerListeners.add(new ActiveUserCounter(config.getMetricsFactory(), cacheCluster));
|
||||
for (DirectoryServerConfiguration directoryServerConfiguration : config.getDirectoryConfiguration().getDirectoryServerConfiguration()) {
|
||||
final DirectoryReconciliationClient directoryReconciliationClient = new DirectoryReconciliationClient(directoryServerConfiguration);
|
||||
final DirectoryReconciler directoryReconciler = new DirectoryReconciler(directoryServerConfiguration.getReplicationName(), directoryReconciliationClient);
|
||||
accountDatabaseCrawlerListeners.add(directoryReconciler);
|
||||
}
|
||||
accountDatabaseCrawlerListeners.add(new AccountCleaner(accountsManager));
|
||||
accountDatabaseCrawlerListeners.add(new RegistrationLockVersionCounter(metricsCluster, config.getMetricsFactory()));
|
||||
accountDatabaseCrawlerListeners.add(new AccountsDynamoDbMigrator(accountsDynamoDb, dynamicConfigurationManager));
|
||||
|
||||
HttpClient currencyClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).connectTimeout(Duration.ofSeconds(10)).build();
|
||||
FixerClient fixerClient = new FixerClient(currencyClient, config.getPaymentsServiceConfiguration().getFixerApiKey());
|
||||
FtxClient ftxClient = new FtxClient(currencyClient);
|
||||
CurrencyConversionManager currencyManager = new CurrencyConversionManager(fixerClient, ftxClient, config.getPaymentsServiceConfiguration().getPaymentCurrencies());
|
||||
|
||||
AccountDatabaseCrawlerCache accountDatabaseCrawlerCache = new AccountDatabaseCrawlerCache(cacheCluster);
|
||||
AccountDatabaseCrawler accountDatabaseCrawler = new AccountDatabaseCrawler(accountsManager, accountDatabaseCrawlerCache, accountDatabaseCrawlerListeners, config.getAccountDatabaseCrawlerConfiguration().getChunkSize(), config.getAccountDatabaseCrawlerConfiguration().getChunkIntervalMs());
|
||||
|
||||
apnSender.setApnFallbackManager(apnFallbackManager);
|
||||
environment.lifecycle().manage(apnFallbackManager);
|
||||
environment.lifecycle().manage(pubSubManager);
|
||||
environment.lifecycle().manage(messageSender);
|
||||
environment.lifecycle().manage(accountDatabaseCrawler);
|
||||
environment.lifecycle().manage(remoteConfigsManager);
|
||||
environment.lifecycle().manage(messagesCache);
|
||||
environment.lifecycle().manage(messagePersister);
|
||||
environment.lifecycle().manage(clientPresenceManager);
|
||||
environment.lifecycle().manage(currencyManager);
|
||||
environment.lifecycle().manage(torExitNodeManager);
|
||||
environment.lifecycle().manage(asnManager);
|
||||
|
||||
AWSCredentials credentials = new BasicAWSCredentials(config.getCdnConfiguration().getAccessKey(), config.getCdnConfiguration().getAccessSecret());
|
||||
AWSCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(credentials);
|
||||
AmazonS3 cdnS3Client = AmazonS3Client.builder().withCredentials(credentialsProvider).withRegion(config.getCdnConfiguration().getRegion()).build();
|
||||
PostPolicyGenerator profileCdnPolicyGenerator = new PostPolicyGenerator(config.getCdnConfiguration().getRegion(), config.getCdnConfiguration().getBucket(), config.getCdnConfiguration().getAccessKey());
|
||||
PolicySigner profileCdnPolicySigner = new PolicySigner(config.getCdnConfiguration().getAccessSecret(), config.getCdnConfiguration().getRegion());
|
||||
|
||||
ServerSecretParams zkSecretParams = new ServerSecretParams(config.getZkConfig().getServerSecret());
|
||||
ServerZkProfileOperations zkProfileOperations = new ServerZkProfileOperations(zkSecretParams);
|
||||
ServerZkAuthOperations zkAuthOperations = new ServerZkAuthOperations(zkSecretParams);
|
||||
boolean isZkEnabled = config.getZkConfig().isEnabled();
|
||||
|
||||
AttachmentControllerV1 attachmentControllerV1 = new AttachmentControllerV1(rateLimiters, config.getAwsAttachmentsConfiguration().getAccessKey(), config.getAwsAttachmentsConfiguration().getAccessSecret(), config.getAwsAttachmentsConfiguration().getBucket());
|
||||
AttachmentControllerV2 attachmentControllerV2 = new AttachmentControllerV2(rateLimiters, config.getAwsAttachmentsConfiguration().getAccessKey(), config.getAwsAttachmentsConfiguration().getAccessSecret(), config.getAwsAttachmentsConfiguration().getRegion(), config.getAwsAttachmentsConfiguration().getBucket());
|
||||
AttachmentControllerV3 attachmentControllerV3 = new AttachmentControllerV3(rateLimiters, config.getGcpAttachmentsConfiguration().getDomain(), config.getGcpAttachmentsConfiguration().getEmail(), config.getGcpAttachmentsConfiguration().getMaxSizeInBytes(), config.getGcpAttachmentsConfiguration().getPathPrefix(), config.getGcpAttachmentsConfiguration().getRsaSigningKey());
|
||||
DonationController donationController = new DonationController(donationExecutor, config.getDonationConfiguration());
|
||||
KeysController keysController = new KeysController(rateLimiters, keysDynamoDb, accountsManager, directoryQueue, preKeyRateLimiter, dynamicConfigurationManager, rateLimitChallengeManager);
|
||||
MessageController messageController = new MessageController(rateLimiters, messageSender, receiptSender, accountsManager, messagesManager, unsealedSenderRateLimiter, apnFallbackManager, dynamicConfigurationManager, rateLimitChallengeManager, reportMessageManager, metricsCluster, declinedMessageReceiptExecutor);
|
||||
ProfileController profileController = new ProfileController(rateLimiters, accountsManager, profilesManager, usernamesManager, dynamicConfigurationManager, cdnS3Client, profileCdnPolicyGenerator, profileCdnPolicySigner, config.getCdnConfiguration().getBucket(), zkProfileOperations, isZkEnabled);
|
||||
StickerController stickerController = new StickerController(rateLimiters, config.getCdnConfiguration().getAccessKey(), config.getCdnConfiguration().getAccessSecret(), config.getCdnConfiguration().getRegion(), config.getCdnConfiguration().getBucket());
|
||||
RemoteConfigController remoteConfigController = new RemoteConfigController(remoteConfigsManager, config.getRemoteConfigConfiguration().getAuthorizedTokens(), config.getRemoteConfigConfiguration().getGlobalConfig());
|
||||
ChallengeController challengeController = new ChallengeController(rateLimitChallengeManager);
|
||||
|
||||
AuthFilter<BasicCredentials, Account> accountAuthFilter = new BasicCredentialAuthFilter.Builder<Account>().setAuthenticator(accountAuthenticator).buildAuthFilter ();
|
||||
AuthFilter<BasicCredentials, DisabledPermittedAccount> disabledPermittedAccountAuthFilter = new BasicCredentialAuthFilter.Builder<DisabledPermittedAccount>().setAuthenticator(disabledPermittedAccountAuthenticator).buildAuthFilter();
|
||||
|
||||
environment.servlets().addFilter("RemoteDeprecationFilter", new RemoteDeprecationFilter(dynamicConfigurationManager))
|
||||
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*");
|
||||
|
||||
environment.jersey().register(MultiRecipientMessageProvider.class);
|
||||
environment.jersey().register(new MetricsApplicationEventListener(TrafficSource.HTTP));
|
||||
environment.jersey().register(new PolymorphicAuthDynamicFeature<>(ImmutableMap.of(Account.class, accountAuthFilter,
|
||||
DisabledPermittedAccount.class, disabledPermittedAccountAuthFilter)));
|
||||
environment.jersey().register(new PolymorphicAuthValueFactoryProvider.Binder<>(ImmutableSet.of(Account.class, DisabledPermittedAccount.class)));
|
||||
environment.jersey().register(new TimestampResponseFilter());
|
||||
environment.jersey().register(new AccountController(pendingAccountsManager, accountsManager, usernamesManager, abusiveHostRules, rateLimiters, smsSender, directoryQueue, messagesManager, dynamicConfigurationManager, turnTokenGenerator, config.getTestDevices(), recaptchaClient, gcmSender, apnSender, backupCredentialsGenerator, verifyExperimentEnrollmentManager));
|
||||
environment.jersey().register(new DeviceController(pendingDevicesManager, accountsManager, messagesManager, directoryQueue, rateLimiters, config.getMaxDevices()));
|
||||
environment.jersey().register(new DirectoryController(directoryCredentialsGenerator));
|
||||
environment.jersey().register(new ProvisioningController(rateLimiters, provisioningManager));
|
||||
environment.jersey().register(new CertificateController(new CertificateGenerator(config.getDeliveryCertificate().getCertificate(), config.getDeliveryCertificate().getPrivateKey(), config.getDeliveryCertificate().getExpiresDays()), zkAuthOperations, isZkEnabled));
|
||||
environment.jersey().register(new VoiceVerificationController(config.getVoiceVerificationConfiguration().getUrl(), config.getVoiceVerificationConfiguration().getLocales()));
|
||||
environment.jersey().register(new SecureStorageController(storageCredentialsGenerator));
|
||||
environment.jersey().register(new SecureBackupController(backupCredentialsGenerator));
|
||||
environment.jersey().register(new PaymentsController(currencyManager, paymentsCredentialsGenerator));
|
||||
environment.jersey().register(attachmentControllerV1);
|
||||
environment.jersey().register(attachmentControllerV2);
|
||||
environment.jersey().register(attachmentControllerV3);
|
||||
environment.jersey().register(donationController);
|
||||
environment.jersey().register(keysController);
|
||||
environment.jersey().register(messageController);
|
||||
environment.jersey().register(profileController);
|
||||
environment.jersey().register(stickerController);
|
||||
environment.jersey().register(remoteConfigController);
|
||||
environment.jersey().register(challengeController);
|
||||
|
||||
///
|
||||
WebSocketEnvironment<Account> webSocketEnvironment = new WebSocketEnvironment<>(environment, config.getWebSocketConfiguration(), 90000);
|
||||
webSocketEnvironment.setAuthenticator(new WebSocketAccountAuthenticator(accountAuthenticator));
|
||||
webSocketEnvironment.setConnectListener(new AuthenticatedConnectListener(receiptSender, messagesManager, messageSender, apnFallbackManager, clientPresenceManager, retrySchedulingExecutor));
|
||||
webSocketEnvironment.jersey().register(MultiRecipientMessageProvider.class);
|
||||
webSocketEnvironment.jersey().register(new MetricsApplicationEventListener(TrafficSource.WEBSOCKET));
|
||||
webSocketEnvironment.jersey().register(new KeepAliveController(clientPresenceManager));
|
||||
webSocketEnvironment.jersey().register(messageController);
|
||||
webSocketEnvironment.jersey().register(profileController);
|
||||
webSocketEnvironment.jersey().register(attachmentControllerV1);
|
||||
webSocketEnvironment.jersey().register(attachmentControllerV2);
|
||||
webSocketEnvironment.jersey().register(attachmentControllerV3);
|
||||
webSocketEnvironment.jersey().register(donationController);
|
||||
webSocketEnvironment.jersey().register(remoteConfigController);
|
||||
|
||||
WebSocketEnvironment<Account> provisioningEnvironment = new WebSocketEnvironment<>(environment, webSocketEnvironment.getRequestLog(), 60000);
|
||||
provisioningEnvironment.setConnectListener(new ProvisioningConnectListener(pubSubManager));
|
||||
provisioningEnvironment.jersey().register(new MetricsApplicationEventListener(TrafficSource.WEBSOCKET));
|
||||
provisioningEnvironment.jersey().register(new KeepAliveController(clientPresenceManager));
|
||||
|
||||
registerCorsFilter(environment);
|
||||
registerExceptionMappers(environment, webSocketEnvironment, provisioningEnvironment);
|
||||
|
||||
RateLimitChallengeExceptionMapper rateLimitChallengeExceptionMapper = new RateLimitChallengeExceptionMapper(rateLimitChallengeManager);
|
||||
|
||||
environment.jersey().register(rateLimitChallengeExceptionMapper);
|
||||
webSocketEnvironment.jersey().register(rateLimitChallengeExceptionMapper);
|
||||
provisioningEnvironment.jersey().register(rateLimitChallengeExceptionMapper);
|
||||
|
||||
WebSocketResourceProviderFactory<Account> webSocketServlet = new WebSocketResourceProviderFactory<>(webSocketEnvironment, Account.class);
|
||||
WebSocketResourceProviderFactory<Account> provisioningServlet = new WebSocketResourceProviderFactory<>(provisioningEnvironment, Account.class);
|
||||
|
||||
ServletRegistration.Dynamic websocket = environment.servlets().addServlet("WebSocket", webSocketServlet );
|
||||
ServletRegistration.Dynamic provisioning = environment.servlets().addServlet("Provisioning", provisioningServlet);
|
||||
|
||||
websocket.addMapping("/v1/websocket/");
|
||||
websocket.setAsyncSupported(true);
|
||||
|
||||
provisioning.addMapping("/v1/websocket/provisioning/");
|
||||
provisioning.setAsyncSupported(true);
|
||||
|
||||
environment.admin().addTask(new SetRequestLoggingEnabledTask());
|
||||
environment.admin().addTask(new SetCrawlerAccelerationTask(accountDatabaseCrawlerCache));
|
||||
|
||||
///
|
||||
|
||||
environment.healthChecks().register("cacheCluster", new RedisClusterHealthCheck(cacheCluster));
|
||||
|
||||
environment.metrics().register(name(CpuUsageGauge.class, "cpu"), new CpuUsageGauge(3, TimeUnit.SECONDS));
|
||||
environment.metrics().register(name(FreeMemoryGauge.class, "free_memory"), new FreeMemoryGauge());
|
||||
environment.metrics().register(name(NetworkSentGauge.class, "bytes_sent"), new NetworkSentGauge());
|
||||
environment.metrics().register(name(NetworkReceivedGauge.class, "bytes_received"), new NetworkReceivedGauge());
|
||||
environment.metrics().register(name(FileDescriptorGauge.class, "fd_count"), new FileDescriptorGauge());
|
||||
environment.metrics().register(name(MaxFileDescriptorGauge.class, "max_fd_count"), new MaxFileDescriptorGauge());
|
||||
environment.metrics().register(name(OperatingSystemMemoryGauge.class, "buffers"), new OperatingSystemMemoryGauge("Buffers"));
|
||||
environment.metrics().register(name(OperatingSystemMemoryGauge.class, "cached"), new OperatingSystemMemoryGauge("Cached"));
|
||||
|
||||
BufferPoolGauges.registerMetrics();
|
||||
GarbageCollectionGauges.registerMetrics();
|
||||
|
||||
new NstatCounters().registerMetrics(recurringJobExecutor, wavefrontConfig.step());
|
||||
}
|
||||
|
||||
private void registerExceptionMappers(Environment environment, WebSocketEnvironment<Account> webSocketEnvironment, WebSocketEnvironment<Account> provisioningEnvironment) {
|
||||
environment.jersey().register(new IOExceptionMapper());
|
||||
environment.jersey().register(new RateLimitExceededExceptionMapper());
|
||||
environment.jersey().register(new InvalidWebsocketAddressExceptionMapper());
|
||||
environment.jersey().register(new DeviceLimitExceededExceptionMapper());
|
||||
environment.jersey().register(new RetryLaterExceptionMapper());
|
||||
|
||||
webSocketEnvironment.jersey().register(new IOExceptionMapper());
|
||||
webSocketEnvironment.jersey().register(new RateLimitExceededExceptionMapper());
|
||||
webSocketEnvironment.jersey().register(new InvalidWebsocketAddressExceptionMapper());
|
||||
webSocketEnvironment.jersey().register(new DeviceLimitExceededExceptionMapper());
|
||||
webSocketEnvironment.jersey().register(new RetryLaterExceptionMapper());
|
||||
|
||||
provisioningEnvironment.jersey().register(new IOExceptionMapper());
|
||||
provisioningEnvironment.jersey().register(new RateLimitExceededExceptionMapper());
|
||||
provisioningEnvironment.jersey().register(new InvalidWebsocketAddressExceptionMapper());
|
||||
provisioningEnvironment.jersey().register(new DeviceLimitExceededExceptionMapper());
|
||||
provisioningEnvironment.jersey().register(new RetryLaterExceptionMapper());
|
||||
}
|
||||
|
||||
private void registerCorsFilter(Environment environment) {
|
||||
FilterRegistration.Dynamic filter = environment.servlets().addFilter("CORS", CrossOriginFilter.class);
|
||||
filter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");
|
||||
filter.setInitParameter("allowedOrigins", "*");
|
||||
filter.setInitParameter("allowedHeaders", "Content-Type,Authorization,X-Requested-With,Content-Length,Accept,Origin,X-Signal-Agent");
|
||||
filter.setInitParameter("allowedMethods", "GET,PUT,POST,DELETE,OPTIONS");
|
||||
filter.setInitParameter("preflightMaxAge", "5184000");
|
||||
filter.setInitParameter("allowCredentials", "true");
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
new WhisperServerService().run(args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import io.dropwizard.auth.Authenticator;
|
||||
import io.dropwizard.auth.basic.BasicCredentials;
|
||||
|
||||
import static com.codahale.metrics.MetricRegistry.name;
|
||||
|
||||
public class AccountAuthenticator extends BaseAccountAuthenticator implements Authenticator<BasicCredentials, Account> {
|
||||
|
||||
private static final String AUTHENTICATION_COUNTER_NAME = name(AccountAuthenticator.class, "authenticate");
|
||||
private static final String GV2_CAPABLE_TAG_NAME = "gv1Migration";
|
||||
|
||||
public AccountAuthenticator(AccountsManager accountsManager) {
|
||||
super(accountsManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Account> authenticate(BasicCredentials basicCredentials) {
|
||||
final Optional<Account> maybeAccount = super.authenticate(basicCredentials, true);
|
||||
|
||||
// TODO Remove this temporary counter when we can replace it with more generic feature adoption system
|
||||
maybeAccount.ifPresent(account -> {
|
||||
Metrics.counter(AUTHENTICATION_COUNTER_NAME, GV2_CAPABLE_TAG_NAME, String.valueOf(account.isGv1MigrationSupported())).increment();
|
||||
});
|
||||
|
||||
return maybeAccount;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class AmbiguousIdentifier {
|
||||
|
||||
private final UUID uuid;
|
||||
private final String number;
|
||||
|
||||
public AmbiguousIdentifier(String target) {
|
||||
if (target.startsWith("+")) {
|
||||
this.uuid = null;
|
||||
this.number = target;
|
||||
} else {
|
||||
this.uuid = UUID.fromString(target);
|
||||
this.number = null;
|
||||
}
|
||||
}
|
||||
|
||||
public UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public String getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public boolean hasUuid() {
|
||||
return uuid != null;
|
||||
}
|
||||
|
||||
public boolean hasNumber() {
|
||||
return number != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return hasUuid() ? uuid.toString() : number;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import java.util.Base64;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
public class Anonymous {
|
||||
|
||||
private final byte[] unidentifiedSenderAccessKey;
|
||||
|
||||
public Anonymous(String header) {
|
||||
try {
|
||||
this.unidentifiedSenderAccessKey = Base64.getDecoder().decode(header);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new WebApplicationException(e, Response.Status.UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getAccessKey() {
|
||||
return unidentifiedSenderAccessKey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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,43 +1,31 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
|
||||
import org.whispersystems.textsecuregcm.util.Base64;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
|
||||
public class AuthorizationHeader {
|
||||
|
||||
private final String number;
|
||||
private final long accountId;
|
||||
private final String password;
|
||||
private final AmbiguousIdentifier identifier;
|
||||
private final long deviceId;
|
||||
private final String password;
|
||||
|
||||
private AuthorizationHeader(String number, long accountId, String password) {
|
||||
this.number = number;
|
||||
this.accountId = accountId;
|
||||
this.password = password;
|
||||
private AuthorizationHeader(AmbiguousIdentifier identifier, long deviceId, String password) {
|
||||
this.identifier = identifier;
|
||||
this.deviceId = deviceId;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public static AuthorizationHeader fromUserAndPassword(String user, String password) throws InvalidAuthorizationHeaderException {
|
||||
try {
|
||||
String[] numberAndId = user.split("\\.");
|
||||
return new AuthorizationHeader(numberAndId[0],
|
||||
return new AuthorizationHeader(new AmbiguousIdentifier(numberAndId[0]),
|
||||
numberAndId.length > 1 ? Long.parseLong(numberAndId[1]) : 1,
|
||||
password);
|
||||
} catch (NumberFormatException nfe) {
|
||||
@@ -61,7 +49,7 @@ public class AuthorizationHeader {
|
||||
throw new InvalidAuthorizationHeaderException("Unsupported authorization method: " + headerParts[0]);
|
||||
}
|
||||
|
||||
String concatenatedValues = new String(Base64.decode(headerParts[1]));
|
||||
String concatenatedValues = new String(Base64.getDecoder().decode(headerParts[1]));
|
||||
|
||||
if (Util.isEmpty(concatenatedValues)) {
|
||||
throw new InvalidAuthorizationHeaderException("Bad decoded value: " + concatenatedValues);
|
||||
@@ -74,17 +62,17 @@ public class AuthorizationHeader {
|
||||
}
|
||||
|
||||
return fromUserAndPassword(credentialParts[0], credentialParts[1]);
|
||||
} catch (IOException ioe) {
|
||||
throw new InvalidAuthorizationHeaderException(ioe);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new InvalidAuthorizationHeaderException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getNumber() {
|
||||
return number;
|
||||
public AmbiguousIdentifier getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public long getDeviceId() {
|
||||
return accountId;
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import com.codahale.metrics.Meter;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.codahale.metrics.SharedMetricRegistries;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import io.dropwizard.auth.basic.BasicCredentials;
|
||||
import io.micrometer.core.instrument.DistributionSummary;
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.util.Constants;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Optional;
|
||||
|
||||
import static com.codahale.metrics.MetricRegistry.name;
|
||||
|
||||
public class BaseAccountAuthenticator {
|
||||
|
||||
private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME);
|
||||
private final Meter authenticationFailedMeter = metricRegistry.meter(name(getClass(), "authentication", "failed" ));
|
||||
private final Meter authenticationSucceededMeter = metricRegistry.meter(name(getClass(), "authentication", "succeeded" ));
|
||||
private final Meter noSuchAccountMeter = metricRegistry.meter(name(getClass(), "authentication", "noSuchAccount" ));
|
||||
private final Meter noSuchDeviceMeter = metricRegistry.meter(name(getClass(), "authentication", "noSuchDevice" ));
|
||||
private final Meter accountDisabledMeter = metricRegistry.meter(name(getClass(), "authentication", "accountDisabled"));
|
||||
private final Meter deviceDisabledMeter = metricRegistry.meter(name(getClass(), "authentication", "deviceDisabled" ));
|
||||
private final Meter invalidAuthHeaderMeter = metricRegistry.meter(name(getClass(), "authentication", "invalidHeader" ));
|
||||
|
||||
private final String daysSinceLastSeenDistributionName = name(getClass(), "authentication", "daysSinceLastSeen");
|
||||
|
||||
private static final String IS_PRIMARY_DEVICE_TAG = "isPrimary";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(BaseAccountAuthenticator.class);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public Optional<Account> authenticate(BasicCredentials basicCredentials, boolean enabledRequired) {
|
||||
try {
|
||||
AuthorizationHeader authorizationHeader = AuthorizationHeader.fromUserAndPassword(basicCredentials.getUsername(), basicCredentials.getPassword());
|
||||
Optional<Account> account = accountsManager.get(authorizationHeader.getIdentifier());
|
||||
|
||||
if (!account.isPresent()) {
|
||||
noSuchAccountMeter.mark();
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Optional<Device> device = account.get().getDevice(authorizationHeader.getDeviceId());
|
||||
|
||||
if (!device.isPresent()) {
|
||||
noSuchDeviceMeter.mark();
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (enabledRequired) {
|
||||
if (!device.get().isEnabled()) {
|
||||
deviceDisabledMeter.mark();
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (!account.get().isEnabled()) {
|
||||
accountDisabledMeter.mark();
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
if (device.get().getAuthenticationCredentials().verify(basicCredentials.getPassword())) {
|
||||
authenticationSucceededMeter.mark();
|
||||
account.get().setAuthenticatedDevice(device.get());
|
||||
updateLastSeen(account.get(), device.get());
|
||||
return account;
|
||||
}
|
||||
|
||||
authenticationFailedMeter.mark();
|
||||
return Optional.empty();
|
||||
} catch (IllegalArgumentException | InvalidAuthorizationHeaderException iae) {
|
||||
invalidAuthHeaderMeter.mark();
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void 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) {
|
||||
DistributionSummary.builder(daysSinceLastSeenDistributionName)
|
||||
.tags(IS_PRIMARY_DEVICE_TAG, String.valueOf(device.isMaster()))
|
||||
.publishPercentileHistogram()
|
||||
.register(Metrics.globalRegistry)
|
||||
.record(Duration.ofMillis(todayInMillisWithOffset - device.getLastSeen()).toDays());
|
||||
|
||||
device.setLastSeen(Util.todayInMillis(clock));
|
||||
accountsManager.update(account);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
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 org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate;
|
||||
import org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate;
|
||||
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;
|
||||
private final int expiresDays;
|
||||
private final ServerCertificate serverCertificate;
|
||||
|
||||
public CertificateGenerator(byte[] serverCertificate, ECPrivateKey privateKey, int expiresDays)
|
||||
throws InvalidProtocolBufferException
|
||||
{
|
||||
this.privateKey = privateKey;
|
||||
this.expiresDays = expiresDays;
|
||||
this.serverCertificate = ServerCertificate.parseFrom(serverCertificate);
|
||||
}
|
||||
|
||||
public byte[] createFor(Account account, Device device, boolean includeE164) throws IOException, 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());
|
||||
|
||||
if (includeE164) {
|
||||
builder.setSender(account.getNumber());
|
||||
}
|
||||
|
||||
byte[] certificate = builder.build().toByteArray();
|
||||
byte[] signature = Curve.calculateSignature(privateKey, certificate);
|
||||
|
||||
return SenderCertificate.newBuilder()
|
||||
.setCertificate(ByteString.copyFrom(certificate))
|
||||
.setSignature(ByteString.copyFrom(signature))
|
||||
.build()
|
||||
.toByteArray();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import java.util.Base64;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
|
||||
public class CombinedUnidentifiedSenderAccessKeys {
|
||||
private final byte[] combinedUnidentifiedSenderAccessKeys;
|
||||
|
||||
public CombinedUnidentifiedSenderAccessKeys(String header) {
|
||||
try {
|
||||
this.combinedUnidentifiedSenderAccessKeys = Base64.getDecoder().decode(header);
|
||||
if (this.combinedUnidentifiedSenderAccessKeys == null || this.combinedUnidentifiedSenderAccessKeys.length != 16) {
|
||||
throw new WebApplicationException("Invalid combined unidentified sender access keys", Status.UNAUTHORIZED);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new WebApplicationException(e, Response.Status.UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getAccessKeys() {
|
||||
return combinedUnidentifiedSenderAccessKeys;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
import java.security.Principal;
|
||||
|
||||
public class DisabledPermittedAccount implements Principal {
|
||||
|
||||
private final Account account;
|
||||
|
||||
public DisabledPermittedAccount(Account account) {
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
public Account getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
// Principal implementation
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean implies(Subject subject) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import io.dropwizard.auth.Authenticator;
|
||||
import io.dropwizard.auth.basic.BasicCredentials;
|
||||
|
||||
public class DisabledPermittedAccountAuthenticator extends BaseAccountAuthenticator implements Authenticator<BasicCredentials, DisabledPermittedAccount> {
|
||||
|
||||
public DisabledPermittedAccountAuthenticator(AccountsManager accountsManager) {
|
||||
super(accountsManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<DisabledPermittedAccount> authenticate(BasicCredentials credentials) {
|
||||
Optional<Account> account = super.authenticate(credentials, false);
|
||||
return account.map(DisabledPermittedAccount::new);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import org.apache.commons.codec.DecoderException;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ExternalServiceCredentialGenerator {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ExternalServiceCredentialGenerator.class);
|
||||
|
||||
private final byte[] key;
|
||||
private final byte[] userIdKey;
|
||||
private final boolean usernameDerivation;
|
||||
|
||||
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey, boolean usernameDerivation) {
|
||||
this.key = key;
|
||||
this.userIdKey = userIdKey;
|
||||
this.usernameDerivation = usernameDerivation;
|
||||
}
|
||||
|
||||
public ExternalServiceCredentials generateFor(String number) {
|
||||
Mac mac = getMacInstance();
|
||||
String username = getUserId(number, mac, usernameDerivation);
|
||||
long currentTimeSeconds = System.currentTimeMillis() / 1000;
|
||||
String prefix = username + ":" + currentTimeSeconds;
|
||||
String output = Hex.encodeHexString(Util.truncate(getHmac(key, prefix.getBytes(), mac), 10));
|
||||
String token = prefix + ":" + output;
|
||||
|
||||
return new ExternalServiceCredentials(username, token);
|
||||
}
|
||||
|
||||
|
||||
public boolean isValid(String token, String number, long currentTimeMillis) {
|
||||
String[] parts = token.split(":");
|
||||
Mac mac = getMacInstance();
|
||||
|
||||
if (parts.length != 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!getUserId(number, mac, usernameDerivation).equals(parts[0])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isValidTime(parts[1], currentTimeMillis)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isValidSignature(parts[0] + ":" + parts[1], parts[2], mac);
|
||||
}
|
||||
|
||||
private String getUserId(String number, Mac mac, boolean usernameDerivation) {
|
||||
if (usernameDerivation) return Hex.encodeHexString(Util.truncate(getHmac(userIdKey, number.getBytes(), mac), 10));
|
||||
else return number;
|
||||
}
|
||||
|
||||
private boolean isValidTime(String timeString, long currentTimeMillis) {
|
||||
try {
|
||||
long tokenTime = Long.parseLong(timeString);
|
||||
long ourTime = TimeUnit.MILLISECONDS.toSeconds(currentTimeMillis);
|
||||
|
||||
return TimeUnit.SECONDS.toHours(Math.abs(ourTime - tokenTime)) < 24;
|
||||
} catch (NumberFormatException e) {
|
||||
logger.warn("Number Format", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isValidSignature(String prefix, String suffix, Mac mac) {
|
||||
try {
|
||||
byte[] ourSuffix = Util.truncate(getHmac(key, prefix.getBytes(), mac), 10);
|
||||
byte[] theirSuffix = Hex.decodeHex(suffix.toCharArray());
|
||||
|
||||
return MessageDigest.isEqual(ourSuffix, theirSuffix);
|
||||
} catch (DecoderException e) {
|
||||
logger.warn("DirectoryCredentials", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Mac getMacInstance() {
|
||||
try {
|
||||
return Mac.getInstance("HmacSHA256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getHmac(byte[] key, byte[] input, Mac mac) {
|
||||
try {
|
||||
mac.init(new SecretKeySpec(key, "HmacSHA256"));
|
||||
return mac.doFinal(input);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class ExternalServiceCredentials {
|
||||
|
||||
@JsonProperty
|
||||
private String username;
|
||||
|
||||
@JsonProperty
|
||||
private String password;
|
||||
|
||||
public ExternalServiceCredentials(String username, String password) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public ExternalServiceCredentials() {}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
|
||||
public class InvalidAuthorizationHeaderException extends Exception {
|
||||
public InvalidAuthorizationHeaderException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
public InvalidAuthorizationHeaderException(Exception e) {
|
||||
super(e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Optional;
|
||||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
public class OptionalAccess {
|
||||
|
||||
public static final String UNIDENTIFIED = "Unidentified-Access-Key";
|
||||
|
||||
public static void verify(Optional<Account> requestAccount,
|
||||
Optional<Anonymous> accessKey,
|
||||
Optional<Account> targetAccount,
|
||||
String deviceSelector)
|
||||
{
|
||||
try {
|
||||
verify(requestAccount, accessKey, targetAccount);
|
||||
|
||||
if (!deviceSelector.equals("*")) {
|
||||
long deviceId = Long.parseLong(deviceSelector);
|
||||
|
||||
Optional<Device> targetDevice = targetAccount.get().getDevice(deviceId);
|
||||
|
||||
if (targetDevice.isPresent() && targetDevice.get().isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (requestAccount.isPresent()) {
|
||||
throw new WebApplicationException(Response.Status.NOT_FOUND);
|
||||
} else {
|
||||
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
throw new WebApplicationException(Response.status(422).build());
|
||||
}
|
||||
}
|
||||
|
||||
public static void verify(Optional<Account> requestAccount,
|
||||
Optional<Anonymous> accessKey,
|
||||
Optional<Account> targetAccount)
|
||||
{
|
||||
if (requestAccount.isPresent() && targetAccount.isPresent() && targetAccount.get().isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
//noinspection ConstantConditions
|
||||
if (requestAccount.isPresent() && (targetAccount.isEmpty() || (targetAccount.isPresent() && !targetAccount.get().isEnabled()))) {
|
||||
throw new WebApplicationException(Response.Status.NOT_FOUND);
|
||||
}
|
||||
|
||||
if (accessKey.isPresent() && targetAccount.isPresent() && targetAccount.get().isEnabled() && targetAccount.get().isUnrestrictedUnidentifiedAccess()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (accessKey.isPresent() &&
|
||||
targetAccount.isPresent() &&
|
||||
targetAccount.get().getUnidentifiedAccessKey().isPresent() &&
|
||||
targetAccount.get().isEnabled() &&
|
||||
MessageDigest.isEqual(accessKey.get().getAccessKey(), targetAccount.get().getUnidentifiedAccessKey().get()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
public class StoredRegistrationLock {
|
||||
|
||||
private final Optional<String> registrationLock;
|
||||
|
||||
private final Optional<String> registrationLockSalt;
|
||||
|
||||
private final Optional<String> deprecatedPin;
|
||||
|
||||
private final long lastSeen;
|
||||
|
||||
public StoredRegistrationLock(Optional<String> registrationLock, Optional<String> registrationLockSalt, Optional<String> deprecatedPin, long lastSeen) {
|
||||
this.registrationLock = registrationLock;
|
||||
this.registrationLockSalt = registrationLockSalt;
|
||||
this.deprecatedPin = deprecatedPin;
|
||||
this.lastSeen = lastSeen;
|
||||
}
|
||||
|
||||
public boolean requiresClientRegistrationLock() {
|
||||
return ((registrationLock.isPresent() && registrationLockSalt.isPresent()) || deprecatedPin.isPresent()) && System.currentTimeMillis() - lastSeen < TimeUnit.DAYS.toMillis(7);
|
||||
}
|
||||
|
||||
public boolean needsFailureCredentials() {
|
||||
return registrationLock.isPresent() && registrationLockSalt.isPresent();
|
||||
}
|
||||
|
||||
public boolean hasDeprecatedPin() {
|
||||
return deprecatedPin.isPresent();
|
||||
}
|
||||
|
||||
public long getTimeRemaining() {
|
||||
return TimeUnit.DAYS.toMillis(7) - (System.currentTimeMillis() - lastSeen);
|
||||
}
|
||||
|
||||
public boolean verify(@Nullable String clientRegistrationLock, @Nullable String clientDeprecatedPin) {
|
||||
if (Util.isEmpty(clientRegistrationLock) && Util.isEmpty(clientDeprecatedPin)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (registrationLock.isPresent() && registrationLockSalt.isPresent() && !Util.isEmpty(clientRegistrationLock)) {
|
||||
return new AuthenticationCredentials(registrationLock.get(), registrationLockSalt.get()).verify(clientRegistrationLock);
|
||||
} else if (deprecatedPin.isPresent() && !Util.isEmpty(clientDeprecatedPin)) {
|
||||
return MessageDigest.isEqual(deprecatedPin.get().getBytes(), clientDeprecatedPin.getBytes());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public StoredRegistrationLock forTime(long timestamp) {
|
||||
return new StoredRegistrationLock(registrationLock, registrationLockSalt, deprecatedPin, timestamp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class StoredVerificationCode {
|
||||
|
||||
@JsonProperty
|
||||
private String code;
|
||||
|
||||
@JsonProperty
|
||||
private long timestamp;
|
||||
|
||||
@JsonProperty
|
||||
private String pushCode;
|
||||
|
||||
@JsonProperty
|
||||
private String twilioVerificationSid;
|
||||
|
||||
public StoredVerificationCode() {
|
||||
}
|
||||
|
||||
public StoredVerificationCode(String code, long timestamp, String pushCode) {
|
||||
this(code, timestamp, pushCode, null);
|
||||
}
|
||||
|
||||
public StoredVerificationCode(String code, long timestamp, String pushCode, String twilioVerificationSid) {
|
||||
this.code = code;
|
||||
this.timestamp = timestamp;
|
||||
this.pushCode = pushCode;
|
||||
this.twilioVerificationSid = twilioVerificationSid;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public String getPushCode() {
|
||||
return pushCode;
|
||||
}
|
||||
|
||||
public Optional<String> getTwilioVerificationSid() {
|
||||
return Optional.ofNullable(twilioVerificationSid);
|
||||
}
|
||||
|
||||
public boolean isValid(String theirCodeString) {
|
||||
if (timestamp + TimeUnit.MINUTES.toMillis(10) < System.currentTimeMillis()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Util.isEmpty(code) || Util.isEmpty(theirCodeString)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
byte[] ourCode = code.getBytes();
|
||||
byte[] theirCode = theirCodeString.getBytes();
|
||||
|
||||
return MessageDigest.isEqual(ourCode, theirCode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TurnToken {
|
||||
|
||||
@JsonProperty
|
||||
private String username;
|
||||
|
||||
@JsonProperty
|
||||
private String password;
|
||||
|
||||
@JsonProperty
|
||||
private List<String> urls;
|
||||
|
||||
public TurnToken(String username, String password, List<String> urls) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.urls = urls;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import org.whispersystems.textsecuregcm.configuration.TurnConfiguration;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class TurnTokenGenerator {
|
||||
|
||||
private final byte[] key;
|
||||
private final List<String> urls;
|
||||
|
||||
public TurnTokenGenerator(TurnConfiguration configuration) {
|
||||
this.key = configuration.getSecret().getBytes();
|
||||
this.urls = configuration.getUris();
|
||||
}
|
||||
|
||||
public TurnToken generate() {
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA1");
|
||||
long validUntilSeconds = (System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1)) / 1000;
|
||||
long user = Math.abs(new SecureRandom().nextInt());
|
||||
String userTime = validUntilSeconds + ":" + user;
|
||||
|
||||
mac.init(new SecretKeySpec(key, "HmacSHA1"));
|
||||
String password = Base64.getEncoder().encodeToString(mac.doFinal(userTime.getBytes()));
|
||||
|
||||
return new TurnToken(userTime, password, urls);
|
||||
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Base64;
|
||||
import java.util.Optional;
|
||||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
public class UnidentifiedAccessChecksum {
|
||||
|
||||
public static String generateFor(Optional<byte[]> unidentifiedAccessKey) {
|
||||
try {
|
||||
if (!unidentifiedAccessKey.isPresent()|| unidentifiedAccessKey.get().length != 16) return null;
|
||||
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(unidentifiedAccessKey.get(), "HmacSHA256"));
|
||||
|
||||
return Base64.getEncoder().encodeToString(mac.doFinal(new byte[32]));
|
||||
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class AccountDatabaseCrawlerConfiguration {
|
||||
|
||||
@JsonProperty
|
||||
private int chunkSize = 1000;
|
||||
|
||||
@JsonProperty
|
||||
private long chunkIntervalMs = 8000L;
|
||||
|
||||
public int getChunkSize() {
|
||||
return chunkSize;
|
||||
}
|
||||
|
||||
public long getChunkIntervalMs() {
|
||||
return chunkIntervalMs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
public class AccountsDatabaseConfiguration extends DatabaseConfiguration {
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
@Valid
|
||||
private RetryConfiguration keyOperationRetry = new RetryConfiguration();
|
||||
|
||||
public RetryConfiguration getKeyOperationRetryConfiguration() {
|
||||
return keyOperationRetry;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
public class AccountsDynamoDbConfiguration extends DynamoDbConfiguration {
|
||||
|
||||
@NotNull
|
||||
private String phoneNumberTableName;
|
||||
|
||||
public String getPhoneNumberTableName() {
|
||||
return phoneNumberTableName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
|
||||
public class ApnConfiguration {
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String teamId;
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String keyId;
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String signingKey;
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String bundleId;
|
||||
|
||||
@JsonProperty
|
||||
private boolean sandbox = false;
|
||||
|
||||
public String getTeamId() {
|
||||
return teamId;
|
||||
}
|
||||
|
||||
public String getKeyId() {
|
||||
return keyId;
|
||||
}
|
||||
|
||||
public String getSigningKey() {
|
||||
return signingKey;
|
||||
}
|
||||
|
||||
public String getBundleId() {
|
||||
return bundleId;
|
||||
}
|
||||
|
||||
public boolean isSandboxEnabled() {
|
||||
return sandbox;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
public class AppConfigConfiguration {
|
||||
|
||||
@JsonProperty
|
||||
@NotEmpty
|
||||
private String application;
|
||||
|
||||
@JsonProperty
|
||||
@NotEmpty
|
||||
private String environment;
|
||||
|
||||
@JsonProperty
|
||||
@NotEmpty
|
||||
private String configuration;
|
||||
|
||||
public String getApplication() {
|
||||
return application;
|
||||
}
|
||||
|
||||
public String getEnvironment() {
|
||||
return environment;
|
||||
}
|
||||
|
||||
public String getConfigurationName() {
|
||||
return configuration;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
public class AwsAttachmentsConfiguration {
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String accessKey;
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String accessSecret;
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String bucket;
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String region;
|
||||
|
||||
public String getAccessKey() {
|
||||
return accessKey;
|
||||
}
|
||||
|
||||
public String getAccessSecret() {
|
||||
return accessSecret;
|
||||
}
|
||||
|
||||
public String getBucket() {
|
||||
return bucket;
|
||||
}
|
||||
|
||||
public String getRegion() {
|
||||
return region;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
public class CdnConfiguration {
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String accessKey;
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String accessSecret;
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String bucket;
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String region;
|
||||
|
||||
public String getAccessKey() {
|
||||
return accessKey;
|
||||
}
|
||||
|
||||
public String getAccessSecret() {
|
||||
return accessSecret;
|
||||
}
|
||||
|
||||
public String getBucket() {
|
||||
return bucket;
|
||||
}
|
||||
|
||||
public String getRegion() {
|
||||
return region;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import javax.validation.constraints.Max;
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
|
||||
|
||||
public class CircuitBreakerConfiguration {
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
@Min(1)
|
||||
@Max(100)
|
||||
private int failureRateThreshold = 50;
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
@Min(1)
|
||||
private int ringBufferSizeInHalfOpenState = 10;
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
@Min(1)
|
||||
private int ringBufferSizeInClosedState = 100;
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
@Min(1)
|
||||
private long waitDurationInOpenStateInSeconds = 10;
|
||||
|
||||
|
||||
public int getFailureRateThreshold() {
|
||||
return failureRateThreshold;
|
||||
}
|
||||
|
||||
public int getRingBufferSizeInHalfOpenState() {
|
||||
return ringBufferSizeInHalfOpenState;
|
||||
}
|
||||
|
||||
public int getRingBufferSizeInClosedState() {
|
||||
return ringBufferSizeInClosedState;
|
||||
}
|
||||
|
||||
public long getWaitDurationInOpenStateInSeconds() {
|
||||
return waitDurationInOpenStateInSeconds;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setFailureRateThreshold(int failureRateThreshold) {
|
||||
this.failureRateThreshold = failureRateThreshold;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setRingBufferSizeInClosedState(int size) {
|
||||
this.ringBufferSizeInClosedState = size;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setRingBufferSizeInHalfOpenState(int size) {
|
||||
this.ringBufferSizeInHalfOpenState = size;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setWaitDurationInOpenStateInSeconds(int seconds) {
|
||||
this.waitDurationInOpenStateInSeconds = seconds;
|
||||
}
|
||||
|
||||
public CircuitBreakerConfig toCircuitBreakerConfig() {
|
||||
return CircuitBreakerConfig.custom()
|
||||
.failureRateThreshold(getFailureRateThreshold())
|
||||
.ringBufferSizeInHalfOpenState(getRingBufferSizeInHalfOpenState())
|
||||
.waitDurationInOpenState(Duration.ofSeconds(getWaitDurationInOpenStateInSeconds()))
|
||||
.ringBufferSizeInClosedState(getRingBufferSizeInClosedState())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import io.dropwizard.db.DataSourceFactory;
|
||||
|
||||
public class DatabaseConfiguration extends DataSourceFactory {
|
||||
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private CircuitBreakerConfiguration circuitBreaker = new CircuitBreakerConfiguration();
|
||||
|
||||
public CircuitBreakerConfiguration getCircuitBreakerConfiguration() {
|
||||
return circuitBreaker;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2013-2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.time.Duration;
|
||||
|
||||
public class DatadogConfiguration {
|
||||
|
||||
@JsonProperty
|
||||
@NotBlank
|
||||
private String apiKey;
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
private Duration step = Duration.ofSeconds(10);
|
||||
|
||||
public String getApiKey() {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
public Duration getStep() {
|
||||
return step;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import org.apache.commons.codec.DecoderException;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
|
||||
public class DirectoryClientConfiguration {
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String userAuthenticationTokenSharedSecret;
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String userAuthenticationTokenUserIdSecret;
|
||||
|
||||
public byte[] getUserAuthenticationTokenSharedSecret() throws DecoderException {
|
||||
return Hex.decodeHex(userAuthenticationTokenSharedSecret.toCharArray());
|
||||
}
|
||||
|
||||
public byte[] getUserAuthenticationTokenUserIdSecret() throws DecoderException {
|
||||
return Hex.decodeHex(userAuthenticationTokenUserIdSecret.toCharArray());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
public class DirectoryConfiguration {
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
@Valid
|
||||
private SqsConfiguration sqs;
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
@Valid
|
||||
private DirectoryClientConfiguration client;
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
@Valid
|
||||
private List<DirectoryServerConfiguration> server;
|
||||
|
||||
public SqsConfiguration getSqsConfiguration() {
|
||||
return sqs;
|
||||
}
|
||||
|
||||
public DirectoryClientConfiguration getDirectoryClientConfiguration() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public List<DirectoryServerConfiguration> getDirectoryServerConfiguration() {
|
||||
return server;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
public class DirectoryServerConfiguration {
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String replicationName;
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String replicationUrl;
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String replicationPassword;
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String replicationCaCertificate;
|
||||
|
||||
public String getReplicationName() {
|
||||
return replicationName;
|
||||
}
|
||||
|
||||
public String getReplicationUrl() {
|
||||
return replicationUrl;
|
||||
}
|
||||
|
||||
public String getReplicationPassword() {
|
||||
return replicationPassword;
|
||||
}
|
||||
|
||||
public String getReplicationCaCertificate() {
|
||||
return replicationCaCertificate;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import java.util.Set;
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
public class DonationConfiguration {
|
||||
|
||||
private String uri;
|
||||
private String apiKey;
|
||||
private String description;
|
||||
private Set<String> supportedCurrencies;
|
||||
private CircuitBreakerConfiguration circuitBreaker = new CircuitBreakerConfiguration();
|
||||
private RetryConfiguration retry = new RetryConfiguration();
|
||||
|
||||
@JsonProperty
|
||||
@NotEmpty
|
||||
public String getUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setUri(final String uri) {
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
@JsonProperty
|
||||
@NotEmpty
|
||||
public String getApiKey() {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setApiKey(final String apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
}
|
||||
|
||||
@JsonProperty
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setDescription(final String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@JsonProperty
|
||||
@NotEmpty
|
||||
public Set<String> getSupportedCurrencies() {
|
||||
return supportedCurrencies;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setSupportedCurrencies(final Set<String> supportedCurrencies) {
|
||||
this.supportedCurrencies = supportedCurrencies;
|
||||
}
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
@Valid
|
||||
public CircuitBreakerConfiguration getCircuitBreaker() {
|
||||
return circuitBreaker;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setCircuitBreaker(final CircuitBreakerConfiguration circuitBreaker) {
|
||||
this.circuitBreaker = circuitBreaker;
|
||||
}
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
@Valid
|
||||
public RetryConfiguration getRetry() {
|
||||
return retry;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setRetry(final RetryConfiguration retry) {
|
||||
this.retry = retry;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import java.time.Duration;
|
||||
|
||||
public class DynamoDbConfiguration {
|
||||
|
||||
private String region;
|
||||
private String tableName;
|
||||
private Duration clientExecutionTimeout = Duration.ofSeconds(30);
|
||||
private Duration clientRequestTimeout = Duration.ofSeconds(10);
|
||||
|
||||
@Valid
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
public String getRegion() {
|
||||
return region;
|
||||
}
|
||||
|
||||
@Valid
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
public String getTableName() {
|
||||
return tableName;
|
||||
}
|
||||
|
||||
@JsonProperty
|
||||
public Duration getClientExecutionTimeout() {
|
||||
return clientExecutionTimeout;
|
||||
}
|
||||
|
||||
@JsonProperty
|
||||
public Duration getClientRequestTimeout() {
|
||||
return clientRequestTimeout;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
public class GcmConfiguration {
|
||||
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private long senderId;
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String apiKey;
|
||||
|
||||
public String getApiKey() {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
public long getSenderId() {
|
||||
return senderId;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.dropwizard.util.Strings;
|
||||
import io.dropwizard.validation.ValidationMethod;
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
public class GcpAttachmentsConfiguration {
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String domain;
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String email;
|
||||
|
||||
@JsonProperty
|
||||
@Min(1)
|
||||
private int maxSizeInBytes;
|
||||
|
||||
@JsonProperty
|
||||
private String pathPrefix;
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String rsaSigningKey;
|
||||
|
||||
public String getDomain() {
|
||||
return domain;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public int getMaxSizeInBytes() {
|
||||
return maxSizeInBytes;
|
||||
}
|
||||
|
||||
public String getPathPrefix() {
|
||||
return pathPrefix;
|
||||
}
|
||||
|
||||
public String getRsaSigningKey() {
|
||||
return rsaSigningKey;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@ValidationMethod(message = "pathPrefix must be empty or start with /")
|
||||
public boolean isPathPrefixValid() {
|
||||
return Strings.isNullOrEmpty(pathPrefix) || pathPrefix.startsWith("/");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class GraphiteConfiguration {
|
||||
@JsonProperty
|
||||
private String host;
|
||||
|
||||
@JsonProperty
|
||||
private int port;
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return host != null && port != 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
public class MaxDeviceConfiguration {
|
||||
|
||||
@JsonProperty
|
||||
@NotEmpty
|
||||
private String number;
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
private int count;
|
||||
|
||||
public String getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
public class MessageCacheConfiguration {
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
@Valid
|
||||
private RedisClusterConfiguration cluster;
|
||||
|
||||
@JsonProperty
|
||||
private int persistDelayMinutes = 10;
|
||||
|
||||
public RedisClusterConfiguration getRedisClusterConfiguration() {
|
||||
return cluster;
|
||||
}
|
||||
|
||||
public int getPersistDelayMinutes() {
|
||||
return persistDelayMinutes;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import java.time.Duration;
|
||||
|
||||
public class MessageDynamoDbConfiguration extends DynamoDbConfiguration {
|
||||
|
||||
private Duration timeToLive = Duration.ofDays(7);
|
||||
|
||||
@Valid
|
||||
public Duration getTimeToLive() {
|
||||
return timeToLive;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import java.time.Duration;
|
||||
|
||||
public class MonitoredS3ObjectConfiguration {
|
||||
|
||||
@JsonProperty
|
||||
@NotBlank
|
||||
private String s3Region;
|
||||
|
||||
@JsonProperty
|
||||
@NotBlank
|
||||
private String s3Bucket;
|
||||
|
||||
@JsonProperty
|
||||
@NotBlank
|
||||
private String objectKey;
|
||||
|
||||
@JsonProperty
|
||||
private long maxSize = 16 * 1024 * 1024;
|
||||
|
||||
@JsonProperty
|
||||
private Duration refreshInterval = Duration.ofMinutes(5);
|
||||
|
||||
public String getS3Region() {
|
||||
return s3Region;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setS3Region(final String s3Region) {
|
||||
this.s3Region = s3Region;
|
||||
}
|
||||
|
||||
public String getS3Bucket() {
|
||||
return s3Bucket;
|
||||
}
|
||||
|
||||
public String getObjectKey() {
|
||||
return objectKey;
|
||||
}
|
||||
|
||||
public long getMaxSize() {
|
||||
return maxSize;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setMaxSize(final long maxSize) {
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
|
||||
public Duration getRefreshInterval() {
|
||||
return refreshInterval;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.apache.commons.codec.DecoderException;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import java.util.List;
|
||||
|
||||
public class PaymentsServiceConfiguration {
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String userAuthenticationTokenSharedSecret;
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String fixerApiKey;
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private List<String> paymentCurrencies;
|
||||
|
||||
public byte[] getUserAuthenticationTokenSharedSecret() throws DecoderException {
|
||||
return Hex.decodeHex(userAuthenticationTokenSharedSecret.toCharArray());
|
||||
}
|
||||
|
||||
public String getFixerApiKey() {
|
||||
return fixerApiKey;
|
||||
}
|
||||
|
||||
public List<String> getPaymentCurrencies() {
|
||||
return paymentCurrencies;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import javax.validation.constraints.Min;
|
||||
|
||||
public class PushConfiguration {
|
||||
|
||||
@JsonProperty
|
||||
@Min(0)
|
||||
private int queueSize = 200;
|
||||
|
||||
public int getQueueSize() {
|
||||
return queueSize;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.time.Duration;
|
||||
|
||||
public class RateLimitsConfiguration {
|
||||
|
||||
@JsonProperty
|
||||
private RateLimitConfiguration smsDestination = new RateLimitConfiguration(2, 2);
|
||||
|
||||
@JsonProperty
|
||||
private RateLimitConfiguration voiceDestination = new RateLimitConfiguration(2, 1.0 / 2.0);
|
||||
|
||||
@JsonProperty
|
||||
private RateLimitConfiguration voiceDestinationDaily = new RateLimitConfiguration(10, 10.0 / (24.0 * 60.0));
|
||||
|
||||
@JsonProperty
|
||||
private RateLimitConfiguration smsVoiceIp = new RateLimitConfiguration(1000, 1000);
|
||||
|
||||
@JsonProperty
|
||||
private RateLimitConfiguration smsVoicePrefix = new RateLimitConfiguration(1000, 1000);
|
||||
|
||||
@JsonProperty
|
||||
private RateLimitConfiguration autoBlock = new RateLimitConfiguration(500, 500);
|
||||
|
||||
@JsonProperty
|
||||
private RateLimitConfiguration verifyNumber = new RateLimitConfiguration(2, 2);
|
||||
|
||||
@JsonProperty
|
||||
private RateLimitConfiguration verifyPin = new RateLimitConfiguration(10, 1 / (24.0 * 60.0));
|
||||
|
||||
@JsonProperty
|
||||
private RateLimitConfiguration attachments = new RateLimitConfiguration(50, 50);
|
||||
|
||||
@JsonProperty
|
||||
private RateLimitConfiguration prekeys = new RateLimitConfiguration(3, 1.0 / 10.0);
|
||||
|
||||
@JsonProperty
|
||||
private RateLimitConfiguration messages = new RateLimitConfiguration(60, 60);
|
||||
|
||||
@JsonProperty
|
||||
private RateLimitConfiguration allocateDevice = new RateLimitConfiguration(2, 1.0 / 2.0);
|
||||
|
||||
@JsonProperty
|
||||
private RateLimitConfiguration verifyDevice = new RateLimitConfiguration(2, 2);
|
||||
|
||||
@JsonProperty
|
||||
private RateLimitConfiguration turnAllocations = new RateLimitConfiguration(60, 60);
|
||||
|
||||
@JsonProperty
|
||||
private RateLimitConfiguration profile = new RateLimitConfiguration(4320, 3);
|
||||
|
||||
@JsonProperty
|
||||
private RateLimitConfiguration stickerPack = new RateLimitConfiguration(50, 20 / (24.0 * 60.0));
|
||||
|
||||
@JsonProperty
|
||||
private RateLimitConfiguration usernameLookup = new RateLimitConfiguration(100, 100 / (24.0 * 60.0));
|
||||
|
||||
@JsonProperty
|
||||
private RateLimitConfiguration usernameSet = new RateLimitConfiguration(100, 100 / (24.0 * 60.0));
|
||||
|
||||
public RateLimitConfiguration getAutoBlock() {
|
||||
return autoBlock;
|
||||
}
|
||||
|
||||
public RateLimitConfiguration getAllocateDevice() {
|
||||
return allocateDevice;
|
||||
}
|
||||
|
||||
public RateLimitConfiguration getVerifyDevice() {
|
||||
return verifyDevice;
|
||||
}
|
||||
|
||||
public RateLimitConfiguration getMessages() {
|
||||
return messages;
|
||||
}
|
||||
|
||||
public RateLimitConfiguration getPreKeys() {
|
||||
return prekeys;
|
||||
}
|
||||
|
||||
public RateLimitConfiguration getAttachments() {
|
||||
return attachments;
|
||||
}
|
||||
|
||||
public RateLimitConfiguration getSmsDestination() {
|
||||
return smsDestination;
|
||||
}
|
||||
|
||||
public RateLimitConfiguration getVoiceDestination() {
|
||||
return voiceDestination;
|
||||
}
|
||||
|
||||
public RateLimitConfiguration getVoiceDestinationDaily() {
|
||||
return voiceDestinationDaily;
|
||||
}
|
||||
|
||||
public RateLimitConfiguration getSmsVoiceIp() {
|
||||
return smsVoiceIp;
|
||||
}
|
||||
|
||||
public RateLimitConfiguration getSmsVoicePrefix() {
|
||||
return smsVoicePrefix;
|
||||
}
|
||||
|
||||
public RateLimitConfiguration getVerifyNumber() {
|
||||
return verifyNumber;
|
||||
}
|
||||
|
||||
public RateLimitConfiguration getVerifyPin() {
|
||||
return verifyPin;
|
||||
}
|
||||
|
||||
public RateLimitConfiguration getTurnAllocations() {
|
||||
return turnAllocations;
|
||||
}
|
||||
|
||||
public RateLimitConfiguration getProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
public RateLimitConfiguration getStickerPack() {
|
||||
return stickerPack;
|
||||
}
|
||||
|
||||
public RateLimitConfiguration getUsernameLookup() {
|
||||
return usernameLookup;
|
||||
}
|
||||
|
||||
public RateLimitConfiguration getUsernameSet() {
|
||||
return usernameSet;
|
||||
}
|
||||
|
||||
public static class RateLimitConfiguration {
|
||||
@JsonProperty
|
||||
private int bucketSize;
|
||||
|
||||
@JsonProperty
|
||||
private double leakRatePerMinute;
|
||||
|
||||
public RateLimitConfiguration(int bucketSize, double leakRatePerMinute) {
|
||||
this.bucketSize = bucketSize;
|
||||
this.leakRatePerMinute = leakRatePerMinute;
|
||||
}
|
||||
|
||||
public RateLimitConfiguration() {}
|
||||
|
||||
public int getBucketSize() {
|
||||
return bucketSize;
|
||||
}
|
||||
|
||||
public double getLeakRatePerMinute() {
|
||||
return leakRatePerMinute;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CardinalityRateLimitConfiguration {
|
||||
@JsonProperty
|
||||
private int maxCardinality;
|
||||
|
||||
@JsonProperty
|
||||
private Duration ttl;
|
||||
|
||||
public CardinalityRateLimitConfiguration() {
|
||||
}
|
||||
|
||||
public CardinalityRateLimitConfiguration(int maxCardinality, Duration ttl) {
|
||||
this.maxCardinality = maxCardinality;
|
||||
this.ttl = ttl;
|
||||
}
|
||||
|
||||
public int getMaxCardinality() {
|
||||
return maxCardinality;
|
||||
}
|
||||
|
||||
public Duration getTtl() {
|
||||
return ttl;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user