Compare commits

...

695 Commits

Author SHA1 Message Date
Jon Chambers
46d64b949e Don't read "soft-deleted" profiles
Nothing is actually "soft-deleting" profiles yet, and this
is a first step toward migrating profiles to a new data
store.
2021-11-24 12:02:07 -05:00
Jon Chambers
6919354520 Fix a counting bug with reported messages 2021-11-23 17:28:39 -05:00
Jon Chambers
a42fe9bfb0 Add crawler names to log messages 2021-11-23 16:22:09 -05:00
Jon Chambers
ee1f8b34ea Add a command for reserving usernames 2021-11-23 16:21:03 -05:00
Jon Chambers
c910fa406d Migrate reserved usernames from a relational database to DynamoDB 2021-11-23 16:21:03 -05:00
Jon Chambers
559205e33f Log cases where accounts are missing or have inconsistent PNIs 2021-11-23 15:40:31 -05:00
Chris Eager
c0756e9c60 Attempt an orderly websocket close on displacement before a hard disconnect 2021-11-23 11:36:32 -07:00
Ehren Kret
bf1190696e Add badge workaround for old Android builds 2021-11-23 09:58:06 -06:00
Jon Chambers
71dd0890de Restore an accidentally-removed PNI consistency check 2021-11-23 10:53:40 -05:00
Jon Chambers
e5acdf1402 Don't update the PNI attribute during general account updates 2021-11-23 10:53:40 -05:00
Jon Chambers
0f08b6bb59 Drop "got successful captcha" messages from INFO to DEBUG 2021-11-22 17:06:34 -05:00
Jon Chambers
6198a7b69a Remove spurious @JsonProperty annotations 2021-11-22 15:43:09 -05:00
Jon Chambers
067aee6664 Remove unused properties from OutgoingMessageEntity 2021-11-22 15:43:09 -05:00
Jon Chambers
138a2ebbd0 Drop transactional logic from phone number identifier migration 2021-11-22 15:32:24 -05:00
Jon Chambers
296f6a7a88 Make phone number identifiers non-optional 2021-11-22 15:32:24 -05:00
Jon Chambers
069ffa9921 Drop PNI migration tools 2021-11-22 15:32:24 -05:00
Jon Chambers
f42fd8a840 Retire unused diagnostic metrics 2021-11-22 15:31:16 -05:00
Jon Chambers
10f27af6f2 Retire old unsealed-sender meters 2021-11-22 15:31:16 -05:00
Jon Chambers
0bbd34d060 Use text blocks where possible 2021-11-22 15:30:31 -05:00
Jon Chambers
282daeb0dc Add a command to assign PNIs to accounts that don't already have one 2021-11-22 15:03:19 -05:00
Jon Chambers
d33b313c11 Break down legacy "get profile" requests by platform 2021-11-19 12:37:39 -05:00
Ehren Kret
fb7316c9ae Return subscription status string in GET 2021-11-19 11:36:01 -06:00
Ehren Kret
279b0a51d9 Use latest invoice on subscription to generate receipts 2021-11-19 11:25:38 -06:00
Ehren Kret
6547d5ebf3 More consistent naming of receipt credentials endpoints 2021-11-19 10:14:00 -06:00
Ehren Kret
4f1ef9a039 Add additional http status codes to /v1/subscription/boost/receipt_credentials 2021-11-19 10:11:33 -06:00
Ehren Kret
4c80714d19 Update sample.yml 2021-11-18 10:49:30 -06:00
Jon Chambers
077ead71a5 Rename legacy profile methods to separate them in metrics 2021-11-18 11:31:15 -05:00
Ehren Kret
caba110266 Revert "Revert "Remove transparent SVG for badging""
This reverts commit 0fdb23c1e9.
2021-11-18 10:23:09 -06:00
Ehren Kret
0fdb23c1e9 Revert "Remove transparent SVG for badging"
This reverts commit 13a84f0c72.
2021-11-18 10:09:34 -06:00
Ehren Kret
13a84f0c72 Remove transparent SVG for badging 2021-11-18 10:04:42 -06:00
Jon Chambers
669bd58e33 Drop the unused Key utility class 2021-11-17 10:57:29 -05:00
Chris Eager
6e82740a9b Update sample.yml 2021-11-16 17:25:45 -07:00
Ehren Kret
7ea43a728d Set boost description from configuration 2021-11-16 17:21:57 -06:00
Chris Eager
71b38356b1 Update to Mockito 4.0.0 2021-11-16 15:56:35 -07:00
Chris Eager
5a99708f56 Update some deprecated usages 2021-11-16 15:56:13 -07:00
Chris Eager
24191d9599 Update Dropwizard to 2.0.25 2021-11-16 15:56:00 -07:00
Chris Eager
482ea8eb40 Update minimum required maven to 3.8.3 2021-11-16 15:55:52 -07:00
Jon Chambers
1dae05651f Add PNIs to account creation and whoami responses 2021-11-16 15:08:10 -05:00
Jon Chambers
5164e92538 Shorten metric names 2021-11-16 15:08:10 -05:00
Jon Chambers
f89a20dbc7 Allow callers to set/retrieve keys by ACI or PNI 2021-11-16 15:08:10 -05:00
Jon Chambers
3a4c5a2bfb Store and retrieve one-time pre-keys by UUID 2021-11-16 15:08:10 -05:00
Jon Chambers
5e1334e8de s/KeysDynamoDb/Keys/ 2021-11-16 15:08:10 -05:00
Jon Chambers
fa6e3d3690 Allow clients to request PNI-based group credentials 2021-11-16 15:08:10 -05:00
Jon Chambers
9383e7716b Resolve CertificateControllerTest warnings and recommendations 2021-11-16 15:08:10 -05:00
Jon Chambers
cfe34fbf0f Allow unsealed-sender messages to be addressed by PNI 2021-11-16 15:08:10 -05:00
Jon Chambers
9fe110625c Add a destinationUuid field to envelopes 2021-11-16 15:08:10 -05:00
Jon Chambers
975f753c2b Add an endpoint for testing whether an account with a given ACI or PNI exists 2021-11-16 15:08:10 -05:00
Jon Chambers
e6237480f8 Require that unidentified access keys be exactly 16 bytes 2021-11-16 15:08:10 -05:00
Chris Eager
966d4e29d4 Update sample.yml config to pass mvn verify 2021-11-16 11:43:07 -07:00
Chris Eager
26f876a2cb Check service configurations in verify phase 2021-11-16 11:43:07 -07:00
Jon Chambers
ab9e6ac48a Revert "Replace zkgroup with libsignal-client"
This reverts commit 73ea6e4251.
2021-11-16 11:35:10 -05:00
Jon Chambers
c1d6c04ab2 Revert "Replace curve25519-java with libsignal-client"
This reverts commit 0011b8925b.
2021-11-16 11:35:10 -05:00
Jon Chambers
888cec3d56 Introduce a filter for correcting numeric "online" flags 2021-11-16 10:15:14 -05:00
Jon Chambers
1461bcc2c2 Correct envelope types for certain iOS builds 2021-11-16 10:15:14 -05:00
Jon Chambers
11f1cf80bd Move MessageControllerTest out of the tests sub-package to expose package-private elements from the class under test 2021-11-16 10:15:14 -05:00
Jordan Rose
c675cc8b26 Test the response code for invalid serialized zkgroup objects
Test by Jon, making sure this is consistent even without up-front size
checking.
2021-11-16 09:52:38 -05:00
Jordan Rose
0011b8925b Replace curve25519-java with libsignal-client
These APIs stemmed from a common source long ago, so there's not much
to change!
2021-11-16 09:52:38 -05:00
Jordan Rose
73ea6e4251 Replace zkgroup with libsignal-client 2021-11-16 09:52:38 -05:00
Jon Chambers
e4441dddbb Consolidate Redis client resources 2021-11-16 09:52:12 -05:00
Chris Eager
8d1d56f694 Update to Java 17 2021-11-15 16:42:43 -07:00
Jon Chambers
2015ba77ca Switch to a disallowed prefix model instead of a disallowed country code model 2021-11-15 15:44:55 -05:00
Chris Eager
7033a0f68f Set checkStaleness to true for protoc 2021-11-12 13:34:15 -07:00
Jon Chambers
6ada76da7f Parallelize assignment of phone number identifiers 2021-11-12 11:03:46 -05:00
Jon Chambers
cbdec0cb22 Remove legacy push latency measurement pathways 2021-11-11 15:44:07 -05:00
Chris Eager
de6e9d31c9 Add dedicated crawler for directory reconciler 2021-11-11 13:38:13 -07:00
Jon Chambers
f0a6be32fc Add a crawler to assign PNIs to existing accounts 2021-11-10 11:15:05 -05:00
Ehren Kret
5c4855cca6 Remove trailing space 2021-11-10 10:11:49 -06:00
Ehren Kret
2e1e380418 Also update description text of boost badge 2021-11-10 10:10:53 -06:00
Ehren Kret
d07f0b4f71 Update badge description text 2021-11-10 09:51:51 -06:00
Jon Chambers
aaa2a6eef1 Break down push latency metrics by VOIP/not-VOIP and optionally by client version 2021-11-10 10:35:41 -05:00
Jon Chambers
b1f56c3324 Resolve formatting complaints 2021-11-10 10:15:14 -05:00
Jon Chambers
da5c0ae4b6 Enable Payments Beta for more country codes 2021-11-10 10:15:14 -05:00
Jon Chambers
1e1394560d Check length of cancellation reason list before getting reason codes 2021-11-09 11:42:44 -05:00
Jon Chambers
bae0196bcf Tolerate null UUID attribute values 2021-11-09 11:00:27 -05:00
Jon Chambers
3398955c1a Add basic support for phone number identifiers 2021-11-09 10:23:08 -05:00
Chris Eager
a1b925d1e0 Reduce visiblity on one constructor 2021-11-08 14:30:52 -07:00
Chris Eager
31c0c3275f Use the latest and in config, @JsonCreator 2021-11-08 14:30:52 -07:00
Chris Eager
0a4392f700 Streamline ExternalServiceCredentialsGenerator construction 2021-11-08 14:30:52 -07:00
Chris Eager
eb86986cf4 Add /v2/directory/auth endpoint 2021-11-08 14:30:52 -07:00
Ehren Kret
1053a47e42 Add an exception mapper for CompletionExceptions 2021-11-04 19:12:31 -05:00
Ehren Kret
99b1f48e0e Copy badges from existing account on re-reg 2021-11-04 18:00:47 -05:00
Jon Chambers
c21eb6aa50 Update to the latest abusive message filter 2021-11-01 15:27:14 -04:00
Jon Chambers
6dddf54222 Consolidate rate-limit counters 2021-11-01 14:33:05 -04:00
Jon Chambers
9e3eb2319e Update to the latest abusive message filter 2021-11-01 13:12:23 -04:00
Jon Chambers
1d8dcda815 Update to the latest abusive message filter 2021-11-01 12:45:50 -04:00
Jon Chambers
ee52a84262 Update to the latest abusive message filter 2021-11-01 12:19:21 -04:00
Jon Chambers
eb51e81faa Configuration-only change 2021-11-01 11:41:26 -04:00
Jon Chambers
d41ef1df18 Configuration-only change 2021-11-01 11:27:02 -04:00
Jon Chambers
66d47aff2c Update deployment configuration 2021-11-01 11:10:19 -04:00
Jon Chambers
c931103712 Remove unused utility classes 2021-11-01 10:51:47 -04:00
Jon Chambers
ad1aeea74b Add an abusive message filter interface and submodule 2021-11-01 10:51:47 -04:00
Ehren Kret
ae7f8af03e Mark boost and subscription configuration as not null 2021-10-28 16:12:23 -07:00
Ehren Kret
a52c91a665 Add names to subscription levels 2021-10-28 14:48:44 -07:00
Ehren Kret
94bf3a3902 Extract logic for created header controlled resource bundles 2021-10-28 14:26:53 -07:00
Ehren Kret
f5a539e128 Add subscriptions resource bundle 2021-10-28 14:15:20 -07:00
Ehren Kret
24480b2090 Add endpoint to fetch information on boost badges 2021-10-26 14:05:59 -07:00
Ehren Kret
a124b3abe9 Fix encoding of boost amounts 2021-10-26 08:08:09 -07:00
Ehren Kret
090d722b61 Add method to retrieve receipt credentials for a boost payment 2021-10-25 14:54:40 -07:00
Ehren Kret
d27ec6fe8d Create boost create endpoint 2021-10-25 12:58:32 -07:00
Jon Chambers
8d34f3447b Drop an unused registration meter 2021-10-25 14:52:21 -04:00
Ehren Kret
72b52965b9 Expand definition of badge SVGs 2021-10-25 10:28:02 -07:00
Jon Chambers
ae7077c643 Refresh accounts from storage when checking for device state changes after requests 2021-10-22 14:02:28 -04:00
Jon Chambers
11598e855f Count non-normalized or impossible numbers 2021-10-22 14:01:54 -04:00
Jon Chambers
534c577f59 Enforce phone number normalization when creating accounts or changing numbers 2021-10-22 14:01:54 -04:00
Jon Chambers
7762afc497 Add a method for verifying that numbers are normalized in addition to being dialable 2021-10-22 14:01:54 -04:00
Jon Chambers
a3fe4b9980 Update/parameterize ValidNumberTest 2021-10-22 14:01:54 -04:00
Jon Chambers
598599cd14 Use a default reportMessage configuration if not specified 2021-10-21 15:34:57 -05:00
Ehren Kret
07cd69ab34 Add endpoint for fetching boost amounts 2021-10-21 13:56:35 -05:00
Jon Chambers
3b764bed7a Make DynamicConfigurationManager generic 2021-10-19 11:52:29 -04:00
Jon Chambers
c91d5c2fdb Count reported messages per sender 2021-10-19 11:47:54 -04:00
Jon Chambers
40f7e6e994 Remove unused imports 2021-10-19 11:47:54 -04:00
Ehren Kret
ee9aa9ce12 Round up by days on receipt expiration 2021-10-18 12:11:27 -05:00
Ehren Kret
08304bf375 Validate GetReceiptCredentialsRequest 2021-10-18 11:57:18 -05:00
Ehren Kret
8b8c6237be Use last subscription created at time as a subscription generation number 2021-10-14 12:06:19 -05:00
Ehren Kret
c0837104cd Bring badge configuration into levels information 2021-10-14 11:35:18 -05:00
Jon Chambers
fe21d014f7 Remove legacy rate-limiting tools 2021-10-14 11:43:18 -04:00
Ehren Kret
75c5032cd3 Add method to set default payment method 2021-10-14 10:37:12 -05:00
Jon Chambers
f84e7aebd0 Count numbers that can't be normalized because another account has the normalized form of the number 2021-10-14 11:10:12 -04:00
Jon Chambers
c379a3d297 Remove deprecated counters 2021-10-14 10:52:20 -04:00
Jon Chambers
eedeaaecee Update rate-limiting for requests matching specific criteria 2021-10-14 10:42:16 -04:00
Ehren Kret
64eeb1e361 Move to using collections for sprites and svgs for badges 2021-10-13 23:25:39 -05:00
Ehren Kret
e07597eba7 Add initial text for donation badges 2021-10-13 23:08:02 -05:00
Ehren Kret
5f2656710c Update badge configuration to new style 2021-10-13 22:58:57 -05:00
Ehren Kret
1af53f2612 Simplify getLevels API 2021-10-13 13:28:31 -05:00
Ehren Kret
c89cfa4927 Move property setting above servlet construction 2021-10-13 13:17:07 -05:00
Ehren Kret
bbde93a3c7 Enable unwrapping of CompletionStage 2021-10-13 11:46:20 -05:00
Ehren Kret
b01b76d78f First pass at subscriptions API
This is an incomplete first pass at building the subscriptions API. More API endpoints are still to be added along with controller tests.
2021-10-12 21:23:20 -05:00
Ehren Kret
75c22038eb Create empty Badges_en.properties file 2021-10-11 22:27:30 -05:00
Jon Chambers
3c1705994d Count accounts with non-normalized phone numbers 2021-10-11 10:13:08 -04:00
Jon Chambers
439d2f5df8 Update to libphonenumber 8.12.33 2021-10-05 15:19:59 -04:00
Chris Eager
d2bc3c7360 Add dynamic configuration to disable directory reconciler 2021-10-04 14:38:47 -07:00
Jon Chambers
9734433f00 Use the default SecureRandom algorithm for tests 2021-10-04 11:37:32 -04:00
Ehren Kret
5bd08800bb Remove public modifier from test class 2021-10-01 14:27:07 -05:00
Ehren Kret
3032415141 Add receipt redemption API to chat server 2021-10-01 12:44:47 -05:00
Jon Chambers
ba58a95a0f Add support for changing phone numbers 2021-10-01 10:15:33 -04:00
Jon Chambers
aa4bd92fee Lazy-load scripts; fall back to eval if evalsha returns NOSCRIPT 2021-09-29 16:08:17 -04:00
Jon Chambers
f37c76dab1 Drop LuaScript 2021-09-29 16:08:17 -04:00
Jon Chambers
863969c77c Resolve ClusterLuaScript warnings/suggestions 2021-09-29 16:08:17 -04:00
Jon Chambers
2383aaaa3d Update ClusterLuaScript formatting 2021-09-29 16:08:17 -04:00
Jon Chambers
715d1157ad Reject Redis commands when disconnected 2021-09-29 16:07:26 -04:00
Jon Chambers
4aaae3f445 Use a single configuration URI for Redis clusters 2021-09-29 14:46:09 -04:00
Jon Chambers
8359ef73f4 Cycle all connected websockets on any device or account enabled state change 2021-09-29 14:18:35 -04:00
Jon Chambers
c6bb649adb Simplify map-building logic 2021-09-29 14:18:35 -04:00
Jon Chambers
e333cbd94d Close websockets on account deletion 2021-09-29 14:11:16 -04:00
Ehren Kret
cc9a825279 Remove checked in generated code 2021-09-29 13:10:27 -05:00
Chris Eager
5189cbe5c7 apply editorconfig formatting 2021-09-29 10:31:39 -07:00
Chris Eager
d1d6e5c652 Filter stale ephemeral messages from cache 2021-09-29 10:31:39 -07:00
Chris Eager
3e5087e60b Remove obsolete ephemeral queue handling 2021-09-29 10:31:39 -07:00
Chris Eager
93c3cea912 Clean up old "ephemeral" metrics 2021-09-29 10:31:39 -07:00
Chris Eager
e824b861d4 Drop accounts table 2021-09-29 10:31:20 -07:00
Chris Eager
e8dd1e0bf2 Fixup formatting 2021-09-29 10:31:20 -07:00
Ehren Kret
533afa4c6e Upgrade to zkgroup 0.8.1 2021-09-28 09:28:28 -05:00
Ehren Kret
559026933d Add low and high detail svgs to badges 2021-09-27 17:00:09 -05:00
Ehren Kret
7864405efd Remove single URL in favor of density based sprite sheets 2021-09-27 16:50:18 -05:00
Jon Chambers
a5575902de Pause if we bump into an exception while trying to persist messages 2021-09-27 13:38:17 -04:00
Ehren Kret
5b9bce59e1 Upgrade to zkgroup 0.8.0 2021-09-24 15:56:28 -05:00
Ehren Kret
041aed2d72 Remove zkgroup enabled flag
The last remnants of a time before zkgroup have been swept away.
2021-09-23 09:24:06 -05:00
Jon Chambers
02a296e500 Use a MIME Base64 decoder for provisioning messages 2021-09-22 16:30:44 -04:00
Jon Chambers
98e41f9a37 Improve Redis exception handling 2021-09-22 10:31:39 -04:00
Chris Eager
6a71d369e2 More Accounts cleanup
* Remove `AccountStore`
* Clean up `AccountsDynamoDb#delete`
* Rename `AccountsDynamoDb` → `Accounts`
* Remove unused configuration
* Move Accounts scan page size to static configuration
* Remove disabled tests and related methods
2021-09-21 15:25:16 -07:00
Ehren Kret
75661fa800 Add JavaTimeModule 2021-09-20 16:42:38 -05:00
Ehren Kret
df5498e1c0 Add test for self badge fetching 2021-09-20 16:42:38 -05:00
Ehren Kret
c0af911197 Show invisible badges to query for self 2021-09-20 16:42:38 -05:00
Ehren Kret
44bc90e5ab Return a badge with additional properties when fetching your own profile 2021-09-20 16:42:38 -05:00
Ehren Kret
5c1cde1b28 Add visibility modifying helper method in AccountBadge 2021-09-20 16:41:09 -05:00
Ehren Kret
3172b571c6 Move "testing" string into BadgeConfiguration 2021-09-20 16:41:09 -05:00
Ehren Kret
17e8b77e88 Add unit test for setting badges 2021-09-20 16:41:09 -05:00
Ehren Kret
8011935a3b Fix compilation issues created by constructor changes 2021-09-20 16:41:09 -05:00
Ehren Kret
3f3052c23c Remove isZkEnabled 2021-09-20 16:41:09 -05:00
Ehren Kret
8f17f45339 Reorder creating of testing badges
Need their expiration time to always get refreshed on set so reorder
how they're created to ensure that's the case.
2021-09-20 16:41:09 -05:00
Ehren Kret
009e2eeb97 Enable editing of badges on profiles 2021-09-20 16:41:09 -05:00
Ehren Kret
c70fa48835 Fix indentation of ProfileController#setProfile 2021-09-20 16:41:09 -05:00
Ehren Kret
bd5f5c407b Add method to merge badge ids list into a profile 2021-09-20 16:41:09 -05:00
Ehren Kret
2bc573a53d Add additional test badges for translation 2021-09-20 16:41:09 -05:00
Ehren Kret
537d61d5bd Add badges to CreateProfileRequest
This will permit users to set the order and visibility of badges on
their profile.
2021-09-20 16:41:09 -05:00
Jon Chambers
09519ae942 Only retry websocket sending if the client is still connected 2021-09-20 14:24:07 -04:00
Chris Eager
2a67b2e610 Remove Accounts Postgres 2021-09-20 11:10:24 -07:00
Chris Eager
8161f55a82 Add dynamic configuration for setting Dynamo as primary 2021-09-17 13:28:45 -07:00
Chris Eager
ecee189ad8 Add AccountDatabaseCrawler.dedicatedDynamoMigrationCrawler 2021-09-17 11:27:20 -07:00
Jon Chambers
ef0900f3ac Add .tx/ to .gitignore 2021-09-17 13:43:52 -04:00
Fedor Indutny
383d744bd8 Log the error message when retrying queue send 2021-09-16 18:03:42 -04:00
Jon Chambers
c2ba8ab562 Identify receipt destinations by UUID instead of e164 2021-09-16 10:47:03 -04:00
Chris Eager
cd49ea43c0 Use queryPaginator when loading messages 2021-09-16 10:46:37 -04:00
Chris Eager
53aa45a2bb Use queryPaginator when deleting messages 2021-09-16 10:46:37 -04:00
Chris Eager
83e0a19561 Migrate MessagesDynamoDbRule to MessagesDynamoDbExtension 2021-09-16 10:46:37 -04:00
Jon Chambers
6a5d475198 Add a "refresh websocket on number change" provider 2021-09-16 10:37:34 -04:00
Jon Chambers
49ccbba2e3 Generalize the "watch for websockets that need to be refreshed" listener 2021-09-16 10:37:34 -04:00
Fedor Indutnyy
41735ed40e Introduce queueDrainRetry counter metric 2021-09-16 10:30:19 -04:00
Ehren Kret
2d11a433c9 Wrap all calls to getAcceptableLanguages
ContainerRequestContext#getAcceptableLanguages throws a
ProcessingException if the header has invalid values in it. Rather than
error out of the request entirely with the exception handler for that
exception, we just treat it as though no Accept-Languages header was
specified.
2021-09-16 09:28:21 -05:00
Ehren Kret
e79ab2521f Rename field in ConfiguredProfileBadgeConverter 2021-09-16 09:28:21 -05:00
Ehren Kret
fb1f99da87 Add a method to enable a badge for all accounts 2021-09-16 09:28:21 -05:00
Ehren Kret
08c6a8c2e5 Add category to badges 2021-09-16 09:28:21 -05:00
Ehren Kret
ce3835e176 Rename id to name in the configuration 2021-09-16 09:28:21 -05:00
Ehren Kret
39f6eadbb9 Add test for add and remove badges 2021-09-16 09:28:21 -05:00
Ehren Kret
16dba09b61 Handle merging badges when adding to account 2021-09-16 09:28:21 -05:00
Ehren Kret
d5ebf2f2ed Rename name to id in Account#removeBadge 2021-09-16 09:28:21 -05:00
Ehren Kret
8a8e6e7b49 Rename name to id in the stored badge information and expose id in the profile endpoint 2021-09-16 09:28:21 -05:00
Ehren Kret
34e21b9f7b Change name to id on AccountBadge
This makes it distinct from the localized name field on the Badge
entity that is returned.
2021-09-16 09:28:21 -05:00
Ehren Kret
98a31d1474 Switch ProfileController to the actual badge converter 2021-09-16 09:28:21 -05:00
Chris Eager
72a0c1be0f Tune mismatch logging 2021-09-15 16:46:10 -07:00
Ehren Kret
5b25e38e41 Ensure badges are in ordered collections 2021-09-15 16:20:15 -05:00
Chris Eager
2fb400280b Remove unused parameter from deleteMessageByDestinationAndGuid 2021-09-15 10:14:08 -07:00
Ehren Kret
79ad09524e Implement the ProfileBadgeConverter interface 2021-09-15 10:32:20 -05:00
Chris Eager
5f8accb492 Add acceptable languages from request to variable 2021-09-14 17:43:39 -07:00
Chris Eager
6fcadc2297 Handle exception reading Accept-Language header 2021-09-13 18:07:16 -07:00
Chris Eager
3f4e1522eb Only put accounts that exhaust optimistic lock retries in migration retry table 2021-09-13 15:00:01 -07:00
Graeme Connell
6304c84cdb Add ContactDiscoveryWriterTest based on mock. 2021-09-13 15:20:21 -06:00
Chris Eager
894297efa9 Add dynamic configuration for doing a mismatch post-check 2021-09-13 13:54:19 -07:00
Chris Eager
a51a7a0901 Add MigrationMismatchedAccounts to AccountsManager 2021-09-13 13:54:19 -07:00
Chris Eager
372e131e25 Update PaymentsControllerTest 2021-09-13 09:58:42 -07:00
Chris Eager
6c6e6a4975 Switch to actions/setup-java’s built-in caching 2021-09-13 09:40:44 -07:00
Sophiah Ho
cd66a1ceb7 fix merge issue after 2021 Aug 15 commit d1735c7e57 (#137) 2021-09-13 09:39:11 -07:00
Sophiah Ho
feb59deb28 Use BigDecimal instead of Double for currency rate calculations (#134)
use BigDecimal instead of double for accuracy
2021-09-10 16:15:57 -05:00
Nicolas Remond
489519a982 Use Map.of() for statically defined map 2021-09-10 14:27:18 -05:00
Dambar Pun
a96865d0f5 Update RedisInputStream.java
Fix code style
2021-09-10 14:24:52 -05:00
Blake Irvin
12e11609a9 pin 3rd-party Actions dep by full SHA
This change follows GitHub's security-hardening guidance. By pinning to a full SHA, we reduce our exposure to supply-chain attacks where a malicious party could compromise the 3rd-party Actions repo, commit malicious code, and then mutate an existing git tag to redirect to a SHA containing the malicious commit.

See https://docs.github.com/en/actions/learn-github-actions/security-hardening-for-github-actions#using-third-party-actions for more.
2021-09-10 14:22:11 -05:00
F2theK
5b404095b0 Added missing config entries
Starting server with sample.yml throws errors because of missing elements in config - not just empty ones
2021-09-10 14:19:10 -05:00
Chris Eager
6a6555e2d5 Add metrics for AuthEnablementRequestEventListener displacements 2021-09-10 12:01:05 -07:00
Chris Eager
49489a6021 Re-check mismatched accounts after a delay, to avoid false positives from concurrent requests 2021-09-10 11:31:44 -07:00
Chris Eager
8cd93d68e4 Add MetricsUtil 2021-09-10 11:31:44 -07:00
Chris Eager
f3b9a8d97f Add account to migration retry table on transient dynamo failure 2021-09-10 11:30:49 -07:00
Chris Eager
b91a69d8b3 Add asynchronous chunk pre-read to AccountDatabaseCrawler 2021-09-10 11:14:11 -07:00
Chris Eager
624e40e3b7 Add separate AccountsDatabaseCrawler for DynamoDB migration 2021-09-10 11:14:11 -07:00
Chris Eager
23a076a204 Update Account#getNextDeviceId to not reuse disable device’s IDs 2021-09-10 10:48:48 -07:00
Chris Eager
016141a05d Add DevicesHelper 2021-09-10 10:48:48 -07:00
Graeme Connell
a064b25a14 Fix CDS writer to use AccountsManager. 2021-09-10 11:36:06 -06:00
Ehren Kret
bd40e32f3b Send acceptable languages instead of request into the profile badge converter 2021-09-10 10:53:04 -05:00
Ehren Kret
81a21c0d5f Use @NotNull since @NotEmpty doesn't support URL 2021-09-10 10:49:31 -05:00
Ehren Kret
6478210330 Update configuration for badges to use URL instead of String 2021-09-10 10:49:31 -05:00
Ehren Kret
aa1c37fe26 Create configuration for badges 2021-09-10 10:14:16 -05:00
Ehren Kret
6ee23b0186 Create resource bundle for badges 2021-09-10 10:11:56 -05:00
Jon Chambers
40eb445592 Add a command to set a user's discoverability in CDS 2021-09-10 10:34:20 -04:00
Jon Chambers
ce7d687205 Add a shutdown monitor that publishes shutdown state as a metric 2021-09-08 16:37:05 -04:00
Chris Eager
758900b7a8 Register AuthEnablementApplicationEventListener 2021-09-08 13:11:09 -07:00
Chris Eager
539b62a829 Add request event listener that handles device.isEnabled changes 2021-09-08 13:11:09 -07:00
Jon Chambers
2866f1b213 Include e164 in account creation (whoami) responses 2021-09-07 16:52:32 -04:00
Ehren Kret
fc1465c05d Wire up stored account badges to the profile endpoints 2021-09-07 15:51:29 -05:00
Ehren Kret
bc887ec6fa Add visibility flag to badge storage 2021-09-07 15:50:29 -05:00
Ehren Kret
84b3d324bb Creates a storage object for badges 2021-09-07 15:49:41 -05:00
Ehren Kret
fc10108788 Make fields final in Badge entity 2021-09-07 15:39:48 -05:00
Ehren Kret
fbbc1bec58 Add badge entity to profile 2021-09-07 15:39:48 -05:00
Graeme Connell
2059bb5ef8 Update test to handle read-then-write in ContactDiscoveryWriter. 2021-09-07 13:41:47 -06:00
gram-signal
b080a5db4d Get-and-set accounts, since other updates may have made them stale.
Co-authored-by: Chris Eager <79161849+eager-signal@users.noreply.github.com>
2021-09-07 13:41:47 -06:00
Graeme Connell
b4aabd799b Canonical discoverability writing. 2021-09-07 13:41:47 -06:00
Jon Chambers
92f035bc2a Add a "change number" device/account capability 2021-09-07 15:07:30 -04:00
Chris Eager
18a6df34bd Add timers to processChunk and deleteRecentlyDeletedUuids 2021-09-03 14:54:51 -07:00
Chris Eager
b1274125c9 Add start/chunk/sleep logging to crawler 2021-09-03 14:54:51 -07:00
Chris Eager
dceebc1c8d Consistently use whenCompleteAsync(…, migrationThreadPool) 2021-09-03 14:02:51 -07:00
Chris Eager
6aadb4b458 Parameterize registration lock constructor when updating account attributes 2021-09-03 14:02:27 -07:00
Fedor Indutnyy
703405b874 Start WebSocket before registering its presence 2021-08-27 16:41:07 -04:00
Jon Chambers
d1735c7e57 Retire AmbiguousIdentifier 2021-08-27 13:40:46 -04:00
Jon Chambers
1f815b49dd Measure APNs rejections by reason 2021-08-27 11:52:29 -04:00
Jon Chambers
a9339b7037 Update to Pushy 0.15.0 2021-08-27 11:52:29 -04:00
Jon Chambers
f2c6ca182d Include the current server version in the tag list for Dropwizard metrics 2021-08-27 11:52:11 -04:00
Jon Chambers
b946c27a20 Remove a metric aggregator 2021-08-27 11:52:11 -04:00
Ehren Kret
9fd6358518 Add missing section to end of LICENSE file 2021-08-26 12:55:56 -05:00
Jon Chambers
8a8a848fac Record error metrics from Twilio Verify 2021-08-26 12:22:17 -04:00
Chris Eager
aeb9f67266 Migrate MessageSenderTest to JUnit 5 2021-08-25 12:25:10 -05:00
Chris Eager
e08c5a412e Insert ephemeral messages in the standard cache queue 2021-08-25 12:25:10 -05:00
Chris Eager
a7443a9ece Don’t persist ephemeral messages; clear ephemeral field when sending to clients 2021-08-25 11:17:00 -05:00
Chris Eager
54fe3b9a43 Update TextSecure.proto 2021-08-25 11:17:00 -05:00
Ehren Kret
ba522b1691 Clean redis message cache structure 2021-08-24 10:30:52 -05:00
Jon Chambers
739c5bf22c Add a counter to estimate announcement group adoption 2021-08-23 17:31:34 -04:00
Chris Eager
7cdadeb791 Register circuit breaker metrics for FaultTolerantPubSubConnection 2021-08-23 15:49:19 -05:00
Chris Eager
dadf43b93e Consolidate directory reconciliation on v3 endpoints 2021-08-19 14:18:38 -05:00
Chris Eager
bd820e6d2e Migrate websocket-resources test to JUnit 5 and .editorconfig 2021-08-19 14:09:57 -05:00
Chris Eager
19f7b207b7 Extract configuration for WebSocket max message sizes 2021-08-19 14:09:57 -05:00
Chris Eager
a398e2269c Update AccountsManager mismatch comparison 2021-08-19 14:08:48 -05:00
Chris Eager
2e28fb97a4 Delete DynamoDB accounts with invalid UUIDs in AccountsManager#create 2021-08-19 14:05:21 -05:00
Chris Eager
5c68d83a93 Add integration test for re-registration with and without Dynamo DB 2021-08-19 14:05:21 -05:00
Chris Eager
0b7c3ad745 .editorconfig formatting 2021-08-16 16:32:26 -05:00
Chris Eager
0cde06557d Catch and log unexpected exceptions keyspace notification executor service 2021-08-16 16:32:26 -05:00
Chris Eager
27844fe692 Add JUnit 5 RedisClusterExtension 2021-08-13 12:07:04 -05:00
Ehren Kret
779051ef9f Add minThreads(64) to multiRecipientMessageExecutor 2021-08-12 13:03:40 -05:00
Ehren Kret
d13741fbd5 Change from using parallel streams to using an ExecutorService 2021-08-12 12:05:01 -05:00
Ehren Kret
f7f870fe62 Execute send multi-recipient message loop in parallel 2021-08-12 12:05:01 -05:00
Chris Eager
de59aa099d Add uncaught exception handler 2021-08-12 11:10:05 -05:00
Ehren Kret
57a478b898 Remove unused local variable 2021-08-12 10:26:23 -05:00
Ehren Kret
3e8d79e147 Remove unused lua script to delete by sender and timestamp 2021-08-11 17:38:55 -05:00
Ehren Kret
a46045d987 Remove unused methods that delete messages by sender and timestamp 2021-08-11 17:30:39 -05:00
Ehren Kret
662c905b80 Remove deprecated delete messages endpoint
DELETE /v1/messages/{source}/{timestamp} has been deprecated a long
time and has minimal usage each day at this point. Dropping support
for this endpoint to improve message cache storage flexibility.
2021-08-11 16:17:44 -05:00
Chris Eager
31022aeb79 Use refreshing AuthenticatedAccount for @Auth 2021-08-11 14:52:25 -05:00
Chris Eager
b3e6a50dee Send 508 status code for legacy clients that produce rate limit challenges 2021-08-11 11:57:30 -05:00
Chris Eager
d29764d11f Only process updates for enabled devices in PushFeedbackProcessor 2021-08-11 11:54:42 -05:00
Chris Eager
f8e4f6727a Reorder crawler listeners so updates happen after read-only processing 2021-08-11 11:54:20 -05:00
Ehren Kret
63d05df8a3 Fix indentation 2021-08-10 10:02:04 -05:00
Ehren Kret
52d13d1d62 Remove unused lua script 2021-08-10 10:02:04 -05:00
Ehren Kret
f58a320223 Remove unused method from MessagesCache 2021-08-10 10:02:04 -05:00
Chris Eager
3e01bc1174 Add metric for content-length header distribution 2021-08-06 14:41:16 -05:00
Jon Chambers
d1ada7f998 Revert "Continue to verify rate limiters by e164 during UUID migration period"
This reverts commit ce5edbb7fc.
2021-08-06 14:33:59 -05:00
Chris Eager
095fc8140e Increase from default binary message size 2021-08-06 12:56:34 -05:00
Jon Chambers
73c368ea86 Use UUIDs instead of e164s to associate accounts with push notifications. 2021-08-04 14:38:28 -04:00
Jon Chambers
ce5edbb7fc Continue to verify rate limiters by e164 during UUID migration period 2021-08-04 14:15:21 -04:00
Jon Chambers
a680639718 Use UUIDs as rate limiter keys. 2021-08-04 14:15:21 -04:00
Ehren Kret
becf6afbdd Block until all UUID bytes are read or EOF 2021-08-03 17:59:48 -05:00
Ehren Kret
1dda015c6a Update multi-recipient message sending to handle unrestricted destinations 2021-08-03 17:31:39 -05:00
Chris Eager
a0427ecf8c Update s3-upload-maven-plugin to 1.6-SNAPSHOT 2021-08-03 11:04:29 -05:00
Chris Eager
cfd31e98ff Move version comparison to after more meaningful checks 2021-08-03 11:03:41 -05:00
Jon Chambers
bcb89924b4 Simplify optimistic write logic 2021-08-03 11:54:26 -04:00
Ehren Kret
23f9199439 Fix dependency resolution error for commons-logging 2021-08-02 13:14:44 -05:00
Ehren Kret
1f6318a919 Rename constant 2021-08-02 13:14:44 -05:00
Ehren Kret
b0667b258b Implement EnterpriseRecaptchaClient 2021-08-02 13:14:44 -05:00
Ehren Kret
4c3a48f5be Use more specific prefix for recaptcha transition 2021-08-02 13:14:44 -05:00
Ehren Kret
33fb7a72de Use RecaptchaClient interface 2021-08-02 13:14:44 -05:00
Ehren Kret
2c808e369c Create a transitional recaptcha client for upgrading 2021-08-02 13:14:44 -05:00
Ehren Kret
906d0be382 Setup recaptcha client interface 2021-08-02 13:14:44 -05:00
Ehren Kret
1c9a3c6105 Bringing in Google Cloud Recaptcha Enterprise libraries 2021-08-02 13:14:44 -05:00
Ehren Kret
2aaddd721f Rename existing captcha client 2021-08-02 13:14:44 -05:00
Jon Chambers
4e2284b83f Retire old GV2 adoption metrics. 2021-08-02 12:51:49 -05:00
Chris Eager
d5d9978e48 Use non-stale account in mutator when adding a new device 2021-08-02 11:38:03 -05:00
Chris Eager
d45659ac76 Reduce contention when updating device.lastSeen 2021-08-02 11:26:15 -05:00
Jon Chambers
13a07dc6cd Drop the active user counter. 2021-07-29 15:40:27 -04:00
Chris Eager
51b7a8d868 Add excluded E164s configuration to pre-registration experiment 2021-07-29 14:16:40 -05:00
Chris Eager
df9c0051c9 Reconcile inactive and undiscoverable accounts when using v3 endpoints 2021-07-29 10:56:44 -05:00
Jon Chambers
331ff83cd5 Drop legacy PIN-based registration lock plumbing 2021-07-29 11:51:14 -04:00
Jon Chambers
44838d6238 Verify that nobody's addressing API calls by e164 any more. 2021-07-29 11:50:36 -04:00
Chris Eager
5400abb065 Better support unhandled exception logging on websocket requests 2021-07-28 14:06:09 -05:00
Jon Chambers
f47fefb73e Lock accounts for the duration of deletion operations. 2021-07-27 13:12:39 -04:00
Jon Chambers
cdef745a7a Drop a not-very-helpful metric (logging works better in this case). 2021-07-27 13:12:39 -04:00
Jon Chambers
1a1eab4ec0 Also clear profiles on re-registration. 2021-07-27 13:05:54 -04:00
Jon Chambers
3a966ef345 Reuse account UUIDs when registering an account with a recently-deleted e164. 2021-07-27 13:05:54 -04:00
Jon Chambers
be20c04cd8 Identify accounts for which to delete keys by UUID. 2021-07-27 13:05:54 -04:00
Jon Chambers
d09dcc90fe Add methods for getting, clearing, locking recently-deleted account records. 2021-07-27 13:05:54 -04:00
Sophiah Ho
1fd1207bf6 Prevent unit tests from failing for machines with a non-US default Locale 2021-07-27 13:01:48 -04:00
Jon Chambers
0117fc12c7 Actually increment the moved "new user" counter. 2021-07-27 12:09:51 -04:00
Jon Chambers
ef9a7fda9a Publish outstanding SQS operation count as a gauge. 2021-07-27 11:15:41 -04:00
Chris Eager
13447df1e0 Update validation for NotNull items in IncomingMessagesList 2021-07-27 10:39:30 -04:00
Jon Chambers
3608c5bfb0 Wait for outstanding requests to be resolved before shutting down the directory queue. 2021-07-27 10:36:53 -04:00
Jon Chambers
34dbff6786 Switch to an async SQS client. 2021-07-27 10:36:53 -04:00
Jon Chambers
a6066bfc2f Migrate DirectoryQueueTest to JUnit 5. 2021-07-27 10:36:53 -04:00
Jon Chambers
8579190cdf Consolidate account creation/directory updates into AccountsManager 2021-07-27 10:27:47 -04:00
Chris Eager
917f667229 Remove AccountController and KeysController from websocket 2021-07-26 14:27:43 -05:00
Chris Eager
317a551bdb Migrate MetricsRequestEventListenerTest to JUnit 5 2021-07-26 12:06:29 -05:00
Chris Eager
27e9271473 Add request path and user agent to unhandled exception logging 2021-07-26 12:06:29 -05:00
Fedor Indutny
11dff6c546 more controllers 2021-07-26 12:06:17 -05:00
Fedor Indutny
e6712937ca fix indent 2021-07-26 12:06:17 -05:00
Fedor Indutny
cf8887bb5a Provide more WebSocket endpoints 2021-07-26 12:06:17 -05:00
Chris Eager
696340f780 Migrate DeviceControllerTest to JUnit 5 2021-07-26 11:18:17 -05:00
Chris Eager
86ddcbaa08 Migrate CertificateControllerTest to JUnit 5 2021-07-26 11:18:17 -05:00
Chris Eager
2144d2a8d8 Migrate AttachmentControllerTest to JUnit 5 2021-07-26 11:18:17 -05:00
Chris Eager
f7af861b31 Migrate SecureStorageControllerTest to JUnit 5 2021-07-26 11:18:17 -05:00
Chris Eager
208a09b3ae Migrate RemoteConfigControllerTest to JUnit 5 2021-07-26 11:18:17 -05:00
Chris Eager
831023e41d Migrate PaymentsControllerTest to JUnit 5 2021-07-26 11:18:17 -05:00
Chris Eager
ff627793d6 Migrate DirectoryControllerTest to JUnit 5 2021-07-26 11:18:17 -05:00
Chris Eager
f971c76a99 Migrate StickerControllerTest to JUnit 5 2021-07-26 11:18:17 -05:00
Chris Eager
8f41176c76 Enable "sms" transport for +98 2021-07-26 10:40:05 -05:00
Ehren Kret
31bbbbb5e0 Raise default message TTL to 14 days 2021-07-20 14:08:08 -05:00
Jon Chambers
effcd6038d Also record dimensional metrics for circuit breakers and retries. 2021-07-19 16:56:16 -04:00
Jon Chambers
12be7d49c2 Clear one-time pre-keys on re-registration. 2021-07-19 10:05:01 -04:00
Jon Chambers
14863b575e Clear one-time pre-keys when a device is unlinked. 2021-07-19 10:05:01 -04:00
Jon Chambers
32a95f96ff Add a pessimistic locking system for operations on recently-deleted account records 2021-07-16 16:52:58 -04:00
Jon Chambers
b757d4b334 Measure how many "send message" requests are still using e164-based addressing. 2021-07-16 16:52:58 -04:00
Chris Eager
bd03d910fe Set authenticated device after updating last seen 2021-07-16 16:52:58 -04:00
Chris Eager
01ef855157 Return a non-stale account from base authenticator when last seen is updated 2021-07-16 16:52:58 -04:00
Chris Eager
817866caf3 Use fresh accounts to update in PushFeedbackProcessor 2021-07-16 16:52:58 -04:00
Chris Eager
158d65c6a7 Add optimistic locking to account updates 2021-07-16 16:52:58 -04:00
realturner
62022c7de1 Migrate AppConfig to SDK v2 to detect and use web identify token 2021-07-16 16:48:33 -04:00
Chris Eager
a824b5575d Add dynamic configuration for using DynamoDB in AccountsDatabaseCrawler 2021-07-06 13:01:24 -05:00
Jon Chambers
78819d5382 Remove expiration logic when checking token validity.
The data store will no longer return tokens that have expired, and we no longer need to check for expiration in application space.
2021-07-06 11:03:49 -04:00
Jon Chambers
d128bc782a Retire Postgres-backed pending account/device tables. 2021-07-06 11:03:49 -04:00
Chris Eager
530b2a310f Ensure active future is always completed 2021-07-02 15:05:11 -05:00
Chris Eager
d5b0d99a54 Remove unused method 2021-07-02 15:05:11 -05:00
Chris Eager
43be72d076 Add test for ManagedPeriodicWork; fix shutdown not awaiting active execution 2021-07-02 15:05:11 -05:00
Chris Eager
9558944e22 Add needsReconciliationIndexName to sample.yml 2021-07-02 15:05:11 -05:00
Chris Eager
0f6c866c8d Update imports 2021-07-02 15:05:11 -05:00
Chris Eager
bac78e9291 Switch DeletedAccountsTableCrawler metrics to a basic Metrics#summary 2021-07-02 15:05:11 -05:00
Chris Eager
c22ea78672 Add crawler to process migration retry accounts 2021-07-02 15:05:11 -05:00
Chris Eager
a85afe827d Avoid NPE by using scheduledFuture as the Gauge state object 2021-07-02 15:05:11 -05:00
Chris Eager
abaed821ec Add additional case to unit test 2021-07-02 15:05:11 -05:00
Chris Eager
6fa9dcd954 Refactor to use shared recurringJobExecutor 2021-07-02 15:05:11 -05:00
Chris Eager
819d59cd79 Update reconciliation crawler to use secondary index 2021-07-02 15:05:11 -05:00
Chris Eager
2f88f0eedb Refactor to use single threaded scheduled executor 2021-07-02 15:05:11 -05:00
Chris Eager
74ff491671 Rename ManagedPeriodicWorkCache to ManagedPeriodicWorkLock 2021-07-02 15:05:11 -05:00
Chris Eager
eac48a6617 Don’t delete accounts after reconciling 2021-07-02 15:05:11 -05:00
Chris Eager
19617c14f8 Improved logging in ManagedPeriodcWork 2021-07-02 15:05:11 -05:00
Chris Eager
fc7291c3e8 Migrate DeletedAccountsTableCrawler to ManagedPeriodicWork 2021-07-02 15:05:11 -05:00
Chris Eager
88db808298 Add abstract ManagedPeriodicWork 2021-07-02 15:05:11 -05:00
Chris Eager
5193abdab3 Add DeletedAccountsTableCrawler 2021-07-02 15:05:11 -05:00
Chris Eager
a315c9be92 Add DeletedAccounts DynamoDB table 2021-07-02 15:05:11 -05:00
Chris Eager
fc1541591a Add AbstractDynamoDbStore#scan 2021-07-02 15:05:11 -05:00
Chris Eager
ae97c4db9f Use editorconfig in AbstractDynamoDbStore 2021-07-02 15:05:11 -05:00
Chris Eager
26bc5973b5 Clear message queue before and after removing a device 2021-07-02 10:48:42 -05:00
Chris Eager
e52b8c8423 Implement DatadogConfig in DatadogConfiguration 2021-07-02 10:48:05 -05:00
Jon Chambers
7395489bac Add tests for pending account/device managers. 2021-07-02 11:30:13 -04:00
Jon Chambers
b384ed7f5c Add a counter for requests for delivery certificates with/without e164s. 2021-07-01 10:59:10 -04:00
Jon Chambers
e3afcae7d3 Gather data to verify safety of retiring legacy reglock system. 2021-07-01 10:58:47 -04:00
Jon Chambers
9faeed7b20 Count E164 authentications versus UUID authentications. 2021-07-01 10:51:34 -04:00
Jon Chambers
49adcca80e Use Optional.isEmpty(). 2021-07-01 10:51:34 -04:00
Jon Chambers
49c43a6816 Simplify distribution summary for "days since last seen." 2021-07-01 10:51:34 -04:00
Jon Chambers
84f85ae098 Collapse various account meters into a single, multi-dimensional counter. 2021-07-01 10:51:34 -04:00
Jon Chambers
3d581941ab Add plumbing and configuration to migrate pending accounts/devices to DynamoDB. 2021-07-01 10:50:52 -04:00
Jon Chambers
d2d39baede Add a DynamoDB-backed stored verification code store. 2021-07-01 10:50:52 -04:00
Jon Chambers
111f5ba024 Use java.time classes for stored verification code expiration; add tests. 2021-07-01 10:50:52 -04:00
Jon Chambers
ce3fb7fa99 Extract a common base class for verification code store tests. 2021-07-01 10:50:52 -04:00
Jon Chambers
fc421d3f21 Introduce a common interface for verification code stores. 2021-07-01 10:50:52 -04:00
Jon Chambers
71bea759c6 Consolidate StoredVerificationCode constructors. 2021-07-01 10:50:52 -04:00
Jon Chambers
bf1dd791a5 Drop caching for pending accounts/devices. 2021-07-01 10:50:52 -04:00
Chris Eager
4c99577c08 Add configuration for Datadog batch size 2021-06-30 16:44:25 -05:00
Graeme Connell
5d5c63e6d4 Update profile controller to S3 AWSv2. 2021-06-30 13:09:18 -06:00
Graeme Connell
42ff3f8432 Switch SQS to Amazon SDKv2. 2021-06-30 12:46:12 -06:00
Chris Eager
be6ef76486 Update DynamoDBLocal to 1.16.0 2021-06-23 13:50:58 -05:00
Chris Eager
bc297e6d34 Update wiremock-jre8 to 2.28.1 2021-06-23 13:50:58 -05:00
Chris Eager
3a526dcbd7 Update mockito to 3.11.1 2021-06-23 13:50:58 -05:00
Ehren Kret
7883352b74 Match random capability generation in test 2021-06-21 17:32:31 -05:00
Ehren Kret
982d122d18 Match random capability generation in test 2021-06-21 17:32:31 -05:00
Ehren Kret
d8d94407c6 Create announcement group capability 2021-06-21 17:32:31 -05:00
Chris Eager
28cfc54170 Update FunctionCounter builder to use non-null object and method 2021-06-11 11:27:45 -05:00
Jon Chambers
2ee7279743 Pause nstat counters. 2021-06-11 12:26:56 -04:00
Jon Chambers
eb1b073385 Add a hostname-aware reporter factory. 2021-06-10 14:23:05 -04:00
Jon Chambers
c634185b6f Standardize a utility method for getting local host names. 2021-06-10 14:23:05 -04:00
Ehren Kret
827a3af419 Code cleanup 2021-06-09 20:44:18 -05:00
Jon Chambers
2c33d22a30 Stop recording specific client versions in metrics until we know we need them again. 2021-06-08 12:25:31 -04:00
Chris Eager
b41ed9d810 Update sample.yml config 2021-06-07 17:21:36 -04:00
Jon Chambers
58d3a12eff Set hostname to lowercase to avoid strange case mismatch issues; log hostname failures. 2021-06-07 17:17:46 -04:00
Jon Chambers
88c4b2be97 Correct a misunderstanding about the metrics host tag. 2021-06-07 16:29:44 -04:00
Jon Chambers
6cbd57f19f Include environment/service/version as common metric tags. 2021-06-04 18:17:09 -04:00
Jon Chambers
5522376584 Include a host tag with metrics. 2021-06-04 18:17:09 -04:00
Jon Chambers
5089c37d28 Drop a pair of unused commands. 2021-06-04 12:35:06 -04:00
Jon Chambers
1ccf24e68c Add a command to check dynamic config files. 2021-06-04 12:34:48 -04:00
Jon Chambers
411f7298f2 Enforce validation constraints for dynamic configuration objects. 2021-06-04 12:34:48 -04:00
Jon Chambers
5b0214c6f2 Make pre-key take operations more null-safe 2021-06-04 11:18:59 -04:00
Jon Chambers
735573e61b Make reporting intervals configurable. 2021-06-03 17:50:41 -04:00
Graeme Connell
c545cff1b3 Switch DynamoDB to AWSv2.
Switch from using com.amazonaws.services.dynamodbv2 to using
software.amazon.awssdk.services.dynamodb for all current DynamoDB uses.
2021-06-03 13:37:10 -06:00
Jon Chambers
cbd9681e3e Configure histograms and exclude high-cardinality metrics. 2021-06-03 14:12:02 -04:00
Jon Chambers
ca876e40ca Add a second metric aggregator. 2021-06-03 14:12:02 -04:00
Jon Chambers
76f5a71727 Include server version in logging tags 2021-06-03 11:24:25 -04:00
Jon Chambers
117de2382d Verify that API consumers can skip/clear VOIP tokens. 2021-06-02 16:50:49 -05:00
Jon Chambers
25e7036451 Send a payload with mutable content for non-VOIP topics. 2021-06-02 16:50:49 -05:00
Jon Chambers
3131bd3dd9 Allow iOS callers to specify whether they're providing a VOIP token for preauth. 2021-06-02 16:50:49 -05:00
Chris Eager
1cf9397bbd Bump dropwizard to 2.0.22 2021-06-02 12:30:30 -05:00
brock-signal
c97be15e79 Fix NPE when a null message comes in from a client 2021-06-01 15:00:41 -06:00
Ehren Kret
164fc40990 Rename receipt type and add new client-to-client plaintext type for decryption error receipts 2021-05-28 11:33:44 -05:00
Ehren Kret
6456af6284 Upgrade to latest protobuf
This upgrades to protobuf 3.17 and uses maven to automatically rebuild
the generated code instead of using prefabricated checked in Java
files.
2021-05-28 11:33:44 -05:00
Chris Eager
81212cc13a Add jgitver configuration to ignore branch names 2021-05-27 14:35:28 -05:00
Ehren Kret
6f0750790c Add metric to count number of legacy messages sent 2021-05-27 11:13:42 -05:00
Chris Eager
3e61b5c49d Add call chain and mismatch check for push token timestamp 2021-05-27 11:10:58 -05:00
Ehren Kret
50c4df4f45 Add deploy phase bindings 2021-05-26 19:42:45 -05:00
Ehren Kret
1eb946f5fe Add jgitver extension 2021-05-26 19:42:45 -05:00
Ehren Kret
7bd402b48d Build refactor in preparations for bringing in jgitver 2021-05-26 19:42:42 -05:00
Chris Eager
90444d5b91 Bump version to 5.95 2021-05-26 11:11:00 -05:00
Chris Eager
5ee093f87c Add mismatch for signed pre-key; remove mismatch for migration version 2021-05-26 10:58:23 -05:00
Chris Eager
623743286c Bump version to 5.94 2021-05-25 11:00:44 -05:00
Chris Eager
67067f1d2d Remove last-seen and registration lock comparisons 2021-05-25 10:47:57 -05:00
Ehren Kret
07f9bb112e Use separate object for multi recipient response
`needsSync` was being sent back from the server in the JSON response
which is an unnecessary and constantly false field in multi-recipient
message sending endpoint as it's always sealed sender.
2021-05-25 10:30:39 -05:00
Ehren Kret
417d48c452 Block downgrading sender key support
Disallow linking an additional device to an account that has already
upgraded to having sender key support where the linked device does not
have sender key support. This should prompt the person attempting to
link the older application to upgrade in order to complete the linking
process.
2021-05-25 10:30:26 -05:00
Chris Eager
358412c78a Bump version to 5.93 2021-05-24 12:15:46 -05:00
Chris Eager
215621a9b0 Remove temporary adaptation for nested IncomingMessage.online 2021-05-24 11:36:15 -05:00
Graeme Connell
c3f53c4dd9 Fix infinite loop in TorExitNodeManager. 2021-05-21 14:50:15 -06:00
Graeme Connell
01514f83a0 Fix up AWS2 config issues introduced in rebase. 2021-05-21 14:50:15 -06:00
Graeme Connell
c10b64c367 Simplify S3ObjectMonitor API, try-with-resource. 2021-05-21 14:50:15 -06:00
Graeme Connell
722055c8b5 Switch S3ObjectMonitor to AWSv2 SDK. 2021-05-21 14:50:15 -06:00
Graeme Connell
680e501f83 Add dependency on AWS 2.x s3. 2021-05-21 14:50:15 -06:00
Ehren Kret
f13f7a5ff4 Bump version to 5.92 2021-05-20 15:13:14 -05:00
Ehren Kret
5290656c3b Fix typo 2021-05-20 15:11:44 -05:00
Chris Eager
93fbb87741 Bump version to 5.91 2021-05-20 14:13:13 -05:00
Chris Eager
ce76c5c117 Move dropwizard-dependencies from parent to dependency management 2021-05-20 14:12:37 -05:00
Chris Eager
e663e1b0a6 Move some duplicated versions to BOMs and properties 2021-05-20 14:12:37 -05:00
Chris Eager
20cdd09171 Reformat indentation 2021-05-20 14:12:37 -05:00
Chris Eager
f98dd80941 Reorganize and expand dependency declarations to fix mvn verify failures 2021-05-20 14:12:37 -05:00
Chris Eager
f84736bd32 Add mvn verify to test workflow 2021-05-20 14:12:37 -05:00
Ehren Kret
9995f271c8 Bump version to 5.90 2021-05-20 10:15:49 -05:00
Ehren Kret
cf59d849b0 @Min does not apply to byte[] use @Size instead 2021-05-20 10:15:49 -05:00
Ehren Kret
ee3b91e4fb Register MultiRecipientMessageProvider with the websocket interface too 2021-05-20 10:15:49 -05:00
Jon Chambers
77f134ddca Bump version to 5.89 2021-05-18 19:00:47 -04:00
Chris Eager
8913192b7e Upgrade to actions/setup-java@v2 2021-05-18 17:34:36 -05:00
Jon Chambers
94ac3f6cc8 Return Optional.empty() for present-but-not-routed IPs. 2021-05-18 17:43:30 -04:00
Jon Chambers
b89de860d3 Add support for getting country codes for ASNs. 2021-05-18 17:43:30 -04:00
Jon Chambers
f8c623074b Introduce an ASN-to-IP manager. 2021-05-18 17:43:30 -04:00
Jon Chambers
1160af9522 Add a utility class for associating IP addresses with ASNs. 2021-05-18 17:43:30 -04:00
Jon Chambers
3056ea8cbc More clearly separate concerns for explicitly getting monitored objects. 2021-05-18 17:00:30 -04:00
Jon Chambers
28e3b23e8c Add an "excessively large object" safeguard. 2021-05-18 17:00:30 -04:00
Jon Chambers
fbaf4a09e2 Use the S3 object monitor to retrieve Tor exit node lists. 2021-05-18 17:00:30 -04:00
Jon Chambers
cfa8cbedc1 Introduce an S3 object monitor. 2021-05-18 17:00:30 -04:00
Ehren Kret
be4c46e674 Set tab width to 8 2021-05-18 15:35:08 -05:00
Chris Eager
bacf524ae6 Add optional logging for mismatches 2021-05-18 14:39:30 -05:00
Graeme Connell
aa65d34c36 Set min/max threads for backup/storage service.
From https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadPoolExecutor.html:
 When a new task is submitted in method execute(java.lang.Runnable),
 and fewer than corePoolSize threads are running, a new thread is
 created to handle the request, even if other worker threads are idle.
 If there are more than corePoolSize but less than maximumPoolSize
 threads running, a new thread will be created only if the queue is full.

Since we utilize an unbounded queue, we'll never hit the condition that
the queue is full, so the pool will never grow past corePoolSize.  Given
that, explicitly state that our max is 1 thread.  This should be a noop
operationally.

Thanks to https://github.com/dropwizard/dropwizard/pull/834 for building
in warnings to help us find this.
2021-05-18 13:34:59 -06:00
Ehren Kret
0cd3640f13 Add more tests 2021-05-18 13:09:40 -05:00
Ehren Kret
c595d9415c Change from quadratic scan to use more memory instead 2021-05-18 13:09:40 -05:00
Ehren Kret
1a604d8c79 Add unit test to readU16 2021-05-18 13:09:40 -05:00
Ehren Kret
f76e6705c0 Add handling of registration id in multi recipient send payload 2021-05-18 13:09:40 -05:00
Chris Eager
10cd60738a Bump version to 5.88 2021-05-17 17:28:04 -05:00
Chris Eager
89470ff536 Add class prefix to counter 2021-05-17 17:17:48 -05:00
Chris Eager
79b8202452 Bump version to 5.87 2021-05-17 15:48:00 -05:00
Chris Eager
d252e579f4 Get more detailzed serialization mismatches 2021-05-17 15:42:54 -05:00
Chris Eager
30b2c2b5ad Fix observed mismatches by swapping in original UUID 2021-05-17 15:42:17 -05:00
Chris Eager
282f39141e Add additional tests for AccountStore#create 2021-05-17 15:42:17 -05:00
Chris Eager
85e4de6933 Switch platform value from null to "unrecognized" in metrics 2021-05-17 15:41:11 -05:00
Jon Chambers
0b993098a8 Explicitly declare commons-lang3 as a dependency. 2021-05-17 15:52:35 -04:00
Chris Eager
1880773fb9 Bump version to 5.86 2021-05-14 17:10:39 -05:00
Chris Eager
00c9023e74 Include server GUID when sending messages over websocket 2021-05-14 17:10:15 -05:00
Chris Eager
d59eabd9d7 Bump veresion to 5.85 2021-05-14 16:08:56 -05:00
Chris Eager
2a3ea13c9e Classify DynamoDB mismatches in AccountsManager 2021-05-14 15:57:47 -05:00
Ehren Kret
6906336dfb Include the uuid with the list of mismatched devices 2021-05-14 14:46:56 -05:00
Chris Eager
514b94a5cb Bump version to 5.84 2021-05-13 18:33:31 -05:00
Chris Eager
df01be2dca Don’t throw exceptions from ReportMessageManager#store() 2021-05-13 18:33:02 -05:00
Jon Chambers
10c6f885fd Bump version to 5.83 2021-05-13 18:23:35 -04:00
Chris Eager
224e6dac31 Fix NullPointerException in WebSocketResourceProviderTest 2021-05-13 17:19:55 -05:00
Chris Eager
3b1eb3a9db Bump dropwizard-dependencies to 2.0.21 2021-05-13 17:19:55 -05:00
Chris Eager
e320626c6e Add report message API 2021-05-13 17:19:34 -05:00
Jon Chambers
03dac2bf7e Break down rate limit overruns by country. 2021-05-13 18:16:47 -04:00
Ehren Kret
730303567f Bump version to 5.82 2021-05-13 12:11:22 -05:00
Ehren Kret
57ff9f86f5 Refactor repeated use of the UserCapabilities constructor 2021-05-13 12:08:59 -05:00
Ehren Kret
bfd2c32d4e Add sender key capability 2021-05-12 18:15:25 -05:00
Ehren Kret
e9a3d52d7f Add an optional description to the payment intent request 2021-05-12 18:14:33 -05:00
Chris Eager
ac7eb88194 Bump version to 5.81 2021-05-12 12:22:22 -05:00
Jon Chambers
d45154f2aa Measure captcha challenge success rates. 2021-05-12 12:20:53 -05:00
Chris Eager
760462f8fb Add configuration for regional SMS verification text 2021-05-12 12:20:46 -05:00
Chris Eager
1999bd2bcb Bump version to 5.80 2021-05-11 16:26:11 -05:00
Jon Chambers
46110d4d65 Add client challenges for prekey and message rate limiters 2021-05-11 16:21:32 -05:00
Ehren Kret
5752853bba Bump version to 5.79 2021-05-11 14:14:12 -05:00
Ehren Kret
02d06af3fc Replace use of MDC with custom fields on the appender 2021-05-11 14:08:20 -05:00
Jon Chambers
09e0934eaf Bump version to 5.78 2021-05-11 10:03:04 -05:00
Ehren Kret
b100f09205 Actually instantiate the donation controller in the service 2021-05-11 10:03:04 -05:00
Ehren Kret
670b69df24 Add host and service tags 2021-05-11 10:03:04 -05:00
Ehren Kret
03a531e1b0 Bump version to 5.77 2021-05-11 10:03:04 -05:00
Ehren Kret
13ecbe7e53 Fix missing defaults 2021-05-11 10:03:04 -05:00
Ehren Kret
17047513c3 Create stripe api endpoint for apple pay donations 2021-05-11 10:03:04 -05:00
Jon Chambers
7bd7d0e84e Bump version to 5.76 2021-05-10 10:53:23 -04:00
Chris Eager
4571042814 Add missing increment to counter 2021-05-10 10:45:46 -04:00
Ehren Kret
9cb89b42bf Create a logstash tcp socket appender factory 2021-05-07 16:02:55 -05:00
Jon Chambers
9a4453c414 Bump version to 5.75 2021-05-07 14:11:16 -04:00
Chris Eager
5fa22bc073 Bump dropwizard from 2.0.13 to 2.0.21 2021-05-07 12:21:16 -05:00
Jon Chambers
bf32b766a5 Don't generate stack traces for rate limit exceptions. 2021-05-07 10:44:31 -04:00
Jon Chambers
f0a8b5a54a Allow the environment to manage the Tor exit node manager's lifecycle. 2021-05-06 15:38:24 -04:00
Jon Chambers
8e68e0e037 Bump version to 5.74 2021-05-06 13:12:30 -04:00
Jon Chambers
b81b811400 Actually instantiate the Tor exit node manager. 2021-05-06 12:21:30 -04:00
Jon Chambers
b41f97233e Measure source country for all pre-key requests. 2021-05-06 11:58:14 -04:00
Jon Chambers
350de1c759 Add a simple utility class for testing if an IP belongs to a Tor exit node. 2021-05-06 11:57:18 -04:00
Jon Chambers
055e8d80a1 Bump version to 5.73 2021-05-04 16:15:35 -04:00
Jon Chambers
dfb8a419e7 Include message IDs when sending message batches. 2021-05-04 16:15:04 -04:00
Jon Chambers
030a791d69 Bump version to 5.72 2021-05-04 15:20:36 -04:00
Jon Chambers
cf495ef7cf Key the message rate limiter to sender e164, not UUID. 2021-05-04 15:18:59 -04:00
Jon Chambers
8fdbcbef44 Send directory updates in batches. 2021-05-04 15:18:43 -04:00
Chris Eager
30c9968928 Bump assertj-core from 3.15.0 to 3.19.0 2021-05-04 14:12:40 -05:00
Chris Eager
f357ad098f Bump wiremock from 2.26.2 to 2.27.2 2021-05-04 14:12:40 -05:00
Ehren Kret
1a8c40c02a Patch code to use new Base64 2021-05-04 13:34:27 -05:00
Ehren Kret
20677d4be1 Fix logic error 2021-05-04 13:34:27 -05:00
Ehren Kret
c448c37cc9 Add logic to handle sending a common payload to multiple recipients 2021-05-04 13:34:27 -05:00
Chris Eager
f117d9ff4d Bump version 5.71 2021-05-03 11:15:31 -05:00
Chris Eager
2dbd7ffc75 Bump lettuce from 6.0.1.RELEASE to 6.0.4.RELEASE 2021-05-03 10:57:43 -05:00
Chris Eager
fac4538f6f Migrate rate limiters to rate limiter cluster 2021-05-03 10:57:34 -05:00
Chris Eager
01e526af25 Bump version to 5.70 2021-04-30 17:19:23 -05:00
Chris Eager
7e805d1592 Add rate limiters cluster to all RateLimiters 2021-04-30 17:18:56 -05:00
Chris Eager
c63bebb3e7 Bump version to 5.69 2021-04-30 16:16:02 -05:00
Chris Eager
0e6cfb460d Fix potential NullPointerException in RateLimiter 2021-04-30 16:15:36 -05:00
Chris Eager
cd6b2512e1 Bump version to 5.68 2021-04-30 15:45:22 -05:00
Chris Eager
4f6b132449 Add secondaryCacheCluster to RateLimiter 2021-04-30 15:26:17 -05:00
Jon Chambers
b7c611a466 Generate final locals and parameters. 2021-04-30 11:53:55 -04:00
Chris Eager
0163242c8a Bump version to 5.67 2021-04-28 16:26:05 -05:00
Chris Eager
7fa17e33e9 Bump libphonenumber to 8.12.21 2021-04-28 16:24:46 -05:00
Ehren Kret
e4dbb8efe7 Fix deserialization for Duration in dynamic config 2021-04-28 15:57:23 -05:00
Jon Chambers
89256fb5b3 Bump version to 5.66 2021-04-26 18:53:34 -04:00
Jon Chambers
59e401f41e Use a MIME Base64 decoder for attachment signing keys. 2021-04-26 18:53:15 -04:00
Jon Chambers
4b42dd1db3 Bump version to 5.65 2021-04-26 18:17:30 -04:00
Jon Chambers
6196856a7c Use the JDK-provided Base64 encoder/decoder. 2021-04-26 18:17:03 -04:00
Jon Chambers
0e8d4f9a61 Drop Bouncy Castle as a dependency. 2021-04-26 17:58:19 -04:00
Chris Eager
97d2d97ee7 Bump version to 5.64 2021-04-26 14:34:22 -05:00
Chris Eager
62315f423c Record duration of successful verifications 2021-04-26 14:31:52 -05:00
Chris Eager
5ee56b022c Add CustomFriendlyName parameter to Twilio Verify requests 2021-04-26 14:31:52 -05:00
Chris Eager
6c37b658ac Migrate VoiceVerificationControllerTest to JUnit 5 2021-04-26 14:31:52 -05:00
Chris Eager
1f53900345 Migrate VoiceVerificationController to Util#findBestLocale 2021-04-26 14:31:52 -05:00
Chris Eager
deece33a0d Fix parameterized testWhoAmI 2021-04-26 14:31:52 -05:00
Chris Eager
13053da97f Add Twilio Verify experiment to AccountController 2021-04-26 14:31:52 -05:00
Chris Eager
4c019aef15 Migrate PendingAccountsTest to JUnit 5 2021-04-26 14:31:52 -05:00
Chris Eager
bab5e5769b Add TwilioVerifyExperimentEnrollmentManager 2021-04-26 14:31:52 -05:00
Chris Eager
f68390e96f Add Twilio Verify methods to SmsSender 2021-04-26 14:31:52 -05:00
Chris Eager
76cbf734ad Add TwilioVerifySender to TwilioSmsSender 2021-04-26 14:31:52 -05:00
Chris Eager
17ba630014 Add TwilioVerifySender 2021-04-26 14:31:52 -05:00
Chris Eager
7057476048 Bump version to 5.63 (configuration-only change) 2021-04-23 15:05:55 -05:00
Chris Eager
3121867f72 Remove incorrect license header 2021-04-23 13:27:22 -05:00
Chris Eager
435410b004 Bump version to 5.62 2021-04-23 11:49:08 -05:00
Chris Eager
f190462879 Fully implement unsealed sender cardinality rate limiter 2021-04-23 11:45:53 -05:00
Chris Eager
7c0ff67625 Bump version to 5.61 2021-04-22 18:16:37 -05:00
Chris Eager
ac72c8b2de Remove log 2021-04-22 18:15:52 -05:00
Chris Eager
20208ae528 Bump version to 5.60 2021-04-22 17:12:20 -05:00
Chris Eager
6c6f073bc2 Pass re-migration account in test 2021-04-22 17:11:10 -05:00
Chris Eager
0663fe30df Add cancellation reason codes to error logging 2021-04-22 17:11:10 -05:00
Jon Chambers
2c0a75586b Bump version to 5.59 2021-04-22 15:25:40 -04:00
Chris Eager
b6cb23cbb5 Handle potentially null item from Dynamo delete outcome 2021-04-22 15:24:58 -04:00
Chris Eager
ee555285ed Bump version to 5.58 2021-04-21 15:30:18 -05:00
Chris Eager
b75456acf3 Allow migration thread pool to be scaled up 2021-04-21 15:23:02 -05:00
Chris Eager
be6d6351b9 Bump version to 5.57 2021-04-21 14:13:52 -05:00
Chris Eager
abafa2ccac Include ATTR_MIGRATION_VERSION in update() 2021-04-21 14:13:13 -05:00
Chris Eager
53e6f419b6 Bump version to 5.56 2021-04-21 11:29:44 -05:00
Jon Chambers
b75dec40ac Drop syslog4j in favor of Dropwizard's/Logback's syslog appender. 2021-04-21 11:23:52 -05:00
Chris Eager
0f4f775ee2 Bump version to 5.55 2021-04-21 11:14:29 -05:00
Chris Eager
5974328d9c Ensure accounts are deleted after batch migration; store migration failures for later processing 2021-04-21 11:13:07 -05:00
Chris Eager
a472774734 Add threadpoool to increase Accounts → Dynamo migration throughput 2021-04-21 11:13:07 -05:00
Chris Eager
166d203e8e Don’t PUT unmigrated accounts in update() 2021-04-21 11:13:07 -05:00
Chris Eager
3b3764535c Bump version to 5.54 2021-04-20 13:01:46 -05:00
Chris Eager
f2a1a65a45 Migrate MessageControllerTest to JUnit 5 2021-04-20 13:00:29 -05:00
Chris Eager
b7c56108ca Rate limit unsealed sender by E164 2021-04-20 13:00:29 -05:00
Jon Chambers
52478e7de0 Test the account crawler against a real redis cluster. 2021-04-20 13:58:27 -04:00
Chris Eager
ae9fd090de Bump version to 5.53 2021-04-16 14:25:08 -05:00
Chris Eager
59bbd0c43c Add Accounts DynamoDB
* Add additional test cases to AccountsTest
* Migrate AccountsManagerTest to JUnit 5
* Add AccountsDynamoDbConfiguration
* Add Account.dynamoDbMigrationversion
* Add DynamicAccountsDynamoDbMigrationConfiguration
* Add AccountsDynamoDb to AccountsManager
* Add AccountsDynamoDbMigrator
2021-04-16 14:24:24 -05:00
Jon Chambers
f6c9b2b6e7 Bump to version 5.52 (config-only change) 2021-04-15 19:03:40 -04:00
Chris Eager
0c0e33bc0e Bump version to 5.51 2021-04-14 11:58:08 -05:00
Jon Chambers
4d33ba48cc Discard some tools intended to detect abusive behavior that turned out to not actually be that helpful. 2021-04-14 11:18:19 -04:00
Chris Eager
18fb23f27c Fix default logger autoconfiguration parse error 2021-04-13 16:15:01 -05:00
Chris Eager
92c25a8373 Fix flaky SenderTest 2021-04-13 16:15:01 -05:00
Sanket.Ghenand@tomtom.com
14f5271c20 logger use current class 2021-04-13 11:44:59 -05:00
Sanket.Ghenand@tomtom.com
37bda0b035 remove unused imports 2021-04-13 11:44:59 -05:00
Jon Chambers
675785a4fd Bump version to 5.50 2021-04-12 18:27:28 -04:00
erdinc
0572951c8a Remove temporary variable 2021-04-08 12:38:06 -05:00
Ewout ter Hoeven
7d766ee39e Add FUNDING.yml to display sponsor button in GitHub
Displays the Sponsor button on top of the GitHub interface. This file is similar to the FUNDING.yml file on the Signal-Desktop repository.
2021-04-08 09:19:08 -04:00
Andrew Bissell
1f24c913a6 add unit tests for SmsSender 2021-04-08 09:17:47 -04:00
Jon Chambers
2a8806ec2e Bump version to 5.49 2021-04-07 16:56:18 -04:00
Chris Eager
ffcabe6fc4 Update metric names in RemoteConfigs 2021-04-07 16:53:06 -04:00
Jon Chambers
365ad3a4f8 Bump version to 5.48 2021-04-01 11:33:24 -04:00
Jon Chambers
2cb788ceb7 Delete secure backups when deleting accounts. 2021-04-01 11:30:48 -04:00
Jon Chambers
257fef9734 Add a secure backup service client. 2021-04-01 11:30:48 -04:00
Jon Chambers
37e0730d2a Bump version to 5.47 2021-03-31 18:06:33 -04:00
Chris Eager
dea359ef91 Add metric tag for prekey target type 2021-03-31 17:59:47 -04:00
Jon Chambers
64c9648dd8 Also trust the GeoTrust Global CA cert for now. We'll remove it soon. 2021-03-31 17:59:32 -04:00
Jon Chambers
6dfd13118d Pin the trusted APNs CA cert. 2021-03-31 17:59:32 -04:00
Jon Chambers
2f6105f9bc Bump version to 5.45 2021-03-30 17:18:18 -04:00
Jon Chambers
5c23f62cec Record deletion errors in logs/metrics. 2021-03-30 17:17:32 -04:00
Jon Chambers
ab4e94edab Revert "Revert "Delete data in the storage service when deleting accounts.""
This reverts commit 91fc0fd623.
2021-03-30 17:17:32 -04:00
Jon Chambers
9589b7758c Disallow generation of certificates with key IDs reserved for testing. 2021-03-30 16:25:36 -04:00
Chris Eager
681cdf8eff Bump version to 5.44 2021-03-30 10:24:55 -05:00
Chris Eager
ad6c271f9d Add dynamic configuration for signup captcha by country code 2021-03-30 10:22:02 -05:00
Chris Eager
c8414a63fb Bump version to 5.43 2021-03-29 14:53:57 -05:00
Chris Eager
c10d9603ad Add metric for international PreKey requests 2021-03-29 14:52:04 -05:00
Chris Eager
91bd061110 Migrate deprecated Lettuce method and enum usages 2021-03-29 14:51:26 -05:00
Chris Eager
83aa59f4dd Align messages_cluster client name 2021-03-29 14:51:26 -05:00
Ehren Kret
3745a0b81d Update from 684 to 776 for payment address length to account for signature 2021-03-27 00:45:47 -05:00
Jon Chambers
e2b093abce Bump version to 5.42 2021-03-26 15:14:04 -04:00
Jon Chambers
7e29ed1cc7 Block attempts to set wallet addresses from unsupported countries. 2021-03-26 12:21:11 -04:00
Jon Chambers
5965f0fd22 Add a dynamically-configured list of allowed country codes for payments. 2021-03-26 12:21:11 -04:00
Jon Chambers
c3c46f2f74 Bump version to 5.41 2021-03-22 10:35:13 -04:00
Jon Chambers
a816aa0186 Revert "Add a storage client method for checking wheter a user has a stored manifest."
This reverts commit 8b6012f8a8.
2021-03-22 10:32:38 -04:00
Jon Chambers
a7bad20eae Revert "Add an admin command for printing a subset of account data."
This reverts commit a288b9df8e.
2021-03-22 10:32:38 -04:00
Jon Chambers
089b6b1644 Retry attempts to get messages after a delay; close connections after a finite number of retries. 2021-03-22 10:32:25 -04:00
Jon Chambers
7509520883 Make sure to release the semaphore even if something goes wrong getting messages. 2021-03-22 10:32:25 -04:00
Chris Eager
9778775046 Bump version to 5.40 2021-03-18 13:00:02 -05:00
Chris Eager
e5ae0572c5 Add android-2021-03 verification format 2021-03-18 12:49:50 -05:00
Chris Eager
63dac3bd9f Migrate AccountControllerTest to JUnit 5 2021-03-18 12:45:20 -05:00
Chris Eager
19295eef46 Add dimensional metrics for account creation and verification 2021-03-18 12:45:20 -05:00
Jon Chambers
0bc1369e04 Work through the full list of supported locales when choosing a language for voice verification. 2021-03-18 13:29:09 -04:00
Jon Chambers
ca2f7d2eed Parse locale strings when sending voice verification codes. 2021-03-18 13:29:09 -04:00
Jon Chambers
3ea535a412 Lower logging level for common failures and record failure metrics. 2021-03-18 12:43:31 -04:00
Jon Chambers
a288b9df8e Add an admin command for printing a subset of account data. 2021-03-18 12:43:12 -04:00
Jon Chambers
8b6012f8a8 Add a storage client method for checking wheter a user has a stored manifest. 2021-03-18 12:43:12 -04:00
Chris Eager
1e5d7582da Bump version to 5.39 2021-03-16 16:55:28 -05:00
Chris Eager
ad838b4827 Add isEnrolled(e164, experiment) for pre-registration experiments 2021-03-16 13:09:10 -05:00
Chris Eager
25f603efc9 Add DynamicPreRegistrationExperimentEnrollmentConfiguration 2021-03-16 13:09:10 -05:00
Chris Eager
152c927929 Reformat to match EditorConig 2021-03-16 13:09:10 -05:00
Chris Eager
b5bd16c6a9 Migrate DynamicConfigurationTest to JUnit 5 2021-03-16 13:09:10 -05:00
Chris Eager
14bfa83bb8 Migrate ExperimentEnrollmentManagerTest to JUnit 5 2021-03-16 13:09:10 -05:00
Chris Eager
5dc8086968 Migrate Twilio numbers from static to dynamic configuration 2021-03-16 12:53:51 -05:00
Jon Chambers
7118340f12 Bump version to 5.38 2021-03-10 15:01:13 -05:00
Jon Chambers
efe7f2e4c1 Remove vestiges of per-country sender ID logic/configuration. 2021-03-10 14:58:46 -05:00
Jon Chambers
fb2fc2335a Require messaging service IDs; remove fallback-to-random-number logic. 2021-03-10 14:58:46 -05:00
Jon Chambers
345e116699 Place our trust in a Twilio message service. 2021-03-10 14:58:46 -05:00
Jon Chambers
e50a1c0646 Revert "Turn off alphanumeric sender ID for all countries."
This reverts commit 3bf0188e7f.
2021-03-10 14:58:46 -05:00
Jon Chambers
a6fd1aa06c Fix a minor style warning. 2021-03-10 14:54:11 -05:00
Jon Chambers
3cdc58200a Copy headers from the initial websocket upgrade request into subsequent resource requests. 2021-03-10 14:54:11 -05:00
Jon Chambers
933dd81d82 Allow callers to specify a TLS version when constructing a FaultTolerantHttpClient. 2021-03-10 10:53:33 -05:00
Jon Chambers
a1434524a4 Allow the storage service client to trust the Signal CA root. 2021-03-10 10:53:33 -05:00
Jon Chambers
cdc6afefe2 Add an affordance for providing a custom trust store to FaultTolerantHttpClient. 2021-03-10 10:53:33 -05:00
Jon Chambers
738ec2a38e Use ForwardedIpUtil everywhere we're handling X-Forwarded-For values. 2021-03-10 10:37:10 -05:00
Jon Chambers
07886a9722 Introduce a utility class for working with forwarding chains in HTTP headers. 2021-03-10 10:37:10 -05:00
Jon Chambers
fde1b49729 Introduce a set of gauges for various network statistics as reported by nstat. 2021-03-09 11:45:53 -05:00
Jon Chambers
58210141f4 Discard unhelpful message count histograms. 2021-03-08 16:57:26 -05:00
brock-signal
e1f35102aa Bump version to 5.37 2021-03-05 16:37:49 -07:00
Jon Chambers
af2a8548c3 Use Durations everywhere, drop unused constructors, and add tests. 2021-03-05 12:47:36 -05:00
brock-signal
1faedd3870 Return Retry-After time to clients when they are rate limited (#421)
* Return Retry-After time to clients when they are rate limited

* Update based on feedback

- New exception type that is mapped differently
- Always report time until allowed on rate limits
- Consume and transform into a differnt exception if we think it will be
  allowed later
2021-03-05 10:23:03 -07:00
Jon Chambers
f57a4171ba Gather IP-based metrics for international, unsealed-sender messages. 2021-03-05 11:54:01 -05:00
Jon Chambers
df9dc82de5 Record days since last seen when somebody's "last seen" date changes. 2021-03-01 15:31:53 -05:00
Jon Chambers
0573f09285 Bump version to 5.36 2021-02-24 18:09:57 -05:00
Jon Chambers
eb6fe11da1 Add tools to decline messages from senders meeting specific conditions 2021-02-24 18:09:26 -05:00
Jon Chambers
823025f3b3 Bump version to 5.35 2021-02-23 16:45:47 -05:00
Jon Chambers
0ee3f0a5b5 Fix a goof where the international unsealed sender country meter was incrementing unconditionally. 2021-02-23 16:45:47 -05:00
Jon Chambers
6bff564129 Bump version to 5.34 2021-02-23 15:36:36 -05:00
Jon Chambers
7dabc92447 Actually increment the international unsealed-sender counter instead of just declaring its existence. 2021-02-23 15:36:36 -05:00
Jon Chambers
78bbe8855b Bump version to 5.33 2021-02-23 14:17:43 -05:00
Jon Chambers
5354104128 Only apply unsealed sender rate limits to targeted country codes. 2021-02-23 14:17:16 -05:00
Jon Chambers
a5118e4daa Record push challenge presence/outcomes by country. 2021-02-23 13:36:37 -05:00
Ehren Kret
b5ade5dc12 Only return payment address from latest profile (#408)
* Only return payment address from latest profile

* Rename `currentVersionedProfile` to `currentProfileVersion`

* Change return type to Optional

* Update service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java

Co-authored-by: Jon Chambers <63609320+jon-signal@users.noreply.github.com>

* Fix broken test

Co-authored-by: Jon Chambers <63609320+jon-signal@users.noreply.github.com>
2021-02-23 12:29:47 -06:00
Jon Chambers
fff8c72f42 Record the rate of unsealed-sender messages from accounts with a non-push-capable master device. 2021-02-23 12:26:23 -05:00
Jon Chambers
06ca5f14fc Record the age of accounts that send unsealed-sender messages. 2021-02-23 12:16:45 -05:00
Jon Chambers
8c9d871268 Log different messages depending on whether rate limits are actually enforced. 2021-02-23 12:16:45 -05:00
Jon Chambers
5951ead1b6 Cache Maven artifacts to reduce the incidence of plugin resolution failure. 2021-02-23 12:07:57 -05:00
Jon Chambers
4a0a0e10d2 Run tests on push to avoid double-running tests on pull requests. 2021-02-23 12:07:57 -05:00
Jon Chambers
7266eeee7a Record the rate of international unsealed-sender attempts. 2021-02-23 12:01:09 -05:00
Jon Chambers
5839ce3e1a Bump version to 5.32 2021-02-19 15:07:53 -05:00
Chris Eager
f85c6bf828 Demonstrate JUnit 5 works by migrating an existing test 2021-02-19 13:30:44 -06:00
Chris Eager
9af9e21e05 Add JUnit Jupiter + JUnit Vintage 2021-02-19 13:30:44 -06:00
Chris Eager
6d16ad2763 Bump maven-surefire-plugin to 3.0.0-M5 2021-02-19 13:30:44 -06:00
Chris Eager
447fba1594 Update to the latest version of libphonenumber 2021-02-19 13:30:16 -06:00
Ehren Kret
93f845610d Remove payments list from Account 2021-02-19 13:18:30 -06:00
Chris Eager
aa8525385a Temporarily support IncomingMessage.online (#404)
iOS versions prior to 5.5.0.7 send `online` on `IncomingMessage`, rather
than on the top-level entity. This adds a temporary server-side adaptation,
to prevent client-side issues, like persistent typing indicators.
2021-02-18 12:21:16 -06:00
Jon Chambers
ec783133c1 Close websockets if anything seems fishy at init time; register close handlers early. 2021-02-18 13:20:19 -05:00
Ehren Kret
f630bddb19 Bump version to 5.31 2021-02-17 16:30:27 -06:00
Moxie Marlinspike
71f0aab2c6 Actually start the currency managed lifecycle 2021-02-17 14:28:08 -08:00
501 changed files with 33163 additions and 25663 deletions

View File

@@ -5,7 +5,7 @@ indent_size = 2
indent_style = space
insert_final_newline = true
max_line_length = 120
tab_width = 2
tab_width = 8
ij_continuation_indent_size = 4
ij_formatter_off_tag = @formatter:off
ij_formatter_on_tag = @formatter:on
@@ -34,18 +34,15 @@ ij_css_use_double_quotes = true
ij_css_value_alignment = do_not_align
[*.feature]
tab_width = 4
ij_continuation_indent_size = 8
ij_gherkin_keep_indents_on_empty_lines = false
[*.gsp]
indent_size = 4
tab_width = 4
ij_continuation_indent_size = 8
ij_gsp_keep_indents_on_empty_lines = false
[*.haml]
tab_width = 4
ij_continuation_indent_size = 8
ij_haml_keep_indents_on_empty_lines = false
@@ -144,8 +141,8 @@ ij_java_for_brace_force = always
ij_java_for_statement_new_line_after_left_paren = false
ij_java_for_statement_right_paren_on_new_line = false
ij_java_for_statement_wrap = normal
ij_java_generate_final_locals = false
ij_java_generate_final_parameters = false
ij_java_generate_final_locals = true
ij_java_generate_final_parameters = true
ij_java_if_brace_force = always
ij_java_imports_layout = $*,|,*
ij_java_indent_case_from_switch = true
@@ -298,7 +295,6 @@ ij_java_wrap_first_method_in_call_chain = false
ij_java_wrap_long_lines = false
[*.less]
tab_width = 4
ij_continuation_indent_size = 8
ij_less_align_closing_brace_with_properties = false
ij_less_blank_lines_around_nested_selector = 1
@@ -332,7 +328,6 @@ ij_protobuf_spaces_within_brackets = false
[*.rs]
indent_size = 4
tab_width = 4
ij_rust_align_multiline_chained_methods = false
ij_rust_align_multiline_parameters = true
ij_rust_align_multiline_parameters_in_calls = true
@@ -392,7 +387,6 @@ ij_scss_use_double_quotes = true
ij_scss_value_alignment = 0
[*.styl]
tab_width = 4
ij_continuation_indent_size = 8
ij_stylus_align_closing_brace_with_properties = false
ij_stylus_blank_lines_around_nested_selector = 1
@@ -876,7 +870,6 @@ ij_coffeescript_var_declaration_wrap = normal
[{*.dot,*.gv}]
indent_size = 4
tab_width = 4
ij_continuation_indent_size = 8
ij_dot_keep_blank_lines_in_code = 2
ij_dot_keep_indents_on_empty_lines = false
@@ -898,13 +891,11 @@ ij_rhtml_keep_indents_on_empty_lines = false
[{*.ft,*.vm,*.vsl}]
indent_size = 4
tab_width = 4
ij_continuation_indent_size = 8
ij_vtl_keep_indents_on_empty_lines = false
[{*.gant,*.gradle,*.groovy,*.gson,*.gy}]
indent_size = 4
tab_width = 4
ij_continuation_indent_size = 8
ij_groovy_align_group_field_declarations = false
ij_groovy_align_multiline_array_initializer_expression = false
@@ -1111,7 +1102,6 @@ ij_ruby_spaces_within_braces = true
[{*.gradle.kts,*.kt,*.kts,*.main.kts}]
indent_size = 4
tab_width = 4
ij_continuation_indent_size = 8
ij_kotlin_align_in_columns_case_branch = false
ij_kotlin_align_multiline_binary_operation = false
@@ -1206,7 +1196,6 @@ ij_json_spaces_within_brackets = false
ij_json_wrap_long_lines = false
[{*.hcl,*.nomad}]
tab_width = 4
ij_continuation_indent_size = 8
ij_hcl_array_wrapping = 2
ij_hcl_keep_blank_lines_in_code = 2
@@ -1252,20 +1241,17 @@ ij_html_uniform_ident = false
[{*.jsf,*.jsp,*.jspf,*.tag,*.tagf,*.xjsp}]
indent_size = 4
tab_width = 4
ij_continuation_indent_size = 8
ij_jsp_jsp_prefer_comma_separated_import_list = false
ij_jsp_keep_indents_on_empty_lines = false
[{*.jspx,*.tagx}]
indent_size = 4
tab_width = 4
ij_continuation_indent_size = 8
ij_jspx_keep_indents_on_empty_lines = false
[{*.lua,*.lua.txt}]
indent_size = 4
tab_width = 4
ij_continuation_indent_size = 8
ij_lua_align_consecutive_variable_declarations = false
ij_lua_align_multiline_parameters = true
@@ -1280,7 +1266,6 @@ ij_lua_spaces_around_assignment_operators = true
[{*.markdown,*.md}]
indent_size = 4
tab_width = 4
ij_continuation_indent_size = 8
ij_markdown_force_one_space_after_blockquote_symbol = true
ij_markdown_force_one_space_after_header_symbol = true
@@ -1294,9 +1279,6 @@ ij_markdown_min_lines_around_block_elements = 1
ij_markdown_min_lines_around_header = 1
ij_markdown_min_lines_between_paragraphs = 1
[{*.mk,GNUmakefile,makefile}]
tab_width = 4
[{*.pb,*.textproto}]
ij_prototext_keep_blank_lines_in_code = 2
ij_prototext_keep_indents_on_empty_lines = false
@@ -1381,7 +1363,6 @@ ij_python_use_continuation_indent_for_collection_and_comprehensions = false
ij_python_wrap_long_lines = false
[{*.tf,*.tfvars}]
tab_width = 4
ij_continuation_indent_size = 8
ij_hcl-terraform_array_wrapping = 2
ij_hcl-terraform_keep_blank_lines_in_code = 2
@@ -1399,7 +1380,6 @@ ij_hcl-terraform_wrap_long_lines = false
[{*.toml,Cargo.lock,Gopkg.lock,Pipfile}]
indent_size = 4
tab_width = 4
ij_continuation_indent_size = 8
ij_toml_keep_indents_on_empty_lines = false

4
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,4 @@
# Copyright 2021 Signal Messenger, LLC
# SPDX-License-Identifier: AGPL-3.0-only
custom: https://signal.org/donate/

View File

@@ -1,6 +1,6 @@
name: Service CI
on: [push, pull_request]
on: [push]
jobs:
build:
@@ -9,8 +9,10 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v1
uses: actions/setup-java@3bc31aaf88e8fc94dc1e632d48af61be5ca8721c
with:
java-version: 11
distribution: 'temurin'
java-version: 17
cache: 'maven'
- name: Build with Maven
run: mvn -e -B package
run: mvn -e -B verify

16
.gitignore vendored
View File

@@ -9,12 +9,20 @@ config/production.yml
config/federated.yml
config/staging.yml
config/testing.yml
service/config/production.yml
service/config/federated.yml
service/config/staging.yml
service/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
/service/src/main/resources/org/signal/badges/Badges_*.properties
!/service/src/main/resources/org/signal/badges/Badges_en.properties
/service/src/main/resources/org/signal/subscriptions/Subscriptions_*.properties
!/service/src/main/resources/org/signal/subscriptions/Subscriptions_en.properties
/.tx/config

11
.gitmodules vendored Normal file
View File

@@ -0,0 +1,11 @@
# Note that the implmentation of the abusive message filter is private; internal
# developers will need to override this URL with:
#
# ```
# git config submodule.abusive-message-filter.url PRIVATE_URL
# ```
#
# External developers may safely ignore this submodule.
[submodule "abusive-message-filter"]
path = abusive-message-filter
url = REDACTED

9
.mvn/extensions.xml Normal file
View 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
View 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>

44
LICENSE
View File

@@ -615,3 +615,47 @@ 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.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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 <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.

View File

@@ -1,47 +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>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>gcm-sender-async</artifactId>
<version>${TextSecureServer.version}</version>
<dependencies>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-retry</artifactId>
<version>${resilience4j.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>mockwebserver</artifactId>
<version>2.1.0</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
<version>1.46</version>
<scope>test</scope>
</dependency>
</dependencies>
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>

View File

@@ -4,32 +4,41 @@
*/
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.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.RecordedRequest;
import com.squareup.okhttp.mockwebserver.rule.MockWebServerRule;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
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 static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.*;
import static org.whispersystems.gcm.server.util.FixtureHelpers.fixture;
import static org.whispersystems.gcm.server.util.JsonHelpers.jsonFixture;
import org.junit.Rule;
import org.junit.Test;
public class SenderTest {
@Rule
public MockWebServerRule server = new MockWebServerRule();
public WireMockRule wireMock = new WireMockRule(options().dynamicPort().dynamicHttpsPort());
private static final ObjectMapper mapper = new ObjectMapper();
@@ -41,11 +50,13 @@ public class SenderTest {
@Test
public void testSuccess() throws InterruptedException, ExecutionException, TimeoutException, IOException {
MockResponse successResponse = new MockResponse().setResponseCode(200)
.setBody(fixture("fixtures/response-success.json"));
server.enqueue(successResponse);
wireMock.stubFor(any(anyUrl())
.willReturn(aResponse()
.withStatus(200)
.withBody(fixture("fixtures/response-success.json"))));
Sender sender = new Sender("foobarbaz", mapper, 10, server.getUrl("/gcm/send").toExternalForm());
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);
@@ -57,20 +68,19 @@ public class SenderTest {
assertNull(result.getError());
assertNull(result.getCanonicalRegistrationId());
RecordedRequest request = server.takeRequest();
assertEquals(request.getPath(), "/gcm/send");
assertEquals(new String(request.getBody()), jsonFixture("fixtures/message-minimal.json"));
assertEquals(request.getHeader("Authorization"), "key=foobarbaz");
assertEquals(request.getHeader("Content-Type"), "application/json");
assertEquals(server.getRequestCount(), 1);
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 {
MockResponse unauthorizedResponse = new MockResponse().setResponseCode(401);
server.enqueue(unauthorizedResponse);
wireMock.stubFor(any(anyUrl())
.willReturn(aResponse()
.withStatus(401)));
Sender sender = new Sender("foobar", mapper, 10, server.getUrl("/gcm/send").toExternalForm());
Sender sender = new Sender("foobar", mapper, 10, "http://localhost:" + wireMock.port() + "/gcm/send");
CompletableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build());
try {
@@ -80,15 +90,16 @@ public class SenderTest {
assertTrue(ee.getCause() instanceof AuthenticationFailedException);
}
assertEquals(server.getRequestCount(), 1);
verify(1, anyRequestedFor(anyUrl()));
}
@Test
public void testBadRequest() throws TimeoutException, InterruptedException {
MockResponse malformed = new MockResponse().setResponseCode(400);
server.enqueue(malformed);
wireMock.stubFor(any(anyUrl())
.willReturn(aResponse()
.withStatus(400)));
Sender sender = new Sender("foobarbaz", mapper, 10, server.getUrl("/gcm/send").toExternalForm());
Sender sender = new Sender("foobarbaz", mapper, 10, "http://localhost:" + wireMock.port() + "/gcm/send");
CompletableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build());
try {
@@ -98,17 +109,16 @@ public class SenderTest {
assertTrue(e.getCause() instanceof InvalidRequestException);
}
assertEquals(server.getRequestCount(), 1);
verify(1, anyRequestedFor(anyUrl()));
}
@Test
public void testServerError() throws TimeoutException, InterruptedException {
MockResponse error = new MockResponse().setResponseCode(503);
server.enqueue(error);
server.enqueue(error);
server.enqueue(error);
wireMock.stubFor(any(anyUrl())
.willReturn(aResponse()
.withStatus(503)));
Sender sender = new Sender("foobarbaz", mapper, 3, server.getUrl("/gcm/send").toExternalForm());
Sender sender = new Sender("foobarbaz", mapper, 3, "http://localhost:" + wireMock.port() + "/gcm/send");
CompletableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build());
try {
@@ -118,27 +128,29 @@ public class SenderTest {
assertTrue(ee.getCause() instanceof ServerFailedException);
}
assertEquals(server.getRequestCount(), 3);
verify(3, anyRequestedFor(anyUrl()));
}
@Test
public void testServerErrorRecovery() throws InterruptedException, ExecutionException, TimeoutException {
MockResponse success = new MockResponse().setResponseCode(200)
.setBody(fixture("fixtures/response-success.json"));
MockResponse error = new MockResponse().setResponseCode(503);
wireMock.stubFor(any(anyUrl()).willReturn(aResponse().withStatus(503)));
server.enqueue(error);
server.enqueue(error);
server.enqueue(error);
server.enqueue(success);
Sender sender = new Sender("foobarbaz", mapper, 4, server.getUrl("/gcm/send").toExternalForm());
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);
assertEquals(server.getRequestCount(), 4);
verify(new CountMatchingStrategy(CountMatchingStrategy.GREATER_THAN, 1), anyRequestedFor(anyUrl()));
assertTrue(result.isSuccess());
assertFalse(result.isThrottled());
assertFalse(result.isUnregistered());
@@ -148,18 +160,14 @@ public class SenderTest {
}
@Test
@Ignore
public void testNetworkError() throws TimeoutException, InterruptedException, IOException {
MockResponse response = new MockResponse().setResponseCode(200)
.setBody(fixture("fixtures/response-success.json"));
public void testNetworkError() throws TimeoutException, InterruptedException {
server.enqueue(response);
server.enqueue(response);
server.enqueue(response);
wireMock.stubFor(any(anyUrl())
.willReturn(ok()));
Sender sender = new Sender("foobarbaz", mapper ,2, server.getUrl("/gcm/send").toExternalForm());
Sender sender = new Sender("foobarbaz", mapper ,2, "http://localhost:" + wireMock.port() + "/gcm/send");
server.get().shutdown();
wireMock.stop();
CompletableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build());
@@ -172,12 +180,11 @@ public class SenderTest {
@Test
public void testNotRegistered() throws InterruptedException, ExecutionException, TimeoutException {
MockResponse response = new MockResponse().setResponseCode(200)
.setBody(fixture("fixtures/response-not-registered.json"));
server.enqueue(response);
wireMock.stubFor(any(anyUrl()).willReturn(aResponse().withStatus(200)
.withBody(fixture("fixtures/response-not-registered.json"))));
Sender sender = new Sender("foobarbaz", mapper,2, server.getUrl("/gcm/send").toExternalForm());
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!")

View File

@@ -4,31 +4,34 @@
*/
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 org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
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 static com.github.tomakehurst.wiremock.client.WireMock.*;
import static junit.framework.TestCase.assertTrue;
import static org.whispersystems.gcm.server.util.FixtureHelpers.fixture;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
public class SimultaneousSenderTest {
@Rule
public WireMockRule wireMock = new WireMockRule(8089);
public WireMockRule wireMock = new WireMockRule(WireMockConfiguration.options().dynamicPort().dynamicHttpsPort());
private static final ObjectMapper mapper = new ObjectMapper();
@@ -45,7 +48,7 @@ public class SimultaneousSenderTest {
.withStatus(200)
.withBody(fixture("fixtures/response-success.json"))));
Sender sender = new Sender("foobarbaz", mapper, 2, "http://localhost:8089/gcm/send");
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++) {
@@ -68,7 +71,7 @@ public class SimultaneousSenderTest {
.willReturn(aResponse()
.withStatus(503)));
Sender sender = new Sender("foobarbaz", mapper, 2, "http://localhost:8089/gcm/send");
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++) {

View File

@@ -1,6 +1,8 @@
<!-- 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>
<!-- 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>

625
pom.xml
View File

@@ -1,211 +1,450 @@
<?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>
<packaging>pom</packaging>
<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>
<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>
<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>
<modules>
<module>redis-dispatch</module>
<module>websocket-resources</module>
<module>gcm-sender-async</module>
<module>service</module>
</modules>
<properties>
<dropwizard.version>2.0.13</dropwizard.version>
<resilience4j.version>1.5.0</resilience4j.version>
<mockito.version>2.25.1</mockito.version>
<aws.sdk.version>1.11.939</aws.sdk.version>
<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.25</dropwizard.version>
<dropwizard-metrics-datadog.version>1.1.13</dropwizard-metrics-datadog.version>
<gson.version>2.8.8</gson.version>
<guava.version>30.1.1-jre</guava.version>
<jaxb.version>2.3.1</jaxb.version>
<jedis.version>2.9.0</jedis.version>
<lettuce.version>6.0.4.RELEASE</lettuce.version>
<libphonenumber.version>8.12.33</libphonenumber.version>
<logstash.logback.version>6.6</logstash.logback.version>
<micrometer.version>1.5.3</micrometer.version>
<mockito.version>4.0.0</mockito.version>
<netty.version>4.1.65.Final</netty.version>
<netty.tcnative-boringssl-static.version>2.0.39.Final</netty.tcnative-boringssl-static.version>
<opentest4j.version>1.2.0</opentest4j.version>
<postgresql.version>9.4-1201-jdbc41</postgresql.version>
<protobuf.version>3.17.1</protobuf.version>
<pushy.version>0.15.0</pushy.version>
<resilience4j.version>1.5.0</resilience4j.version>
<semver4j.version>3.1.0</semver4j.version>
<slf4j.version>1.7.30</slf4j.version>
<stripe.version>20.79.0</stripe.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<TextSecureServer.version>5.30</TextSecureServer.version>
</properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<parent>
<groupId>org.whispersystems.textsecure</groupId>
<artifactId>TextSecureServer</artifactId>
<version>JGITVER</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-dependencies</artifactId>
<version>2.0.13</version>
</parent>
<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>com.google.cloud</groupId>
<artifactId>libraries-bom</artifactId>
<version>20.9.0</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>
<groupId>org.whispersystems.textsecure</groupId>
<artifactId>TextSecureServer</artifactId>
<version>1.0</version>
<dependencies>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-core</artifactId>
<version>${dropwizard.version}</version>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-jdbi3</artifactId>
<version>${dropwizard.version}</version>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-auth</artifactId>
<version>${dropwizard.version}</version>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-client</artifactId>
<version>${dropwizard.version}</version>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-migrations</artifactId>
<version>${dropwizard.version}</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-servlets</artifactId>
<version>${dropwizard.version}</version>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-testing</artifactId>
<version>${dropwizard.version}</version>
<scope>test</scope>
</dependency>
<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.26.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>
<version>3.15.0</version>
<scope>test</scope>
</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.coursera</groupId>
<artifactId>dropwizard-metrics-datadog</artifactId>
<version>${dropwizard-metrics-datadog.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>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.stripe</groupId>
<artifactId>stripe-java</artifactId>
<version>${stripe.version}</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.1</version>
<configuration>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
</archive>
</configuration>
</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.31.0</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>
<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>
</dependencies>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M1</version>
<configuration>
<systemProperties>
<property>
<name>sqlite4java.library.path</name>
<value>${project.build.directory}/lib</value>
</property>
</systemProperties>
</configuration>
</plugin>
<profiles>
<profile>
<id>include-abusive-message-filter</id>
<activation>
<file>
<exists>abusive-message-filter/pom.xml</exists>
</file>
</activation>
<modules>
<module>abusive-message-filter</module>
</modules>
</profile>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.4.1</version>
<configuration>
<rules><dependencyConvergence/></rules>
</configuration>
</plugin>
<profile>
<id>exclude-abusive-message-filter</id>
<activation>
<file>
<missing>abusive-message-filter/pom.xml</missing>
</file>
</activation>
</profile>
</profiles>
</plugins>
</build>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.0</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.18.0:exe:${os.detected.classifier}</protocArtifact>
<checkStaleness>true</checkStaleness>
</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>
<release>17</release>
</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.8.3</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>

View File

@@ -1,16 +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>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
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>
<artifactId>redis-dispatch</artifactId>
<version>${TextSecureServer.version}</version>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
</dependencies>
</project>
</project>

View File

@@ -5,7 +5,7 @@
package org.whispersystems.dispatch;
public interface DispatchChannel {
public void onDispatchMessage(String channel, byte[] message);
public void onDispatchSubscribed(String channel);
public void onDispatchUnsubscribed(String channel);
void onDispatchMessage(String channel, byte[] message);
void onDispatchSubscribed(String channel);
void onDispatchUnsubscribed(String channel);
}

View File

@@ -59,9 +59,7 @@ public class DispatchManager extends Thread {
logger.warn("Subscription error", e);
}
if (previous.isPresent()) {
dispatchUnsubscription(name, previous.get());
}
previous.ifPresent(channel -> dispatchUnsubscription(name, channel));
}
public synchronized void unsubscribe(String name, DispatchChannel channel) {
@@ -132,46 +130,28 @@ public class DispatchManager extends Thread {
}
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);
new Thread(() -> {
synchronized (DispatchManager.this) {
try {
for (String name : subscriptions.keySet()) {
pubSubConnection.subscribe(name);
}
} catch (IOException e) {
logger.warn("***** RESUBSCRIPTION ERROR *****", e);
}
}
}.start();
}).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);
}
});
executor.execute(() -> channel.onDispatchMessage(name, message));
}
private void dispatchSubscription(final String name, final DispatchChannel channel) {
executor.execute(new Runnable() {
@Override
public void run() {
channel.onDispatchSubscribed(name);
}
});
executor.execute(() -> channel.onDispatchSubscribed(name));
}
private void dispatchUnsubscription(final String name, final DispatchChannel channel) {
executor.execute(new Runnable() {
@Override
public void run() {
channel.onDispatchUnsubscribed(name);
}
});
executor.execute(() -> channel.onDispatchUnsubscribed(name));
}
}

View File

@@ -20,7 +20,7 @@ public class RedisInputStream {
}
public String readLine() throws IOException {
ByteArrayOutputStream boas = new ByteArrayOutputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
boolean foundCr = false;
@@ -31,14 +31,14 @@ public class RedisInputStream {
throw new IOException("Stream closed!");
}
boas.write(character);
baos.write(character);
if (foundCr && character == LF) break;
else if (character == CR) foundCr = true;
else if (foundCr) foundCr = false;
}
byte[] data = boas.toByteArray();
byte[] data = baos.toByteArray();
return new String(data, 0, data.length-2);
}

View File

@@ -8,6 +8,6 @@ import org.whispersystems.dispatch.redis.PubSubConnection;
public interface RedisPubSubConnectionFactory {
public PubSubConnection connect();
PubSubConnection connect();
}

View File

@@ -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>${parent.artifactId}-${TextSecureServer.version}.jar</include>
<include>${parent.artifactId}-${project.version}.jar</include>
</includes>
</fileSet>
</fileSets>
</assembly>
</assembly>

View File

@@ -1,127 +1,337 @@
# Example, relatively minimal, configuration that passes validation (see `io.dropwizard.cli.CheckCommand`)
#
# `unset` values will need to be set to work properly.
# Most other values are technically valid for a local/demonstration environment, but are probably not production-ready.
stripe:
apiKey: unset
idempotencyKeyGenerator: abcdefg12345678= # base64 for creating request idempotency hash
boostDescription: >
Example
dynamoDbClientConfiguration:
region: us-west-2 # AWS Region
dynamoDbTables:
issuedReceipts:
tableName: Example_IssuedReceipts
expiration: P30D # Duration of time until rows expire
generator: abcdefg12345678= # random base64-encoded binary sequence
redeemedReceipts:
tableName: Example_RedeemedReceipts
expiration: P30D # Duration of time until rows expire
subscriptions:
tableName: Example_Subscriptions
twilio: # Twilio gateway configuration
accountId:
accountToken:
numbers: # Numbers allocated in Twilio
- # First number
- # Second number
- # Third number
- # ...
- # Nth number
nanpaMessagingServiceSid: # Twilio SID for the messaging service to use for NANPA.
messagingServiceSid: # Twilio SID for the message service to use for non-NANPA.
localDomain: # Domain Twilio can connect back to for calls. Should be domain of your service.
iosVerificationText: # Text to use for the verification message on iOS. Will be passed to String.format with the verification code as argument 1.
androidNgVerificationText: # 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.
android202001VerificationText: # 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.
genericVerificationText: # Text to use when the client type is unrecognized. Will be passed to String.format with the verification code as argument 1.
senderId:
defaultSenderId: # Sender ID to use for country codes not found in either the overrides or omitted lists.
countryCodesWithoutSenderId:
- # First country code
- # Second country code
- # ...
- # Nth country code
countrySpecificSenderIds:
- countryCode: # First country code
senderId: # Sender ID to use for this country
- countryCode: # Second country code
senderId: # Sender ID to use for this country
- countryCode: # ...
senderId: # ...
- countryCode: # Nth country code
senderId: # Sender ID to use for this country
accountId: unset
accountToken: unset
nanpaMessagingServiceSid: unset # Twilio SID for the messaging service to use for NANPA.
messagingServiceSid: unset # Twilio SID for the message service to use for non-NANPA.
verifyServiceSid: unset # Twilio SID for a Verify service
localDomain: example.com # Domain Twilio can connect back to for calls. Should be domain of your service.
defaultClientVerificationTexts:
ios: example %1$s # Text to use for the verification message on iOS. Will be passed to String.format with the verification code as argument 1.
androidNg: example %1$s # 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: example %1$s # 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: example %1$s # 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: example %1$s # 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: example %1$s # all keys from defaultClientVerificationTexts are required
androidNg: example %1$s
android202001: example %1$s
android202103: example %1$s
generic: example %1$s
androidAppHash: example # Hash appended to Android
verifyServiceFriendlyName: example # Service name used in template. Requires Twilio account rep to enable
push:
queueSize: # Size of push pending queue
redphone:
authKey: # Deprecated
queueSize: 1000 # Size of push pending queue
turn: # TURN server configuration
secret: # TURN server secret
secret: example # TURN server secret
uris:
- stun:yourdomain:80
- stun:yourdomain.com:443
- turn:yourdomain:443?transport=udp
- turn:etc.com:80?transport=udp
- stun:example.com:80
- stun:another.example.com:443
- turn:example.com:443?transport=udp
- turn:ya.example.com:80?transport=udp
cacheCluster: # Redis server configuration for cache cluster
urls:
configurationUri: redis://redis.example.com:6379/
clientPresenceCluster: # Redis server configuration for client presence cluster
configurationUri: redis://redis.example.com:6379/
pubsub: # Redis server configuration for pubsub cluster
url: redis://redis.example.com:6379/
replicaUrls:
- redis://redis.example.com:6379/
pushSchedulerCluster: # Redis server configuration for push scheduler cluster
configurationUri: redis://redis.example.com:6379/
rateLimitersCluster: # Redis server configuration for rate limiters cluster
configurationUri: 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
userAuthenticationTokenSharedSecret: 00000f # hex-encoded secret shared with CDS used to generate auth tokens for Signal users
userAuthenticationTokenUserIdSecret: 00000f # 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
accessKey: test # AWS SQS accessKey
accessSecret: test # AWS SQS accessSecret
queueUrls: # AWS SQS queue urls
- https://sqs.example.com/directory.fifo
server: # One or more CDS servers
- replicationName: example # CDS replication name
replicationUrl: cds.example.com # CDS replication endpoint base url
replicationPassword: example # CDS replication endpoint password
replicationCaCertificate: | # CDS replication endpoint TLS certificate trust root
-----BEGIN CERTIFICATE-----
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
AAAAAAAAAAAAAAAAAAAA
-----END CERTIFICATE-----
directoryV2:
client: # Configuration for interfacing with Contact Discovery Service v2 cluster
userAuthenticationTokenSharedSecret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with CDS to generate auth tokens for Signal users
messageCache: # Redis server configuration for message store cache
persistDelayMinutes:
persistDelayMinutes: 1
cluster:
urls:
- redis://redis.example.com:6379/
messageStore: # Postgresql database configuration for message store
driverClass: org.postgresql.Driver
user:
password:
url:
configurationUri: redis://redis.example.com:6379/
metricsCluster:
urls:
- redis://redis.example.com:6379/
configurationUri: redis://redis.example.com:6379/
messageDynamoDb: # DynamoDB table configuration
region: us-west-2
tableName: Example_Messages
keysDynamoDb: # DynamoDB table configuration
region: us-west-2
tableName: Example_PreKeys
accountsDynamoDb: # DynamoDB table configuration
region: us-west-2
tableName: Example_Accounts
phoneNumberTableName: Example_Accounts_PhoneNumbers
phoneNumberIdentifierTableName: Example_Accounts_PhoneNumberIdentifiers
deletedAccountsDynamoDb: # DynamoDb table configuration
region: us-west-2
tableName: Example_DeletedAccounts
needsReconciliationIndexName: NeedsReconciliation
deletedAccountsLockDynamoDb: # DynamoDb table configuration
region: us-west-2
tableName: Example_DeletedAccountsLock
pendingAccountsDynamoDb: # DynamoDB table configuration
region: us-west-2
tableName: Example_PendingAccounts
pendingDevicesDynamoDb: # DynamoDB table configuration
region: us-west-2
tableName: Example_PendingDevices
reservedUsernamesDynamoDb: # DynamoDB table configuration
region: us-west-2
tableName: Example_ReservedUsernames
phoneNumberIdentifiersDynamoDb: # DynamoDB table configuration
region: us-west-2
tableName: Example_PhoneNumberIdentifiers
pushChallengeDynamoDb: # DynamoDB table configuration
region: us-west-2
tableName: Example_PushChallenges
reportMessageDynamoDb: # DynamoDB table configuration
region: us-west-2
tableName: Example_ReportedMessages
awsAttachments: # AWS S3 configuration
accessKey:
accessSecret:
bucket:
region:
accessKey: test
accessSecret: test
bucket: aws-attachments
region: us-west-2
gcpAttachments: # GCP Storage configuration
domain:
email:
maxSizeInBytes:
domain: example.com
email: user@example.cocm
maxSizeInBytes: 1024
pathPrefix:
rsaSigningKey:
rsaSigningKey: |
-----BEGIN PRIVATE KEY-----
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
AAAAAAAA
-----END PRIVATE KEY-----
profiles: # AWS S3 configuration
accessKey:
accessSecret:
bucket:
region:
database: # Postgresql database configuration
abuseDatabase: # Postgresql database configuration
driverClass: org.postgresql.Driver
user:
password:
url:
user: example
password: password
url: jdbc:postgresql://example.com:5432/abusedb
accountsDatabase: # Postgresql database configuration
driverClass: org.postgresql.Driver
user: example
password: password
url: jdbc:postgresql://example.com:5432/accountsdb
accountDatabaseCrawler:
chunkSize: 10 # accounts per run
chunkIntervalMs: 60000 # time per run
apn: # Apple Push Notifications configuration
sandbox: true
bundleId:
keyId:
teamId:
signingKey:
bundleId: com.example.textsecuregcm
keyId: unset
teamId: unset
signingKey: |
-----BEGIN PRIVATE KEY-----
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
AAAAAAAA
-----END PRIVATE KEY-----
gcm: # GCM Configuration
senderId:
apiKey:
senderId: 123456789
apiKey: unset
micrometer: # Micrometer metrics config
- name: "example"
- uri: "https://metrics.example.com/"
- apiKey:
- accountId:
cdn:
accessKey: test # AWS Access Key ID
accessSecret: test # AWS Access Secret
bucket: cdn # S3 Bucket name
region: us-west-2 # AWS region
datadog:
apiKey: unset
environment: dev
unidentifiedDelivery:
certificate: ABCD1234
privateKey: ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789AAAAAAA
expiresDays: 7
voiceVerification:
url: https://cdn-ca.signal.org/verification/
locales:
- en
recaptcha:
secret: unset
recaptchaV2:
siteKey: unset
scoreFloor: 1.0
projectPath: projects/example
credentialConfigurationJson: "{ }" # service account configuration for backend authentication
storageService:
uri: storage.example.com
userAuthenticationTokenSharedSecret: 00000f
storageCaCertificate: |
-----BEGIN CERTIFICATE-----
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
AAAAAAAAAAAAAAAAAAAA
-----END CERTIFICATE-----
backupService:
uri: backup.example.com
userAuthenticationTokenSharedSecret: 00000f
backupCaCertificate: |
-----BEGIN CERTIFICATE-----
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
AAAAAAAAAAAAAAAAAAAA
-----END CERTIFICATE-----
zkConfig:
serverPublic: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
serverSecret: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzAA==
appConfig:
application: example
environment: example
configuration: example
remoteConfig:
authorizedTokens:
@@ -130,6 +340,64 @@ remoteConfig:
- # ...
- # Nth authorized token
globalConfig: # keys and values that are given to clients on GET /v1/config
EXAMPLE_KEY: VALUE
paymentService:
userAuthenticationTokenSharedSecret: # hex-encoded 32-byte secret shared with MobileCoin services used to generate auth tokens for Signal users
paymentsService:
userAuthenticationTokenSharedSecret: 0000000f0000000f0000000f0000000f0000000f0000000f0000000f0000000f # hex-encoded 32-byte secret shared with MobileCoin services used to generate auth tokens for Signal users
fixerApiKey: unset
paymentCurrencies:
# list of symbols for supported currencies
- MOB
donation:
uri: donation.example.com # value
supportedCurrencies:
- # 1st supported currency
- # 2nd supported currency
- # ...
- # Nth supported currency
badges:
badges:
- id: TEST
category: other
sprites: # exactly 6
- sprite-1.png
- sprite-2.png
- sprite-3.png
- sprite-4.png
- sprite-5.png
- sprite-6.png
svg: example.svg
svgs:
- light: example-light.svg
dark: example-dark.svg
badgeIdsEnabledForAll:
- TEST
receiptLevels:
'1': TEST
subscription: # configuration for Stripe subscriptions
badgeGracePeriod: P15D
levels:
500:
badge: EXAMPLE
prices:
# list of ISO 4217 currency codes and amounts for the given badge level
xts:
amount: '10'
id: price_example # stripe ID
boost:
level: 1
expiration: P90D
badge: EXAMPLE
currencies:
# ISO 4217 currency codes and amounts in those currencies
xts:
- '1'
- '2'
- '4'
- '8'
- '20'
- '40'

View File

@@ -1,287 +1,620 @@
<?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>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
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>
<artifactId>service</artifactId>
<version>${TextSecureServer.version}</version>
<pluginRepositories>
<pluginRepository>
<id>ossrh-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<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>
<dependencies>
<dependency>
<groupId>org.whispersystems.textsecure</groupId>
<artifactId>redis-dispatch</artifactId>
<version>${TextSecureServer.version}</version>
</dependency>
<dependency>
<groupId>org.whispersystems.textsecure</groupId>
<artifactId>websocket-resources</artifactId>
<version>${TextSecureServer.version}</version>
</dependency>
<dependency>
<groupId>org.whispersystems.textsecure</groupId>
<artifactId>gcm-sender-async</artifactId>
<version>${TextSecureServer.version}</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.signal</groupId>
<artifactId>zkgroup-java</artifactId>
<version>0.7.0</version>
</dependency>
<dependency>
<groupId>org.whispersystems.textsecure</groupId>
<artifactId>redis-dispatch</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.whispersystems.textsecure</groupId>
<artifactId>websocket-resources</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.whispersystems.textsecure</groupId>
<artifactId>gcm-sender-async</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.signal</groupId>
<artifactId>zkgroup-java</artifactId>
<version>0.8.2</version>
</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.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
<version>1.46</version>
</dependency>
<dependency>
<groupId>org.syslog4j</groupId>
<artifactId>syslog4j</artifactId>
<version>0.9.30</version>
</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>io.github.resilience4j</groupId>
<artifactId>resilience4j-circuitbreaker</artifactId>
<version>${resilience4j.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-retry</artifactId>
<version>${resilience4j.version}</version>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-wavefront</artifactId>
<version>1.5.3</version>
</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>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>${aws.sdk.version}</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-sqs</artifactId>
<version>${aws.sdk.version}</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-appconfig</artifactId>
<version>${aws.sdk.version}</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-dynamodb</artifactId>
<version>${aws.sdk.version}</version>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-testing</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
<type>jar</type>
<scope>compile</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>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.0.1.RELEASE</version>
</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>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4-1201-jdbc41</version>
</dependency>
<dependency>
<groupId>org.whispersystems</groupId>
<artifactId>curve25519-java</artifactId>
<version>0.5.0</version>
</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>com.eatthepath</groupId>
<artifactId>pushy</artifactId>
<version>0.14.2</version>
</dependency>
<dependency>
<groupId>com.eatthepath</groupId>
<artifactId>pushy-dropwizard-metrics-listener</artifactId>
<version>0.14.2</version>
<exclusions>
<exclusion>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-datadog</artifactId>
</dependency>
<dependency>
<groupId>org.coursera</groupId>
<artifactId>dropwizard-metrics-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>io.netty</groupId>
<artifactId>netty-tcnative-boringssl-static</artifactId>
<version>2.0.34.Final</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sts</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sqs</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>dynamodb</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>appconfig</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-core</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>dynamodb-lock-client</artifactId>
<version>1.1.0</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.vdurmont</groupId>
<artifactId>semver4j</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>com.googlecode.libphonenumber</groupId>
<artifactId>libphonenumber</artifactId>
<version>8.12.17</version>
</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>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<version>2.30</version>
<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.eatthepath</groupId>
<artifactId>pushy</artifactId>
</dependency>
<dependency>
<groupId>com.eatthepath</groupId>
<artifactId>pushy-dropwizard-metrics-listener</artifactId>
</dependency>
<dependency>
<groupId>com.opentable.components</groupId>
<artifactId>otj-pg-embedded</artifactId>
<version>0.13.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-tcnative-boringssl-static</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.signal</groupId>
<artifactId>embedded-redis</artifactId>
<version>0.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.vdurmont</groupId>
<artifactId>semver4j</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.uuid</groupId>
<artifactId>java-uuid-generator</artifactId>
<version>3.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</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>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</dependency>
<dependency>
<groupId>pl.pragmatists</groupId>
<artifactId>JUnitParams</artifactId>
<version>1.1.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<dependency>
<groupId>com.googlecode.libphonenumber</groupId>
<artifactId>libphonenumber</artifactId>
</dependency>
<dependency>
<groupId>net.sourceforge.argparse4j</groupId>
<artifactId>argparse4j</artifactId>
</dependency>
<build>
<finalName>${parent.artifactId}-${TextSecureServer.version}</finalName>
<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.16.0</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.antlr</groupId>
<artifactId>antlr4-runtime</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-recaptchaenterprise</artifactId>
</dependency>
<dependency>
<groupId>com.stripe</groupId>
<artifactId>stripe-java</artifactId>
</dependency>
<dependency>
<groupId>pl.pragmatists</groupId>
<artifactId>JUnitParams</artifactId>
<version>1.1.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>exclude-abusive-message-filter</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>1.6</version>
<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>
<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>
<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>
<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>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.4</version>
<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>
<descriptors>
<descriptor>assembly.xml</descriptor>
</descriptors>
<files>${project.basedir}/config/deploy.properties</files>
</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>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.signal</groupId>
<artifactId>s3-upload-maven-plugin</artifactId>
<version>1.6-SNAPSHOT</version>
<configuration>
<source>${project.build.directory}/${project.build.finalName}-bin.tar.gz</source>
<bucketName>${deploy.bucketName}</bucketName>
<region>${deploy.bucketRegion}</region>
<destination>${project.build.finalName}-bin.tar.gz</destination>
</configuration>
<executions>
<execution>
<id>deploy-to-s3</id>
<phase>deploy</phase>
<goals>
<goal>s3-upload</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</build>
</profile>
</profiles>
<build>
<finalName>${project.parent.artifactId}-${project.version}</finalName>
<plugins>
<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>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>check-all-service-config</id>
<phase>verify</phase>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>org.whispersystems.textsecuregcm.CheckServiceConfigurations</mainClass>
<classpathScope>test</classpathScope>
<arguments>
<argument>${project.basedir}/config</argument>
</arguments>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,3 +0,0 @@
all:
protoc --java_out=../src/main/java/ TextSecure.proto PubSubMessage.proto

View File

@@ -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;
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.i18n;
import com.google.common.annotations.VisibleForTesting;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.ResourceBundle;
import java.util.ResourceBundle.Control;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
public class HeaderControlledResourceBundleLookup {
private static final int MAX_LOCALES = 15;
private final ResourceBundleFactory resourceBundleFactory;
public HeaderControlledResourceBundleLookup() {
this(ResourceBundle::getBundle);
}
@VisibleForTesting
public HeaderControlledResourceBundleLookup(
@Nonnull final ResourceBundleFactory resourceBundleFactory) {
this.resourceBundleFactory = Objects.requireNonNull(resourceBundleFactory);
}
@Nonnull
private List<Locale> getAcceptableLocales(final List<Locale> acceptableLanguages) {
return acceptableLanguages.stream().limit(MAX_LOCALES).distinct().collect(Collectors.toList());
}
@Nonnull
public ResourceBundle getResourceBundle(final String baseName, final List<Locale> acceptableLocales) {
final List<Locale> deduplicatedLocales = getAcceptableLocales(acceptableLocales);
final Locale desiredLocale = deduplicatedLocales.isEmpty() ? Locale.getDefault() : deduplicatedLocales.get(0);
// define a control with a fallback order as specified in the header
Control control = new Control() {
@Override
public List<String> getFormats(final String baseName) {
Objects.requireNonNull(baseName);
return Control.FORMAT_PROPERTIES;
}
@Override
public Locale getFallbackLocale(final String baseName, final Locale locale) {
Objects.requireNonNull(baseName);
if (locale.equals(Locale.getDefault())) {
return null;
}
final int localeIndex = deduplicatedLocales.indexOf(locale);
if (localeIndex < 0 || localeIndex >= deduplicatedLocales.size() - 1) {
return Locale.getDefault();
}
// [0, deduplicatedLocales.size() - 2] is now the possible range for localeIndex
return deduplicatedLocales.get(localeIndex + 1);
}
};
return resourceBundleFactory.createBundle(baseName, desiredLocale, control);
}
}

View File

@@ -0,0 +1,13 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.i18n;
import java.util.Locale;
import java.util.ResourceBundle;
public interface ResourceBundleFactory {
ResourceBundle createBundle(String baseName, Locale locale, ResourceBundle.Control control);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm;
@@ -7,30 +7,49 @@ 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.AbusiveMessageFilterConfiguration;
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.BadgesConfiguration;
import org.whispersystems.textsecuregcm.configuration.BoostConfiguration;
import org.whispersystems.textsecuregcm.configuration.CdnConfiguration;
import org.whispersystems.textsecuregcm.configuration.DatabaseConfiguration;
import org.whispersystems.textsecuregcm.configuration.DatadogConfiguration;
import org.whispersystems.textsecuregcm.configuration.DeletedAccountsDynamoDbConfiguration;
import org.whispersystems.textsecuregcm.configuration.DirectoryConfiguration;
import org.whispersystems.textsecuregcm.configuration.DirectoryV2Configuration;
import org.whispersystems.textsecuregcm.configuration.DonationConfiguration;
import org.whispersystems.textsecuregcm.configuration.DynamoDbClientConfiguration;
import org.whispersystems.textsecuregcm.configuration.DynamoDbConfiguration;
import org.whispersystems.textsecuregcm.configuration.DynamoDbTables;
import org.whispersystems.textsecuregcm.configuration.GcmConfiguration;
import org.whispersystems.textsecuregcm.configuration.GcpAttachmentsConfiguration;
import org.whispersystems.textsecuregcm.configuration.AccountsDatabaseConfiguration;
import org.whispersystems.textsecuregcm.configuration.MaxDeviceConfiguration;
import org.whispersystems.textsecuregcm.configuration.MessageCacheConfiguration;
import org.whispersystems.textsecuregcm.configuration.MessageDynamoDbConfiguration;
import org.whispersystems.textsecuregcm.configuration.MicrometerConfiguration;
import org.whispersystems.textsecuregcm.configuration.PaymentsServiceConfiguration;
import org.whispersystems.textsecuregcm.configuration.PushConfiguration;
import org.whispersystems.textsecuregcm.configuration.RateLimitsConfiguration;
import org.whispersystems.textsecuregcm.configuration.RecaptchaConfiguration;
import org.whispersystems.textsecuregcm.configuration.RecaptchaV2Configuration;
import org.whispersystems.textsecuregcm.configuration.RedisClusterConfiguration;
import org.whispersystems.textsecuregcm.configuration.RedisConfiguration;
import org.whispersystems.textsecuregcm.configuration.RemoteConfigConfiguration;
import org.whispersystems.textsecuregcm.configuration.ReportMessageConfiguration;
import org.whispersystems.textsecuregcm.configuration.SecureBackupServiceConfiguration;
import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration;
import org.whispersystems.textsecuregcm.configuration.StripeConfiguration;
import org.whispersystems.textsecuregcm.configuration.SubscriptionConfiguration;
import org.whispersystems.textsecuregcm.configuration.TestDeviceConfiguration;
import org.whispersystems.textsecuregcm.configuration.TurnConfiguration;
import org.whispersystems.textsecuregcm.configuration.TwilioConfiguration;
@@ -39,16 +58,24 @@ import org.whispersystems.textsecuregcm.configuration.VoiceVerificationConfigura
import org.whispersystems.textsecuregcm.configuration.ZkConfig;
import org.whispersystems.websocket.configuration.WebSocketConfiguration;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/** @noinspection MismatchedQueryAndUpdateOfCollection, WeakerAccess */
public class WhisperServerConfiguration extends Configuration {
@NotNull
@Valid
@JsonProperty
private StripeConfiguration stripe;
@NotNull
@Valid
@JsonProperty
private DynamoDbClientConfiguration dynamoDbClientConfiguration;
@NotNull
@Valid
@JsonProperty
private DynamoDbTables dynamoDbTables;
@NotNull
@Valid
@JsonProperty
@@ -77,7 +104,7 @@ public class WhisperServerConfiguration extends Configuration {
@NotNull
@Valid
@JsonProperty
private MicrometerConfiguration micrometer;
private DatadogConfiguration datadog;
@NotNull
@Valid
@@ -99,6 +126,11 @@ public class WhisperServerConfiguration extends Configuration {
@JsonProperty
private DirectoryConfiguration directory;
@NotNull
@Valid
@JsonProperty
private DirectoryV2Configuration directoryV2;
@NotNull
@Valid
@JsonProperty
@@ -109,6 +141,11 @@ public class WhisperServerConfiguration extends Configuration {
@JsonProperty
private RedisClusterConfiguration pushSchedulerCluster;
@NotNull
@Valid
@JsonProperty
private RedisClusterConfiguration rateLimitersCluster;
@NotNull
@Valid
@JsonProperty
@@ -129,6 +166,51 @@ public class WhisperServerConfiguration extends Configuration {
@JsonProperty
private DynamoDbConfiguration keysDynamoDb;
@Valid
@NotNull
@JsonProperty
private AccountsDynamoDbConfiguration accountsDynamoDb;
@Valid
@NotNull
@JsonProperty
private DynamoDbConfiguration phoneNumberIdentifiersDynamoDb;
@Valid
@NotNull
@JsonProperty
private DeletedAccountsDynamoDbConfiguration deletedAccountsDynamoDb;
@Valid
@NotNull
@JsonProperty
private DynamoDbConfiguration deletedAccountsLockDynamoDb;
@Valid
@NotNull
@JsonProperty
private DynamoDbConfiguration pushChallengeDynamoDb;
@Valid
@NotNull
@JsonProperty
private DynamoDbConfiguration reportMessageDynamoDb;
@Valid
@NotNull
@JsonProperty
private DynamoDbConfiguration pendingAccountsDynamoDb;
@Valid
@NotNull
@JsonProperty
private DynamoDbConfiguration pendingDevicesDynamoDb;
@Valid
@NotNull
@JsonProperty
private DynamoDbConfiguration reservedUsernamesDynamoDb;
@Valid
@NotNull
@JsonProperty
@@ -194,6 +276,11 @@ public class WhisperServerConfiguration extends Configuration {
@JsonProperty
private RecaptchaConfiguration recaptcha;
@Valid
@NotNull
@JsonProperty
private RecaptchaV2Configuration recaptchaV2;
@Valid
@NotNull
@JsonProperty
@@ -224,12 +311,57 @@ public class WhisperServerConfiguration extends Configuration {
@JsonProperty
private AppConfigConfiguration appConfig;
@Valid
@NotNull
@JsonProperty
private DonationConfiguration donation;
@Valid
@NotNull
@JsonProperty
private BadgesConfiguration badges;
@Valid
@JsonProperty
@NotNull
private SubscriptionConfiguration subscription;
@Valid
@JsonProperty
@NotNull
private BoostConfiguration boost;
@Valid
@NotNull
@JsonProperty
private ReportMessageConfiguration reportMessage = new ReportMessageConfiguration();
@Valid
@JsonProperty
private AbusiveMessageFilterConfiguration abusiveMessageFilter;
private Map<String, String> transparentDataIndex = new HashMap<>();
public StripeConfiguration getStripe() {
return stripe;
}
public DynamoDbClientConfiguration getDynamoDbClientConfiguration() {
return dynamoDbClientConfiguration;
}
public DynamoDbTables getDynamoDbTables() {
return dynamoDbTables;
}
public RecaptchaConfiguration getRecaptchaConfiguration() {
return recaptcha;
}
public RecaptchaV2Configuration getRecaptchaV2Configuration() {
return recaptchaV2;
}
public VoiceVerificationConfiguration getVoiceVerificationConfiguration() {
return voiceVerification;
}
@@ -274,6 +406,10 @@ public class WhisperServerConfiguration extends Configuration {
return directory;
}
public DirectoryV2Configuration getDirectoryV2Configuration() {
return directoryV2;
}
public SecureStorageServiceConfiguration getSecureStorageServiceConfiguration() {
return storageService;
}
@@ -294,6 +430,10 @@ public class WhisperServerConfiguration extends Configuration {
return pushSchedulerCluster;
}
public RedisClusterConfiguration getRateLimitersCluster() {
return rateLimitersCluster;
}
public MessageDynamoDbConfiguration getMessageDynamoDbConfiguration() {
return messageDynamoDb;
}
@@ -302,6 +442,22 @@ public class WhisperServerConfiguration extends Configuration {
return keysDynamoDb;
}
public AccountsDynamoDbConfiguration getAccountsDynamoDbConfiguration() {
return accountsDynamoDb;
}
public DynamoDbConfiguration getPhoneNumberIdentifiersDynamoDbConfiguration() {
return phoneNumberIdentifiersDynamoDb;
}
public DeletedAccountsDynamoDbConfiguration getDeletedAccountsDynamoDbConfiguration() {
return deletedAccountsDynamoDb;
}
public DynamoDbConfiguration getDeletedAccountsLockDynamoDbConfiguration() {
return deletedAccountsLockDynamoDb;
}
public DatabaseConfiguration getAbuseDatabaseConfiguration() {
return abuseDatabase;
}
@@ -330,8 +486,8 @@ public class WhisperServerConfiguration extends Configuration {
return cdn;
}
public MicrometerConfiguration getMicrometerConfiguration() {
return micrometer;
public DatadogConfiguration getDatadogConfiguration() {
return datadog;
}
public UnidentifiedDeliveryConfiguration getDeliveryCertificate() {
@@ -383,4 +539,48 @@ public class WhisperServerConfiguration extends Configuration {
public AppConfigConfiguration getAppConfig() {
return appConfig;
}
public DynamoDbConfiguration getPushChallengeDynamoDbConfiguration() {
return pushChallengeDynamoDb;
}
public DynamoDbConfiguration getReportMessageDynamoDbConfiguration() {
return reportMessageDynamoDb;
}
public DynamoDbConfiguration getPendingAccountsDynamoDbConfiguration() {
return pendingAccountsDynamoDb;
}
public DynamoDbConfiguration getPendingDevicesDynamoDbConfiguration() {
return pendingDevicesDynamoDb;
}
public DynamoDbConfiguration getReservedUsernamesDynamoDbConfiguration() {
return reservedUsernamesDynamoDb;
}
public DonationConfiguration getDonationConfiguration() {
return donation;
}
public BadgesConfiguration getBadges() {
return badges;
}
public SubscriptionConfiguration getSubscription() {
return subscription;
}
public BoostConfiguration getBoost() {
return boost;
}
public ReportMessageConfiguration getReportMessageConfiguration() {
return reportMessage;
}
public AbusiveMessageFilterConfiguration getAbusiveMessageFilterConfiguration() {
return abusiveMessageFilter;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm;
@@ -7,15 +7,9 @@ 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.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.document.DynamoDB;
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;
@@ -23,6 +17,7 @@ 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 com.google.common.collect.Lists;
import io.dropwizard.Application;
import io.dropwizard.auth.AuthFilter;
import io.dropwizard.auth.PolymorphicAuthDynamicFeature;
@@ -35,19 +30,21 @@ 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.Id;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.config.MeterFilter;
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
import io.micrometer.wavefront.WavefrontConfig;
import io.micrometer.wavefront.WavefrontMeterRegistry;
import io.micrometer.datadog.DatadogMeterRegistry;
import java.net.http.HttpClient;
import java.security.Security;
import java.time.Clock;
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.ServiceLoader;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
@@ -56,27 +53,43 @@ import java.util.concurrent.TimeUnit;
import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletRegistration;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.eclipse.jetty.servlets.CrossOriginFilter;
import org.glassfish.jersey.server.ServerProperties;
import org.jdbi.v3.core.Jdbi;
import org.signal.i18n.HeaderControlledResourceBundleLookup;
import org.signal.zkgroup.ServerSecretParams;
import org.signal.zkgroup.auth.ServerZkAuthOperations;
import org.signal.zkgroup.profiles.ServerZkProfileOperations;
import org.signal.zkgroup.receipts.ReceiptCredentialPresentation;
import org.signal.zkgroup.receipts.ServerZkReceiptOperations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.dispatch.DispatchManager;
import org.whispersystems.textsecuregcm.abuse.AbusiveMessageFilter;
import org.whispersystems.textsecuregcm.abuse.FilterAbusiveMessages;
import org.whispersystems.textsecuregcm.auth.AccountAuthenticator;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.CertificateGenerator;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccount;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccountAuthenticator;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator;
import org.whispersystems.textsecuregcm.auth.WebsocketRefreshApplicationEventListener;
import org.whispersystems.textsecuregcm.badges.ConfiguredProfileBadgeConverter;
import org.whispersystems.textsecuregcm.badges.ResourceBundleLevelTranslator;
import org.whispersystems.textsecuregcm.configuration.DirectoryServerConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.controllers.AcceptNumericOnlineFlagRequestFilter;
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.DirectoryV2Controller;
import org.whispersystems.textsecuregcm.controllers.DonationController;
import org.whispersystems.textsecuregcm.controllers.KeepAliveController;
import org.whispersystems.textsecuregcm.controllers.KeysController;
import org.whispersystems.textsecuregcm.controllers.MessageController;
@@ -87,19 +100,33 @@ 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.SubscriptionController;
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.ContentLengthFilter;
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.CompletionExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.DeviceLimitExceededExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.IOExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.ImpossiblePhoneNumberExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.InvalidWebsocketAddressExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.NonNormalizedPhoneNumberExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.RateLimitChallengeExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.RetryLaterExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.ServerRejectedExceptionMapper;
import org.whispersystems.textsecuregcm.metrics.ApplicationShutdownMonitor;
import org.whispersystems.textsecuregcm.metrics.BufferPoolGauges;
import org.whispersystems.textsecuregcm.metrics.CpuUsageGauge;
import org.whispersystems.textsecuregcm.metrics.FileDescriptorGauge;
@@ -107,11 +134,13 @@ 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.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;
@@ -121,77 +150,101 @@ 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.recaptcha.EnterpriseRecaptchaClient;
import org.whispersystems.textsecuregcm.recaptcha.LegacyRecaptchaClient;
import org.whispersystems.textsecuregcm.recaptcha.TransitionalRecaptchaClient;
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.AccountsManager;
import org.whispersystems.textsecuregcm.storage.ActiveUserCounter;
import org.whispersystems.textsecuregcm.storage.ContactDiscoveryWriter;
import org.whispersystems.textsecuregcm.storage.DeletedAccounts;
import org.whispersystems.textsecuregcm.storage.DeletedAccountsDirectoryReconciler;
import org.whispersystems.textsecuregcm.storage.DeletedAccountsManager;
import org.whispersystems.textsecuregcm.storage.DeletedAccountsTableCrawler;
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.IssuedReceiptsManager;
import org.whispersystems.textsecuregcm.storage.Keys;
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.PendingAccounts;
import org.whispersystems.textsecuregcm.storage.PendingAccountsManager;
import org.whispersystems.textsecuregcm.storage.PendingDevices;
import org.whispersystems.textsecuregcm.storage.PendingDevicesManager;
import org.whispersystems.textsecuregcm.storage.NonNormalizedAccountCrawlerListener;
import org.whispersystems.textsecuregcm.storage.PhoneNumberIdentifiers;
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.RedeemedReceiptsManager;
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.StoredVerificationCodeManager;
import org.whispersystems.textsecuregcm.storage.SubscriptionManager;
import org.whispersystems.textsecuregcm.storage.Usernames;
import org.whispersystems.textsecuregcm.storage.UsernamesManager;
import org.whispersystems.textsecuregcm.storage.VerificationCodeStore;
import org.whispersystems.textsecuregcm.stripe.StripeManager;
import org.whispersystems.textsecuregcm.util.Constants;
import org.whispersystems.textsecuregcm.util.DynamoDbFromConfig;
import org.whispersystems.textsecuregcm.util.HostnameUtil;
import org.whispersystems.textsecuregcm.util.logging.LoggingUnhandledExceptionMapper;
import org.whispersystems.textsecuregcm.util.logging.UncaughtExceptionHandler;
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.CheckDynamicConfigurationCommand;
import org.whispersystems.textsecuregcm.workers.DeleteUserCommand;
import org.whispersystems.textsecuregcm.workers.GetRedisCommandStatsCommand;
import org.whispersystems.textsecuregcm.workers.GetRedisSlowlogCommand;
import org.whispersystems.textsecuregcm.workers.ReserveUsernameCommand;
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.SetUserDiscoverabilityCommand;
import org.whispersystems.textsecuregcm.workers.ZkParamsCommand;
import org.whispersystems.websocket.WebSocketResourceProviderFactory;
import org.whispersystems.websocket.setup.WebSocketEnvironment;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
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.s3.S3Client;
public class WhisperServerService extends Application<WhisperServerConfiguration> {
static {
Security.addProvider(new BouncyCastleProvider());
}
private static final Logger log = LoggerFactory.getLogger(WhisperServerService.class);
@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.addCommand(new CheckDynamicConfigurationCommand());
bootstrap.addCommand(new SetUserDiscoverabilityCommand());
bootstrap.addCommand(new ReserveUsernameCommand());
bootstrap.addBundle(new NameableMigrationsBundle<WhisperServerConfiguration>("accountdb", "accountsdb.xml") {
@Override
@@ -200,7 +253,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
}
});
bootstrap.addBundle(new NameableMigrationsBundle<WhisperServerConfiguration>("abusedb", "abusedb.xml") {
@Override
public PooledDataSourceFactory getDataSourceFactory(WhisperServerConfiguration configuration) {
@@ -215,40 +267,53 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
}
@Override
public void run(WhisperServerConfiguration config, Environment environment)
throws Exception
{
public void run(WhisperServerConfiguration config, Environment environment) throws Exception {
final Clock clock = Clock.systemUTC();
final int availableProcessors = Runtime.getRuntime().availableProcessors();
UncaughtExceptionHandler.register();
SharedMetricRegistries.add(Constants.METRICS_NAME, environment.metrics());
Metrics.addRegistry(new WavefrontMeterRegistry(new WavefrontConfig() {
@Override
public String get(final String key) {
return null;
}
final DistributionStatisticConfig defaultDistributionStatisticConfig = DistributionStatisticConfig.builder()
.percentiles(.75, .95, .99, .999)
.build();
@Override
public String uri() {
return config.getMicrometerConfiguration().getUri();
}
{
final DatadogMeterRegistry datadogMeterRegistry = new DatadogMeterRegistry(
config.getDatadogConfiguration(), io.micrometer.core.instrument.Clock.SYSTEM);
@Override
public int batchSize() {
return config.getMicrometerConfiguration().getBatchSize();
}
}, Clock.SYSTEM) {
@Override
protected DistributionStatisticConfig defaultHistogramConfig() {
return DistributionStatisticConfig.builder()
.percentiles(.75, .95, .99, .999)
.build()
.merge(super.defaultHistogramConfig());
}
});
datadogMeterRegistry.config().commonTags(
Tags.of(
"service", "chat",
"host", HostnameUtil.getLocalHostname(),
"version", WhisperServerVersion.getServerVersion(),
"env", config.getDatadogConfiguration().getEnvironment()))
.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))
.meterFilter(new MeterFilter() {
@Override
public DistributionStatisticConfig configure(final Id id, final DistributionStatisticConfig config) {
return defaultDistributionStatisticConfig.merge(config);
}
});
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);
HeaderControlledResourceBundleLookup headerControlledResourceBundleLookup =
new HeaderControlledResourceBundleLookup();
ConfiguredProfileBadgeConverter profileBadgeConverter = new ConfiguredProfileBadgeConverter(
clock, config.getBadges(), headerControlledResourceBundleLookup);
ResourceBundleLevelTranslator resourceBundleLevelTranslator = new ResourceBundleLevelTranslator(
headerControlledResourceBundleLookup);
JdbiFactory jdbiFactory = new JdbiFactory(DefaultNameStrategy.CHECK_EMPTY);
Jdbi accountJdbi = jdbiFactory.build(environment, config.getAccountsDatabaseConfiguration(), "accountdb");
Jdbi abuseJdbi = jdbiFactory.build(environment, config.getAbuseDatabaseConfiguration(), "abusedb" );
@@ -256,202 +321,406 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
FaultTolerantDatabase accountDatabase = new FaultTolerantDatabase("accounts_database", accountJdbi, config.getAccountsDatabaseConfiguration().getCircuitBreakerConfiguration());
FaultTolerantDatabase abuseDatabase = new FaultTolerantDatabase("abuse_database", abuseJdbi, config.getAbuseDatabaseConfiguration().getCircuitBreakerConfiguration());
AmazonDynamoDBClientBuilder messageDynamoDbClientBuilder = AmazonDynamoDBClientBuilder
.standard()
.withRegion(config.getMessageDynamoDbConfiguration().getRegion())
.withClientConfiguration(new ClientConfiguration().withClientExecutionTimeout(((int) config.getMessageDynamoDbConfiguration().getClientExecutionTimeout().toMillis()))
.withRequestTimeout((int) config.getMessageDynamoDbConfiguration().getClientRequestTimeout().toMillis()))
.withCredentials(InstanceProfileCredentialsProvider.getInstance());
DynamoDbAsyncClient dynamoDbAsyncClient = DynamoDbFromConfig.asyncClient(
config.getDynamoDbClientConfiguration(),
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create());
AmazonDynamoDBClientBuilder keysDynamoDbClientBuilder = AmazonDynamoDBClientBuilder
.standard()
.withRegion(config.getKeysDynamoDbConfiguration().getRegion())
.withClientConfiguration(new ClientConfiguration().withClientExecutionTimeout(((int) config.getKeysDynamoDbConfiguration().getClientExecutionTimeout().toMillis()))
.withRequestTimeout((int) config.getKeysDynamoDbConfiguration().getClientRequestTimeout().toMillis()))
.withCredentials(InstanceProfileCredentialsProvider.getInstance());
DynamoDbClient messageDynamoDb = DynamoDbFromConfig.client(config.getMessageDynamoDbConfiguration(),
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create());
DynamoDB messageDynamoDb = new DynamoDB(messageDynamoDbClientBuilder.build());
DynamoDB preKeyDynamoDb = new DynamoDB(keysDynamoDbClientBuilder.build());
DynamoDbClient preKeyDynamoDb = DynamoDbFromConfig.client(config.getKeysDynamoDbConfiguration(),
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create());
Accounts accounts = new Accounts(accountDatabase);
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);
DynamoDbClient accountsDynamoDbClient = DynamoDbFromConfig.client(config.getAccountsDynamoDbConfiguration(),
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create());
DynamoDbClient reservedUsernamesDynamoDbClient = DynamoDbFromConfig.client(config.getReservedUsernamesDynamoDbConfiguration(),
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create());
DynamoDbClient phoneNumberIdentifiersDynamoDbClient =
DynamoDbFromConfig.client(config.getPhoneNumberIdentifiersDynamoDbConfiguration(),
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create());
DynamoDbClient deletedAccountsDynamoDbClient = DynamoDbFromConfig.client(config.getDeletedAccountsDynamoDbConfiguration(),
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 pendingAccountsDynamoDbClient = DynamoDbFromConfig.client(
config.getPendingAccountsDynamoDbConfiguration(),
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create());
DynamoDbClient pendingDevicesDynamoDbClient = DynamoDbFromConfig.client(
config.getPendingDevicesDynamoDbConfiguration(),
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create());
AmazonDynamoDB deletedAccountsLockDynamoDbClient = AmazonDynamoDBClientBuilder.standard()
.withRegion(config.getDeletedAccountsLockDynamoDbConfiguration().getRegion())
.withClientConfiguration(new ClientConfiguration().withClientExecutionTimeout(
((int) config.getDeletedAccountsLockDynamoDbConfiguration().getClientExecutionTimeout().toMillis()))
.withRequestTimeout(
(int) config.getDeletedAccountsLockDynamoDbConfiguration().getClientRequestTimeout().toMillis()))
.withCredentials(InstanceProfileCredentialsProvider.getInstance())
.build();
DeletedAccounts deletedAccounts = new DeletedAccounts(deletedAccountsDynamoDbClient,
config.getDeletedAccountsDynamoDbConfiguration().getTableName(),
config.getDeletedAccountsDynamoDbConfiguration().getNeedsReconciliationIndexName());
Accounts accounts = new Accounts(accountsDynamoDbClient,
config.getAccountsDynamoDbConfiguration().getTableName(),
config.getAccountsDynamoDbConfiguration().getPhoneNumberTableName(),
config.getAccountsDynamoDbConfiguration().getPhoneNumberIdentifierTableName(),
config.getAccountsDynamoDbConfiguration().getScanPageSize());
PhoneNumberIdentifiers phoneNumberIdentifiers = new PhoneNumberIdentifiers(phoneNumberIdentifiersDynamoDbClient,
config.getPhoneNumberIdentifiersDynamoDbConfiguration().getTableName());
Usernames usernames = new Usernames(accountDatabase);
ReservedUsernames reservedUsernames = new ReservedUsernames(reservedUsernamesDynamoDbClient,
config.getReservedUsernamesDynamoDbConfiguration().getTableName());
Profiles profiles = new Profiles(accountDatabase);
Keys keys = new Keys(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(), config.getReportMessageConfiguration().getReportTtl());
VerificationCodeStore pendingAccounts = new VerificationCodeStore(pendingAccountsDynamoDbClient, config.getPendingAccountsDynamoDbConfiguration().getTableName());
VerificationCodeStore pendingDevices = new VerificationCodeStore(pendingDevicesDynamoDbClient, config.getPendingDevicesDynamoDbConfiguration().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 redisClientResources = ClientResources.builder().build();
ConnectionEventLogger.logConnectionEvents(redisClientResources);
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("message_insert_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 cacheCluster = new FaultTolerantRedisCluster("main_cache_cluster", config.getCacheClusterConfiguration(), redisClientResources);
FaultTolerantRedisCluster messagesCluster = new FaultTolerantRedisCluster("messages_cluster", config.getMessageCacheConfiguration().getRedisClusterConfiguration(), redisClientResources);
FaultTolerantRedisCluster clientPresenceCluster = new FaultTolerantRedisCluster("client_presence_cluster", config.getClientPresenceClusterConfiguration(), redisClientResources);
FaultTolerantRedisCluster metricsCluster = new FaultTolerantRedisCluster("metrics_cluster", config.getMetricsClusterConfiguration(), redisClientResources);
FaultTolerantRedisCluster pushSchedulerCluster = new FaultTolerantRedisCluster("push_scheduler", config.getPushSchedulerCluster(), redisClientResources);
FaultTolerantRedisCluster rateLimitersCluster = new FaultTolerantRedisCluster("rate_limiters", config.getRateLimitersCluster(), redisClientResources);
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 recurringJobExecutor = environment.lifecycle()
.scheduledExecutorService(name(getClass(), "recurringJob-%d")).threads(6).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 multiRecipientMessageExecutor = environment.lifecycle()
.executorService(name(getClass(), "multiRecipientMessage-%d")).minThreads(64).maxThreads(64).build();
ExecutorService stripeExecutor = environment.lifecycle().executorService(name(getClass(), "stripe-%d")).
maxThreads(availableProcessors). // mostly this is IO bound so tying to number of processors is tenuous at best
minThreads(availableProcessors). // mostly this is IO bound so tying to number of processors is tenuous at best
allowCoreThreadTimeOut(true).
build();
StripeManager stripeManager = new StripeManager(config.getStripe().getApiKey(), stripeExecutor,
config.getStripe().getIdempotencyKeyGenerator(), config.getStripe().getBoostDescription());
ExternalServiceCredentialGenerator directoryCredentialsGenerator = new ExternalServiceCredentialGenerator(
config.getDirectoryConfiguration().getDirectoryClientConfiguration().getUserAuthenticationTokenSharedSecret(),
config.getDirectoryConfiguration().getDirectoryClientConfiguration().getUserAuthenticationTokenUserIdSecret());
ExternalServiceCredentialGenerator directoryV2CredentialsGenerator = new ExternalServiceCredentialGenerator(
config.getDirectoryV2Configuration().getDirectoryV2ClientConfiguration()
.getUserAuthenticationTokenSharedSecret(), false);
DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager =
new DynamicConfigurationManager<>(config.getAppConfig().getApplication(),
config.getAppConfig().getEnvironment(),
config.getAppConfig().getConfigurationName(),
DynamicConfiguration.class);
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(), true);
ExternalServiceCredentialGenerator backupCredentialsGenerator = new ExternalServiceCredentialGenerator(
config.getSecureBackupServiceConfiguration().getUserAuthenticationTokenSharedSecret(), true);
ExternalServiceCredentialGenerator paymentsCredentialsGenerator = new ExternalServiceCredentialGenerator(
config.getPaymentsServiceConfiguration().getUserAuthenticationTokenSharedSecret(), true);
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);
StoredVerificationCodeManager pendingAccountsManager = new StoredVerificationCodeManager(pendingAccounts);
StoredVerificationCodeManager pendingDevicesManager = new StoredVerificationCodeManager(pendingDevices);
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);
MessagesManager messagesManager = new MessagesManager(messagesDynamoDb, messagesCache, pushLatencyManager);
AccountsManager accountsManager = new AccountsManager(accounts, cacheCluster, directoryQueue, keysDynamoDb, messagesManager, usernamesManager, profilesManager);
RemoteConfigsManager remoteConfigsManager = new RemoteConfigsManager(remoteConfigs);
PushLatencyManager pushLatencyManager = new PushLatencyManager(metricsCluster, dynamicConfigurationManager);
ReportMessageManager reportMessageManager = new ReportMessageManager(reportMessageDynamoDb, rateLimitersCluster, Metrics.globalRegistry, config.getReportMessageConfiguration().getCounterTtl());
MessagesManager messagesManager = new MessagesManager(messagesDynamoDb, messagesCache, pushLatencyManager, reportMessageManager);
DeletedAccountsManager deletedAccountsManager = new DeletedAccountsManager(deletedAccounts,
deletedAccountsLockDynamoDbClient, config.getDeletedAccountsLockDynamoDbConfiguration().getTableName());
AccountsManager accountsManager = new AccountsManager(accounts, phoneNumberIdentifiers, cacheCluster,
deletedAccountsManager, directoryQueue, keys, messagesManager, usernamesManager, profilesManager,
pendingAccountsManager, secureStorageClient, secureBackupClient, clientPresenceManager, clock);
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, cacheCluster);
RateLimiters rateLimiters = new RateLimiters(config.getLimitsConfiguration(), dynamicConfigurationManager, rateLimitersCluster);
ProvisioningManager provisioningManager = new ProvisioningManager(pubSubManager);
IssuedReceiptsManager issuedReceiptsManager = new IssuedReceiptsManager(
config.getDynamoDbTables().getIssuedReceipts().getTableName(),
config.getDynamoDbTables().getIssuedReceipts().getExpiration(),
dynamoDbAsyncClient,
config.getDynamoDbTables().getIssuedReceipts().getGenerator());
RedeemedReceiptsManager redeemedReceiptsManager = new RedeemedReceiptsManager(
clock,
config.getDynamoDbTables().getRedeemedReceipts().getTableName(),
dynamoDbAsyncClient,
config.getDynamoDbTables().getRedeemedReceipts().getExpiration());
SubscriptionManager subscriptionManager = new SubscriptionManager(
config.getDynamoDbTables().getSubscriptions().getTableName(), dynamoDbAsyncClient);
AccountAuthenticator accountAuthenticator = new AccountAuthenticator(accountsManager);
DisabledPermittedAccountAuthenticator disabledPermittedAccountAuthenticator = new DisabledPermittedAccountAuthenticator(accountsManager);
ExternalServiceCredentialGenerator directoryCredentialsGenerator = new ExternalServiceCredentialGenerator(config.getDirectoryConfiguration().getDirectoryClientConfiguration().getUserAuthenticationTokenSharedSecret(),
config.getDirectoryConfiguration().getDirectoryClientConfiguration().getUserAuthenticationTokenUserIdSecret(),
true);
RateLimitResetMetricsManager rateLimitResetMetricsManager = new RateLimitResetMetricsManager(metricsCluster, Metrics.globalRegistry);
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);
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());
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());
LegacyRecaptchaClient legacyRecaptchaClient = new LegacyRecaptchaClient(config.getRecaptchaConfiguration().getSecret());
EnterpriseRecaptchaClient enterpriseRecaptchaClient = new EnterpriseRecaptchaClient(
config.getRecaptchaV2Configuration().getScoreFloor().doubleValue(),
config.getRecaptchaV2Configuration().getSiteKey(),
config.getRecaptchaV2Configuration().getProjectPath(),
config.getRecaptchaV2Configuration().getCredentialConfigurationJson());
TransitionalRecaptchaClient transitionalRecaptchaClient = new TransitionalRecaptchaClient(legacyRecaptchaClient, enterpriseRecaptchaClient);
PushChallengeManager pushChallengeManager = new PushChallengeManager(apnSender, gcmSender, pushChallengeDynamoDb);
RateLimitChallengeManager rateLimitChallengeManager = new RateLimitChallengeManager(pushChallengeManager,
transitionalRecaptchaClient, 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);
final List<AccountDatabaseCrawlerListener> directoryReconciliationAccountDatabaseCrawlerListeners = new ArrayList<>();
final List<DeletedAccountsDirectoryReconciler> deletedAccountsDirectoryReconcilers = new ArrayList<>();
for (DirectoryServerConfiguration directoryServerConfiguration : config.getDirectoryConfiguration()
.getDirectoryServerConfiguration()) {
final DirectoryReconciliationClient directoryReconciliationClient = new DirectoryReconciliationClient(
directoryServerConfiguration);
final DirectoryReconciler directoryReconciler = new DirectoryReconciler(
directoryServerConfiguration.getReplicationName(), directoryReconciliationClient,
dynamicConfigurationManager);
// reconcilers are read-only
directoryReconciliationAccountDatabaseCrawlerListeners.add(directoryReconciler);
final DeletedAccountsDirectoryReconciler deletedAccountsDirectoryReconciler = new DeletedAccountsDirectoryReconciler(
directoryServerConfiguration.getReplicationName(), directoryReconciliationClient);
deletedAccountsDirectoryReconcilers.add(deletedAccountsDirectoryReconciler);
}
accountDatabaseCrawlerListeners.add(new AccountCleaner(accountsManager));
accountDatabaseCrawlerListeners.add(new RegistrationLockVersionCounter(metricsCluster, config.getMetricsFactory()));
AccountDatabaseCrawlerCache directoryReconciliationAccountDatabaseCrawlerCache = new AccountDatabaseCrawlerCache(
cacheCluster, AccountDatabaseCrawlerCache.DIRECTORY_RECONCILER_PREFIX);
AccountDatabaseCrawler directoryReconciliationAccountDatabaseCrawler = new AccountDatabaseCrawler(
"Reconciliation crawler",
accountsManager,
directoryReconciliationAccountDatabaseCrawlerCache, directoryReconciliationAccountDatabaseCrawlerListeners,
config.getAccountDatabaseCrawlerConfiguration().getChunkSize(),
config.getAccountDatabaseCrawlerConfiguration().getChunkIntervalMs()
);
// TODO listeners must be ordered so that ones that directly update accounts come last, so that read-only ones are not working with stale data
final List<AccountDatabaseCrawlerListener> accountDatabaseCrawlerListeners = List.of(
new NonNormalizedAccountCrawlerListener(accountsManager, metricsCluster),
new ContactDiscoveryWriter(accountsManager),
// PushFeedbackProcessor may update device properties
new PushFeedbackProcessor(accountsManager),
// delete accounts last
new AccountCleaner(accountsManager)
);
AccountDatabaseCrawlerCache accountDatabaseCrawlerCache = new AccountDatabaseCrawlerCache(cacheCluster,
AccountDatabaseCrawlerCache.GENERAL_PURPOSE_PREFIX);
AccountDatabaseCrawler accountDatabaseCrawler = new AccountDatabaseCrawler("General-purpose account crawler",
accountsManager,
accountDatabaseCrawlerCache, accountDatabaseCrawlerListeners,
config.getAccountDatabaseCrawlerConfiguration().getChunkSize(),
config.getAccountDatabaseCrawlerConfiguration().getChunkIntervalMs()
);
DeletedAccountsTableCrawler deletedAccountsTableCrawler = new DeletedAccountsTableCrawler(deletedAccountsManager, deletedAccountsDirectoryReconcilers, cacheCluster, recurringJobExecutor);
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(new ApplicationShutdownMonitor());
environment.lifecycle().manage(apnFallbackManager);
environment.lifecycle().manage(pubSubManager);
environment.lifecycle().manage(messageSender);
environment.lifecycle().manage(accountDatabaseCrawler);
environment.lifecycle().manage(directoryReconciliationAccountDatabaseCrawler);
environment.lifecycle().manage(deletedAccountsTableCrawler);
environment.lifecycle().manage(remoteConfigsManager);
environment.lifecycle().manage(messagesCache);
environment.lifecycle().manage(messagePersister);
environment.lifecycle().manage(clientPresenceManager);
environment.lifecycle().manage(currencyManager);
environment.lifecycle().manage(directoryQueue);
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());
StaticCredentialsProvider cdnCredentialsProvider = StaticCredentialsProvider
.create(AwsBasicCredentials.create(
config.getCdnConfiguration().getAccessKey(),
config.getCdnConfiguration().getAccessSecret()));
S3Client cdnS3Client = S3Client.builder()
.credentialsProvider(cdnCredentialsProvider)
.region(Region.of(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();
ServerSecretParams zkSecretParams = new ServerSecretParams(config.getZkConfig().getServerSecret());
ServerZkProfileOperations zkProfileOperations = new ServerZkProfileOperations(zkSecretParams);
ServerZkAuthOperations zkAuthOperations = new ServerZkAuthOperations(zkSecretParams);
ServerZkReceiptOperations zkReceiptOperations = new ServerZkReceiptOperations(zkSecretParams);
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());
KeysController keysController = new KeysController(rateLimiters, keysDynamoDb, accountsManager, directoryQueue);
MessageController messageController = new MessageController(rateLimiters, messageSender, receiptSender, accountsManager, messagesManager, apnFallbackManager, dynamicConfigurationManager);
ProfileController profileController = new ProfileController(rateLimiters, accountsManager, profilesManager, usernamesManager, 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());
AuthFilter<BasicCredentials, AuthenticatedAccount> accountAuthFilter = new BasicCredentialAuthFilter.Builder<AuthenticatedAccount>().setAuthenticator(
accountAuthenticator).buildAuthFilter();
AuthFilter<BasicCredentials, DisabledPermittedAuthenticatedAccount> disabledPermittedAccountAuthFilter = new BasicCredentialAuthFilter.Builder<DisabledPermittedAuthenticatedAccount>().setAuthenticator(
disabledPermittedAccountAuthenticator).buildAuthFilter();
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))
environment.servlets()
.addFilter("RemoteDeprecationFilter", new RemoteDeprecationFilter(dynamicConfigurationManager))
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*");
// TODO Remove on or after 2022-03-01
final AcceptNumericOnlineFlagRequestFilter acceptNumericOnlineFlagRequestFilter =
new AcceptNumericOnlineFlagRequestFilter("v1/messages/multi_recipient");
environment.jersey().register(new ContentLengthFilter(TrafficSource.HTTP));
environment.jersey().register(acceptNumericOnlineFlagRequestFilter);
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 PolymorphicAuthDynamicFeature<>(ImmutableMap.of(AuthenticatedAccount.class, accountAuthFilter,
DisabledPermittedAuthenticatedAccount.class, disabledPermittedAccountAuthFilter)));
environment.jersey().register(new PolymorphicAuthValueFactoryProvider.Binder<>(
ImmutableSet.of(AuthenticatedAccount.class, DisabledPermittedAuthenticatedAccount.class)));
environment.jersey().register(new WebsocketRefreshApplicationEventListener(accountsManager, clientPresenceManager));
environment.jersey().register(new TimestampResponseFilter());
environment.jersey().register(new AccountController(pendingAccountsManager, accountsManager, usernamesManager, abusiveHostRules, rateLimiters, smsSender, directoryQueue, messagesManager, turnTokenGenerator, config.getTestDevices(), recaptchaClient, gcmSender, apnSender, backupCredentialsGenerator));
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(keysController);
environment.jersey().register(messageController);
environment.jersey().register(profileController);
environment.jersey().register(stickerController);
environment.jersey().register(remoteConfigController);
environment.jersey().register(new VoiceVerificationController(config.getVoiceVerificationConfiguration().getUrl(),
config.getVoiceVerificationConfiguration().getLocales()));
///
WebSocketEnvironment<Account> webSocketEnvironment = new WebSocketEnvironment<>(environment, config.getWebSocketConfiguration(), 90000);
WebSocketEnvironment<AuthenticatedAccount> webSocketEnvironment = new WebSocketEnvironment<>(environment,
config.getWebSocketConfiguration(), 90000);
webSocketEnvironment.setAuthenticator(new WebSocketAccountAuthenticator(accountAuthenticator));
webSocketEnvironment.setConnectListener(new AuthenticatedConnectListener(receiptSender, messagesManager, messageSender, apnFallbackManager, clientPresenceManager));
webSocketEnvironment.setConnectListener(
new AuthenticatedConnectListener(receiptSender, messagesManager, messageSender, apnFallbackManager,
clientPresenceManager, retrySchedulingExecutor));
webSocketEnvironment.jersey().register(new WebsocketRefreshApplicationEventListener(accountsManager, clientPresenceManager));
webSocketEnvironment.jersey().register(new ContentLengthFilter(TrafficSource.WEBSOCKET));
webSocketEnvironment.jersey().register(acceptNumericOnlineFlagRequestFilter);
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(remoteConfigController);
WebSocketEnvironment<Account> provisioningEnvironment = new WebSocketEnvironment<>(environment, webSocketEnvironment.getRequestLog(), 60000);
// these should be common, but use @Auth DisabledPermittedAccount, which isnt supported yet on websocket
environment.jersey().register(
new AccountController(pendingAccountsManager, accountsManager, usernamesManager, abusiveHostRules, rateLimiters,
smsSender, dynamicConfigurationManager, turnTokenGenerator, config.getTestDevices(),
transitionalRecaptchaClient, gcmSender, apnSender, backupCredentialsGenerator,
verifyExperimentEnrollmentManager));
environment.jersey().register(new KeysController(rateLimiters, keys, accountsManager, preKeyRateLimiter, rateLimitChallengeManager));
final List<Object> commonControllers = Lists.newArrayList(
new AttachmentControllerV1(rateLimiters, config.getAwsAttachmentsConfiguration().getAccessKey(), config.getAwsAttachmentsConfiguration().getAccessSecret(), config.getAwsAttachmentsConfiguration().getBucket()),
new AttachmentControllerV2(rateLimiters, config.getAwsAttachmentsConfiguration().getAccessKey(), config.getAwsAttachmentsConfiguration().getAccessSecret(), config.getAwsAttachmentsConfiguration().getRegion(), config.getAwsAttachmentsConfiguration().getBucket()),
new AttachmentControllerV3(rateLimiters, config.getGcpAttachmentsConfiguration().getDomain(), config.getGcpAttachmentsConfiguration().getEmail(), config.getGcpAttachmentsConfiguration().getMaxSizeInBytes(), config.getGcpAttachmentsConfiguration().getPathPrefix(), config.getGcpAttachmentsConfiguration().getRsaSigningKey()),
new CertificateController(new CertificateGenerator(config.getDeliveryCertificate().getCertificate(), config.getDeliveryCertificate().getPrivateKey(), config.getDeliveryCertificate().getExpiresDays()), zkAuthOperations),
new ChallengeController(rateLimitChallengeManager),
new DeviceController(pendingDevicesManager, accountsManager, messagesManager, keys, rateLimiters, config.getMaxDevices()),
new DirectoryController(directoryCredentialsGenerator),
new DirectoryV2Controller(directoryV2CredentialsGenerator),
new DonationController(clock, zkReceiptOperations, redeemedReceiptsManager, accountsManager, config.getBadges(),
ReceiptCredentialPresentation::new, stripeExecutor, config.getDonationConfiguration(), config.getStripe()),
new MessageController(rateLimiters, messageSender, receiptSender, accountsManager, messagesManager, unsealedSenderRateLimiter, apnFallbackManager,
rateLimitChallengeManager, reportMessageManager, multiRecipientMessageExecutor),
new PaymentsController(currencyManager, paymentsCredentialsGenerator),
new ProfileController(clock, rateLimiters, accountsManager, profilesManager, usernamesManager, dynamicConfigurationManager, profileBadgeConverter, config.getBadges(), cdnS3Client, profileCdnPolicyGenerator, profileCdnPolicySigner, config.getCdnConfiguration().getBucket(), zkProfileOperations),
new ProvisioningController(rateLimiters, provisioningManager),
new RemoteConfigController(remoteConfigsManager, config.getRemoteConfigConfiguration().getAuthorizedTokens(), config.getRemoteConfigConfiguration().getGlobalConfig()),
new SecureBackupController(backupCredentialsGenerator),
new SecureStorageController(storageCredentialsGenerator),
new StickerController(rateLimiters, config.getCdnConfiguration().getAccessKey(),
config.getCdnConfiguration().getAccessSecret(), config.getCdnConfiguration().getRegion(),
config.getCdnConfiguration().getBucket())
);
if (config.getSubscription() != null && config.getBoost() != null) {
commonControllers.add(new SubscriptionController(clock, config.getSubscription(), config.getBoost(),
subscriptionManager, stripeManager, zkReceiptOperations, issuedReceiptsManager, profileBadgeConverter,
resourceBundleLevelTranslator));
}
for (Object controller : commonControllers) {
environment.jersey().register(controller);
webSocketEnvironment.jersey().register(controller);
}
boolean registeredAbusiveMessageFilter = false;
for (final AbusiveMessageFilter filter : ServiceLoader.load(AbusiveMessageFilter.class)) {
if (filter.getClass().isAnnotationPresent(FilterAbusiveMessages.class)) {
try {
filter.configure(config.getAbusiveMessageFilterConfiguration().getEnvironment());
environment.lifecycle().manage(filter);
environment.jersey().register(filter);
webSocketEnvironment.jersey().register(filter);
log.info("Registered abusive message filter: {}", filter.getClass().getName());
registeredAbusiveMessageFilter = true;
} catch (final Exception e) {
log.warn("Failed to register abusive message filter: {}", filter.getClass().getName(), e);
}
} else {
log.warn("Abusive message filter {} not annotated with @FilterAbusiveMessages and will not be installed",
filter.getClass().getName());
}
}
if (!registeredAbusiveMessageFilter) {
log.warn("No abusive message filters installed");
}
WebSocketEnvironment<AuthenticatedAccount> provisioningEnvironment = new WebSocketEnvironment<>(environment,
webSocketEnvironment.getRequestLog(), 60000);
provisioningEnvironment.jersey().register(new WebsocketRefreshApplicationEventListener(accountsManager, clientPresenceManager));
provisioningEnvironment.setConnectListener(new ProvisioningConnectListener(pubSubManager));
provisioningEnvironment.jersey().register(new MetricsApplicationEventListener(TrafficSource.WEBSOCKET));
provisioningEnvironment.jersey().register(new KeepAliveController(clientPresenceManager));
@@ -459,10 +728,23 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
registerCorsFilter(environment);
registerExceptionMappers(environment, webSocketEnvironment, provisioningEnvironment);
WebSocketResourceProviderFactory<Account> webSocketServlet = new WebSocketResourceProviderFactory<>(webSocketEnvironment, Account.class);
WebSocketResourceProviderFactory<Account> provisioningServlet = new WebSocketResourceProviderFactory<>(provisioningEnvironment, Account.class);
RateLimitChallengeExceptionMapper rateLimitChallengeExceptionMapper = new RateLimitChallengeExceptionMapper(
rateLimitChallengeManager);
ServletRegistration.Dynamic websocket = environment.servlets().addServlet("WebSocket", webSocketServlet );
environment.jersey().register(rateLimitChallengeExceptionMapper);
webSocketEnvironment.jersey().register(rateLimitChallengeExceptionMapper);
provisioningEnvironment.jersey().register(rateLimitChallengeExceptionMapper);
environment.jersey().property(ServerProperties.UNWRAP_COMPLETION_STAGE_IN_WRITER_ENABLE, Boolean.TRUE);
webSocketEnvironment.jersey().property(ServerProperties.UNWRAP_COMPLETION_STAGE_IN_WRITER_ENABLE, Boolean.TRUE);
provisioningEnvironment.jersey().property(ServerProperties.UNWRAP_COMPLETION_STAGE_IN_WRITER_ENABLE, Boolean.TRUE);
WebSocketResourceProviderFactory<AuthenticatedAccount> webSocketServlet = new WebSocketResourceProviderFactory<>(
webSocketEnvironment, AuthenticatedAccount.class, config.getWebSocketConfiguration());
WebSocketResourceProviderFactory<AuthenticatedAccount> provisioningServlet = new WebSocketResourceProviderFactory<>(
provisioningEnvironment, AuthenticatedAccount.class, config.getWebSocketConfiguration());
ServletRegistration.Dynamic websocket = environment.servlets().addServlet("WebSocket", webSocketServlet);
ServletRegistration.Dynamic provisioning = environment.servlets().addServlet("Provisioning", provisioningServlet);
websocket.addMapping("/v1/websocket/");
@@ -474,8 +756,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
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));
@@ -484,28 +764,35 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
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"));
environment.metrics()
.register(name(OperatingSystemMemoryGauge.class, "buffers"), new OperatingSystemMemoryGauge("Buffers"));
environment.metrics()
.register(name(OperatingSystemMemoryGauge.class, "cached"), new OperatingSystemMemoryGauge("Cached"));
BufferPoolGauges.registerMetrics();
GarbageCollectionGauges.registerMetrics();
}
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());
private void registerExceptionMappers(Environment environment,
WebSocketEnvironment<AuthenticatedAccount> webSocketEnvironment,
WebSocketEnvironment<AuthenticatedAccount> provisioningEnvironment) {
webSocketEnvironment.jersey().register(new IOExceptionMapper());
webSocketEnvironment.jersey().register(new RateLimitExceededExceptionMapper());
webSocketEnvironment.jersey().register(new InvalidWebsocketAddressExceptionMapper());
webSocketEnvironment.jersey().register(new DeviceLimitExceededExceptionMapper());
provisioningEnvironment.jersey().register(new IOExceptionMapper());
provisioningEnvironment.jersey().register(new RateLimitExceededExceptionMapper());
provisioningEnvironment.jersey().register(new InvalidWebsocketAddressExceptionMapper());
provisioningEnvironment.jersey().register(new DeviceLimitExceededExceptionMapper());
List.of(
new LoggingUnhandledExceptionMapper(),
new CompletionExceptionMapper(),
new IOExceptionMapper(),
new RateLimitExceededExceptionMapper(),
new InvalidWebsocketAddressExceptionMapper(),
new DeviceLimitExceededExceptionMapper(),
new RetryLaterExceptionMapper(),
new ServerRejectedExceptionMapper(),
new ImpossiblePhoneNumberExceptionMapper(),
new NonNormalizedPhoneNumberExceptionMapper()
).forEach(exceptionMapper -> {
environment.jersey().register(exceptionMapper);
webSocketEnvironment.jersey().register(exceptionMapper);
provisioningEnvironment.jersey().register(exceptionMapper);
});
}
private void registerCorsFilter(Environment environment) {

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.abuse;
import io.dropwizard.lifecycle.Managed;
import javax.ws.rs.container.ContainerRequestFilter;
import java.io.IOException;
/**
* An abusive message filter is a {@link ContainerRequestFilter} that filters requests to message-sending endpoints to
* detect and respond to patterns of abusive behavior.
* <p/>
* Abusive message filters are managed components that are generally loaded dynamically via a
* {@link java.util.ServiceLoader}. Their {@link #configure(String)} method will be called prior to be adding to the
* server's pool of {@link Managed} objects.
* <p/>
* Abusive message filters must be annotated with {@link FilterAbusiveMessages}, a name binding annotation that
* restricts the endpoints to which the filter may apply.
*/
public interface AbusiveMessageFilter extends ContainerRequestFilter, Managed {
/**
* Configures this abusive message filter. This method will be called before the filter is added to the server's pool
* of managed objects and before the server processes any requests.
*
* @param environmentName the name of the environment in which this filter is running (e.g. "staging" or "production")
* @throws IOException if the filter could not read its configuration source for any reason
*/
void configure(String environmentName) throws IOException;
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.abuse;
import javax.ws.rs.NameBinding;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* A name-binding annotation that associates {@link AbusiveMessageFilter}s with resource methods.
*/
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface FilterAbusiveMessages {
}

View File

@@ -0,0 +1,16 @@
/*
* Copyright 2021 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;
public interface AccountAndAuthenticatedDeviceHolder {
Account getAccount();
Device getAuthenticatedDevice();
}

View File

@@ -1,42 +1,38 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tag;
import org.apache.http.auth.AUTH;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import java.util.List;
import java.util.Optional;
import io.dropwizard.auth.Authenticator;
import io.dropwizard.auth.basic.BasicCredentials;
import java.util.Optional;
import io.micrometer.core.instrument.Metrics;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import static com.codahale.metrics.MetricRegistry.name;
public class AccountAuthenticator extends BaseAccountAuthenticator implements Authenticator<BasicCredentials, Account> {
public class AccountAuthenticator extends BaseAccountAuthenticator implements
Authenticator<BasicCredentials, AuthenticatedAccount> {
private static final String AUTHENTICATION_COUNTER_NAME = name(AccountAuthenticator.class, "authenticate");
private static final String GV2_CAPABLE_TAG_NAME = "gv1Migration";
private static final String AUTHENTICATION_COUNTER_NAME = name(AccountAuthenticator.class, "authenticate");
public AccountAuthenticator(AccountsManager accountsManager) {
super(accountsManager);
}
@Override
public Optional<Account> authenticate(BasicCredentials basicCredentials) {
final Optional<Account> maybeAccount = super.authenticate(basicCredentials, true);
public Optional<AuthenticatedAccount> authenticate(BasicCredentials basicCredentials) {
final Optional<AuthenticatedAccount> maybeAuthenticatedAccount = 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();
});
// TODO Remove after announcement groups have launched
maybeAuthenticatedAccount.ifPresent(authenticatedAccount ->
Metrics.counter(AUTHENTICATION_COUNTER_NAME,
"supportsAnnouncementGroups",
String.valueOf(authenticatedAccount.getAccount().isAnnouncementGroupSupported()))
.increment());
return maybeAccount;
return maybeAuthenticatedAccount;
}
}

View File

@@ -1,45 +0,0 @@
/*
* 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;
}
}

View File

@@ -5,11 +5,9 @@
package org.whispersystems.textsecuregcm.auth;
import org.whispersystems.textsecuregcm.util.Base64;
import java.util.Base64;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import java.io.IOException;
public class Anonymous {
@@ -17,8 +15,8 @@ public class Anonymous {
public Anonymous(String header) {
try {
this.unidentifiedSenderAccessKey = Base64.decode(header);
} catch (IOException e) {
this.unidentifiedSenderAccessKey = Base64.getDecoder().decode(header);
} catch (IllegalArgumentException e) {
throw new WebApplicationException(e, Response.Status.UNAUTHORIZED);
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import com.google.common.annotations.VisibleForTesting;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.monitoring.RequestEvent;
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.Pair;
/**
* This {@link WebsocketRefreshRequirementProvider} observes intra-request changes in {@link Account#isEnabled()} and
* {@link Device#isEnabled()}.
* <p>
* If a change in {@link Account#isEnabled()} or any associated {@link Device#isEnabled()} is observed, then any active
* WebSocket connections for the account must be closed in order for clients to get a refreshed
* {@link io.dropwizard.auth.Auth} object with a current device list.
*
* @see AuthenticatedAccount
* @see DisabledPermittedAuthenticatedAccount
*/
public class AuthEnablementRefreshRequirementProvider implements WebsocketRefreshRequirementProvider {
private final AccountsManager accountsManager;
private static final Logger logger = LoggerFactory.getLogger(AuthEnablementRefreshRequirementProvider.class);
private static final String ACCOUNT_UUID = AuthEnablementRefreshRequirementProvider.class.getName() + ".accountUuid";
private static final String DEVICES_ENABLED = AuthEnablementRefreshRequirementProvider.class.getName() + ".devicesEnabled";
public AuthEnablementRefreshRequirementProvider(final AccountsManager accountsManager) {
this.accountsManager = accountsManager;
}
@VisibleForTesting
static Map<Long, Boolean> buildDevicesEnabledMap(final Account account) {
return account.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::isEnabled));
}
@Override
public void handleRequestFiltered(final RequestEvent requestEvent) {
if (requestEvent.getUriInfo().getMatchedResourceMethod().getInvocable().getHandlingMethod().getAnnotation(ChangesDeviceEnabledState.class) != null) {
// The authenticated principal, if any, will be available after filters have run.
// Now that the account is known, capture a snapshot of `isEnabled` for the account's devices before carrying out
// the requests business logic.
ContainerRequestUtil.getAuthenticatedAccount(requestEvent.getContainerRequest()).ifPresent(account ->
setAccount(requestEvent.getContainerRequest(), account));
}
}
public static void setAccount(final ContainerRequest containerRequest, final Account account) {
containerRequest.setProperty(ACCOUNT_UUID, account.getUuid());
containerRequest.setProperty(DEVICES_ENABLED, buildDevicesEnabledMap(account));
}
@Override
public List<Pair<UUID, Long>> handleRequestFinished(final RequestEvent requestEvent) {
// Now that the request is finished, check whether `isEnabled` changed for any of the devices. If the value did
// change or if a devices was added or removed, all devices must disconnect and reauthenticate.
if (requestEvent.getContainerRequest().getProperty(DEVICES_ENABLED) != null) {
@SuppressWarnings("unchecked") final Map<Long, Boolean> initialDevicesEnabled =
(Map<Long, Boolean>) requestEvent.getContainerRequest().getProperty(DEVICES_ENABLED);
return accountsManager.getByAccountIdentifier((UUID) requestEvent.getContainerRequest().getProperty(ACCOUNT_UUID)).map(account -> {
final Set<Long> deviceIdsToDisplace;
final Map<Long, Boolean> currentDevicesEnabled = buildDevicesEnabledMap(account);
if (!initialDevicesEnabled.equals(currentDevicesEnabled)) {
deviceIdsToDisplace = new HashSet<>(initialDevicesEnabled.keySet());
deviceIdsToDisplace.addAll(currentDevicesEnabled.keySet());
} else {
deviceIdsToDisplace = Collections.emptySet();
}
return deviceIdsToDisplace.stream()
.map(deviceId -> new Pair<>(account.getUuid(), deviceId))
.collect(Collectors.toList());
}).orElseGet(() -> {
logger.error("Request had account, but it is no longer present");
return Collections.emptyList();
});
} else
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import java.security.Principal;
import java.util.function.Supplier;
import javax.security.auth.Subject;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.util.Pair;
public class AuthenticatedAccount implements Principal, AccountAndAuthenticatedDeviceHolder {
private final Supplier<Pair<Account, Device>> accountAndDevice;
public AuthenticatedAccount(final Supplier<Pair<Account, Device>> accountAndDevice) {
this.accountAndDevice = accountAndDevice;
}
@Override
public Account getAccount() {
return accountAndDevice.get().first();
}
@Override
public Device getAuthenticatedDevice() {
return accountAndDevice.get().second();
}
// Principal implementation
@Override
public String getName() {
return null;
}
@Override
public boolean implies(final Subject subject) {
return false;
}
}

View File

@@ -1,81 +0,0 @@
/*
* 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;
public class AuthorizationHeader {
private final AmbiguousIdentifier identifier;
private final long deviceId;
private final String 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(new AmbiguousIdentifier(numberAndId[0]),
numberAndId.length > 1 ? Long.parseLong(numberAndId[1]) : 1,
password);
} catch (NumberFormatException nfe) {
throw new InvalidAuthorizationHeaderException(nfe);
}
}
public static AuthorizationHeader fromFullHeader(String header) throws InvalidAuthorizationHeaderException {
try {
if (header == null) {
throw new InvalidAuthorizationHeaderException("Null header");
}
String[] headerParts = header.split(" ");
if (headerParts == null || headerParts.length < 2) {
throw new InvalidAuthorizationHeaderException("Invalid authorization header: " + header);
}
if (!"Basic".equals(headerParts[0])) {
throw new InvalidAuthorizationHeaderException("Unsupported authorization method: " + headerParts[0]);
}
String concatenatedValues = new String(Base64.decode(headerParts[1]));
if (Util.isEmpty(concatenatedValues)) {
throw new InvalidAuthorizationHeaderException("Bad decoded value: " + concatenatedValues);
}
String[] credentialParts = concatenatedValues.split(":");
if (credentialParts == null || credentialParts.length < 2) {
throw new InvalidAuthorizationHeaderException("Badly formated credentials: " + concatenatedValues);
}
return fromUserAndPassword(credentialParts[0], credentialParts[1]);
} catch (IOException ioe) {
throw new InvalidAuthorizationHeaderException(ioe);
}
}
public AmbiguousIdentifier getIdentifier() {
return identifier;
}
public long getDeviceId() {
return deviceId;
}
public String getPassword() {
return password;
}
}

View File

@@ -1,42 +1,38 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* Copyright 2013-2021 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 static com.codahale.metrics.MetricRegistry.name;
import com.google.common.annotations.VisibleForTesting;
import io.dropwizard.auth.basic.BasicCredentials;
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 io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tags;
import java.time.Clock;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Optional;
import static com.codahale.metrics.MetricRegistry.name;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.storage.RefreshingAccountAndDeviceSupplier;
import org.whispersystems.textsecuregcm.util.Pair;
import org.whispersystems.textsecuregcm.util.Util;
public class BaseAccountAuthenticator {
private 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 static final String AUTHENTICATION_COUNTER_NAME = name(BaseAccountAuthenticator.class, "authentication");
private static final String AUTHENTICATION_SUCCEEDED_TAG_NAME = "succeeded";
private static final String AUTHENTICATION_FAILURE_REASON_TAG_NAME = "reason";
private static final String AUTHENTICATION_ENABLED_REQUIRED_TAG_NAME = "enabledRequired";
private final Logger logger = LoggerFactory.getLogger(AccountAuthenticator.class);
private static final String DAYS_SINCE_LAST_SEEN_DISTRIBUTION_NAME = name(BaseAccountAuthenticator.class, "daysSinceLastSeen");
private static final String IS_PRIMARY_DEVICE_TAG = "isPrimary";
private final AccountsManager accountsManager;
private final Clock clock;
@@ -51,59 +47,100 @@ public class BaseAccountAuthenticator {
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());
static Pair<String, Long> getIdentifierAndDeviceId(final String basicUsername) {
final String identifier;
final long deviceId;
if (!account.isPresent()) {
noSuchAccountMeter.mark();
final int deviceIdSeparatorIndex = basicUsername.indexOf('.');
if (deviceIdSeparatorIndex == -1) {
identifier = basicUsername;
deviceId = Device.MASTER_ID;
} else {
identifier = basicUsername.substring(0, deviceIdSeparatorIndex);
deviceId = Long.parseLong(basicUsername.substring(deviceIdSeparatorIndex + 1));
}
return new Pair<>(identifier, deviceId);
}
public Optional<AuthenticatedAccount> authenticate(BasicCredentials basicCredentials, boolean enabledRequired) {
boolean succeeded = false;
String failureReason = null;
try {
final UUID accountUuid;
final long deviceId;
{
final Pair<String, Long> identifierAndDeviceId = getIdentifierAndDeviceId(basicCredentials.getUsername());
accountUuid = UUID.fromString(identifierAndDeviceId.first());
deviceId = identifierAndDeviceId.second();
}
Optional<Account> account = accountsManager.getByAccountIdentifier(accountUuid);
if (account.isEmpty()) {
failureReason = "noSuchAccount";
return Optional.empty();
}
Optional<Device> device = account.get().getDevice(authorizationHeader.getDeviceId());
Optional<Device> device = account.get().getDevice(deviceId);
if (!device.isPresent()) {
noSuchDeviceMeter.mark();
if (device.isEmpty()) {
failureReason = "noSuchDevice";
return Optional.empty();
}
if (enabledRequired) {
if (!device.get().isEnabled()) {
deviceDisabledMeter.mark();
failureReason = "deviceDisabled";
return Optional.empty();
}
if (!account.get().isEnabled()) {
accountDisabledMeter.mark();
failureReason = "accountDisabled";
return Optional.empty();
}
}
if (device.get().getAuthenticationCredentials().verify(basicCredentials.getPassword())) {
authenticationSucceededMeter.mark();
account.get().setAuthenticatedDevice(device.get());
updateLastSeen(account.get(), device.get());
return account;
succeeded = true;
final Account authenticatedAccount = updateLastSeen(account.get(), device.get());
return Optional.of(new AuthenticatedAccount(
new RefreshingAccountAndDeviceSupplier(authenticatedAccount, device.get().getId(), accountsManager)));
}
authenticationFailedMeter.mark();
return Optional.empty();
} catch (IllegalArgumentException | InvalidAuthorizationHeaderException iae) {
invalidAuthHeaderMeter.mark();
failureReason = "invalidHeader";
return Optional.empty();
} finally {
Tags tags = Tags.of(
AUTHENTICATION_SUCCEEDED_TAG_NAME, String.valueOf(succeeded),
AUTHENTICATION_ENABLED_REQUIRED_TAG_NAME, String.valueOf(enabledRequired));
if (StringUtils.isNotBlank(failureReason)) {
tags = tags.and(AUTHENTICATION_FAILURE_REASON_TAG_NAME, failureReason);
}
Metrics.counter(AUTHENTICATION_COUNTER_NAME, tags).increment();
}
}
@VisibleForTesting
public void updateLastSeen(Account account, Device device) {
public Account updateLastSeen(Account account, Device device) {
final long lastSeenOffsetSeconds = Math.abs(account.getUuid().getLeastSignificantBits()) % ChronoUnit.DAYS.getDuration().toSeconds();
final long todayInMillisWithOffset = Util.todayInMillisGivenOffsetFromNow(clock, Duration.ofSeconds(lastSeenOffsetSeconds).negated());
if (device.getLastSeen() < todayInMillisWithOffset) {
device.setLastSeen(Util.todayInMillis(clock));
accountsManager.update(account);
Metrics.summary(DAYS_SINCE_LAST_SEEN_DISTRIBUTION_NAME, IS_PRIMARY_DEVICE_TAG, String.valueOf(device.isMaster()))
.record(Duration.ofMillis(todayInMillisWithOffset - device.getLastSeen()).toDays());
return accountsManager.updateDeviceLastSeen(account, device, Util.todayInMillis(clock));
}
return account;
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import java.util.Base64;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.util.Pair;
public class BasicAuthorizationHeader {
private final String username;
private final long deviceId;
private final String password;
private BasicAuthorizationHeader(final String username, final long deviceId, final String password) {
this.username = username;
this.deviceId = deviceId;
this.password = password;
}
public static BasicAuthorizationHeader fromString(final String header) throws InvalidAuthorizationHeaderException {
try {
if (StringUtils.isBlank(header)) {
throw new InvalidAuthorizationHeaderException("Blank header");
}
final int spaceIndex = header.indexOf(' ');
if (spaceIndex == -1) {
throw new InvalidAuthorizationHeaderException("Invalid authorization header: " + header);
}
final String authorizationType = header.substring(0, spaceIndex);
if (!"Basic".equals(authorizationType)) {
throw new InvalidAuthorizationHeaderException("Unsupported authorization method: " + authorizationType);
}
final String credentials;
try {
credentials = new String(Base64.getDecoder().decode(header.substring(spaceIndex + 1)));
} catch (final IndexOutOfBoundsException e) {
throw new InvalidAuthorizationHeaderException("Missing credentials");
}
if (StringUtils.isEmpty(credentials)) {
throw new InvalidAuthorizationHeaderException("Bad decoded value: " + credentials);
}
final int credentialSeparatorIndex = credentials.indexOf(':');
if (credentialSeparatorIndex == -1) {
throw new InvalidAuthorizationHeaderException("Badly-formatted credentials: " + credentials);
}
final String usernameComponent = credentials.substring(0, credentialSeparatorIndex);
final String username;
final long deviceId;
{
final Pair<String, Long> identifierAndDeviceId =
BaseAccountAuthenticator.getIdentifierAndDeviceId(usernameComponent);
username = identifierAndDeviceId.first();
deviceId = identifierAndDeviceId.second();
}
final String password = credentials.substring(credentialSeparatorIndex + 1);
if (StringUtils.isAnyBlank(username, password)) {
throw new InvalidAuthorizationHeaderException("Username or password were blank");
}
return new BasicAuthorizationHeader(username, deviceId, password);
} catch (final IllegalArgumentException | IndexOutOfBoundsException e) {
throw new InvalidAuthorizationHeaderException(e);
}
}
public String getUsername() {
return username;
}
public long getDeviceId() {
return deviceId;
}
public String getPassword() {
return password;
}
}

View File

@@ -13,10 +13,10 @@ 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 org.whispersystems.textsecuregcm.util.Base64;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.util.Base64;
import java.util.concurrent.TimeUnit;
public class CertificateGenerator {
@@ -33,11 +33,11 @@ public class CertificateGenerator {
this.serverCertificate = ServerCertificate.parseFrom(serverCertificate);
}
public byte[] createFor(Account account, Device device, boolean includeE164) throws IOException, InvalidKeyException {
public byte[] createFor(Account account, Device device, boolean includeE164) throws InvalidKeyException {
SenderCertificate.Certificate.Builder builder = SenderCertificate.Certificate.newBuilder()
.setSenderDevice(Math.toIntExact(device.getId()))
.setExpires(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(expiresDays))
.setIdentityKey(ByteString.copyFrom(Base64.decode(account.getIdentityKey())))
.setIdentityKey(ByteString.copyFrom(Base64.getDecoder().decode(account.getIdentityKey())))
.setSigner(serverCertificate)
.setSenderUuid(account.getUuid().toString());

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Indicates that an endpoint may change the "enabled" state of one or more devices associated with an account, and that
* any websockets associated with the account may need to be refreshed after a call to that endpoint.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ChangesDeviceEnabledState {
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import org.glassfish.jersey.server.ContainerRequest;
import org.whispersystems.textsecuregcm.storage.Account;
import javax.ws.rs.core.SecurityContext;
import java.util.Optional;
class ContainerRequestUtil {
static Optional<Account> getAuthenticatedAccount(final ContainerRequest request) {
return Optional.ofNullable(request.getSecurityContext())
.map(SecurityContext::getUserPrincipal)
.map(principal -> principal instanceof AccountAndAuthenticatedDeviceHolder
? ((AccountAndAuthenticatedDeviceHolder) principal).getAccount() : null);
}
}

View File

@@ -1,36 +0,0 @@
/*
* 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;
}
}

View File

@@ -1,27 +1,25 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* Copyright 2013-2021 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;
import java.util.Optional;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
public class DisabledPermittedAccountAuthenticator extends BaseAccountAuthenticator implements Authenticator<BasicCredentials, DisabledPermittedAccount> {
public class DisabledPermittedAccountAuthenticator extends BaseAccountAuthenticator implements
Authenticator<BasicCredentials, DisabledPermittedAuthenticatedAccount> {
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);
public Optional<DisabledPermittedAuthenticatedAccount> authenticate(BasicCredentials credentials) {
Optional<AuthenticatedAccount> account = super.authenticate(credentials, false);
return account.map(DisabledPermittedAuthenticatedAccount::new);
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import java.security.Principal;
import javax.security.auth.Subject;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Device;
public class DisabledPermittedAuthenticatedAccount implements Principal, AccountAndAuthenticatedDeviceHolder {
private final AuthenticatedAccount authenticatedAccount;
public DisabledPermittedAuthenticatedAccount(final AuthenticatedAccount authenticatedAccount) {
this.authenticatedAccount = authenticatedAccount;
}
@Override
public Account getAccount() {
return authenticatedAccount.getAccount();
}
@Override
public Device getAuthenticatedDevice() {
return authenticatedAccount.getAuthenticatedDevice();
}
// Principal implementation
@Override
public String getName() {
return null;
}
@Override
public boolean implies(Subject subject) {
return false;
}
}

View File

@@ -1,97 +1,71 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* Copyright 2013-2021 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 com.google.common.annotations.VisibleForTesting;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Clock;
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;
import org.apache.commons.codec.binary.Hex;
import org.whispersystems.textsecuregcm.util.Util;
public class ExternalServiceCredentialGenerator {
private final Logger logger = LoggerFactory.getLogger(ExternalServiceCredentialGenerator.class);
private final byte[] key;
private final byte[] userIdKey;
private final boolean usernameDerivation;
private final boolean prependUsername;
private final Clock clock;
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey, boolean usernameDerivation) {
this.key = key;
this.userIdKey = userIdKey;
this.usernameDerivation = usernameDerivation;
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey) {
this(key, userIdKey, true, true);
}
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;
public ExternalServiceCredentialGenerator(byte[] key, boolean prependUsername) {
this(key, new byte[0], false, prependUsername);
}
@VisibleForTesting
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey, boolean usernameDerivation) {
this(key, userIdKey, usernameDerivation, true);
}
private ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey, boolean usernameDerivation,
boolean prependUsername) {
this(key, userIdKey, usernameDerivation, prependUsername, Clock.systemUTC());
}
@VisibleForTesting
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey, boolean usernameDerivation,
boolean prependUsername, Clock clock) {
this.key = key;
this.userIdKey = userIdKey;
this.usernameDerivation = usernameDerivation;
this.prependUsername = prependUsername;
this.clock = clock;
}
public ExternalServiceCredentials generateFor(String identity) {
Mac mac = getMacInstance();
String username = getUserId(identity, mac, usernameDerivation);
long currentTimeSeconds = clock.millis() / 1000;
String prefix = username + ":" + currentTimeSeconds;
String output = Hex.encodeHexString(Util.truncate(getHmac(key, prefix.getBytes(), mac), 10));
String token = (prependUsername ? prefix : currentTimeSeconds) + ":" + 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");

View File

@@ -5,12 +5,15 @@
package org.whispersystems.textsecuregcm.auth;
public class InvalidAuthorizationHeaderException extends Exception {
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response.Status;
public class InvalidAuthorizationHeaderException extends WebApplicationException {
public InvalidAuthorizationHeaderException(String s) {
super(s);
super(s, Status.UNAUTHORIZED);
}
public InvalidAuthorizationHeaderException(Exception e) {
super(e);
super(e, Status.UNAUTHORIZED);
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.monitoring.RequestEvent;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.util.Pair;
public class PhoneNumberChangeRefreshRequirementProvider implements WebsocketRefreshRequirementProvider {
private static final String INITIAL_NUMBER_KEY =
PhoneNumberChangeRefreshRequirementProvider.class.getName() + ".initialNumber";
@Override
public void handleRequestFiltered(final RequestEvent requestEvent) {
ContainerRequestUtil.getAuthenticatedAccount(requestEvent.getContainerRequest())
.ifPresent(account -> requestEvent.getContainerRequest().setProperty(INITIAL_NUMBER_KEY, account.getNumber()));
}
@Override
public List<Pair<UUID, Long>> handleRequestFinished(final RequestEvent requestEvent) {
final String initialNumber = (String) requestEvent.getContainerRequest().getProperty(INITIAL_NUMBER_KEY);
if (initialNumber != null) {
final Optional<Account> maybeAuthenticatedAccount =
ContainerRequestUtil.getAuthenticatedAccount(requestEvent.getContainerRequest());
return maybeAuthenticatedAccount
.filter(account -> !initialNumber.equals(account.getNumber()))
.map(account -> account.getDevices().stream()
.map(device -> new Pair<>(account.getUuid(), device.getId()))
.collect(Collectors.toList()))
.orElse(Collections.emptyList());
} else {
return Collections.emptyList();
}
}
}

View File

@@ -9,7 +9,6 @@ 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;
@@ -20,42 +19,33 @@ public class StoredRegistrationLock {
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) {
public StoredRegistrationLock(Optional<String> registrationLock, Optional<String> registrationLockSalt, 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);
return registrationLock.isPresent() && registrationLockSalt.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)) {
public boolean verify(@Nullable String clientRegistrationLock) {
if (Util.isEmpty(clientRegistrationLock)) {
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;
}
@@ -63,6 +53,6 @@ public class StoredRegistrationLock {
@VisibleForTesting
public StoredRegistrationLock forTime(long timestamp) {
return new StoredRegistrationLock(registrationLock, registrationLockSalt, deprecatedPin, timestamp);
return new StoredRegistrationLock(registrationLock, registrationLockSalt, timestamp);
}
}

View File

@@ -5,30 +5,42 @@
package org.whispersystems.textsecuregcm.auth;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.whispersystems.textsecuregcm.util.Util;
import java.security.MessageDigest;
import java.util.concurrent.TimeUnit;
import java.time.Duration;
import java.util.Optional;
import javax.annotation.Nullable;
import org.whispersystems.textsecuregcm.util.Util;
public class StoredVerificationCode {
@JsonProperty
private String code;
private final String code;
@JsonProperty
private long timestamp;
private final long timestamp;
@JsonProperty
private String pushCode;
private final String pushCode;
public StoredVerificationCode() {}
@JsonProperty
@Nullable
private final String twilioVerificationSid;
public StoredVerificationCode(String code, long timestamp, String pushCode) {
this.code = code;
public static final Duration EXPIRATION = Duration.ofMinutes(10);
@JsonCreator
public StoredVerificationCode(
@JsonProperty("code") final String code,
@JsonProperty("timestamp") final long timestamp,
@JsonProperty("pushCode") final String pushCode,
@JsonProperty("twilioVerificationSid") @Nullable final String twilioVerificationSid) {
this.code = code;
this.timestamp = timestamp;
this.pushCode = pushCode;
this.pushCode = pushCode;
this.twilioVerificationSid = twilioVerificationSid;
}
public String getCode() {
@@ -43,19 +55,18 @@ public class StoredVerificationCode {
return pushCode;
}
public boolean isValid(String theirCodeString) {
if (timestamp + TimeUnit.MINUTES.toMillis(10) < System.currentTimeMillis()) {
return false;
}
public Optional<String> getTwilioVerificationSid() {
return Optional.ofNullable(twilioVerificationSid);
}
public boolean isValid(String theirCodeString) {
if (Util.isEmpty(code) || Util.isEmpty(theirCodeString)) {
return false;
}
byte[] ourCode = code.getBytes();
byte[] ourCode = code.getBytes();
byte[] theirCode = theirCodeString.getBytes();
return MessageDigest.isEqual(ourCode, theirCode);
}
}

View File

@@ -6,13 +6,13 @@
package org.whispersystems.textsecuregcm.auth;
import org.whispersystems.textsecuregcm.configuration.TurnConfiguration;
import org.whispersystems.textsecuregcm.util.Base64;
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;
@@ -34,7 +34,7 @@ public class TurnTokenGenerator {
String userTime = validUntilSeconds + ":" + user;
mac.init(new SecretKeySpec(key, "HmacSHA1"));
String password = Base64.encodeBytes(mac.doFinal(userTime.getBytes()));
String password = Base64.getEncoder().encodeToString(mac.doFinal(userTime.getBytes()));
return new TurnToken(userTime, password, urls);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {

View File

@@ -5,12 +5,11 @@
package org.whispersystems.textsecuregcm.auth;
import org.whispersystems.textsecuregcm.util.Base64;
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")
@@ -23,7 +22,7 @@ public class UnidentifiedAccessChecksum {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(unidentifiedAccessKey.get(), "HmacSHA256"));
return Base64.encodeBytes(mac.doFinal(new byte[32]));
return Base64.getEncoder().encodeToString(mac.doFinal(new byte[32]));
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new AssertionError(e);
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import org.glassfish.jersey.server.monitoring.ApplicationEvent;
import org.glassfish.jersey.server.monitoring.ApplicationEventListener;
import org.glassfish.jersey.server.monitoring.RequestEvent;
import org.glassfish.jersey.server.monitoring.RequestEventListener;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
/**
* Delegates request events to a listener that watches for intra-request changes that require websocket refreshes
*/
public class WebsocketRefreshApplicationEventListener implements ApplicationEventListener {
private final WebsocketRefreshRequestEventListener websocketRefreshRequestEventListener;
public WebsocketRefreshApplicationEventListener(final AccountsManager accountsManager,
final ClientPresenceManager clientPresenceManager) {
this.websocketRefreshRequestEventListener = new WebsocketRefreshRequestEventListener(clientPresenceManager,
new AuthEnablementRefreshRequirementProvider(accountsManager),
new PhoneNumberChangeRefreshRequirementProvider());
}
@Override
public void onEvent(final ApplicationEvent event) {
}
@Override
public RequestEventListener onRequest(final RequestEvent requestEvent) {
return websocketRefreshRequestEventListener;
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Metrics;
import org.glassfish.jersey.server.monitoring.RequestEvent;
import org.glassfish.jersey.server.monitoring.RequestEvent.Type;
import org.glassfish.jersey.server.monitoring.RequestEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Context;
import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
public class WebsocketRefreshRequestEventListener implements RequestEventListener {
private final ClientPresenceManager clientPresenceManager;
private final WebsocketRefreshRequirementProvider[] providers;
private static final Counter DISPLACED_ACCOUNTS = Metrics.counter(
name(WebsocketRefreshRequestEventListener.class, "displacedAccounts"));
private static final Counter DISPLACED_DEVICES = Metrics.counter(
name(WebsocketRefreshRequestEventListener.class, "displacedDevices"));
private static final Logger logger = LoggerFactory.getLogger(WebsocketRefreshRequestEventListener.class);
public WebsocketRefreshRequestEventListener(
final ClientPresenceManager clientPresenceManager,
final WebsocketRefreshRequirementProvider... providers) {
this.clientPresenceManager = clientPresenceManager;
this.providers = providers;
}
@Context
private ResourceInfo resourceInfo;
@Override
public void onEvent(final RequestEvent event) {
if (event.getType() == Type.REQUEST_FILTERED) {
for (final WebsocketRefreshRequirementProvider provider : providers) {
provider.handleRequestFiltered(event);
}
} else if (event.getType() == Type.FINISHED) {
final AtomicInteger displacedDevices = new AtomicInteger(0);
Arrays.stream(providers)
.flatMap(provider -> provider.handleRequestFinished(event).stream())
.distinct()
.forEach(pair -> {
try {
displacedDevices.incrementAndGet();
clientPresenceManager.displacePresence(pair.first(), pair.second());
} catch (final Exception e) {
logger.error("Could not displace device presence", e);
}
});
if (displacedDevices.get() > 0) {
DISPLACED_ACCOUNTS.increment();
DISPLACED_DEVICES.increment(displacedDevices.get());
}
}
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import java.util.List;
import java.util.UUID;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.monitoring.RequestEvent;
import org.whispersystems.textsecuregcm.util.Pair;
/**
* A websocket refresh requirement provider watches for intra-request changes (e.g. to authentication status) that
* require a websocket refresh.
*/
public interface WebsocketRefreshRequirementProvider {
/**
* Processes a request after filters have run and the request has been mapped to a destination controller.
*
* @param requestEvent the request event to observe
*/
void handleRequestFiltered(RequestEvent requestEvent);
/**
* Processes a request after all normal request handling has been completed.
*
* @param requestEvent the request event to observe
* @return a list of pairs of account UUID/device ID pairs identifying websockets that need to be refreshed as a
* result of the observed request
*/
List<Pair<UUID, Long>> handleRequestFinished(RequestEvent requestEvent);
}

View File

@@ -0,0 +1,14 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.badges;
import java.util.List;
import java.util.Locale;
import org.whispersystems.textsecuregcm.entities.Badge;
public interface BadgeTranslator {
Badge translate(List<Locale> acceptableLanguages, String badgeId);
}

View File

@@ -0,0 +1,131 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.badges;
import com.google.common.annotations.VisibleForTesting;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.signal.i18n.HeaderControlledResourceBundleLookup;
import org.whispersystems.textsecuregcm.configuration.BadgeConfiguration;
import org.whispersystems.textsecuregcm.configuration.BadgesConfiguration;
import org.whispersystems.textsecuregcm.entities.Badge;
import org.whispersystems.textsecuregcm.entities.BadgeSvg;
import org.whispersystems.textsecuregcm.entities.SelfBadge;
import org.whispersystems.textsecuregcm.storage.AccountBadge;
public class ConfiguredProfileBadgeConverter implements ProfileBadgeConverter, BadgeTranslator {
@VisibleForTesting
static final String BASE_NAME = "org.signal.badges.Badges";
private final Clock clock;
private final Map<String, BadgeConfiguration> knownBadges;
private final List<String> badgeIdsEnabledForAll;
private final HeaderControlledResourceBundleLookup headerControlledResourceBundleLookup;
public ConfiguredProfileBadgeConverter(
final Clock clock,
final BadgesConfiguration badgesConfiguration,
final HeaderControlledResourceBundleLookup headerControlledResourceBundleLookup) {
this.clock = clock;
this.knownBadges = badgesConfiguration.getBadges().stream()
.collect(Collectors.toMap(BadgeConfiguration::getId, Function.identity()));
this.badgeIdsEnabledForAll = badgesConfiguration.getBadgeIdsEnabledForAll();
this.headerControlledResourceBundleLookup = headerControlledResourceBundleLookup;
}
@Override
public Badge translate(final List<Locale> acceptableLanguages, final String badgeId) {
final ResourceBundle resourceBundle = headerControlledResourceBundleLookup.getResourceBundle(BASE_NAME,
acceptableLanguages);
final BadgeConfiguration configuration = knownBadges.get(badgeId);
return newBadge(
false,
configuration.getId(),
configuration.getCategory(),
resourceBundle.getString(configuration.getId() + "_name"),
resourceBundle.getString(configuration.getId() + "_description"),
configuration.getSprites(),
configuration.getSvg(),
configuration.getSvgs(),
null,
false);
}
@Override
public List<Badge> convert(
final List<Locale> acceptableLanguages,
final List<AccountBadge> accountBadges,
final boolean isSelf) {
if (accountBadges.isEmpty() && badgeIdsEnabledForAll.isEmpty()) {
return List.of();
}
final Instant now = clock.instant();
final ResourceBundle resourceBundle = headerControlledResourceBundleLookup.getResourceBundle(BASE_NAME,
acceptableLanguages);
List<Badge> badges = accountBadges.stream()
.filter(accountBadge -> (isSelf || accountBadge.isVisible())
&& now.isBefore(accountBadge.getExpiration())
&& knownBadges.containsKey(accountBadge.getId()))
.map(accountBadge -> {
BadgeConfiguration configuration = knownBadges.get(accountBadge.getId());
return newBadge(
isSelf,
accountBadge.getId(),
configuration.getCategory(),
resourceBundle.getString(accountBadge.getId() + "_name"),
resourceBundle.getString(accountBadge.getId() + "_description"),
configuration.getSprites(),
configuration.getSvg(),
configuration.getSvgs(),
accountBadge.getExpiration(),
accountBadge.isVisible());
})
.collect(Collectors.toCollection(ArrayList::new));
badges.addAll(badgeIdsEnabledForAll.stream().filter(knownBadges::containsKey).map(id -> {
BadgeConfiguration configuration = knownBadges.get(id);
return newBadge(
isSelf,
id,
configuration.getCategory(),
resourceBundle.getString(id + "_name"),
resourceBundle.getString(id + "_description"),
configuration.getSprites(),
configuration.getSvg(),
configuration.getSvgs(),
now.plus(Duration.ofDays(1)),
true);
}).collect(Collectors.toList()));
return badges;
}
private Badge newBadge(
final boolean isSelf,
final String id,
final String category,
final String name,
final String description,
final List<String> sprites,
final String svg,
final List<BadgeSvg> svgs,
final Instant expiration,
final boolean visible) {
if (isSelf) {
return new SelfBadge(id, category, name, description, sprites, svg, svgs, expiration, visible);
} else {
return new Badge(id, category, name, description, sprites, svg, svgs);
}
}
}

View File

@@ -0,0 +1,13 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.badges;
import java.util.List;
import java.util.Locale;
public interface LevelTranslator {
String translate(List<Locale> acceptableLanguages, String badgeId);
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.badges;
import java.util.List;
import java.util.Locale;
import org.whispersystems.textsecuregcm.entities.Badge;
import org.whispersystems.textsecuregcm.storage.AccountBadge;
public interface ProfileBadgeConverter {
/**
* Converts the {@link AccountBadge}s for an account into the objects
* that can be returned on a profile fetch.
*/
List<Badge> convert(List<Locale> acceptableLanguages, List<AccountBadge> accountBadges, boolean isSelf);
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.badges;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.ResourceBundle;
import javax.annotation.Nonnull;
import org.signal.i18n.HeaderControlledResourceBundleLookup;
public class ResourceBundleLevelTranslator implements LevelTranslator {
private static final String BASE_NAME = "org.signal.subscriptions.Subscriptions";
private final HeaderControlledResourceBundleLookup headerControlledResourceBundleLookup;
public ResourceBundleLevelTranslator(
@Nonnull final HeaderControlledResourceBundleLookup headerControlledResourceBundleLookup) {
this.headerControlledResourceBundleLookup = Objects.requireNonNull(headerControlledResourceBundleLookup);
}
@Override
public String translate(final List<Locale> acceptableLanguages, final String badgeId) {
final ResourceBundle resourceBundle = headerControlledResourceBundleLookup.getResourceBundle(BASE_NAME,
acceptableLanguages);
return resourceBundle.getString(badgeId);
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import javax.validation.constraints.NotBlank;
public class AbusiveMessageFilterConfiguration {
@JsonProperty
@NotBlank
private final String environment;
@JsonCreator
public AbusiveMessageFilterConfiguration(@JsonProperty("environment") final String environment) {
this.environment = environment;
}
public String getEnvironment() {
return environment;
}
}

View File

@@ -0,0 +1,31 @@
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import javax.validation.constraints.NotNull;
public class AccountsDynamoDbConfiguration extends DynamoDbConfiguration {
@NotNull
private String phoneNumberTableName;
@NotNull
private String phoneNumberIdentifierTableName;
private int scanPageSize = 100;
@JsonProperty
public String getPhoneNumberTableName() {
return phoneNumberTableName;
}
@JsonProperty
public String getPhoneNumberIdentifierTableName() {
return phoneNumberIdentifierTableName;
}
@JsonProperty
public int getScanPageSize() {
return scanPageSize;
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.entities.BadgeSvg;
import org.whispersystems.textsecuregcm.util.ExactlySize;
public class BadgeConfiguration {
public static final String CATEGORY_TESTING = "testing";
private final String id;
private final String category;
private final List<String> sprites;
private final String svg;
private final List<BadgeSvg> svgs;
@JsonCreator
public BadgeConfiguration(
@JsonProperty("id") final String id,
@JsonProperty("category") final String category,
@JsonProperty("sprites") final List<String> sprites,
@JsonProperty("svg") final String svg,
@JsonProperty("svgs") final List<BadgeSvg> svgs) {
this.id = id;
this.category = category;
this.sprites = sprites;
this.svg = svg;
this.svgs = svgs;
}
@NotEmpty
public String getId() {
return id;
}
@NotEmpty
public String getCategory() {
return category;
}
@NotNull
@ExactlySize(6)
public List<String> getSprites() {
return sprites;
}
@NotEmpty
public String getSvg() {
return svg;
}
@NotNull
public List<BadgeSvg> getSvgs() {
return svgs;
}
public boolean isTestBadge() {
return CATEGORY_TESTING.equals(category);
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.annotation.Nulls;
import io.dropwizard.validation.ValidationMethod;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
public class BadgesConfiguration {
private final List<BadgeConfiguration> badges;
private final List<String> badgeIdsEnabledForAll;
private final Map<Long, String> receiptLevels;
@JsonCreator
public BadgesConfiguration(
@JsonProperty("badges") @JsonSetter(nulls = Nulls.AS_EMPTY) final List<BadgeConfiguration> badges,
@JsonProperty("badgeIdsEnabledForAll") @JsonSetter(nulls = Nulls.AS_EMPTY) final List<String> badgeIdsEnabledForAll,
@JsonProperty("receiptLevels") @JsonSetter(nulls = Nulls.AS_EMPTY) final Map<Long, String> receiptLevels) {
this.badges = Objects.requireNonNull(badges);
this.badgeIdsEnabledForAll = Objects.requireNonNull(badgeIdsEnabledForAll);
this.receiptLevels = Objects.requireNonNull(receiptLevels);
}
@Valid
@NotNull
public List<BadgeConfiguration> getBadges() {
return badges;
}
@Valid
@NotNull
public List<String> getBadgeIdsEnabledForAll() {
return badgeIdsEnabledForAll;
}
@Valid
@NotNull
public Map<Long, String> getReceiptLevels() {
return receiptLevels;
}
@JsonIgnore
@ValidationMethod(message = "contains receipt level mappings that are not configured badges")
public boolean isAllReceiptLevelsConfigured() {
final Set<String> badgeNames = badges.stream().map(BadgeConfiguration::getId).collect(Collectors.toSet());
return badgeNames.containsAll(receiptLevels.values());
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.math.BigDecimal;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import javax.validation.Valid;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.util.ExactlySize;
public class BoostConfiguration {
private final long level;
private final Duration expiration;
private final Map<String, List<BigDecimal>> currencies;
private final String badge;
@JsonCreator
public BoostConfiguration(
@JsonProperty("level") long level,
@JsonProperty("expiration") Duration expiration,
@JsonProperty("currencies") Map<String, List<BigDecimal>> currencies,
@JsonProperty("badge") String badge) {
this.level = level;
this.expiration = expiration;
this.currencies = currencies;
this.badge = badge;
}
public long getLevel() {
return level;
}
@NotNull
public Duration getExpiration() {
return expiration;
}
@Valid
@NotNull
public Map<@NotEmpty String, @Valid @ExactlySize(6) List<@DecimalMin("0.01") @NotNull BigDecimal>> getCurrencies() {
return currencies;
}
@NotEmpty
public String getBadge() {
return badge;
}
}

View File

@@ -7,15 +7,15 @@ package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
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
@@ -39,6 +39,9 @@ public class CircuitBreakerConfiguration {
@Min(1)
private long waitDurationInOpenStateInSeconds = 10;
@JsonProperty
private List<String> ignoredExceptions = Collections.emptyList();
public int getFailureRateThreshold() {
return failureRateThreshold;
@@ -56,6 +59,18 @@ public class CircuitBreakerConfiguration {
return waitDurationInOpenStateInSeconds;
}
public List<Class> getIgnoredExceptions() {
return ignoredExceptions.stream()
.map(name -> {
try {
return Class.forName(name);
} catch (final ClassNotFoundException e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toList());
}
@VisibleForTesting
public void setFailureRateThreshold(int failureRateThreshold) {
this.failureRateThreshold = failureRateThreshold;
@@ -76,9 +91,15 @@ public class CircuitBreakerConfiguration {
this.waitDurationInOpenStateInSeconds = seconds;
}
@VisibleForTesting
public void setIgnoredExceptions(final List<String> ignoredExceptions) {
this.ignoredExceptions = ignoredExceptions;
}
public CircuitBreakerConfig toCircuitBreakerConfig() {
return CircuitBreakerConfig.custom()
.failureRateThreshold(getFailureRateThreshold())
.ignoreExceptions(getIgnoredExceptions().toArray(new Class[0]))
.ringBufferSizeInHalfOpenState(getRingBufferSizeInHalfOpenState())
.waitDurationInOpenState(Duration.ofSeconds(getWaitDurationInOpenStateInSeconds()))
.ringBufferSizeInClosedState(getRingBufferSizeInClosedState())

View File

@@ -0,0 +1,61 @@
/*
* 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 io.micrometer.datadog.DatadogConfig;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.time.Duration;
public class DatadogConfiguration implements DatadogConfig {
@JsonProperty
@NotBlank
private String apiKey;
@JsonProperty
@NotNull
private Duration step = Duration.ofSeconds(10);
@JsonProperty
@NotBlank
private String environment;
@JsonProperty
@Min(1)
private int batchSize = 5_000;
@Override
public String apiKey() {
return apiKey;
}
@Override
public Duration step() {
return step;
}
public String getEnvironment() {
return environment;
}
@Override
public int batchSize() {
return batchSize;
}
@Override
public String hostTag() {
return "host";
}
@Override
public String get(final String key) {
return null;
}
}

View File

@@ -0,0 +1,13 @@
package org.whispersystems.textsecuregcm.configuration;
import javax.validation.constraints.NotNull;
public class DeletedAccountsDynamoDbConfiguration extends DynamoDbConfiguration {
@NotNull
private String needsReconciliationIndexName;
public String getNeedsReconciliationIndexName() {
return needsReconciliationIndexName;
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.whispersystems.textsecuregcm.util.ExactlySize;
public class DirectoryV2ClientConfiguration {
private final byte[] userAuthenticationTokenSharedSecret;
@JsonCreator
public DirectoryV2ClientConfiguration(
@JsonProperty("userAuthenticationTokenSharedSecret") final byte[] userAuthenticationTokenSharedSecret) {
this.userAuthenticationTokenSharedSecret = userAuthenticationTokenSharedSecret;
}
@ExactlySize({32})
public byte[] getUserAuthenticationTokenSharedSecret() {
return userAuthenticationTokenSharedSecret;
}
}

View File

@@ -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.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import javax.validation.Valid;
public class DirectoryV2Configuration {
private final DirectoryV2ClientConfiguration clientConfiguration;
@JsonCreator
public DirectoryV2Configuration(@JsonProperty("client") DirectoryV2ClientConfiguration clientConfiguration) {
this.clientConfiguration = clientConfiguration;
}
@Valid
public DirectoryV2ClientConfiguration getDirectoryV2ClientConfiguration() {
return clientConfiguration;
}
}

View File

@@ -0,0 +1,78 @@
/*
* 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 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
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;
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.time.Duration;
import javax.validation.constraints.NotEmpty;
public class DynamoDbClientConfiguration {
private final String region;
private final Duration clientExecutionTimeout;
private final Duration clientRequestTimeout;
@JsonCreator
public DynamoDbClientConfiguration(
@JsonProperty("region") final String region,
@JsonProperty("clientExcecutionTimeout") final Duration clientExecutionTimeout,
@JsonProperty("clientRequestTimeout") final Duration clientRequestTimeout) {
this.region = region;
this.clientExecutionTimeout = clientExecutionTimeout != null ? clientExecutionTimeout : Duration.ofSeconds(30);
this.clientRequestTimeout = clientRequestTimeout != null ? clientRequestTimeout : Duration.ofSeconds(10);
}
@NotEmpty
public String getRegion() {
return region;
}
public Duration getClientExecutionTimeout() {
return clientExecutionTimeout;
}
public Duration getClientRequestTimeout() {
return clientRequestTimeout;
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.time.Duration;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
public class DynamoDbTables {
public static class Table {
private final String tableName;
@JsonCreator
public Table(
@JsonProperty("tableName") final String tableName) {
this.tableName = tableName;
}
@NotEmpty
public String getTableName() {
return tableName;
}
}
public static class TableWithExpiration extends Table {
private final Duration expiration;
@JsonCreator
public TableWithExpiration(
@JsonProperty("tableName") final String tableName,
@JsonProperty("expiration") final Duration expiration) {
super(tableName);
this.expiration = expiration;
}
@NotNull
public Duration getExpiration() {
return expiration;
}
}
private final IssuedReceiptsTableConfiguration issuedReceipts;
private final TableWithExpiration redeemedReceipts;
private final Table subscriptions;
@JsonCreator
public DynamoDbTables(
@JsonProperty("issuedReceipts") final IssuedReceiptsTableConfiguration issuedReceipts,
@JsonProperty("redeemedReceipts") final TableWithExpiration redeemedReceipts,
@JsonProperty("subscriptions") final Table subscriptions) {
this.issuedReceipts = issuedReceipts;
this.redeemedReceipts = redeemedReceipts;
this.subscriptions = subscriptions;
}
@Valid
@NotNull
public IssuedReceiptsTableConfiguration getIssuedReceipts() {
return issuedReceipts;
}
@Valid
@NotNull
public TableWithExpiration getRedeemedReceipts() {
return redeemedReceipts;
}
@Valid
@NotNull
public Table getSubscriptions() {
return subscriptions;
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2021 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;
import javax.validation.constraints.NotEmpty;
public class IssuedReceiptsTableConfiguration extends DynamoDbTables.TableWithExpiration {
private final byte[] generator;
public IssuedReceiptsTableConfiguration(
@JsonProperty("tableName") final String tableName,
@JsonProperty("expiration") final Duration expiration,
@JsonProperty("generator") final byte[] generator) {
super(tableName, expiration);
this.generator = generator;
}
@NotEmpty
public byte[] getGenerator() {
return generator;
}
}

View File

@@ -5,13 +5,12 @@
package org.whispersystems.textsecuregcm.configuration;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import java.time.Duration;
import javax.validation.Valid;
public class MessageDynamoDbConfiguration extends DynamoDbConfiguration {
private Duration timeToLive = Duration.ofDays(7);
private Duration timeToLive = Duration.ofDays(14);
@Valid
public Duration getTimeToLive() {

View File

@@ -1,29 +0,0 @@
/*
* 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.Positive;
public class MicrometerConfiguration {
@JsonProperty
private String uri;
@JsonProperty
@Positive
private int batchSize = 10_000;
public String getUri() {
return uri;
}
public int getBatchSize() {
return batchSize;
}
}

View File

@@ -9,7 +9,6 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import java.util.List;

View File

@@ -63,6 +63,9 @@ public class RateLimitsConfiguration {
@JsonProperty
private RateLimitConfiguration usernameSet = new RateLimitConfiguration(100, 100 / (24.0 * 60.0));
@JsonProperty
private RateLimitConfiguration checkAccountExistence = new RateLimitConfiguration(1_000, 1_000 / 60.0);
public RateLimitConfiguration getAutoBlock() {
return autoBlock;
}
@@ -135,6 +138,10 @@ public class RateLimitsConfiguration {
return usernameSet;
}
public RateLimitConfiguration getCheckAccountExistence() {
return checkAccountExistence;
}
public static class RateLimitConfiguration {
@JsonProperty
private int bucketSize;
@@ -165,16 +172,12 @@ public class RateLimitsConfiguration {
@JsonProperty
private Duration ttl;
@JsonProperty
private Duration ttlJitter;
public CardinalityRateLimitConfiguration() {
}
public CardinalityRateLimitConfiguration(int maxCardinality, Duration ttl, Duration ttlJitter) {
public CardinalityRateLimitConfiguration(int maxCardinality, Duration ttl) {
this.maxCardinality = maxCardinality;
this.ttl = ttl;
this.ttlJitter = ttlJitter;
}
public int getMaxCardinality() {
@@ -184,9 +187,5 @@ public class RateLimitsConfiguration {
public Duration getTtl() {
return ttl;
}
public Duration getTtlJitter() {
return ttlJitter;
}
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import java.math.BigDecimal;
import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
public class RecaptchaV2Configuration {
private BigDecimal scoreFloor;
private String projectPath;
private String siteKey;
private String credentialConfigurationJson;
@DecimalMin("0")
@DecimalMax("1")
@NotNull
public BigDecimal getScoreFloor() {
return scoreFloor;
}
@NotEmpty
public String getProjectPath() {
return projectPath;
}
@NotEmpty
public String getSiteKey() {
return siteKey;
}
@NotEmpty
public String getCredentialConfigurationJson() {
return credentialConfigurationJson;
}
}

View File

@@ -11,41 +11,40 @@ import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.time.Duration;
import java.util.List;
public class RedisClusterConfiguration {
@JsonProperty
@NotEmpty
private List<String> urls;
@JsonProperty
@NotEmpty
private String configurationUri;
@JsonProperty
@NotNull
private Duration timeout = Duration.ofMillis(3_000);
@JsonProperty
@NotNull
private Duration timeout = Duration.ofMillis(3_000);
@JsonProperty
@NotNull
@Valid
private CircuitBreakerConfiguration circuitBreaker = new CircuitBreakerConfiguration();
@JsonProperty
@NotNull
@Valid
private CircuitBreakerConfiguration circuitBreaker = new CircuitBreakerConfiguration();
@JsonProperty
@NotNull
@Valid
private RetryConfiguration retry = new RetryConfiguration();
@JsonProperty
@NotNull
@Valid
private RetryConfiguration retry = new RetryConfiguration();
public List<String> getUrls() {
return urls;
}
public String getConfigurationUri() {
return configurationUri;
}
public Duration getTimeout() {
return timeout;
}
public Duration getTimeout() {
return timeout;
}
public CircuitBreakerConfiguration getCircuitBreakerConfiguration() {
return circuitBreaker;
}
public CircuitBreakerConfiguration getCircuitBreakerConfiguration() {
return circuitBreaker;
}
public RetryConfiguration getRetryConfiguration() {
return retry;
}
public RetryConfiguration getRetryConfiguration() {
return retry;
}
}

View File

@@ -0,0 +1,29 @@
/*
* 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.NotNull;
import java.time.Duration;
public class ReportMessageConfiguration {
@JsonProperty
@NotNull
private final Duration reportTtl = Duration.ofDays(7);
@JsonProperty
@NotNull
private final Duration counterTtl = Duration.ofDays(1);
public Duration getReportTtl() {
return reportTtl;
}
public Duration getCounterTtl() {
return counterTtl;
}
}

View File

@@ -6,7 +6,11 @@
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
@@ -16,8 +20,51 @@ public class SecureBackupServiceConfiguration {
@JsonProperty
private String userAuthenticationTokenSharedSecret;
@NotBlank
@JsonProperty
private String uri;
@NotBlank
@JsonProperty
private String backupCaCertificate;
@NotNull
@Valid
@JsonProperty
private CircuitBreakerConfiguration circuitBreaker = new CircuitBreakerConfiguration();
@NotNull
@Valid
@JsonProperty
private RetryConfiguration retry = new RetryConfiguration();
public byte[] getUserAuthenticationTokenSharedSecret() throws DecoderException {
return Hex.decodeHex(userAuthenticationTokenSharedSecret.toCharArray());
}
@VisibleForTesting
public void setUri(final String uri) {
this.uri = uri;
}
public String getUri() {
return uri;
}
@VisibleForTesting
public void setBackupCaCertificate(final String backupCaCertificate) {
this.backupCaCertificate = backupCaCertificate;
}
public String getBackupCaCertificate() {
return backupCaCertificate;
}
public CircuitBreakerConfiguration getCircuitBreakerConfiguration() {
return circuitBreaker;
}
public RetryConfiguration getRetryConfiguration() {
return retry;
}
}

View File

@@ -24,6 +24,10 @@ public class SecureStorageServiceConfiguration {
@JsonProperty
private String uri;
@NotBlank
@JsonProperty
private String storageCaCertificate;
@NotNull
@Valid
@JsonProperty
@@ -47,6 +51,15 @@ public class SecureStorageServiceConfiguration {
return uri;
}
@VisibleForTesting
public void setStorageCaCertificate(final String certificatePem) {
this.storageCaCertificate = certificatePem;
}
public String getStorageCaCertificate() {
return storageCaCertificate;
}
public CircuitBreakerConfiguration getCircuitBreakerConfiguration() {
return circuitBreaker;
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import javax.validation.constraints.NotEmpty;
public class StripeConfiguration {
private final String apiKey;
private final byte[] idempotencyKeyGenerator;
private final String boostDescription;
@JsonCreator
public StripeConfiguration(
@JsonProperty("apiKey") final String apiKey,
@JsonProperty("idempotencyKeyGenerator") final byte[] idempotencyKeyGenerator,
@JsonProperty("boostDescription") final String boostDescription) {
this.apiKey = apiKey;
this.idempotencyKeyGenerator = idempotencyKeyGenerator;
this.boostDescription = boostDescription;
}
@NotEmpty
public String getApiKey() {
return apiKey;
}
@NotEmpty
public byte[] getIdempotencyKeyGenerator() {
return idempotencyKeyGenerator;
}
@NotEmpty
public String getBoostDescription() {
return boostDescription;
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.dropwizard.validation.ValidationMethod;
import java.time.Duration;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
public class SubscriptionConfiguration {
private final Duration badgeGracePeriod;
private final Map<Long, SubscriptionLevelConfiguration> levels;
@JsonCreator
public SubscriptionConfiguration(
@JsonProperty("badgeGracePeriod") @Valid Duration badgeGracePeriod,
@JsonProperty("levels") @Valid Map<@NotNull @Min(1) Long, @NotNull @Valid SubscriptionLevelConfiguration> levels) {
this.badgeGracePeriod = badgeGracePeriod;
this.levels = levels;
}
public Duration getBadgeGracePeriod() {
return badgeGracePeriod;
}
public Map<Long, SubscriptionLevelConfiguration> getLevels() {
return levels;
}
@JsonIgnore
@ValidationMethod(message = "has a mismatch between the levels supported currencies")
public boolean isCurrencyListSameAcrossAllLevels() {
Optional<SubscriptionLevelConfiguration> any = levels.values().stream().findAny();
if (any.isEmpty()) {
return true;
}
Set<String> currencies = any.get().getPrices().keySet();
return levels.values().stream().allMatch(level -> currencies.equals(level.getPrices().keySet()));
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Map;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
public class SubscriptionLevelConfiguration {
private final String badge;
private final String product;
private final Map<String, SubscriptionPriceConfiguration> prices;
@JsonCreator
public SubscriptionLevelConfiguration(
@JsonProperty("badge") @NotEmpty String badge,
@JsonProperty("product") @NotEmpty String product,
@JsonProperty("prices") @Valid Map<@NotEmpty String, @NotNull @Valid SubscriptionPriceConfiguration> prices) {
this.badge = badge;
this.product = product;
this.prices = prices;
}
public String getBadge() {
return badge;
}
public String getProduct() {
return product;
}
public Map<String, SubscriptionPriceConfiguration> getPrices() {
return prices;
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.math.BigDecimal;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
public class SubscriptionPriceConfiguration {
private final String id;
private final BigDecimal amount;
@JsonCreator
public SubscriptionPriceConfiguration(
@JsonProperty("id") @NotEmpty String id,
@JsonProperty("amount") @NotNull @DecimalMin("0.01") BigDecimal amount) {
this.id = id;
this.amount = amount;
}
public String getId() {
return id;
}
public BigDecimal getAmount() {
return amount;
}
}

View File

@@ -5,11 +5,11 @@
package org.whispersystems.textsecuregcm.configuration;
import com.google.common.annotations.VisibleForTesting;
import java.util.Collections;
import java.util.Map;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
public class TwilioConfiguration {
@@ -19,15 +19,18 @@ public class TwilioConfiguration {
@NotEmpty
private String accountToken;
@NotNull
private List<String> numbers;
@NotEmpty
private String localDomain;
@NotEmpty
private String messagingServiceSid;
@NotEmpty
private String nanpaMessagingServiceSid;
@NotEmpty
private String verifyServiceSid;
@NotNull
@Valid
private CircuitBreakerConfiguration circuitBreaker = new CircuitBreakerConfiguration();
@@ -36,21 +39,17 @@ public class TwilioConfiguration {
@Valid
private RetryConfiguration retry = new RetryConfiguration();
@NotNull
@Valid
private TwilioSenderIdConfiguration senderId = new TwilioSenderIdConfiguration();
private TwilioVerificationTextConfiguration defaultClientVerificationTexts;
@Valid
private Map<String,TwilioVerificationTextConfiguration> regionalClientVerificationTexts = Collections.emptyMap();
@NotEmpty
private String iosVerificationText;
private String androidAppHash;
@NotEmpty
private String androidNgVerificationText;
@NotEmpty
private String android202001VerificationText;
@NotEmpty
private String genericVerificationText;
private String verifyServiceFriendlyName;
public String getAccountId() {
return accountId;
@@ -69,16 +68,6 @@ public class TwilioConfiguration {
public void setAccountToken(String accountToken) {
this.accountToken = accountToken;
}
public List<String> getNumbers() {
return numbers;
}
@VisibleForTesting
public void setNumbers(List<String> numbers) {
this.numbers = numbers;
}
public String getLocalDomain() {
return localDomain;
}
@@ -106,6 +95,15 @@ public class TwilioConfiguration {
this.nanpaMessagingServiceSid = nanpaMessagingServiceSid;
}
public String getVerifyServiceSid() {
return verifyServiceSid;
}
@VisibleForTesting
public void setVerifyServiceSid(String verifyServiceSid) {
this.verifyServiceSid = verifyServiceSid;
}
public CircuitBreakerConfiguration getCircuitBreaker() {
return circuitBreaker;
}
@@ -124,48 +122,38 @@ public class TwilioConfiguration {
this.retry = retry;
}
public TwilioSenderIdConfiguration getSenderId() {
return senderId;
public TwilioVerificationTextConfiguration getDefaultClientVerificationTexts() {
return defaultClientVerificationTexts;
}
@VisibleForTesting
public void setSenderId(TwilioSenderIdConfiguration senderId) {
this.senderId = senderId;
public void setDefaultClientVerificationTexts(TwilioVerificationTextConfiguration defaultClientVerificationTexts) {
this.defaultClientVerificationTexts = defaultClientVerificationTexts;
}
public String getIosVerificationText() {
return iosVerificationText;
public Map<String,TwilioVerificationTextConfiguration> getRegionalClientVerificationTexts() {
return regionalClientVerificationTexts;
}
@VisibleForTesting
public void setIosVerificationText(String iosVerificationText) {
this.iosVerificationText = iosVerificationText;
public void setRegionalClientVerificationTexts(final Map<String,TwilioVerificationTextConfiguration> regionalClientVerificationTexts) {
this.regionalClientVerificationTexts = regionalClientVerificationTexts;
}
public String getAndroidNgVerificationText() {
return androidNgVerificationText;
public String getAndroidAppHash() {
return androidAppHash;
}
@VisibleForTesting
public void setAndroidNgVerificationText(String androidNgVerificationText) {
this.androidNgVerificationText = androidNgVerificationText;
public void setAndroidAppHash(String androidAppHash) {
this.androidAppHash = androidAppHash;
}
public String getAndroid202001VerificationText() {
return android202001VerificationText;
public void setVerifyServiceFriendlyName(String serviceFriendlyName) {
this.verifyServiceFriendlyName = serviceFriendlyName;
}
@VisibleForTesting
public void setAndroid202001VerificationText(String android202001VerificationText) {
this.android202001VerificationText = android202001VerificationText;
}
public String getGenericVerificationText() {
return genericVerificationText;
}
@VisibleForTesting
public void setGenericVerificationText(String genericVerificationText) {
this.genericVerificationText = genericVerificationText;
public String getVerifyServiceFriendlyName() {
return verifyServiceFriendlyName;
}
}

View File

@@ -1,56 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.google.common.annotations.VisibleForTesting;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class TwilioSenderIdConfiguration {
@NotEmpty
private String defaultSenderId;
@NotNull
@Valid
private List<TwilioCountrySenderIdConfiguration> countrySpecificSenderIds = new ArrayList<>();
@NotNull
@Valid
private Set<String> countryCodesWithoutSenderId = new HashSet<>();
public String getDefaultSenderId() {
return defaultSenderId;
}
@VisibleForTesting
public void setDefaultSenderId(String defaultSenderId) {
this.defaultSenderId = defaultSenderId;
}
public List<TwilioCountrySenderIdConfiguration> getCountrySpecificSenderIds() {
return countrySpecificSenderIds;
}
@VisibleForTesting
public void setCountrySpecificSenderIds(List<TwilioCountrySenderIdConfiguration> countrySpecificSenderIds) {
this.countrySpecificSenderIds = countrySpecificSenderIds;
}
public Set<String> getCountryCodesWithoutSenderId() {
return countryCodesWithoutSenderId;
}
@VisibleForTesting
public void setCountryCodesWithoutSenderId(Set<String> countryCodesWithoutSenderId) {
this.countryCodesWithoutSenderId = countryCodesWithoutSenderId;
}
}

View File

@@ -0,0 +1,70 @@
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import javax.validation.constraints.NotEmpty;
public class TwilioVerificationTextConfiguration {
@JsonProperty
@NotEmpty
private String ios;
@JsonProperty
@NotEmpty
private String androidNg;
@JsonProperty
@NotEmpty
private String android202001;
@JsonProperty
@NotEmpty
private String android202103;
@JsonProperty
@NotEmpty
private String generic;
public String getIosText() {
return ios;
}
public void setIosText(String ios) {
this.ios = ios;
}
public String getAndroidNgText() {
return androidNg;
}
public void setAndroidNgText(final String androidNg) {
this.androidNg = androidNg;
}
public String getAndroid202001Text() {
return android202001;
}
public void setAndroid202001Text(final String android202001) {
this.android202001 = android202001;
}
public String getAndroid202103Text() {
return android202103;
}
public void setAndroid202103Text(final String android202103) {
this.android202103 = android202103;
}
public String getGenericText() {
return generic;
}
public void setGenericText(final String generic) {
this.generic = generic;
}
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.databind.util.StdConverter;
import java.net.MalformedURLException;
import java.net.URL;
final class URLDeserializationConverter extends StdConverter<String, URL> {
@Override
public URL convert(final String value) {
try {
return new URL(value);
} catch (MalformedURLException e) {
throw new IllegalArgumentException(e);
}
}
}

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.databind.util.StdConverter;
import java.net.URL;
final class URLSerializationConverter extends StdConverter<URL, String> {
@Override
public String convert(final URL value) {
return value.toString();
}
}

View File

@@ -26,10 +26,6 @@ public class ZkConfig {
@NotNull
private byte[] serverPublic;
@JsonProperty
@NotNull
private Boolean enabled;
public byte[] getServerSecret() {
return serverSecret;
}
@@ -37,8 +33,4 @@ public class ZkConfig {
public byte[] getServerPublic() {
return serverPublic;
}
public boolean isEnabled() {
return enabled;
}
}

View File

@@ -1,12 +1,12 @@
package org.whispersystems.textsecuregcm.configuration.dynamic;
import com.fasterxml.jackson.annotation.JsonProperty;
import javax.validation.Valid;
import com.google.common.annotations.VisibleForTesting;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.validation.Valid;
public class DynamicConfiguration {
@@ -14,6 +14,10 @@ public class DynamicConfiguration {
@Valid
private Map<String, DynamicExperimentEnrollmentConfiguration> experiments = Collections.emptyMap();
@JsonProperty
@Valid
private Map<String, DynamicPreRegistrationExperimentEnrollmentConfiguration> preRegistrationExperiments = Collections.emptyMap();
@JsonProperty
@Valid
private DynamicRateLimitsConfiguration limits = new DynamicRateLimitsConfiguration();
@@ -26,13 +30,41 @@ public class DynamicConfiguration {
@Valid
private DynamicMessageRateConfiguration messageRate = new DynamicMessageRateConfiguration();
@JsonProperty
@Valid
private DynamicPaymentsConfiguration payments = new DynamicPaymentsConfiguration();
@JsonProperty
private Set<String> featureFlags = Collections.emptySet();
public Optional<DynamicExperimentEnrollmentConfiguration> getExperimentEnrollmentConfiguration(final String experimentName) {
@JsonProperty
@Valid
private DynamicTwilioConfiguration twilio = new DynamicTwilioConfiguration();
@JsonProperty
private DynamicSignupCaptchaConfiguration signupCaptcha = new DynamicSignupCaptchaConfiguration();
@JsonProperty
@Valid
private DynamicRateLimitChallengeConfiguration rateLimitChallenge = new DynamicRateLimitChallengeConfiguration();
@JsonProperty
private DynamicDirectoryReconcilerConfiguration directoryReconciler = new DynamicDirectoryReconcilerConfiguration();
@JsonProperty
@Valid
private DynamicPushLatencyConfiguration pushLatency = new DynamicPushLatencyConfiguration(Collections.emptyMap());
public Optional<DynamicExperimentEnrollmentConfiguration> getExperimentEnrollmentConfiguration(
final String experimentName) {
return Optional.ofNullable(experiments.get(experimentName));
}
public Optional<DynamicPreRegistrationExperimentEnrollmentConfiguration> getPreRegistrationEnrollmentConfiguration(
final String experimentName) {
return Optional.ofNullable(preRegistrationExperiments.get(experimentName));
}
public DynamicRateLimitsConfiguration getLimits() {
return limits;
}
@@ -45,7 +77,36 @@ public class DynamicConfiguration {
return messageRate;
}
public DynamicPaymentsConfiguration getPaymentsConfiguration() {
return payments;
}
public Set<String> getActiveFeatureFlags() {
return featureFlags;
}
public DynamicTwilioConfiguration getTwilioConfiguration() {
return twilio;
}
@VisibleForTesting
public void setTwilioConfiguration(DynamicTwilioConfiguration twilioConfiguration) {
this.twilio = twilioConfiguration;
}
public DynamicSignupCaptchaConfiguration getSignupCaptchaConfiguration() {
return signupCaptcha;
}
public DynamicRateLimitChallengeConfiguration getRateLimitChallengeConfiguration() {
return rateLimitChallenge;
}
public DynamicDirectoryReconcilerConfiguration getDirectoryReconcilerConfiguration() {
return directoryReconciler;
}
public DynamicPushLatencyConfiguration getPushLatencyConfiguration() {
return pushLatency;
}
}

View File

@@ -0,0 +1,18 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration.dynamic;
import com.fasterxml.jackson.annotation.JsonProperty;
public class DynamicDirectoryReconcilerConfiguration {
@JsonProperty
private boolean enabled = true;
public boolean isEnabled() {
return enabled;
}
}

View File

@@ -11,7 +11,6 @@ import javax.validation.Valid;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration.dynamic;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Collections;
import java.util.List;
public class DynamicPaymentsConfiguration {
@JsonProperty
private List<String> disallowedPrefixes = Collections.emptyList();
public List<String> getDisallowedPrefixes() {
return disallowedPrefixes;
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration.dynamic;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Collections;
import java.util.Set;
import javax.validation.Valid;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
public class DynamicPreRegistrationExperimentEnrollmentConfiguration {
@JsonProperty
@Valid
private Set<String> enrolledE164s = Collections.emptySet();
@JsonProperty
@Valid
private Set<String> excludedE164s = Collections.emptySet();
@JsonProperty
@Valid
private Set<String> includedCountryCodes = Collections.emptySet();
@JsonProperty
@Valid
private Set<String> excludedCountryCodes = Collections.emptySet();
@JsonProperty
@Valid
@Min(0)
@Max(100)
private int enrollmentPercentage = 0;
public Set<String> getEnrolledE164s() {
return enrolledE164s;
}
public Set<String> getExcludedE164s() {
return excludedE164s;
}
public Set<String> getIncludedCountryCodes() {
return includedCountryCodes;
}
public Set<String> getExcludedCountryCodes() {
return excludedCountryCodes;
}
public int getEnrollmentPercentage() {
return enrollmentPercentage;
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration.dynamic;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.vdurmont.semver4j.Semver;
import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
import java.util.Map;
import java.util.Set;
public class DynamicPushLatencyConfiguration {
private final Map<ClientPlatform, Set<Semver>> instrumentedVersions;
@JsonCreator
public DynamicPushLatencyConfiguration(@JsonProperty("instrumentedVersions") final Map<ClientPlatform, Set<Semver>> instrumentedVersions) {
this.instrumentedVersions = instrumentedVersions;
}
public Map<ClientPlatform, Set<Semver>> getInstrumentedVersions() {
return instrumentedVersions;
}
}

Some files were not shown because too many files have changed in this diff Show More