Compare commits

...

483 Commits

Author SHA1 Message Date
Chris Eager
f361f436d8 Support PayPal for recurring donations 2023-01-17 12:20:17 -06:00
Chris Eager
a34b5a6122 grpc, guava: use version from google cloud libraries-bom 2023-01-17 11:20:46 -06:00
Chris Eager
f75ea18ccb Add test for GoogleCloudAdminEventLogger 2023-01-17 11:20:46 -06:00
Dimitris Apostolou
9a06c40a28 Fix typos 2023-01-13 16:05:06 -06:00
Chris Eager
e6ab97dc5a Update enabled-required authenticator metrics 2023-01-13 14:05:56 -06:00
Chris Eager
ba73f757e2 Update google libraries-bom to 26.1.3, firebase-admin to 9.1.1 2023-01-13 12:22:55 -06:00
Chris Eager
30f131096d Update AWS SDK v1 to 1.12.376 2023-01-13 12:17:39 -06:00
Chris Eager
b8ce922f92 Update logstash-logback-encoder to 7.2 2023-01-13 12:17:39 -06:00
Chris Eager
11b62345e1 Update mockito to 4.11.0 2023-01-13 12:17:39 -06:00
Chris Eager
77289ecb51 Update micrometer to 1.10.3 2023-01-13 12:17:39 -06:00
Chris Eager
dfb0b68997 Update DynamoDBLocal to 1.20.0 2023-01-13 12:17:39 -06:00
Chris Eager
d545f60fc4 Update wiremock to 2.35.0 2023-01-13 12:17:39 -06:00
Chris Eager
5cda6e9d84 Update pushy to 0.15.2 2023-01-13 12:17:39 -06:00
Chris Eager
7caba89210 Update AWS SDK v2 to 2.19.8 2023-01-13 12:17:39 -06:00
Chris Eager
b8967b75c6 Update dropwizard to 2.0.34 2023-01-13 12:17:39 -06:00
Chris Eager
74d9849472 Update badge strings 2023-01-13 12:08:05 -06:00
Fedor Indutny
96b753cfd0 Add an extra kb to max sticker size 2023-01-13 12:07:45 -06:00
Jon Chambers
5a89e66fc0 Convert AccountIdentityResponse to a record 2023-01-13 12:36:17 -05:00
Jon Chambers
b4a143b9de Convert RegistrationLockFailure to a record 2023-01-13 12:36:02 -05:00
Jon Chambers
050035dd52 Convert ExternalServiceCredentials to a record 2023-01-13 12:36:02 -05:00
Jon Chambers
7018062606 Explicitly create registration sessions 2023-01-09 15:27:07 -05:00
Jon Chambers
9e1485de0a Assume stored verification codes will always have a session ID instead of a verification code 2023-01-09 15:27:07 -05:00
Jon Chambers
4e358b891f Retire StoredVerificationCode#twilioVerificationSid 2023-01-09 15:27:07 -05:00
Ehren Kret
4044a9df30 stop warning about lack of syntax specification during proto generation 2023-01-09 12:20:07 -06:00
Ehren Kret
5a7b675001 import cleanup on controllers package 2023-01-09 12:20:07 -06:00
Ehren Kret
3be4e4bc57 remove unused exception type 2023-01-09 12:20:07 -06:00
Chris Eager
5de51919bb Remove Subscriptions.PCI 2023-01-05 12:02:34 -06:00
Chris Eager
b02b00818b Remove Subscriptions.PCI attribute 2023-01-04 11:31:46 -06:00
Chris Eager
010f88a2ad Remove Subscriptions.C attribute 2023-01-04 11:31:46 -06:00
Jon Chambers
60edf4835f Add a pni capability to UserCapabilities 2022-12-21 16:26:07 -05:00
Jon Chambers
a60450d931 Convert UserCapabilities to a record 2022-12-21 16:26:07 -05:00
erik-signal
d138fa45df Handle edge cases of Math.abs on integers. 2022-12-20 12:25:04 -05:00
Katherine Yen
2c2c497c12 Define reregistrationIdleDays DistributionSummary with custom expiry 2022-12-20 09:21:24 -08:00
Katherine Yen
cb5d3840d9 Add paymentActivation capability 2022-12-20 09:20:42 -08:00
Fedor Indutny
9aceaa7a4d Introduce ArtController 2022-12-19 11:58:16 -08:00
Katherine Yen
636c8ba384 Add metric for distribution of account idle time at reregistration 2022-12-16 13:50:29 -08:00
Ravi Khadiwala
ac78eb1425 Update to the latest version of the abusive message filter 2022-12-16 11:28:30 -06:00
Ravi Khadiwala
65ad3fe623 Add hCaptcha support 2022-12-16 11:28:30 -06:00
Sergey Skrobotov
dcec90fc52 Update to the latest version of the abusive message filter 2022-12-13 13:30:47 -08:00
Chris Eager
24ac32e6e6 Add PayPalExperienceProfileInput.userAction 2022-12-13 10:03:58 -06:00
Katherine Yen
26f5ffdde3 Enable case-sensitive usernames 2022-12-13 07:59:37 -08:00
Jon Chambers
a883426402 Simplify account cleaner 2022-12-06 16:21:25 -06:00
Chris Eager
2f21e930e2 Add minimum one-time donation amont to validation error map 2022-12-06 16:21:15 -06:00
Chris Eager
5fb158635c Use existing WebApplicationException entity, if available 2022-12-06 16:21:15 -06:00
Chris Eager
6f844f9ebb Update to the latest version of the abusive message filter 2022-12-06 16:20:17 -06:00
Sergey Skrobotov
d88e358016 Update to the latest version of the abusive message filter 2022-12-05 10:07:40 -08:00
Sergey Skrobotov
9cf2635528 some accounts classes refactorings 2022-12-05 09:30:40 -08:00
Chris Eager
d0e7579f13 Revert transaction descriptor 2022-12-01 18:52:45 -06:00
Chris Eager
cda82b0ea0 Update kotlin + Apollo 2022-12-01 18:11:35 -06:00
Chris Eager
2ecbb18fe5 Add support for one-time PayPal donations 2022-12-01 18:11:35 -06:00
Chris Eager
d40d2389a9 Update to Maven 3.8.6 2022-12-01 18:09:38 -06:00
Chris Eager
df8fb5cab7 Move messages cache stale discard to a separate scheduler 2022-12-01 18:09:28 -06:00
katherine-signal
99ad211c01 Enforce minimum amount by currency for one time donations 2022-11-28 11:44:59 -08:00
katherine-signal
fb4ed20ff5 Remove groups v2 capability
* wip removing groups v2 capabilities

* comments

* finish removing groups v2 references

* hardcode gv1migration flag on user capability, remove other references
2022-11-21 09:31:47 -08:00
Jon Chambers
cb50b44d8f Allow the account cleaner to operate on multiple accounts in parallel 2022-11-18 11:15:00 -05:00
Jon Chambers
ae57853ec4 Simplify deletion reason reporting 2022-11-18 11:15:00 -05:00
Jon Chambers
2881c0fd7e Allow the account cleaner to act on all accounts in a crawled chunk 2022-11-18 11:15:00 -05:00
Chris Eager
483fb0968b Use badge name in level configuration for one-time donations 2022-11-18 11:05:23 -05:00
Jon Chambers
4d37418c15 Update to the latest version of the abusive message filter 2022-11-18 10:55:15 -05:00
Jon Chambers
e8ee4b50ff Retire the legacy "abusive hosts" system in favor of newer tools 2022-11-18 10:54:25 -05:00
Chris Eager
4f8aa2eee2 Mark flaky test @Disabled 2022-11-17 13:23:42 -06:00
Chris Eager
397d3cb45a Add consolidated subscription configuration API 2022-11-16 12:27:00 -06:00
Chris Eager
e883d727fb Note deprecation of localized string 2022-11-16 12:09:00 -06:00
Chris Eager
986545a140 Set error_if_incomplete for subscription payment behavior 2022-11-16 12:08:21 -06:00
Sergey Skrobotov
836307b0c7 adding a metric for ipv4/ipv6 requests count 2022-11-15 11:17:01 -08:00
Sergey Skrobotov
b5a75d3079 Update to the latest version of the abusive message filter 2022-11-15 11:16:55 -08:00
Sergey Skrobotov
c32067759c refactoring: use constants for header names 2022-11-15 11:16:49 -08:00
Chris Eager
7fb7abb593 Update to micrometer 1.10.0 2022-11-15 11:16:41 -08:00
Erik Osheim
0d50b58c60 Update to the latest version of the abusive message filter 2022-11-11 17:09:24 -05:00
Chris Eager
bdf4e24266 Update to the latest version of the abusive message filter 2022-11-11 13:54:19 -06:00
Chris Eager
f41bdf1acb Make MessagesController#getPendingMessages fully async 2022-11-11 13:19:57 -06:00
Chris Eager
77d691df59 Always use reactived message processing in WebSocketConnection 2022-11-11 13:14:39 -06:00
Chris Eager
12300761ab Update reactor-bom to 2020.0.24 2022-11-11 13:14:26 -06:00
Chris Eager
25efcbda81 Update lettuce to 6.2.1.RELEASE 2022-11-11 13:14:26 -06:00
Jon Chambers
a01f96e0e4 Temporarily disable account freezing on contention 2022-11-10 18:53:58 -05:00
erik-signal
1d1e3ba79d Add metric to track newly-locked accounts. 2022-11-10 12:55:08 -05:00
Jon Chambers
2c9c50711f Avoid reading from a stale Account after a contested reglock event 2022-11-10 12:41:50 -05:00
Jon Chambers
d3f0ab8c6d Introduce an alternative exchange rate data provider 2022-11-10 10:25:06 -05:00
erik-signal
80a3a8a43c Lock account when number owner lacks registration lock. 2022-11-09 14:03:09 -05:00
Chris Eager
e6e6eb323d Update metric name 2022-11-08 11:15:42 -06:00
Chris Eager
681a5bafb4 Update MessagesManager#getMessagesForDevice
- add `subscribeOn()`
- use `CompletableFuture` for consistency
2022-11-08 09:38:52 -06:00
Chris Eager
5bec89ecc8 Measure individual message timeouts 2022-11-08 09:37:37 -06:00
Chris Eager
69ed0edb74 Revert "Add more detailed queue processing rate metrics"
This reverts commit bbbab4b8a4.
2022-11-08 09:35:39 -06:00
Chris Eager
ad5925908e Change dispatch queues to LinkedBlockingQueues 2022-11-04 11:08:17 -05:00
Chris Eager
d186245c5c Move all receipt sending work to executor 2022-11-04 11:08:06 -05:00
Chris Eager
bbbab4b8a4 Add more detailed queue processing rate metrics 2022-11-04 11:06:38 -05:00
Chris Eager
f83080eb8d Update metric name 2022-11-03 14:50:20 -05:00
Chris Eager
e0178fa0ea Move additional handling of MessagesManager#delete to executor 2022-11-03 13:02:25 -05:00
Chris Eager
c6a79ca176 Enable metrics on messages fluxes 2022-11-03 13:02:25 -05:00
Chris Eager
6426e6cc49 Enable reactor Schedulers metrics 2022-11-03 13:02:25 -05:00
Chris Eager
b13cb098ce lettuce: set publishOnScheduler to true 2022-11-03 13:02:25 -05:00
Jon Chambers
afda5ca98f Add a test for checking push challenge tokens 2022-11-03 11:14:59 -05:00
Chris Eager
eb57d87513 Remove message listener key only after successfully unsubscribing 2022-11-03 11:09:11 -05:00
Chris Eager
fbf6b9826e tests: only call SQLite.setLibraryPath once 2022-11-03 11:08:43 -05:00
Chris Eager
a01b29a6bd set off_session=true for subscription updates 2022-11-02 14:34:26 -05:00
Chris Eager
102992b095 Set off_session=true when creating subscriptions 2022-11-02 11:30:29 -05:00
Chris Eager
bd69905f2e Remove obsolete donation endpoint 2022-11-02 11:29:03 -05:00
Chris Eager
ce5a4bd94a Update wiremock to 2.34.0 2022-11-02 11:24:54 -05:00
Chris Eager
f65a613815 Update jackson to 2.13.4 2022-11-02 11:24:54 -05:00
sergey-signal
d87c8468bd Update to the latest version of the abusive message filter (#1138) 2022-11-02 09:23:38 -07:00
Chris Eager
aa829af43b Handle expected case of empty flux in message deletion 2022-10-31 12:29:25 -05:00
Chris Eager
c10fda8363 Use reactive streams for WebSocket message queue
Initially, uses `ExperimentEnrollmentManager` to do a safe rollout.
2022-10-31 10:35:37 -05:00
Jon Chambers
4252284405 Update to the latest version of the abusive message filter 2022-10-28 10:50:49 -04:00
Jon Chambers
74d65b37a8 Discard old Twilio machinery and rely entirely on the stand-alone registration service 2022-10-28 10:40:37 -04:00
sergey-signal
78f95e4859 Update to the latest version of the abusive message filter (#1132) 2022-10-27 14:01:16 -07:00
Jon Chambers
91626dea45 Count accounts rather than devices that are stories-capable 2022-10-25 16:36:05 -04:00
sergey-signal
5868d9969a minor changes to utility classes (#1127) 2022-10-25 08:48:56 -07:00
erik-signal
90490c9c84 Clean up the TestClock code a bit more. 2022-10-21 15:27:15 -04:00
Chris Eager
8ea794baef Add additional handling for nullable field in recurring donation record 2022-10-21 12:56:39 -05:00
Chris Eager
70a6c3e8e5 Update to libsignal-server 0.21.1 2022-10-21 12:54:18 -05:00
Jon Chambers
4813803c49 Add .java-version to .gitignore 2022-10-21 12:40:11 -04:00
erik-signal
fe60cf003f Clean up testing with clocks. 2022-10-21 12:39:47 -04:00
erik-signal
0c357bc340 Add metrics tracking story capability adoption. 2022-10-20 12:25:03 -04:00
Chris Eager
b711288faa Run GitHub Action in a container 2022-10-18 16:59:35 -05:00
Jon Chambers
44a5d86641 Revert "Update to libsignal-server 0.21.0"
This reverts commit cccccb4dd6.
2022-10-18 11:44:50 -04:00
Jon Chambers
e7048aa9cf Allow the reconciliation client to trust multiple CA certificates to facilitate certificate rotation 2022-10-18 11:17:47 -04:00
Jon Chambers
0120a85c39 Allow HTTP clients to trust multiple certificates to support certificate rollover 2022-10-18 11:17:47 -04:00
Jon Chambers
a41d047f58 Retire CertificateExpirationGauge in favor of other expiration monitoring tools 2022-10-18 11:17:47 -04:00
Chris Eager
cccccb4dd6 Update to libsignal-server 0.21.0 2022-10-18 11:17:29 -04:00
Jon Chambers
0a64e31625 Check verification codes for changing phone numbers against the stand-alone registration service when possible 2022-10-18 11:17:15 -04:00
Jon Chambers
3c6c6c3706 Use the gRPC BOM instead of calling out dependencies individually 2022-10-18 11:16:56 -04:00
Jon Chambers
8088b58b3b Clarify default value for includeE164 2022-10-18 11:16:06 -04:00
erik-signal
a7d5d51fb4 Improve testing of MultiRecipientMessageProvider 2022-10-17 16:50:39 -04:00
Chris Eager
378d7987a8 device capabilities: prevent stories downgrade 2022-10-17 15:25:13 -04:00
erik-signal
3e0baf82a4 Filter unknown UUIDs for /multi_recipient&story=true. 2022-10-13 15:33:51 -04:00
Chris Eager
7a2683a06b Remove /.tx/config from .gitignore 2022-10-11 15:04:50 -05:00
erik-signal
17a3c90286 Add "urgent" query parameter to /v1/messages/multi_recipient endpoint. 2022-10-11 11:10:11 -04:00
Chris Eager
6341770768 Update SubscriptionManager to store processor+customerId in a single attribute and a map
- add `type` query parameter to `/v1/subscription/{subscriberId}/create_payment_method`
2022-10-07 14:26:17 -05:00
Jon Chambers
308437ec93 Resolve gRPC/Netty version conflicts 2022-10-06 16:23:47 -04:00
Jon Chambers
d3d4916d6c Update to the latest version of the abusive message filter 2022-10-06 15:43:37 -04:00
Jon Chambers
d2fa00f0c6 Add experiment to test standalone registration service 2022-10-06 15:42:53 -04:00
erik-signal
d6c9652a70 Fix internal server error when sending stories to unknown recipient. 2022-10-06 13:53:57 -04:00
Jon Chambers
0d20b73e76 Update to the latest version of the abusive message filter 2022-10-05 15:20:49 -04:00
Jon Chambers
3c655cdd5a Migrate to "regionCode" instead of "region" to avoid tag name conflicts 2022-10-05 15:15:46 -04:00
Jon Chambers
fc5cd3a9ca Update to protobuf-java 3.21.7 2022-10-05 15:15:34 -04:00
Jon Chambers
83ab926f96 Add a dimension for story messages 2022-10-05 15:15:22 -04:00
erik-signal
56e54e0724 Update to the latest version of the abusive message filter 2022-10-05 13:19:47 -04:00
erik-signal
544e4fb89a Adjust routing for stories. 2022-10-05 12:20:42 -04:00
erik-signal
966c3a8f47 Add routing for stories. 2022-10-05 10:44:50 -04:00
Ravi Khadiwala
c2ab72c77e Update to the latest version of the abusive message filter 2022-09-30 12:57:21 -05:00
Ravi Khadiwala
4468ee3142 Update to the latest version of the abusive message filter 2022-09-30 12:10:02 -05:00
Ravi Khadiwala
c82c2c0ba4 Add country tag to twilio failures 2022-09-30 12:03:46 -05:00
Ravi Khadiwala
6e595a0959 add an optionals utility and fix push challenge metric 2022-09-30 12:02:47 -05:00
Ravi Khadiwala
a79d709039 Return 403 when a push challenge is incorrect 2022-09-30 12:02:47 -05:00
Ravi Khadiwala
538a07542e Update to the latest version of the abusive message filter 2022-09-22 11:20:48 -05:00
Ravi Khadiwala
07ed765250 Update abusive message filter and filter account creates 2022-09-20 14:52:18 -05:00
Ravi Khadiwala
2e497b5834 Fix operator order in metric calculation 2022-09-15 14:04:18 -05:00
Ravi Khadiwala
61b3cecd17 Fix missing increment on recaptcha counter 2022-09-14 17:07:26 -05:00
Ravi Khadiwala
a4a666bb80 Add metrics for recaptcha reasons 2022-09-14 16:00:11 -05:00
Ravi Khadiwala
c14621a09f Add metrics for captcha scores 2022-09-14 16:00:11 -05:00
Ravi Khadiwala
d0a8899daf Change discriminator seperator and default width 2022-09-14 15:53:15 -05:00
Chris Eager
65dbcb3e5f Remove duplicate bom from dependencyManagement 2022-09-12 16:54:31 -05:00
Chris Eager
7f725b67c4 Update to the latest version of the abusive message filter 2022-09-12 11:24:37 -05:00
Chris Eager
e25252dc69 Remove unused exception 2022-09-12 11:19:15 -05:00
Chris Eager
8b65c11e1e Update batch check entities from two optional fields to a single field 2022-09-12 11:19:01 -05:00
Chris Eager
320c5eac53 Add support for PNIs at v1/profile/identity_check/batch 2022-09-09 10:55:34 -05:00
Ehren Kret
8199e0d2d5 Set resource field on log entry 2022-09-07 19:37:26 -05:00
Ehren Kret
53387f5a0c Register polymorphic serialization 2022-09-07 19:37:26 -05:00
Ehren Kret
7d171a79d7 Remove redundant @NotNull annotation 2022-09-07 19:37:26 -05:00
Ehren Kret
3b99bb9e78 Log remote config delete events 2022-09-07 19:37:26 -05:00
Ehren Kret
132f026c75 Improve readability of event code 2022-09-07 19:37:26 -05:00
Ehren Kret
abd0f9630c Create GCP Logging implementation of AdminEventLogger 2022-09-07 19:37:26 -05:00
Ehren Kret
a4508ec84f Add new event logging module 2022-09-07 19:37:26 -05:00
Ehren Kret
6119b6ab89 Upgrade java-uuid-generator dependency 2022-09-07 19:37:26 -05:00
Ehren Kret
307ac47ce0 Update DynamoDBLocal dependency version 2022-09-07 19:37:26 -05:00
Ravi Khadiwala
4032ddd4fd Add reserve/confirm for usernames 2022-09-07 11:49:49 -05:00
Chris Eager
98c8dc05f1 Update to the latest version of the abusive message filter 2022-09-07 11:49:01 -05:00
Chris Eager
4c677ec2da Remove deprecated /v1/attachments 2022-09-07 11:48:16 -05:00
Chris Eager
c05692e417 Update deprecated CircuitBreakerConfig usage 2022-09-07 11:47:15 -05:00
Chris Eager
1e7aa89664 Update resilience4j to 1.7.0 2022-09-07 11:47:15 -05:00
gram-signal
ae1edf3c5c Remove experiment associated with auth1->auth2 rollout. 2022-08-31 12:10:46 -06:00
gram-signal
b17f41c3e8 Check if dashes work in dynamic configuration keys. 2022-08-29 15:51:37 -06:00
gram-signal
08db4ba54b Update authentication to use HKDF_SHA256. 2022-08-29 14:20:47 -06:00
gram-signal
cb6cc39679 Ignore null identity key. 2022-08-29 13:26:49 -06:00
Jon Chambers
b6bf6c994c Remove a spurious @Nullable annotation 2022-08-26 15:22:23 -04:00
Jon Chambers
3bb4709563 Add CLDR region as a dimension 2022-08-26 12:41:51 -04:00
Jon Chambers
b280c768a4 Allow signup captchas to target CLDR two-letter region codes 2022-08-26 12:41:51 -04:00
Chris Eager
d23e89fb9c Update micrometer to 1.9.3 2022-08-25 13:46:36 -07:00
Chris Eager
3a27bd0318 Update test dependencies 2022-08-25 13:40:46 -07:00
Chris Eager
616513edaf Remove unused jdbi dependency 2022-08-25 13:40:46 -07:00
Chris Eager
09a51020e9 Update stripe-java to 21.2.0 2022-08-25 13:40:46 -07:00
Chris Eager
cb8cb94d1a Update aws java v1 SDK to 1.12.287 2022-08-25 13:40:46 -07:00
Chris Eager
2440dc0089 Update netty to 4.1.79.Final 2022-08-25 13:40:46 -07:00
Chris Eager
2336eef333 Update aws java v2 SDK to 2.17.258 2022-08-25 13:40:46 -07:00
Chris Eager
a0e948627c Update jackson to 2.13.3 2022-08-25 13:40:46 -07:00
Chris Eager
88159af588 Update dropwizard to 2.0.32 2022-08-25 13:40:46 -07:00
Chris Eager
38b77bb550 Update libphonenumber to 8.12.54 2022-08-25 13:40:32 -07:00
Jon Chambers
e72d1d0b6f Stop reading attribute-based messages from the messages table 2022-08-22 13:37:39 -07:00
Ravi Khadiwala
1891622e69 Zero-pad discriminators less than initial width 2022-08-22 13:36:38 -07:00
Chris Eager
628a112b38 Include country code for verify failure 2022-08-19 12:21:05 -07:00
Jon Chambers
50f5d760c9 Use existing tagging tools for keepalive counters 2022-08-16 13:16:19 -07:00
Jon Chambers
7292a88ea3 Record table performance metrics around reported messages 2022-08-16 13:15:30 -07:00
Jon Chambers
07cb3ab576 Add a "sealed sender" dimension to the sent message counter 2022-08-16 13:11:12 -07:00
Chris Eager
27b749abbd Filter expired items from Dynamo 2022-08-16 13:09:47 -07:00
Chris Eager
27f67a077c Add metrics for report-verification-succeeded response 2022-08-16 13:08:16 -07:00
Ravi Khadiwala
393e15815b Rename secondary account key namespace for usernames 2022-08-15 10:51:52 -05:00
Ravi Khadiwala
a7f1cd25b9 Remove UAK normalization code
All accounts now have UAKs in top-level attributes
2022-08-15 10:47:52 -05:00
Ravi Khadiwala
953cd2ae0c Revert "Delete any leftover usernames in the accounts db"
This reverts commit a44c18e9b7.

Old username cleanup is finished.
2022-08-15 10:45:38 -05:00
ravi-signal
a84a7dbc3d Add support for generating discriminators
- adds `PUT accounts/username` endpoint
- adds `GET accounts/username/{username}` to lookup aci by username
- deletes `PUT accounts/username/{username}`, `GET profile/username/{username}`
- adds randomized discriminator generation
2022-08-15 10:44:36 -05:00
Chris Eager
24d01f1ab2 Revert "device capabilities: prevent stories downgrade"
This reverts commit 1c67233eb0.
2022-08-12 14:21:27 -05:00
Chris Eager
06eb890761 Improve e164 normalization check by re-parsing without country code 2022-08-12 10:52:55 -07:00
Chris Eager
6d0345d327 Clean up Util 2022-08-12 10:52:55 -07:00
Chris Eager
1c67233eb0 device capabilities: prevent stories downgrade 2022-08-12 10:51:16 -07:00
Jon Chambers
b4281c5a70 Send non-urgent push notifications with lower priority 2022-08-12 11:06:31 -04:00
Jon Chambers
5f6b66dad6 Add support for scheduling background push notifications 2022-08-12 10:57:59 -04:00
Jon Chambers
c2be0af9d9 Refactor ApnPushNotificationSchedulerTest to use a Clock 2022-08-12 10:57:59 -04:00
Jon Chambers
c111e9a35a Update to the latest version of the abusive message filter 2022-08-12 10:50:53 -04:00
Jon Chambers
a53a85d788 Refactor scheduled APNs notifications in preparation for future development 2022-08-12 10:47:49 -04:00
Ravi Khadiwala
a44c18e9b7 Delete any leftover usernames in the accounts db
The account username field should not currently be populated
2022-08-11 16:23:51 -05:00
Jon Chambers
4d78437fe4 Add a country code dimension to the non-normalized number counter 2022-08-10 15:03:01 -04:00
Jon Chambers
2bfe2c8ff8 Add an "urgent" dimension to the "sent messages" counter 2022-08-10 15:00:46 -04:00
Chris Eager
65da844d70 Small test cleanup 2022-08-09 15:32:44 -05:00
Chris Eager
5275c27ee1 Fix incorrect test Javadoc 2022-08-09 13:06:15 -07:00
Chris Eager
390580a19d Count cases when the a message’s destination UUID doesn’t match the account’s PNI 2022-08-09 13:06:15 -07:00
Jon Chambers
147917454f Measure the depth of the queue for the FCM executor 2022-08-04 15:53:26 -04:00
Jon Chambers
39562775d9 Use a fixed-size thread pool for sending FCM notifications 2022-08-04 15:37:22 -04:00
Jon Chambers
4a0ef1f834 Measure the time taken to send APNs push notifications 2022-08-04 10:43:07 -04:00
Jon Chambers
85b16b674d Measure the time taken to send FCM push notifications 2022-08-04 10:43:07 -04:00
Jon Chambers
ab5d8ba120 Use ApiFutures#addCallback for FCM futures 2022-08-04 10:43:07 -04:00
Jon Chambers
28076335e0 Generate APNs payloads using a payload builder 2022-08-04 10:37:30 -04:00
Jon Chambers
9e9333424f Retire RetryingApnsClient 2022-08-04 09:59:18 -04:00
Jon Chambers
6f0faae4ce Introduce common push notification interfaces/pathways 2022-08-03 10:07:53 -04:00
Jon Chambers
0d24828539 Drop the gcm-sender-async module 2022-08-02 17:31:35 -04:00
Jon Chambers
0a6d724f2c Remove GCMSender 2022-08-02 17:31:35 -04:00
Jon Chambers
8956e1e0cf Check for null FCM error codes 2022-08-02 17:29:31 -04:00
Jon Chambers
c9ae991aa3 Add an experiment to allow a phased transition from the old GCM API to the current FCM API 2022-08-02 15:34:09 -04:00
Jon Chambers
421d594507 Introduce an FcmSender 2022-08-02 15:34:09 -04:00
Jon Chambers
9c03f2e468 Add support for receiving, storing, and returning urgent flags on messages 2022-08-02 12:05:23 -04:00
Chris Eager
1175ff5867 Log cause when queue processing hits max retries 2022-08-02 08:36:16 -07:00
Chris Eager
55df593561 Clean up MessageAvailabilityListener if the websocket client is closed 2022-08-02 08:35:16 -07:00
Chris Eager
a06a663b94 Use the envelope’s destination UUID for receipt source UUID 2022-08-02 08:34:20 -07:00
Chris Eager
3d2f7e731f Remove Envelope.source 2022-08-02 08:34:20 -07:00
Chris Eager
2575372639 Add missing increment() to displacement counter 2022-08-02 08:30:54 -07:00
Jon Chambers
faa6e8324a Fix a test issue where we were ignoring some test parameters 2022-08-01 11:02:33 -04:00
Jon Chambers
d0e3fb1901 Unconditionally write messages to the messages table as envelopes 2022-08-01 10:44:27 -04:00
Jon Chambers
04287c5073 Optionally write messages as envelopes to the messages table 2022-07-29 11:10:50 -04:00
Jon Chambers
0c76fdd36c Read bare envelopes from the messages table if possible 2022-07-29 11:10:50 -04:00
Jon Chambers
d582942244 Update to the latest version of the abusive message filter 2022-07-29 10:59:02 -04:00
Jon Chambers
3636626e09 Make Envelope the main unit of currency when working with stored messages 2022-07-29 10:59:02 -04:00
Jon Chambers
3e0919106d Add a method to build an OutgoingMessageEntity from an Envelope 2022-07-29 10:59:02 -04:00
Jon Chambers
d385838dc1 Add methods to convert IncomingMessage/OutgoingMessageEntity instances into Envelope entities 2022-07-29 10:59:02 -04:00
Jon Chambers
e28f1e8ceb Remove the unused destination property from IncomingMessage 2022-07-29 10:59:02 -04:00
Jon Chambers
3d875f1ce5 Convert incoming/outgoing message entities to records 2022-07-29 10:59:02 -04:00
Jon Chambers
c4c5397b44 Convert group credential redemption time to a long 2022-07-28 10:08:25 -04:00
Jon Chambers
6b6f9b2405 Add PNI to GroupCredentials responses that contain AuthCredentialWithPni instances 2022-07-28 10:08:25 -04:00
Jon Chambers
7d4a8d03a4 Mark old group credential getter as @Deprecated 2022-07-28 10:08:25 -04:00
Jon Chambers
e9119da040 Retire the (unused!) binary message format 2022-07-28 09:59:00 -04:00
Chris Eager
aa36dc95ef Add MicrometerCommandLatencyRecorder to Redis clusters 2022-07-27 14:23:14 -07:00
Chris Eager
a6f9409a39 Remove dynamic configuration feature flags; add DynamicMessagePersisterConfiguration 2022-07-27 14:19:10 -07:00
Chris Eager
41a113e22c Stop queue persistence attempt if items aren’t removed from cache 2022-07-27 14:19:10 -07:00
Chris Eager
4cfcdb0c96 editorconfig formatting 2022-07-27 14:19:10 -07:00
Ravi Khadiwala
36050f580e Handle duplicate device ids more gracefully 2022-07-27 11:15:32 -05:00
Jon Chambers
98760b631b Don't wrap "change number" arguments in Optional 2022-07-27 10:55:37 -04:00
Jon Chambers
d00aa1e77a Fix an inconsistent check for the presence/absence of "change number" arguments 2022-07-27 10:55:37 -04:00
Jon Chambers
dce391a248 Add support for setting PNI-associated registration IDs and identity keys when changing numbers 2022-07-26 15:19:27 -04:00
Antonin Tritz
c252118cfc External links in comments from http to https 2022-07-26 15:17:58 -04:00
Chris Eager
e9fd32de79 Only attempt to unsubscribe if the queue name is not null 2022-07-26 10:20:10 -07:00
Chris Eager
788246a56f Update Lettuce to 6.1.9 2022-07-26 09:22:13 -07:00
Chris Eager
bc02fe3831 Only unsubscribe from keyspace notifications if the node has the slot 2022-07-26 09:21:50 -07:00
Jon Chambers
d290aad27b Associate source/destination country codes for reported messages 2022-07-14 09:31:12 -04:00
Jon Chambers
6754ec5e10 Pass disconnection reason (if known) to clients 2022-07-13 15:30:03 -04:00
Jon Chambers
1ba00a66eb Pass the reason for displacement to presence displacement listeners 2022-07-13 15:30:03 -04:00
Jon Chambers
1dd7d33e23 Simplify Device entity 2022-07-13 13:55:20 -04:00
Jon Chambers
e200548e35 Introduce an account change validator 2022-07-13 12:24:39 -04:00
Jon Chambers
fdf7b69996 Remove a temporary workaround for incorrect envelope types from iOS clients 2022-07-13 11:30:52 -04:00
Jon Chambers
92d36b725f Allow presence keys to expire if not periodically renewed 2022-07-13 11:28:55 -04:00
Jon Chambers
4e131858ca Generalize scope of and expand size of websocket scheduled executor service 2022-07-13 11:28:55 -04:00
Ravi Khadiwala
a45d95905e Be permissive in account-create accept-language
Currently, if we fail to parse a user's accept-language in account
creation, creation will fail. While it's a suboptimal experience to get
a verify code in the wrong language, it might be better than not being
able to sign up at all.
2022-07-13 11:22:31 -04:00
Ehren Kret
0fdfdabf2a merge GroupController into CertificateController 2022-07-01 13:04:24 -05:00
Jon Chambers
a25e967978 Remove spurious mocking 2022-07-01 12:26:22 -05:00
Jon Chambers
38e30c7513 Allow callers to get an expiring profile key credential 2022-07-01 12:26:22 -05:00
Jon Chambers
e38e5fa17d Allow callers to request a combined group auth credential 2022-07-01 12:26:22 -05:00
Jon Chambers
c1f9bedf2f Update to libsignal-server 0.18 2022-06-23 14:25:28 -04:00
Jon Chambers
dd5d0ea2b3 Update to the latest version of the abusive message filter 2022-06-23 08:37:27 -04:00
Ehren Kret
42fd29d38b Update badge description text to remove boost & sustainer language 2022-06-21 13:36:12 -05:00
Ehren Kret
bf6d3aa324 Fix batching issue that was causing duplication in large queries 2022-06-20 12:41:10 -05:00
Ehren Kret
023ccc6563 Add a unit test for the batch identity check endpoint 2022-06-20 12:32:31 -05:00
Ehren Kret
da49db5b9e Move batch identity checks off the common fork join pool 2022-06-20 11:07:33 -05:00
Ehren Kret
cc8dda28cc Simplify logic for batching bulk identity check request 2022-06-20 10:28:20 -05:00
Ehren Kret
47300c1d44 Upgrade vavr to 0.10.4 from 0.10.2 2022-06-20 10:27:50 -05:00
Ehren Kret
d31550d444 Only wrap Base64 decode with the exception catch 2022-06-20 09:42:33 -05:00
Ehren Kret
51f37350eb Because one shouldn't take the size of null things 2022-06-17 15:03:25 -05:00
Ehren Kret
ecfa161da8 Validate the request 2022-06-17 14:32:38 -05:00
Ehren Kret
e3778c17ea Use POST not GET for request requiring body 2022-06-17 14:31:15 -05:00
Ehren Kret
cbc95415b7 Add endpoint to batch check identity keys 2022-06-17 12:20:30 -05:00
Ehren Kret
776c0aa488 Don't use inner class imports 2022-06-17 11:21:30 -05:00
Ravi Khadiwala
327eb0219d Bypass account registration captcha on test devices 2022-06-13 15:01:46 -07:00
Jon Chambers
8507b6a1f0 Update to libphonenumber 8.12.50 2022-06-13 14:46:57 -07:00
gram-signal
a853748303 Revert "Update ChangeNumber to allow reset of registration IDs."
This reverts commit 7001ad1445.
2022-06-09 11:51:50 -06:00
Jon Chambers
192e884e4a Update to embedded-redis 0.8.3 2022-06-03 09:31:32 -04:00
gram-signal
7001ad1445 Update ChangeNumber to allow reset of registration IDs. 2022-06-02 16:37:32 -06:00
Ravi Khadiwala
5cfb133f79 Use redis for abusive hosts autoblock
Also delete postgres dependencies that we no longer need
2022-05-31 10:08:10 -05:00
Ehren Kret
5df24edebf Remove all unused imports remaining in project 2022-05-25 17:15:20 -05:00
Ehren Kret
95d0293a96 Remove unused counter 2022-05-25 16:57:01 -05:00
Ehren Kret
f5a2efb57c Clarify hidden static method call 2022-05-25 16:51:12 -05:00
Ehren Kret
e4b9ae4eee Clear up warnings 2022-05-25 16:50:58 -05:00
Ehren Kret
bc1ac5a37f Remove unused fields 2022-05-25 16:50:43 -05:00
Ehren Kret
96ac56faac Remove unused import 2022-05-25 16:38:11 -05:00
Ehren Kret
f0bc444388 Remove unused timer 2022-05-25 16:37:51 -05:00
Ravi Khadiwala
8584f47d95 Add more metrics to createAccount captcha 2022-05-25 15:50:11 -04:00
Jon Chambers
f6235b8c08 Check for newly-expired accounts before previously-expired accounts for metric accuracy 2022-05-25 15:49:54 -04:00
Jon Chambers
d452e90470 Move AccountCleaner into its own crawler 2022-05-25 15:46:18 -04:00
Jon Chambers
418a869451 Increase max deletions per chunk to 256 2022-05-25 14:19:04 -04:00
Jon Chambers
cf89e2215c Fully delete already-expired accounts 2022-05-25 14:19:04 -04:00
Jon Chambers
a4ca1ef1a8 Move AccountCleanerTest out of the test package; reduce visibility of test-only fields 2022-05-25 14:19:04 -04:00
Ehren Kret
c38572307d Remove unused imports 2022-05-20 10:20:45 -07:00
Fedor Indutny
20902df122 Fix ZRANGE arguments in get_items.lua 2022-05-19 12:14:02 -07:00
Fedor Indutny
d31ddb72f3 Optimize message deletion by skipping lookup 2022-05-18 13:02:21 -07:00
Ehren Kret
d5f2d86bd2 Add Maven Wrapper 2022-05-13 14:14:37 -05:00
Jon Chambers
2ce8bcd565 Update to the latest version of the abusive message filter 2022-05-11 14:27:32 -04:00
Jon Chambers
75c92eaa93 Drop high-cardinality distribution summaries 2022-05-11 14:26:38 -04:00
Chris Eager
0445adcac3 Conclude ReportMessageManager ACI migration 2022-05-11 11:26:24 -07:00
Sgn-32
c45ff61954 Update libphonenumber to 8.12.48 2022-05-11 14:17:13 -04:00
gram-signal
06dd4c5026 Derive username from ACI for CDS{H,I} (#989)
* Derive username from ACI for CDS{H,I}

* Update sample YAML.
2022-05-02 08:41:38 -06:00
Ehren Kret
058caadf4f Use BigDecimal#compareTo for numeric equality testing 2022-04-29 14:20:09 -05:00
Ehren Kret
7b7d309105 Apply stripe conversion factor to gift badge amount check 2022-04-29 13:48:10 -05:00
Ehren Kret
63be7b93ce Record level on boost payment intent 2022-04-29 12:06:15 -05:00
Ehren Kret
578ea12b59 Add gift badges to user capabilities 2022-04-29 11:08:36 -05:00
Ehren Kret
364e59be57 Add shape to duration to ensure it's render as an integer 2022-04-29 10:14:39 -05:00
Ehren Kret
fece4dac9e Add duration to boost badges
Lets clients know how long the badge will last for after purchase.
2022-04-29 10:14:39 -05:00
Ehren Kret
ce85c1aabc Add name and description for gift badge 2022-04-29 10:14:39 -05:00
Ehren Kret
0ac2ce5e72 Add gift badge to the level output for boost badges 2022-04-29 10:14:39 -05:00
Ehren Kret
391c800bf5 Add gift configuration to subscription controller 2022-04-29 10:14:39 -05:00
Ehren Kret
9c27b58194 Update sample configuration file 2022-04-29 10:14:39 -05:00
Ehren Kret
f6471cf8f9 Add gift configuration source 2022-04-29 10:14:39 -05:00
Ehren Kret
f21e9bcc4d Upgrade jackson dependencies 2022-04-29 10:14:39 -05:00
Jon Chambers
1eaff753a6 Count "forbidden identity key change" events 2022-04-22 15:53:43 -04:00
JanLukasGithub
9b3a8897cd Change copyright to 2022 2022-04-22 14:20:34 -04:00
Jon Chambers
40f8cddfb2 Update to libsignal-server 0.16.0 2022-04-20 16:00:12 -04:00
Ehren Kret
c29d5de1eb Refactor two more switch statements to new switch style 2022-04-19 13:39:38 -05:00
Ehren Kret
d94c171d63 Use new style switch statement 2022-04-19 13:35:26 -05:00
Ehren Kret
2717967d61 Revert submodule change from 473ecbdf2d 2022-04-19 10:53:58 -05:00
Ehren Kret
53203dbcef Refactor common pattern for checking Account capabilities 2022-04-19 10:33:54 -05:00
Ehren Kret
9e66f8ac11 Add gift badges device capability 2022-04-19 10:33:54 -05:00
Ehren Kret
796fb3b4cd Refactor Device#equals method 2022-04-19 10:33:54 -05:00
gram-signal
473ecbdf2d Allow primary to set and provide new signed prekeys for linked devices (#950) 2022-04-15 12:39:47 -06:00
Chris Eager
7b3703506b Remove number from ReportMessageManager#store 2022-04-13 16:41:32 -04:00
Jon Chambers
5816f76bbe Add support for getting (limited) profiles by phone number identifier 2022-04-13 16:27:57 -04:00
Ehren Kret
355996bafc Add outcome type to subscription information endpoint 2022-04-08 12:31:31 -05:00
Ehren Kret
c2bb46f41d Add outcome network status and outcome reason to subscription info 2022-04-08 12:31:31 -05:00
Ehren Kret
12f76c24b1 Add failure information for the latest charge on a subscription 2022-04-08 12:31:31 -05:00
amit
4b8ebc9a17 Revert "newlines for a test."
I had expected this to be rejected -- whoops!

This reverts commit 42a109e593.
2022-03-29 12:47:34 -07:00
amit
42a109e593 newlines for a test. 2022-03-29 11:29:43 -07:00
Jon Chambers
8064e68873 Update libsignal-server to 0.15.1 to resolve a glibc version conflict 2022-03-29 12:24:45 -04:00
Jon Chambers
3dc0d0bb92 Revert "Revert "Replace curve25519-java with libsignal-server""
This reverts commit c06a5ac96c.
2022-03-29 12:24:45 -04:00
Jon Chambers
2bb8f92af1 Revert "Revert "Replace zkgroup with libsignal-server""
This reverts commit fa3a9570d6.
2022-03-29 12:24:45 -04:00
Jon Chambers
5b7d5d2b93 Reduce "unrecoverable error" messages when sending GCM notifications from WARN to DEBUG 2022-03-25 14:34:44 -07:00
Jon Chambers
2b27db18d8 Count GCM failures by error code 2022-03-25 14:34:44 -07:00
Jon Chambers
f3c811cc03 Move "no local subscription" warnings from WARN to DEBUG 2022-03-25 14:34:44 -07:00
Chris Eager
df415208a4 Update to the latest version of the abusive message filter 2022-03-25 14:32:55 -07:00
Chris Eager
77fd01bd9f Accept source ACI at /v1/messages/report 2022-03-25 14:27:09 -07:00
Jon Chambers
fa3a9570d6 Revert "Replace zkgroup with libsignal-server"
This reverts commit 86a09b16ff.
2022-03-24 12:11:46 -04:00
Jon Chambers
c06a5ac96c Revert "Replace curve25519-java with libsignal-server"
This reverts commit 06a57ef811.
2022-03-24 12:11:46 -04:00
Jon Chambers
33467b42da Remove a deprecated/unused field 2022-03-24 10:50:49 -04:00
Jon Chambers
13fb641113 Make field name casing consistent 2022-03-24 10:50:49 -04:00
Jon Chambers
53f17c2baa Drop the legacy message and relay fields from message entities 2022-03-24 10:50:49 -04:00
Jordan Rose
06a57ef811 Replace curve25519-java with libsignal-server
These APIs stemmed from a common source long ago, so there's not much
to change!
2022-03-24 10:50:18 -04:00
Jordan Rose
86a09b16ff Replace zkgroup with libsignal-server 2022-03-24 10:50:18 -04:00
Ravi Khadiwala
c70d7535b9 Make TURN configuration dynamic
Also enables conditionally including more TURN servers for gradual
rollouts
2022-03-23 14:38:02 -05:00
Ravi Khadiwala
8541360bf3 Update to the latest version of the abusive message filter 2022-03-23 14:32:55 -05:00
Jon Chambers
2a832d36d7 Remove AcceptNumericOnlineFlagRequestFilter 2022-03-23 14:31:01 -05:00
gram-signal
1578c89475 Only allow primary device to update identity key. 2022-03-22 14:39:04 -06:00
Chris Eager
5c13e54149 Various dependency updates
- protobuf-java to 3.19.4
- libphonenumber to 8.12.45
- logstash-logback-encoder to 7.0.1
- mockito to 4.3.1
2022-03-22 09:58:08 -04:00
Jon Chambers
8e74cf6633 Update to the latest version of the abusive message filter 2022-03-22 09:56:27 -04:00
Jon Chambers
bab6b36e4d Count reported messages by destination country code in addition to source country code 2022-03-22 09:56:27 -04:00
Jon Chambers
f75e616397 Introduce a listener pattern for reported messages 2022-03-22 09:56:27 -04:00
Jon Chambers
941a9c3b39 Update to the latest version of the abusive message filter 2022-03-17 16:08:20 -04:00
Jon Chambers
7ba0f604e6 Tag the push challenge request counter by country 2022-03-17 16:07:48 -04:00
Chris Eager
cf8a4cc939 Decrease receipt sender executor thread pool 2022-03-17 13:07:03 -07:00
Jon Chambers
ee78daeeef Update to the latest version of the abusive message filter 2022-03-16 15:30:43 -04:00
Jon Chambers
2f6b0b1a55 Tag push challenge attempt metrics by country code 2022-03-16 15:00:04 -04:00
Jon Chambers
c048074c31 Tag captcha attempt metrics with UA platform 2022-03-16 15:00:04 -04:00
Ravi Khadiwala
5ca89709e3 Update to the latest version of the abusive message filter 2022-03-14 14:15:51 -05:00
Ravi Khadiwala
5a88ff0811 Use the async dynamo client to batch uak updates 2022-03-14 14:02:16 -05:00
Chris Eager
de68c251f8 Instrument the receipt sender executor 2022-03-11 17:20:52 -08:00
Chris Eager
7c9ae3561d Send delivery receipts asynchronously 2022-03-11 16:34:22 -08:00
Chris Eager
b608ece57e Remove supportsAnnouncementGroups metric 2022-03-11 16:18:00 -08:00
Chris Eager
8dfffebaf1 Remove unnecessary check for destination UUID 2022-03-11 16:17:54 -08:00
Jon Chambers
109a3bb2b9 Update to Pushy 0.15.1 2022-03-10 11:08:53 -05:00
Chris Eager
fef37f739b Remove unused classes and methods 2022-03-10 11:08:38 -05:00
Jon Chambers
7a5615182a Update to the latest version of the abusive message filter 2022-03-10 11:06:04 -05:00
Ravi Khadiwala
02a7003ffe Update to the latest version of the abusive message filter 2022-03-09 14:14:47 -06:00
Ravi Khadiwala
1571f14815 Add a feature flag to disable account normalization 2022-03-09 14:03:21 -06:00
Ravi Khadiwala
9cb098ad8a Add a top-level uak to existing items
Items wirtten before we started storing the uak at
the top level only store the uak in the
account blob. The will be updated on account
crawl
2022-03-09 14:03:21 -06:00
Jon Chambers
6283f5952d Update to the latest version of the abusive message filter 2022-03-08 10:03:38 -05:00
Jon Chambers
9b9edbae0e Drop DeadLetterHandler (which is functionally unused) 2022-03-08 10:03:06 -05:00
Chris Eager
491155d1cf Remove @Consumes from GET/DELETE in /v1/subscriptions 2022-03-04 15:36:02 -08:00
Chris Eager
54207254f1 Remove unused configuration 2022-03-04 11:44:17 -08:00
Chris Eager
1395dcc0be Make the enterprise client canonical 2022-03-04 11:44:17 -08:00
Chris Eager
2a68d9095d Remove transitional and legacy client 2022-03-04 11:44:17 -08:00
Chris Eager
a984b3640e Further refine score distribution summary 2022-03-04 11:44:17 -08:00
Jon Chambers
f9c1e411aa Remove netty-tcnative as a dependency 2022-03-04 10:45:18 -05:00
Jon Chambers
f6cbc32ee7 Align "link device" and "fetch pre-keys" rate limits 2022-03-04 10:45:06 -05:00
Chris Eager
602614acf6 Refine assessment metrics 2022-03-03 16:09:49 -08:00
Chris Eager
3854b7d472 Remove @Consumes from GET /v1/config 2022-03-03 16:08:55 -08:00
Jon Chambers
5e25481088 Fix a trivial typo in a constant's name 2022-03-03 10:59:19 -05:00
Jon Chambers
fe86e15d80 Remove PNI repair code 2022-03-03 10:25:11 -05:00
Jon Chambers
179b4a69eb Clear one-time pre-keys when PNIs are assigned 2022-03-03 10:24:54 -05:00
Chris Eager
eee6307789 Move score floor to dynamic configuration, add distribution summary 2022-03-02 15:18:33 -08:00
Chris Eager
9fc5002619 Add stories capability 2022-03-02 15:16:21 -08:00
Ravi Khadiwala
faa6ae284a Add uak as a top level attribute 2022-03-02 10:41:09 -06:00
Jon Chambers
8b4355b21d Add a "challenge issued" counter tagged by country and platform 2022-03-02 10:40:47 -06:00
Jon Chambers
e8835da740 Tag captcha success rate counter by country 2022-03-02 10:40:47 -06:00
Dimitris Apostolou
75854e104e Fix typo
Co-authored-by: Chris Eager <79161849+eager-signal@users.noreply.github.com>
2022-03-01 14:14:52 -08:00
Dimitris Apostolou
93d06e3f4d Fix typos 2022-03-01 14:14:52 -08:00
Chris Eager
c560b9229c Update to the latest version of the abusive message filter 2022-03-01 10:43:12 -08:00
Chris Eager
935e268dec Parameterize sitekey 2022-03-01 10:40:42 -08:00
Jon Chambers
3a1c716c73 Remove an unused rate limiter 2022-02-25 13:50:17 -08:00
Chris Eager
f3457502a6 Support different v2 captcha actions 2022-02-25 13:49:47 -08:00
Ravi Khadiwala
7ded802df4 Update to the latest version of the abusive message filter 2022-02-24 17:30:02 -06:00
Ravi Khadiwala
d3cd1d1b15 Use GetLatestConfiguration in config manager
Use StartConfigurationSession/GetLatestConfiguration instead of
GetConfiguration since the latter has been deprecated
2022-02-23 15:36:33 -06:00
Ravi Khadiwala
f5a75c6319 Simplify RateLimitExceeded with no retry-duration
- Avoid passing negative durations in error cases
- Drop unused message
- Return a duration for a bad forwarded-for
2022-02-23 15:25:24 -06:00
Ravi Khadiwala
ae3a5c5f5e Return a Retry-After on rate-limited responses
Previously, only endpoints throwing a RetryLaterException would include
a Retry-After header in the 413 response. Now, by default, all
RateLimitExceededExceptions will be marshalled into a 413 with a
Retry-After included if possible.
2022-02-23 15:25:24 -06:00
Jon Chambers
43792e2426 Update to the latest version of the abusive message filter 2022-02-22 11:03:41 -05:00
Jon Chambers
551d639951 Update Postgres driver to 42.3.3 2022-02-18 10:41:29 -05:00
Jordan Rose
c367a71223 APNS: include a collapse-id for non-VOIP notifications
This has two benefits:

- The APNS server should only send an iOS client a single push
  notification for any missed messages while the device is offline
  (server-side coalescing). Note that the client can still turn that
  into multiple "user notifications" as it pulls from its queue.

- If multiple notifications get delivered but iOS is unable to process
  them (say, because the phone just restarted and hasn't been unlocked
  yet), the user should only get one "You may have received messages"
  notification (client-side coalescing).
2022-02-18 10:41:10 -05:00
Chris Eager
d259ef0348 Update rate limit exceeded counter 2022-02-17 13:23:48 -08:00
Chris Eager
288cbf4a80 Clean up null-ability of incoming message entity fields 2022-02-17 13:23:48 -08:00
Chris Eager
ba5e5a780f Throw an exception instead of using Optional<Resposne> 2022-02-17 13:23:48 -08:00
Chris Eager
73fa3c3fe4 Add test for content length validation 2022-02-17 13:23:48 -08:00
Chris Eager
579eb85175 Reject invalid envelope types 2022-02-17 13:23:48 -08:00
Chris Eager
b2b20072ae Add MicrometerRegistryManager 2022-02-17 13:18:30 -08:00
Chris Eager
a2c4d3fe95 Use a strong reference to the application shutdown gauge 2022-02-17 13:18:30 -08:00
Ravi Khadiwala
31e2be2e4d Fixup invalid accept-language counter
- Fix name
- Add platform/version tags to the counter
2022-02-10 11:57:25 -06:00
Ravi Khadiwala
9f5d97e1c6 Silence noisy warnings for invalid Accept-Language 2022-02-10 11:57:25 -06:00
Ravi Khadiwala
baaae6cd9f Add @NotNull to controller args where appropriate
Notably, `@Valid` doesn't imply `@NotNull`
2022-02-10 11:57:04 -06:00
Chris Eager
ed398aa7b9 Add DeviceCapabilities.pni 2022-02-09 15:56:01 -08:00
Chris Eager
6e2ae42dab Add platform to metrics 2022-02-09 15:55:07 -08:00
Chris Eager
7f832ad783 Update to AWS SDK 2.17.125, 1.12.154 2022-02-07 16:46:25 -08:00
Chris Eager
2ce6f8cb6c Update to Dropwizard 2.0.28 2022-02-07 16:46:18 -08:00
Chris Eager
2574125199 Update libphonenumber to 8.12.42 2022-02-07 16:45:48 -08:00
Jordan Rose
41bf2b2c42 Add a binary format for incoming messages
The existing, general incoming message endpoint accepts messages as
JSON strings containing base64 data, along with all the metadata as
other JSON keys. That's not very efficient, and we don't make use of
that full generality anyway. This commit introduces a new binary
format that supports everything we're using from the old format (with
the help of some query parameters like multi-recipient messages).
2022-02-07 16:05:03 -08:00
Jon Chambers
51bac394ec Update to the latest version of the abusive message filter 2022-02-02 17:32:45 -05:00
Jon Chambers
b696649c9d Update to the latest version of the abusive message filter 2022-01-31 16:24:09 -05:00
Chris Eager
b4828ad8de Update embedded-redis to 0.8.2 2022-01-31 13:01:52 -08:00
Chris Eager
639d634426 Restore displaced UUID from deleted accounts table when present 2022-01-31 13:01:36 -08:00
Chris Eager
5358fc4f43 Use setRegistrationLockFromAttributes 2022-01-31 12:56:59 -08:00
Jon Chambers
6a654ab90b Update to the latest version of the abusive message filter 2022-01-28 11:26:40 -05:00
Jon Chambers
99eda80a78 Measure rate limit challenge responses by platform 2022-01-27 16:05:52 -05:00
Chris Eager
a6182acc9c Require any transitive dependencies on log4j to use 2.17.1 2022-01-27 10:25:13 -08:00
Jon Chambers
2241e4d8ea Update to the latest version of the abusive message filter 2022-01-26 16:30:45 -05:00
Jon Chambers
cbbdea1ba4 Impose more stringent requirements on cardinality checks 2022-01-26 16:13:12 -05:00
Jon Chambers
05e7c98620 Return an AccountIdentityResponse when changing phone numbers 2022-01-26 14:44:53 -05:00
Jon Chambers
1f1d618dea Rename AccountCreationResult to AccountIdentityResponse (since it's not just for account creation any more) 2022-01-26 14:44:53 -05:00
Jon Chambers
b18117ef89 Add tests for unidentified access when requesting profiles 2022-01-25 14:07:44 -05:00
Jon Chambers
44cb796574 Add more detail to "could not get acceptable languages" logging 2022-01-25 12:21:35 -05:00
Jon Chambers
ccf60ffc4b Update to the latest version of the abusive message filter 2022-01-25 10:39:50 -05:00
Jon Chambers
f69db11f42 Drop some unused dynamic configuration properties 2022-01-21 18:46:37 -05:00
Chris Eager
96a680dcf0 Remove displaced account from directory when changing numbers 2022-01-21 18:46:11 -05:00
Jon Chambers
c8367c9b7a Update to the latest version of the abusive message filter 2022-01-14 14:48:08 -05:00
Jon Chambers
c612663490 Handle null AccountAttributes when verifying linked devices 2022-01-14 14:47:46 -05:00
Jon Chambers
de5d967d18 Track metrics for dynamic config load failures 2022-01-14 14:47:12 -05:00
Jon Chambers
7fc63f7847 Allow callers to specify one or more dynamic config classes 2022-01-14 14:45:29 -05:00
Ehren Kret
49009cbcad Apply GitHub stale application 2022-01-07 11:37:52 -06:00
Chris Eager
b5fbeffb86 Remove obsolete deployment files 2022-01-06 12:52:37 -08:00
Jordan Rose
146655e997 Add a "sameAvatar" flag to CreateProfileRequest
If sameAvatar is set (and "avatar" is also set), the avatar field for
a profile will be copied from the existing profile. This saves S3
churn and client bandwidth.
2022-01-04 13:57:41 -08:00
Chris Eager
87d66f04d8 Update to the latest abusive message filter 2022-01-03 15:16:29 -08:00
Chris Eager
bb27dd0c3b Migrate from Object[] parameters to Stream<Arguments> 2022-01-03 15:10:02 -08:00
Chris Eager
f45a1c232f Exclude junit from transitive dependencies 2022-01-03 15:10:02 -08:00
Chris Eager
d7a3c12bbe Drop unused DynamoDB class rules 2022-01-03 15:10:02 -08:00
Chris Eager
a1e84f5a88 Migrate service tests to JUnit 5 2022-01-03 15:10:02 -08:00
Chris Eager
b758737907 Migrate remaining JUnit 4 Redis cluster tests to RedisClusterExtension
* Increase redis cluster initialization wait to 10 seconds
* Move to JUnit 5 `Assumptions`
2022-01-03 14:59:39 -08:00
Chris Eager
c488c14d25 Migrate gcm-sender-async tests to JUnit 5 2021-12-21 09:39:02 -08:00
Chris Eager
5e0cca0702 Migrate redis-dispatch to JUnit 5 2021-12-21 09:39:02 -08:00
Chris Eager
8559e46e4a Use JUnit 5 by default for all sub projects 2021-12-21 09:39:02 -08:00
Chris Eager
4bc00e00e3 Update to the latest abusive message filter 2021-12-20 11:33:04 -08:00
Chris Eager
3e777df86c Timeout sendNextMessagePage after 5 minutes 2021-12-20 11:31:11 -08:00
Chris Eager
278b4e810d Add (failing) test for send message timeouts 2021-12-20 11:31:11 -08:00
Chris Eager
346c7cd743 Remove null-check of destination UUID 2021-12-20 11:31:11 -08:00
Ehren Kret
867bf97d8f Require any transitive dependencies on log4j to use 2.17.0 2021-12-19 07:21:00 -06:00
Jon Chambers
8a67949168 Trivial typo fix 2021-12-16 12:44:58 -05:00
Jon Chambers
5baa51d547 Migrate challenge-issuing configuration into the abusive message filter module 2021-12-16 12:22:19 -05:00
Jon Chambers
616db337e1 Remove an old rate limiting feature flag 2021-12-16 12:22:19 -05:00
Jon Chambers
3895871462 Repair missing PNIs in JSON blobs on account load 2021-12-16 11:17:51 -05:00
Jon Chambers
a87b84fbe2 Return an empty response if somebody requests a profile key credential with a non-existent version 2021-12-16 10:30:55 -05:00
Chris Eager
b2f0ace9db Update dropwizard to 2.0.26 2021-12-15 16:34:52 -08:00
Jon Chambers
20c95e2606 Register ApplicationShutdownMonitor last
This will make it start last and shut down first, which is pretty much what we want for shutdown state monitoring
2021-12-15 19:27:23 -05:00
Jon Chambers
22dccaeddb Count cases where we can find a given account, but not the given profile version 2021-12-15 15:53:47 -05:00
Jon Chambers
e611a70ba4 Remove usernames from profile responses 2021-12-15 15:53:47 -05:00
Jon Chambers
66845d7080 Refactor: separate the various types of profile responses 2021-12-15 15:53:47 -05:00
Jon Chambers
4ea7278c6f Remove unversioned profile properties from Account entities 2021-12-15 15:53:47 -05:00
Jon Chambers
2b2e26f14b Remove deprecated, unversioned profile setters 2021-12-15 15:53:47 -05:00
Jon Chambers
b496ef8d6f Fix an issue where the deleted accounts lock client was trying to talk to the deleted accounts table 2021-12-15 13:16:32 -05:00
504 changed files with 60030 additions and 17964 deletions

View File

@@ -146,7 +146,7 @@ ij_java_generate_final_parameters = true
ij_java_if_brace_force = always
ij_java_imports_layout = $*,|,*
ij_java_indent_case_from_switch = true
ij_java_insert_inner_class_imports = true
ij_java_insert_inner_class_imports = false
ij_java_insert_override_annotation = true
ij_java_keep_blank_lines_before_right_brace = 2
ij_java_keep_blank_lines_between_package_declaration_and_header = 2
@@ -1135,7 +1135,7 @@ ij_kotlin_field_annotation_wrap = split_into_lines
ij_kotlin_finally_on_new_line = false
ij_kotlin_if_rparen_on_new_line = false
ij_kotlin_import_nested_classes = false
ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^
ij_kotlin_imports_layout = *
ij_kotlin_insert_whitespaces_in_simple_one_line_method = true
ij_kotlin_keep_blank_lines_before_right_brace = 2
ij_kotlin_keep_blank_lines_in_code = 2
@@ -1151,9 +1151,9 @@ ij_kotlin_method_call_chain_wrap = off
ij_kotlin_method_parameters_new_line_after_left_paren = false
ij_kotlin_method_parameters_right_paren_on_new_line = false
ij_kotlin_method_parameters_wrap = off
ij_kotlin_name_count_to_use_star_import = 5
ij_kotlin_name_count_to_use_star_import_for_members = 3
ij_kotlin_packages_to_use_import_on_demand = java.util.*,kotlinx.android.synthetic.**,io.ktor.**
ij_kotlin_name_count_to_use_star_import = 999
ij_kotlin_name_count_to_use_star_import_for_members = 999
ij_kotlin_packages_to_use_import_on_demand =
ij_kotlin_parameter_annotation_wrap = off
ij_kotlin_space_after_comma = true
ij_kotlin_space_after_extend_colon = true

0
.github/stale.yml vendored Normal file
View File

View File

@@ -5,14 +5,19 @@ on: [push]
jobs:
build:
runs-on: ubuntu-latest
container: ubuntu:22.04
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@3bc31aaf88e8fc94dc1e632d48af61be5ca8721c
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
- name: Set up JDK 17
uses: actions/setup-java@de1bb2b0c5634f0fc4438d7aa9944e68f9bf86cc # v3.6.0
with:
distribution: 'temurin'
java-version: 17
cache: 'maven'
env:
# work around an issue with actions/runner setting an incorrect HOME in containers, which breaks maven caching
# https://github.com/actions/setup-java/issues/356
HOME: /root
- name: Build with Maven
run: mvn -e -B verify
run: ./mvnw -e -B verify

2
.gitignore vendored
View File

@@ -16,6 +16,7 @@ config/deploy.properties
/service/config/testing.yml
/service/config/deploy.properties
/service/dependency-reduced-pom.xml
.java-version
.opsmanage
put.sh
deployer-staging.properties
@@ -25,4 +26,3 @@ deployer.log
!/service/src/main/resources/org/signal/badges/Badges_en.properties
/service/src/main/resources/org/signal/subscriptions/Subscriptions_*.properties
!/service/src/main/resources/org/signal/subscriptions/Subscriptions_en.properties
/.tx/config

BIN
.mvn/wrapper/maven-wrapper.jar vendored Normal file

Binary file not shown.

18
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View File

@@ -0,0 +1,18 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar

View File

@@ -13,7 +13,7 @@ Cryptography Notice
This distribution includes cryptographic software. The country in which you currently reside may have restrictions on the import, possession, use, and/or re-export to another country, of encryption software.
BEFORE using any encryption software, please check your country's laws, regulations and policies concerning the import, possession, or use, and re-export of encryption software, to see if this is permitted.
See <http://www.wassenaar.org/> for more information.
See <https://www.wassenaar.org/> for more information.
The U.S. Government Department of Commerce, Bureau of Industry and Security (BIS), has classified this software as Export Commodity Control Number (ECCN) 5D002.C.1, which includes information security software using or performing cryptographic functions with asymmetric algorithms.
The form and manner of this distribution makes it eligible for export under the License Exception ENC Technology Software Unrestricted (TSU) exception (see the BIS Export Administration Regulations, Section 740.13) for both object code and source code.
@@ -21,6 +21,6 @@ The form and manner of this distribution makes it eligible for export under the
License
---------------------
Copyright 2013-2021 Signal Messenger, LLC
Copyright 2013-2022 Signal Messenger, LLC
Licensed under the AGPLv3: https://www.gnu.org/licenses/agpl-3.0.html

86
event-logger/pom.xml Normal file
View File

@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2022 Signal Messenger, LLC
~ SPDX-License-Identifier: AGPL-3.0-only
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>TextSecureServer</artifactId>
<groupId>org.whispersystems.textsecure</groupId>
<version>JGITVER</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>event-logger</artifactId>
<dependencies>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-logging</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<exclusions>
<exclusion>
<groupId>org.jetbrains</groupId>
<!--
depends on an outdated version (13.0) for JDK 6 compatibility, but its safe to override
https://youtrack.jetbrains.com/issue/KT-25047
-->
<artifactId>annotations</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-serialization-json</artifactId>
<version>${kotlinx-serialization.version}</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<configuration>
<compilerPlugins>
<plugin>kotlinx-serialization</plugin>
</compilerPlugins>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-serialization</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.event
import java.util.Collections
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.polymorphic
import kotlinx.serialization.modules.subclass
val module = SerializersModule {
polymorphic(Event::class) {
subclass(RemoteConfigSetEvent::class)
subclass(RemoteConfigDeleteEvent::class)
}
}
val jsonFormat = Json { serializersModule = module }
sealed interface Event
@Serializable
data class RemoteConfigSetEvent(
val token: String,
val name: String,
val percentage: Int,
val defaultValue: String? = null,
val value: String? = null,
val hashKey: String? = null,
val uuids: Collection<String> = Collections.emptyList(),
) : Event
@Serializable
data class RemoteConfigDeleteEvent(
val token: String,
val name: String,
) : Event

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.event
import com.google.cloud.logging.LogEntry
import com.google.cloud.logging.Logging
import com.google.cloud.logging.MonitoredResourceUtil
import com.google.cloud.logging.Payload.JsonPayload
import com.google.cloud.logging.Severity
import com.google.protobuf.Struct
import com.google.protobuf.util.JsonFormat
import kotlinx.serialization.encodeToString
interface AdminEventLogger {
fun logEvent(event: Event, labels: Map<String, String>?)
fun logEvent(event: Event) = logEvent(event, null)
}
class NoOpAdminEventLogger : AdminEventLogger {
override fun logEvent(event: Event, labels: Map<String, String>?) {}
}
class GoogleCloudAdminEventLogger(private val logging: Logging, private val projectId: String, private val logName: String) : AdminEventLogger {
override fun logEvent(event: Event, labels: Map<String, String>?) {
val structBuilder = Struct.newBuilder()
JsonFormat.parser().merge(jsonFormat.encodeToString(event), structBuilder)
val struct = structBuilder.build()
val logEntryBuilder = LogEntry.newBuilder(JsonPayload.of(struct))
.setLogName(logName)
.setSeverity(Severity.NOTICE)
.setResource(MonitoredResourceUtil.getResource(projectId, "project"));
if (labels != null) {
logEntryBuilder.setLabels(labels);
}
logging.write(listOf(logEntryBuilder.build()))
}
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.event
import com.google.cloud.logging.Logging
import org.junit.jupiter.api.Test
import org.mockito.Mockito.mock
class GoogleCloudAdminEventLoggerTest {
@Test
fun logEvent() {
val logging = mock(Logging::class.java)
val logger = GoogleCloudAdminEventLogger(logging, "my-project", "test")
val event = RemoteConfigDeleteEvent("token", "test")
logger.logEvent(event)
}
}

View File

@@ -1,52 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>TextSecureServer</artifactId>
<groupId>org.whispersystems.textsecure</groupId>
<version>JGITVER</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>gcm-sender-async</artifactId>
<dependencies>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-retry</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -1,9 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.gcm.server;
public class AuthenticationFailedException extends Exception {
}

View File

@@ -1,9 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.gcm.server;
public class InvalidRequestException extends Exception {
}

View File

@@ -1,144 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.gcm.server;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.whispersystems.gcm.server.internal.GcmRequestEntity;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class Message {
private static final ObjectMapper objectMapper = new ObjectMapper();
private final String collapseKey;
private final Long ttl;
private final Boolean delayWhileIdle;
private final Map<String, String> data;
private final List<String> registrationIds;
private final String priority;
private Message(String collapseKey, Long ttl, Boolean delayWhileIdle,
Map<String, String> data, List<String> registrationIds,
String priority)
{
this.collapseKey = collapseKey;
this.ttl = ttl;
this.delayWhileIdle = delayWhileIdle;
this.data = data;
this.registrationIds = registrationIds;
this.priority = priority;
}
public String serialize() throws JsonProcessingException {
GcmRequestEntity requestEntity = new GcmRequestEntity(collapseKey, ttl, delayWhileIdle,
data, registrationIds, priority);
return objectMapper.writeValueAsString(requestEntity);
}
/**
* Construct a new Message using a Builder.
* @return A new Builder.
*/
public static Builder newBuilder() {
return new Builder();
}
public static class Builder {
private String collapseKey = null;
private Long ttl = null;
private Boolean delayWhileIdle = null;
private Map<String, String> data = null;
private List<String> registrationIds = new LinkedList<>();
private String priority = null;
private Builder() {}
/**
* @param collapseKey The GCM collapse key to use (optional).
* @return The Builder.
*/
public Builder withCollapseKey(String collapseKey) {
this.collapseKey = collapseKey;
return this;
}
/**
* @param seconds The TTL (in seconds) for this message (optional).
* @return The Builder.
*/
public Builder withTtl(long seconds) {
this.ttl = seconds;
return this;
}
/**
* @param delayWhileIdle Set GCM delay_while_idle (optional).
* @return The Builder.
*/
public Builder withDelayWhileIdle(boolean delayWhileIdle) {
this.delayWhileIdle = delayWhileIdle;
return this;
}
/**
* Set a key in the GCM JSON payload delivered to the application (optional).
* @param key The key to set.
* @param value The value to set.
* @return The Builder.
*/
public Builder withDataPart(String key, String value) {
if (data == null) {
data = new HashMap<>();
}
data.put(key, value);
return this;
}
/**
* Set the destination GCM registration ID (mandatory).
* @param registrationId The destination GCM registration ID.
* @return The Builder.
*/
public Builder withDestination(String registrationId) {
this.registrationIds.clear();
this.registrationIds.add(registrationId);
return this;
}
/**
* Set the GCM message priority (optional).
*
* @param priority Valid values are "normal" and "high."
* On iOS, these correspond to APNs priority 5 and 10.
* @return The Builder.
*/
public Builder withPriority(String priority) {
this.priority = priority;
return this;
}
/**
* Construct a message object.
*
* @return An immutable message object, as configured by this builder.
*/
public Message build() {
if (registrationIds.isEmpty()) {
throw new IllegalArgumentException("You must specify a destination!");
}
return new Message(collapseKey, ttl, delayWhileIdle, data, registrationIds, priority);
}
}
}

View File

@@ -1,80 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.gcm.server;
/**
* The result of a GCM send operation.
*/
public class Result {
private final String canonicalRegistrationId;
private final String messageId;
private final String error;
Result(String canonicalRegistrationId, String messageId, String error) {
this.canonicalRegistrationId = canonicalRegistrationId;
this.messageId = messageId;
this.error = error;
}
/**
* Returns the "canonical" GCM registration ID for this destination.
* See GCM documentation for details.
* @return The canonical GCM registration ID.
*/
public String getCanonicalRegistrationId() {
return canonicalRegistrationId;
}
/**
* @return If a "canonical" GCM registration ID is present in the response.
*/
public boolean hasCanonicalRegistrationId() {
return canonicalRegistrationId != null && !canonicalRegistrationId.isEmpty();
}
/**
* @return The assigned GCM message ID, if successful.
*/
public String getMessageId() {
return messageId;
}
/**
* @return The raw error string, if present.
*/
public String getError() {
return error;
}
/**
* @return If the send was a success.
*/
public boolean isSuccess() {
return messageId != null && !messageId.isEmpty() && (error == null || error.isEmpty());
}
/**
* @return If the destination GCM registration ID is no longer registered.
*/
public boolean isUnregistered() {
return "NotRegistered".equals(error);
}
/**
* @return If messages to this device are being throttled.
*/
public boolean isThrottled() {
return "DeviceMessageRateExceeded".equals(error);
}
/**
* @return If the destination GCM registration ID is invalid.
*/
public boolean isInvalidRegistrationId() {
return "InvalidRegistration".equals(error);
}
}

View File

@@ -1,154 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.gcm.server;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.annotations.VisibleForTesting;
import io.github.resilience4j.retry.IntervalFunction;
import io.github.resilience4j.retry.Retry;
import io.github.resilience4j.retry.RetryConfig;
import org.whispersystems.gcm.server.internal.GcmResponseEntity;
import org.whispersystems.gcm.server.internal.GcmResponseListEntity;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse.BodyHandlers;
import java.security.SecureRandom;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeoutException;
/**
* The main interface to sending GCM messages. Thread safe.
*
* @author Moxie Marlinspike
*/
public class Sender {
private static final String PRODUCTION_URL = "https://fcm.googleapis.com/fcm/send";
private final String authorizationHeader;
private final URI uri;
private final Retry retry;
private final ObjectMapper mapper;
private final ScheduledExecutorService executorService;
private final HttpClient[] clients = new HttpClient[10];
private final SecureRandom random = new SecureRandom();
/**
* Construct a Sender instance.
*
* @param apiKey Your application's GCM API key.
*/
public Sender(String apiKey, ObjectMapper mapper) {
this(apiKey, mapper, 10);
}
/**
* Construct a Sender instance with a specified retry count.
*
* @param apiKey Your application's GCM API key.
* @param retryCount The number of retries to attempt on a network error or 500 response.
*/
public Sender(String apiKey, ObjectMapper mapper, int retryCount) {
this(apiKey, mapper, retryCount, PRODUCTION_URL);
}
@VisibleForTesting
public Sender(String apiKey, ObjectMapper mapper, int retryCount, String url) {
this.mapper = mapper;
this.executorService = Executors.newSingleThreadScheduledExecutor();
this.uri = URI.create(url);
this.authorizationHeader = String.format("key=%s", apiKey);
this.retry = Retry.of("fcm-sender", RetryConfig.custom()
.maxAttempts(retryCount)
.intervalFunction(IntervalFunction.ofExponentialRandomBackoff(Duration.ofMillis(100), 2.0))
.retryOnException(this::isRetryableException)
.build());
for (int i=0;i<clients.length;i++) {
this.clients[i] = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.connectTimeout(Duration.ofSeconds(10))
.build();
}
}
private boolean isRetryableException(Throwable throwable) {
while (throwable instanceof CompletionException) {
throwable = throwable.getCause();
}
return throwable instanceof ServerFailedException ||
throwable instanceof TimeoutException ||
throwable instanceof IOException;
}
/**
* Asynchronously send a message.
*
* @param message The message to send.
* @return A future.
*/
public CompletableFuture<Result> send(Message message) {
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(uri)
.header("Authorization", authorizationHeader)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(message.serialize()))
.timeout(Duration.ofSeconds(10))
.build();
return retry.executeCompletionStage(executorService,
() -> getClient().sendAsync(request, BodyHandlers.ofByteArray())
.thenApply(response -> {
switch (response.statusCode()) {
case 400: throw new CompletionException(new InvalidRequestException());
case 401: throw new CompletionException(new AuthenticationFailedException());
case 204:
case 200: return response.body();
default: throw new CompletionException(new ServerFailedException("Bad status: " + response.statusCode()));
}
})
.thenApply(responseBytes -> {
try {
List<GcmResponseEntity> responseList = mapper.readValue(responseBytes, GcmResponseListEntity.class).getResults();
if (responseList == null || responseList.size() == 0) {
throw new CompletionException(new IOException("Empty response list!"));
}
GcmResponseEntity responseEntity = responseList.get(0);
return new Result(responseEntity.getCanonicalRegistrationId(),
responseEntity.getMessageId(),
responseEntity.getError());
} catch (IOException e) {
throw new CompletionException(e);
}
})).toCompletableFuture();
} catch (JsonProcessingException e) {
return CompletableFuture.failedFuture(e);
}
}
public Retry getRetry() {
return retry;
}
private HttpClient getClient() {
return clients[random.nextInt(clients.length)];
}
}

View File

@@ -1,15 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.gcm.server;
public class ServerFailedException extends Exception {
public ServerFailedException(String message) {
super(message);
}
public ServerFailedException(Exception e) {
super(e);
}
}

View File

@@ -1,46 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.gcm.server.internal;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import java.util.Map;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class GcmRequestEntity {
@JsonProperty(value = "collapse_key")
private String collapseKey;
@JsonProperty(value = "time_to_live")
private Long ttl;
@JsonProperty(value = "delay_while_idle")
private Boolean delayWhileIdle;
@JsonProperty(value = "data")
private Map<String, String> data;
@JsonProperty(value = "registration_ids")
private List<String> registrationIds;
@JsonProperty
private String priority;
public GcmRequestEntity(String collapseKey, Long ttl, Boolean delayWhileIdle,
Map<String, String> data, List<String> registrationIds,
String priority)
{
this.collapseKey = collapseKey;
this.ttl = ttl;
this.delayWhileIdle = delayWhileIdle;
this.data = data;
this.registrationIds = registrationIds;
this.priority = priority;
}
}

View File

@@ -1,31 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.gcm.server.internal;
import com.fasterxml.jackson.annotation.JsonProperty;
public class GcmResponseEntity {
@JsonProperty(value = "message_id")
private String messageId;
@JsonProperty(value = "registration_id")
private String canonicalRegistrationId;
@JsonProperty
private String error;
public String getMessageId() {
return messageId;
}
public String getCanonicalRegistrationId() {
return canonicalRegistrationId;
}
public String getError() {
return error;
}
}

View File

@@ -1,19 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.gcm.server.internal;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public class GcmResponseListEntity {
@JsonProperty
private List<GcmResponseEntity> results;
public List<GcmResponseEntity> getResults() {
return results;
}
}

View File

@@ -1,49 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.gcm.server;
import org.junit.Test;
import java.io.IOException;
import static org.junit.Assert.assertEquals;
import static org.whispersystems.gcm.server.util.JsonHelpers.jsonFixture;
public class MessageTest {
@Test
public void testMinimal() throws IOException {
Message message = Message.newBuilder()
.withDestination("1")
.build();
assertEquals(message.serialize(), jsonFixture("fixtures/message-minimal.json"));
}
@Test
public void testComplete() throws IOException {
Message message = Message.newBuilder()
.withDestination("1")
.withCollapseKey("collapse")
.withDelayWhileIdle(true)
.withTtl(10)
.withPriority("high")
.build();
assertEquals(message.serialize(), jsonFixture("fixtures/message-complete.json"));
}
@Test
public void testWithData() throws IOException {
Message message = Message.newBuilder()
.withDestination("2")
.withDataPart("key1", "value1")
.withDataPart("key2", "value2")
.build();
assertEquals(message.serialize(), jsonFixture("fixtures/message-data.json"));
}
}

View File

@@ -1,200 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.gcm.server;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.any;
import static com.github.tomakehurst.wiremock.client.WireMock.anyRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl;
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
import static com.github.tomakehurst.wiremock.client.WireMock.ok;
import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.whispersystems.gcm.server.util.FixtureHelpers.fixture;
import static org.whispersystems.gcm.server.util.JsonHelpers.jsonFixture;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.tomakehurst.wiremock.client.CountMatchingStrategy;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.junit.Rule;
import org.junit.Test;
public class SenderTest {
@Rule
public WireMockRule wireMock = new WireMockRule(options().dynamicPort().dynamicHttpsPort());
private static final ObjectMapper mapper = new ObjectMapper();
static {
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
@Test
public void testSuccess() throws InterruptedException, ExecutionException, TimeoutException, IOException {
wireMock.stubFor(any(anyUrl())
.willReturn(aResponse()
.withStatus(200)
.withBody(fixture("fixtures/response-success.json"))));
Sender sender = new Sender("foobarbaz", mapper, 10, "http://localhost:" + wireMock.port() + "/gcm/send");
CompletableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build());
Result result = future.get(10, TimeUnit.SECONDS);
assertTrue(result.isSuccess());
assertFalse(result.isThrottled());
assertFalse(result.isUnregistered());
assertEquals(result.getMessageId(), "1:08");
assertNull(result.getError());
assertNull(result.getCanonicalRegistrationId());
verify(1, postRequestedFor(urlEqualTo("/gcm/send"))
.withHeader("Authorization", equalTo("key=foobarbaz"))
.withHeader("Content-Type", equalTo("application/json"))
.withRequestBody(equalTo(jsonFixture("fixtures/message-minimal.json"))));
}
@Test
public void testBadApiKey() throws InterruptedException, TimeoutException {
wireMock.stubFor(any(anyUrl())
.willReturn(aResponse()
.withStatus(401)));
Sender sender = new Sender("foobar", mapper, 10, "http://localhost:" + wireMock.port() + "/gcm/send");
CompletableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build());
try {
future.get(10, TimeUnit.SECONDS);
throw new AssertionError();
} catch (ExecutionException ee) {
assertTrue(ee.getCause() instanceof AuthenticationFailedException);
}
verify(1, anyRequestedFor(anyUrl()));
}
@Test
public void testBadRequest() throws TimeoutException, InterruptedException {
wireMock.stubFor(any(anyUrl())
.willReturn(aResponse()
.withStatus(400)));
Sender sender = new Sender("foobarbaz", mapper, 10, "http://localhost:" + wireMock.port() + "/gcm/send");
CompletableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build());
try {
future.get(10, TimeUnit.SECONDS);
throw new AssertionError();
} catch (ExecutionException e) {
assertTrue(e.getCause() instanceof InvalidRequestException);
}
verify(1, anyRequestedFor(anyUrl()));
}
@Test
public void testServerError() throws TimeoutException, InterruptedException {
wireMock.stubFor(any(anyUrl())
.willReturn(aResponse()
.withStatus(503)));
Sender sender = new Sender("foobarbaz", mapper, 3, "http://localhost:" + wireMock.port() + "/gcm/send");
CompletableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build());
try {
future.get(10, TimeUnit.SECONDS);
throw new AssertionError();
} catch (ExecutionException ee) {
assertTrue(ee.getCause() instanceof ServerFailedException);
}
verify(3, anyRequestedFor(anyUrl()));
}
@Test
public void testServerErrorRecovery() throws InterruptedException, ExecutionException, TimeoutException {
wireMock.stubFor(any(anyUrl()).willReturn(aResponse().withStatus(503)));
Sender sender = new Sender("foobarbaz", mapper, 4, "http://localhost:" + wireMock.port() + "/gcm/send");
CompletableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build());
// up to three failures can happen, with 100ms exponential backoff
// if we end up using the fourth, and finaly try, it would be after ~700 ms
CompletableFuture.delayedExecutor(300, TimeUnit.MILLISECONDS).execute(() ->
wireMock.stubFor(any(anyUrl())
.willReturn(aResponse()
.withStatus(200)
.withBody(fixture("fixtures/response-success.json"))))
);
Result result = future.get(10, TimeUnit.SECONDS);
verify(new CountMatchingStrategy(CountMatchingStrategy.GREATER_THAN, 1), anyRequestedFor(anyUrl()));
assertTrue(result.isSuccess());
assertFalse(result.isThrottled());
assertFalse(result.isUnregistered());
assertEquals(result.getMessageId(), "1:08");
assertNull(result.getError());
assertNull(result.getCanonicalRegistrationId());
}
@Test
public void testNetworkError() throws TimeoutException, InterruptedException {
wireMock.stubFor(any(anyUrl())
.willReturn(ok()));
Sender sender = new Sender("foobarbaz", mapper ,2, "http://localhost:" + wireMock.port() + "/gcm/send");
wireMock.stop();
CompletableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build());
try {
future.get(10, TimeUnit.SECONDS);
} catch (ExecutionException e) {
assertTrue(e.getCause() instanceof IOException);
}
}
@Test
public void testNotRegistered() throws InterruptedException, ExecutionException, TimeoutException {
wireMock.stubFor(any(anyUrl()).willReturn(aResponse().withStatus(200)
.withBody(fixture("fixtures/response-not-registered.json"))));
Sender sender = new Sender("foobarbaz", mapper,2, "http://localhost:" + wireMock.port() + "/gcm/send");
CompletableFuture<Result> future = sender.send(Message.newBuilder()
.withDestination("2")
.withDataPart("message", "new message!")
.build());
Result result = future.get(10, TimeUnit.SECONDS);
assertFalse(result.isSuccess());
assertTrue(result.isUnregistered());
assertFalse(result.isThrottled());
assertEquals(result.getError(), "NotRegistered");
}
}

View File

@@ -1,89 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.gcm.server;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
import static junit.framework.TestCase.assertTrue;
import static org.whispersystems.gcm.server.util.FixtureHelpers.fixture;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
public class SimultaneousSenderTest {
@Rule
public WireMockRule wireMock = new WireMockRule(WireMockConfiguration.options().dynamicPort().dynamicHttpsPort());
private static final ObjectMapper mapper = new ObjectMapper();
static {
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
@Test
public void testSimultaneousSuccess() throws TimeoutException, InterruptedException, ExecutionException, JsonProcessingException {
stubFor(post(urlPathEqualTo("/gcm/send"))
.willReturn(aResponse()
.withStatus(200)
.withBody(fixture("fixtures/response-success.json"))));
Sender sender = new Sender("foobarbaz", mapper, 2, "http://localhost:" + wireMock.port() + "/gcm/send");
List<CompletableFuture<Result>> results = new LinkedList<>();
for (int i=0;i<1000;i++) {
results.add(sender.send(Message.newBuilder().withDestination("1").build()));
}
for (CompletableFuture<Result> future : results) {
Result result = future.get(60, TimeUnit.SECONDS);
if (!result.isSuccess()) {
throw new AssertionError(result.getError());
}
}
}
@Test
@Ignore
public void testSimultaneousFailure() throws TimeoutException, InterruptedException {
stubFor(post(urlPathEqualTo("/gcm/send"))
.willReturn(aResponse()
.withStatus(503)));
Sender sender = new Sender("foobarbaz", mapper, 2, "http://localhost:" + wireMock.port() + "/gcm/send");
List<CompletableFuture<Result>> futures = new LinkedList<>();
for (int i=0;i<1000;i++) {
futures.add(sender.send(Message.newBuilder().withDestination("1").build()));
}
for (CompletableFuture<Result> future : futures) {
try {
Result result = future.get(60, TimeUnit.SECONDS);
} catch (ExecutionException e) {
assertTrue(e.getCause().toString(), e.getCause() instanceof ServerFailedException);
}
}
}
}

View File

@@ -1,47 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.gcm.server.util;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import java.io.IOException;
import java.nio.charset.Charset;
/**
* A set of helper method for fixture files.
*/
public class FixtureHelpers {
private FixtureHelpers() { /* singleton */ }
/**
* Reads the given fixture file from the classpath (e. g. {@code src/test/resources})
* and returns its contents as a UTF-8 string.
*
* @param filename the filename of the fixture file
* @return the contents of {@code src/test/resources/{filename}}
* @throws IllegalArgumentException if an I/O error occurs.
*/
public static String fixture(String filename) {
return fixture(filename, Charsets.UTF_8);
}
/**
* Reads the given fixture file from the classpath (e. g. {@code src/test/resources})
* and returns its contents as a string.
*
* @param filename the filename of the fixture file
* @param charset the character set of {@code filename}
* @return the contents of {@code src/test/resources/{filename}}
* @throws IllegalArgumentException if an I/O error occurs.
*/
private static String fixture(String filename, Charset charset) {
try {
return Resources.toString(Resources.getResource(filename), charset).trim();
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
}

View File

@@ -1,30 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.gcm.server.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import static org.whispersystems.gcm.server.util.FixtureHelpers.fixture;
public class JsonHelpers {
private static final ObjectMapper objectMapper = new ObjectMapper();
public static String asJson(Object object) throws JsonProcessingException {
return objectMapper.writeValueAsString(object);
}
public static <T> T fromJson(String value, Class<T> clazz) throws IOException {
return objectMapper.readValue(value, clazz);
}
public static String jsonFixture(String filename) throws IOException {
return objectMapper.writeValueAsString(objectMapper.readValue(fixture(filename), JsonNode.class));
}
}

View File

@@ -1,7 +0,0 @@
{
"priority" : "high",
"collapse_key" : "collapse",
"time_to_live" : 10,
"delay_while_idle" : true,
"registration_ids" : ["1"]
}

View File

@@ -1,7 +0,0 @@
{
"data" : {
"key1" : "value1",
"key2" : "value2"
},
"registration_ids" : ["2"]
}

View File

@@ -1,3 +0,0 @@
{
"registration_ids" : ["1"]
}

View File

@@ -1,8 +0,0 @@
{ "multicast_id": 216,
"success": 0,
"failure": 1,
"canonical_ids": 0,
"results": [
{ "error": "NotRegistered"}
]
}

View File

@@ -1,8 +0,0 @@
{ "multicast_id": 108,
"success": 1,
"failure": 0,
"canonical_ids": 0,
"results": [
{ "message_id": "1:08" }
]
}

View File

@@ -1,8 +0,0 @@
<configuration>
<!-- Turning down the wiremock logging -->
<logger name="com.github.tomakehurst.wiremock" level="WARN"/>
<logger name="wiremock.org" level="ERROR"/>
<logger name="WireMock" level="WARN"/>
<!-- wiremock has per endpoint servlet logging -->
<logger name="/" level="WARN"/>
</configuration>

316
mvnw vendored Executable file
View File

@@ -0,0 +1,316 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /usr/local/etc/mavenrc ] ; then
. /usr/local/etc/mavenrc
fi
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`\\unset -f command; \\command -v java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" \
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

188
mvnw.cmd vendored Normal file
View File

@@ -0,0 +1,188 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% ^
%JVM_CONFIG_MAVEN_PROPS% ^
%MAVEN_OPTS% ^
%MAVEN_DEBUG_OPTS% ^
-classpath %WRAPPER_JAR% ^
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%"=="on" pause
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
cmd /C exit /B %ERROR_CODE%

129
pom.xml
View File

@@ -35,39 +35,43 @@
</pluginRepositories>
<modules>
<module>event-logger</module>
<module>redis-dispatch</module>
<module>websocket-resources</module>
<module>gcm-sender-async</module>
<module>service</module>
</modules>
<properties>
<aws.sdk.version>1.11.939</aws.sdk.version>
<aws.sdk2.version>2.16.66</aws.sdk2.version>
<aws.sdk.version>1.12.376</aws.sdk.version>
<aws.sdk2.version>2.19.8</aws.sdk2.version>
<braintree.version>3.19.0</braintree.version>
<commons-codec.version>1.15</commons-codec.version>
<commons-csv.version>1.8</commons-csv.version>
<commons-csv.version>1.9.0</commons-csv.version>
<commons-io.version>2.9.0</commons-io.version>
<dropwizard.version>2.0.25</dropwizard.version>
<dropwizard.version>2.0.34</dropwizard.version>
<dropwizard-metrics-datadog.version>1.1.13</dropwizard-metrics-datadog.version>
<gson.version>2.8.8</gson.version>
<guava.version>30.1.1-jre</guava.version>
<google-cloud-libraries.version>26.1.3</google-cloud-libraries.version>
<grpc.version>1.51.1</grpc.version> <!-- this should be kept in sync with the value from Googles libraries-bom -->
<gson.version>2.9.0</gson.version>
<jackson.version>2.13.4</jackson.version>
<jaxb.version>2.3.1</jaxb.version>
<jedis.version>2.9.0</jedis.version>
<lettuce.version>6.0.4.RELEASE</lettuce.version>
<libphonenumber.version>8.12.33</libphonenumber.version>
<logstash.logback.version>6.6</logstash.logback.version>
<micrometer.version>1.5.3</micrometer.version>
<mockito.version>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>
<kotlin.version>1.8.0</kotlin.version>
<kotlinx-serialization.version>1.4.1</kotlinx-serialization.version>
<lettuce.version>6.2.1.RELEASE</lettuce.version>
<libphonenumber.version>8.12.54</libphonenumber.version>
<logstash.logback.version>7.2</logstash.logback.version>
<micrometer.version>1.10.3</micrometer.version>
<mockito.version>4.11.0</mockito.version>
<netty.version>4.1.82.Final</netty.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>
<protobuf.version>3.21.7</protobuf.version>
<pushy.version>0.15.2</pushy.version>
<resilience4j.version>1.7.0</resilience4j.version>
<semver4j.version>3.1.0</semver4j.version>
<slf4j.version>1.7.30</slf4j.version>
<stripe.version>20.79.0</stripe.version>
<stripe.version>21.2.0</stripe.version>
<vavr.version>0.10.4</vavr.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
@@ -78,6 +82,13 @@
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>${jackson.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-dependencies</artifactId>
@@ -85,6 +96,13 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Needed for gRPC with Java 9+ -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId>
<version>6.0.53</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-bom</artifactId>
@@ -109,7 +127,7 @@
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>libraries-bom</artifactId>
<version>20.9.0</version>
<version>${google-cloud-libraries.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -127,7 +145,20 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-bom</artifactId>
<version>2020.0.24</version> <!-- 3.4.x, see https://github.com/reactor/reactor#bom-versioning-scheme -->
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-bom</artifactId>
<version>${kotlin.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.eatthepath</groupId>
<artifactId>pushy</artifactId>
@@ -138,11 +169,6 @@
<artifactId>pushy-dropwizard-metrics-listener</artifactId>
<version>${pushy.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
@@ -174,10 +200,9 @@
<version>${lettuce.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-tcnative-boringssl-static</artifactId>
<version>${netty.tcnative-boringssl-static.version}</version>
<scope>runtime</scope>
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
<version>${vavr.version}</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
@@ -223,12 +248,6 @@
<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>
@@ -261,6 +280,11 @@
<artifactId>stripe-java</artifactId>
<version>${stripe.version}</version>
</dependency>
<dependency>
<groupId>com.braintreepayments.gateway</groupId>
<artifactId>braintree-java</artifactId>
<version>${braintree.version}</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
@@ -269,20 +293,18 @@
<dependency>
<groupId>org.signal</groupId>
<artifactId>embedded-redis</artifactId>
<version>0.8.1</version>
<version>0.8.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.zonky.test.postgres</groupId>
<artifactId>embedded-postgres-binaries-bom</artifactId>
<version>11.13.0</version>
<type>pom</type>
<scope>import</scope>
<groupId>org.signal</groupId>
<artifactId>libsignal-server</artifactId>
<version>0.21.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-bom</artifactId>
<version>2.16.0</version>
<version>2.17.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -299,7 +321,7 @@
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8</artifactId>
<version>2.32.0</version>
<version>2.35.0</version>
<scope>test</scope>
<exclusions>
<exclusion>
@@ -324,8 +346,14 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit-pioneer</groupId>
<artifactId>junit-pioneer</artifactId>
<version>1.9.1</version>
<scope>test</scope>
</dependency>
@@ -369,13 +397,16 @@
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.18.0:exe:${os.detected.classifier}</protocArtifact>
<checkStaleness>true</checkStaleness>
<checkStaleness>false</checkStaleness>
<protocArtifact>com.google.protobuf:protoc:3.21.1:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
<goal>test-compile</goal>
</goals>
</execution>
@@ -451,7 +482,7 @@
<rules>
<dependencyConvergence/>
<requireMavenVersion>
<version>3.8.3</version>
<version>3.8.6</version>
</requireMavenVersion>
</rules>
</configuration>

View File

@@ -115,7 +115,7 @@ public class PubSubConnection {
byte[] channelName = inputStream.readFully(channelNameHeader.getStringLength());
inputStream.readLine();
IntReply subscriptionCount = new IntReply(inputStream.readLine());
new IntReply(inputStream.readLine());
return new String(channelName);
}

View File

@@ -4,28 +4,25 @@
*/
package org.whispersystems.dispatch;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExternalResource;
import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.whispersystems.dispatch.io.RedisPubSubConnectionFactory;
import org.whispersystems.dispatch.redis.PubSubConnection;
import org.whispersystems.dispatch.redis.PubSubReply;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.stubbing.Answer;
import org.whispersystems.dispatch.io.RedisPubSubConnectionFactory;
import org.whispersystems.dispatch.redis.PubSubConnection;
import org.whispersystems.dispatch.redis.PubSubReply;
public class DispatchManagerTest {
private PubSubConnection pubSubConnection;
@@ -33,31 +30,23 @@ public class DispatchManagerTest {
private DispatchManager dispatchManager;
private PubSubReplyInputStream pubSubReplyInputStream;
@Rule
public ExternalResource resource = new ExternalResource() {
@Override
protected void before() throws Throwable {
pubSubConnection = mock(PubSubConnection.class );
socketFactory = mock(RedisPubSubConnectionFactory.class);
pubSubReplyInputStream = new PubSubReplyInputStream();
@BeforeEach
void setUp() throws Exception {
pubSubConnection = mock(PubSubConnection.class );
socketFactory = mock(RedisPubSubConnectionFactory.class);
pubSubReplyInputStream = new PubSubReplyInputStream();
when(socketFactory.connect()).thenReturn(pubSubConnection);
when(pubSubConnection.read()).thenAnswer(new Answer<PubSubReply>() {
@Override
public PubSubReply answer(InvocationOnMock invocationOnMock) throws Throwable {
return pubSubReplyInputStream.read();
}
});
when(socketFactory.connect()).thenReturn(pubSubConnection);
when(pubSubConnection.read()).thenAnswer((Answer<PubSubReply>) invocationOnMock -> pubSubReplyInputStream.read());
dispatchManager = new DispatchManager(socketFactory, Optional.empty());
dispatchManager.start();
}
dispatchManager = new DispatchManager(socketFactory, Optional.empty());
dispatchManager.start();
}
@Override
protected void after() {
}
};
@AfterEach
void tearDown() {
dispatchManager.shutdown();
}
@Test
public void testConnect() {
@@ -65,7 +54,7 @@ public class DispatchManagerTest {
}
@Test
public void testSubscribe() throws IOException {
public void testSubscribe() {
DispatchChannel dispatchChannel = mock(DispatchChannel.class);
dispatchManager.subscribe("foo", dispatchChannel);
pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.SUBSCRIBE, "foo", Optional.empty()));
@@ -74,7 +63,7 @@ public class DispatchManagerTest {
}
@Test
public void testSubscribeUnsubscribe() throws IOException {
public void testSubscribeUnsubscribe() {
DispatchChannel dispatchChannel = mock(DispatchChannel.class);
dispatchManager.subscribe("foo", dispatchChannel);
dispatchManager.unsubscribe("foo", dispatchChannel);
@@ -86,7 +75,7 @@ public class DispatchManagerTest {
}
@Test
public void testMessages() throws IOException {
public void testMessages() {
DispatchChannel fooChannel = mock(DispatchChannel.class);
DispatchChannel barChannel = mock(DispatchChannel.class);

View File

@@ -4,9 +4,9 @@
*/
package org.whispersystems.dispatch.redis;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
@@ -18,12 +18,12 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.security.SecureRandom;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
public class PubSubConnectionTest {
class PubSubConnectionTest {
private static final String REPLY = "*3\r\n" +
"$9\r\n" +
@@ -60,8 +60,7 @@ public class PubSubConnectionTest {
@Test
public void testSubscribe() throws IOException {
// ByteChannel byteChannel = mock(ByteChannel.class);
void testSubscribe() throws IOException {
OutputStream outputStream = mock(OutputStream.class);
Socket socket = mock(Socket.class );
when(socket.getOutputStream()).thenReturn(outputStream);
@@ -76,7 +75,7 @@ public class PubSubConnectionTest {
}
@Test
public void testUnsubscribe() throws IOException {
void testUnsubscribe() throws IOException {
OutputStream outputStream = mock(OutputStream.class);
Socket socket = mock(Socket.class );
when(socket.getOutputStream()).thenReturn(outputStream);
@@ -91,7 +90,7 @@ public class PubSubConnectionTest {
}
@Test
public void testTricklyResponse() throws Exception {
void testTricklyResponse() throws Exception {
InputStream inputStream = mockInputStreamFor(new TrickleInputStream(REPLY.getBytes()));
OutputStream outputStream = mock(OutputStream.class);
Socket socket = mock(Socket.class );
@@ -103,7 +102,7 @@ public class PubSubConnectionTest {
}
@Test
public void testFullResponse() throws Exception {
void testFullResponse() throws Exception {
InputStream inputStream = mockInputStreamFor(new FullInputStream(REPLY.getBytes()));
OutputStream outputStream = mock(OutputStream.class);
Socket socket = mock(Socket.class );
@@ -115,7 +114,7 @@ public class PubSubConnectionTest {
}
@Test
public void testRandomLengthResponse() throws Exception {
void testRandomLengthResponse() throws Exception {
InputStream inputStream = mockInputStreamFor(new RandomInputStream(REPLY.getBytes()));
OutputStream outputStream = mock(OutputStream.class);
Socket socket = mock(Socket.class );

View File

@@ -5,42 +5,41 @@
package org.whispersystems.dispatch.redis.protocol;
import org.junit.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.IOException;
import org.junit.jupiter.api.Test;
import static org.junit.Assert.assertEquals;
class ArrayReplyHeaderTest {
public class ArrayReplyHeaderTest {
@Test(expected = IOException.class)
public void testNull() throws IOException {
new ArrayReplyHeader(null);
}
@Test(expected = IOException.class)
public void testBadPrefix() throws IOException {
new ArrayReplyHeader(":3");
}
@Test(expected = IOException.class)
public void testEmpty() throws IOException {
new ArrayReplyHeader("");
}
@Test(expected = IOException.class)
public void testTruncated() throws IOException {
new ArrayReplyHeader("*");
}
@Test(expected = IOException.class)
public void testBadNumber() throws IOException {
new ArrayReplyHeader("*ABC");
@Test
void testNull() {
assertThrows(IOException.class, () -> new ArrayReplyHeader(null));
}
@Test
public void testValid() throws IOException {
void testBadPrefix() {
assertThrows(IOException.class, () -> new ArrayReplyHeader(":3"));
}
@Test
void testEmpty() {
assertThrows(IOException.class, () -> new ArrayReplyHeader(""));
}
@Test
void testTruncated() {
assertThrows(IOException.class, () -> new ArrayReplyHeader("*"));
}
@Test
void testBadNumber() {
assertThrows(IOException.class, () -> new ArrayReplyHeader("*ABC"));
}
@Test
void testValid() throws IOException {
assertEquals(4, new ArrayReplyHeader("*4").getElementCount());
}

View File

@@ -4,36 +4,36 @@
*/
package org.whispersystems.dispatch.redis.protocol;
import org.junit.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.IOException;
import org.junit.jupiter.api.Test;
import static org.junit.Assert.assertEquals;
class IntReplyHeaderTest {
public class IntReplyHeaderTest {
@Test(expected = IOException.class)
public void testNull() throws IOException {
new IntReply(null);
}
@Test(expected = IOException.class)
public void testEmpty() throws IOException {
new IntReply("");
}
@Test(expected = IOException.class)
public void testBadNumber() throws IOException {
new IntReply(":A");
}
@Test(expected = IOException.class)
public void testBadFormat() throws IOException {
new IntReply("*");
@Test
void testNull() {
assertThrows(IOException.class, () -> new IntReply(null));
}
@Test
public void testValid() throws IOException {
void testEmpty() {
assertThrows(IOException.class, () -> new IntReply(""));
}
@Test
void testBadNumber() {
assertThrows(IOException.class, () -> new IntReply(":A"));
}
@Test
void testBadFormat() {
assertThrows(IOException.class, () -> new IntReply("*"));
}
@Test
void testValid() throws IOException {
assertEquals(23, new IntReply(":23").getValue());
}
}

View File

@@ -4,48 +4,32 @@
*/
package org.whispersystems.dispatch.redis.protocol;
import org.junit.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.IOException;
import org.junit.jupiter.api.Test;
import static org.junit.Assert.assertEquals;
public class StringReplyHeaderTest {
class StringReplyHeaderTest {
@Test
public void testNull() {
try {
new StringReplyHeader(null);
throw new AssertionError();
} catch (IOException e) {
// good
}
void testNull() {
assertThrows(IOException.class, () -> new StringReplyHeader(null));
}
@Test
public void testBadNumber() {
try {
new StringReplyHeader("$100A");
throw new AssertionError();
} catch (IOException e) {
// good
}
void testBadNumber() {
assertThrows(IOException.class, () -> new StringReplyHeader("$100A"));
}
@Test
public void testBadPrefix() {
try {
new StringReplyHeader("*");
throw new AssertionError();
} catch (IOException e) {
// good
}
void testBadPrefix() {
assertThrows(IOException.class, () -> new StringReplyHeader("*"));
}
@Test
public void testValid() throws IOException {
void testValid() throws IOException {
assertEquals(1000, new StringReplyHeader("$1000").getStringLength());
}
}

View File

@@ -3,11 +3,37 @@
# `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.
adminEventLoggingConfiguration:
credentials: |
Some credentials text
blah blah blah
projectId: some-project-id
logName: some-log-name
stripe:
apiKey: unset
idempotencyKeyGenerator: abcdefg12345678= # base64 for creating request idempotency hash
boostDescription: >
Example
supportedCurrencies:
- xts
# - ...
# - Nth supported currency
braintree:
merchantId: unset
publicKey: unset
privateKey: unset
environment: unset
graphqlUrl: unset
merchantAccounts:
# ISO 4217 currency code and its corresponding sub-merchant account
'xts': unset
supportedCurrencies:
- xts
# - ...
# - Nth supported currency
dynamoDbClientConfiguration:
region: us-west-2 # AWS Region
@@ -55,40 +81,6 @@ dynamoDbTables:
subscriptions:
tableName: Example_Subscriptions
twilio: # Twilio gateway configuration
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: 1000 # Size of push pending queue
turn: # TURN server configuration
secret: example # TURN server secret
uris:
- 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
configurationUri: redis://redis.example.com:6379/
@@ -119,32 +111,34 @@ directory:
- 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-----
replicationCaCertificates: # 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
userIdTokenSharedSecret: bbcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with CDS to generate auth identity tokens for Signal users
messageCache: # Redis server configuration for message store cache
persistDelayMinutes: 1
@@ -195,12 +189,6 @@ gcpAttachments: # GCP Storage configuration
AAAAAAAA
-----END PRIVATE KEY-----
abuseDatabase: # Postgresql database configuration
driverClass: org.postgresql.Driver
user: example
password: password
url: jdbc:postgresql://example.com:5432/abusedb
accountDatabaseCrawler:
chunkSize: 10 # accounts per run
chunkIntervalMs: 60000 # time per run
@@ -218,9 +206,9 @@ apn: # Apple Push Notifications configuration
AAAAAAAA
-----END PRIVATE KEY-----
gcm: # GCM Configuration
senderId: 123456789
apiKey: unset
fcm: # FCM configuration
credentials: |
{ "json": true }
cdn:
accessKey: test # AWS Access Key ID
@@ -243,65 +231,65 @@ voiceVerification:
- en
recaptcha:
secret: unset
recaptchaV2:
siteKey: unset
scoreFloor: 1.0
projectPath: projects/example
credentialConfigurationJson: "{ }" # service account configuration for backend authentication
hCaptcha:
apiKey: unset
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-----
storageCaCertificates:
- |
-----BEGIN CERTIFICATE-----
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
AAAAAAAAAAAAAAAAAAAA
-----END CERTIFICATE-----
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-----
backupCaCertificates:
- |
-----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
@@ -324,17 +312,16 @@ remoteConfig:
paymentsService:
userAuthenticationTokenSharedSecret: 0000000f0000000f0000000f0000000f0000000f0000000f0000000f0000000f # hex-encoded 32-byte secret shared with MobileCoin services used to generate auth tokens for Signal users
fixerApiKey: unset
coinMarketCapApiKey: unset
coinMarketCapCurrencyIds:
MOB: 7878
paymentCurrencies:
# list of symbols for supported currencies
- MOB
donation:
uri: donation.example.com # value
supportedCurrencies:
- # 1st supported currency
- # 2nd supported currency
- # ...
- # Nth supported currency
artService:
userAuthenticationTokenSharedSecret: 0000000f0000000f0000000f0000000f0000000f0000000f0000000f0000000f # hex-encoded 32-byte secret not shared with any external service, but used in ArtController
userAuthenticationTokenUserIdSecret: 00000f # hex-encoded secret to obscure user phone numbers from Sticker Creator
badges:
badges:
@@ -365,18 +352,54 @@ subscription: # configuration for Stripe subscriptions
# list of ISO 4217 currency codes and amounts for the given badge level
xts:
amount: '10'
id: price_example # stripe ID
processorIds:
STRIPE: price_example # stripe Price ID
BRAINTREE: plan_example # braintree Plan ID
boost:
level: 1
expiration: P90D
badge: EXAMPLE
oneTimeDonations:
boost:
level: 1
expiration: P90D
badge: EXAMPLE
gift:
level: 10
expiration: P90D
badge: EXAMPLE
currencies:
# ISO 4217 currency codes and amounts in those currencies
xts:
- '1'
- '2'
- '4'
- '8'
- '20'
- '40'
minimum: '0.5'
gift: '2'
boosts:
- '1'
- '2'
- '4'
- '8'
- '20'
- '40'
registrationService:
host: registration.example.com
apiKey: EXAMPLE
registrationCaCertificate: | # Registration service TLS certificate trust root
-----BEGIN CERTIFICATE-----
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
AAAAAAAAAAAAAAAAAAAA
-----END CERTIFICATE-----

View File

@@ -24,6 +24,11 @@
<artifactId>jakarta.ws.rs-api</artifactId>
</dependency>
<dependency>
<groupId>org.whispersystems.textsecure</groupId>
<artifactId>event-logger</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.whispersystems.textsecure</groupId>
<artifactId>redis-dispatch</artifactId>
@@ -34,30 +39,15 @@
<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.9.0</version>
</dependency>
<dependency>
<groupId>org.whispersystems</groupId>
<artifactId>curve25519-java</artifactId>
<version>0.5.0</version>
<artifactId>libsignal-server</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-core</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-jdbi3</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-auth</artifactId>
@@ -129,24 +119,10 @@
<artifactId>logstash-logback-encoder</artifactId>
</dependency>
<dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi3-core</artifactId>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-jdbi3</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-healthchecks</artifactId>
@@ -176,6 +152,12 @@
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-testing</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
@@ -200,6 +182,12 @@
<artifactId>commons-csv</artifactId>
</dependency>
<dependency>
<groupId>com.google.firebase</groupId>
<artifactId>firebase-admin</artifactId>
<version>9.1.1</version>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
@@ -213,6 +201,30 @@
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-retry</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-reactor</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
</dependency>
<!-- Needed for gRPC with Java 9+ -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
@@ -272,12 +284,12 @@
<artifactId>appconfig</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-core</artifactId>
<groupId>software.amazon.awssdk</groupId>
<artifactId>appconfigdata</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<artifactId>aws-java-sdk-core</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
@@ -301,12 +313,6 @@
<artifactId>lettuce-core</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.eatthepath</groupId>
<artifactId>pushy</artifactId>
@@ -316,12 +322,6 @@
<artifactId>pushy-dropwizard-metrics-listener</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-tcnative-boringssl-static</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.vdurmont</groupId>
<artifactId>semver4j</artifactId>
@@ -351,6 +351,12 @@
<groupId>org.glassfish.jersey.test-framework</groupId>
<artifactId>jersey-test-framework-core</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
@@ -368,13 +374,6 @@
</exclusions>
</dependency>
<dependency>
<groupId>io.zonky.test</groupId>
<artifactId>embedded-postgres</artifactId>
<version>1.3.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.almworks.sqlite4java</groupId>
<artifactId>sqlite4java</artifactId>
@@ -385,23 +384,21 @@
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.3.16.RELEASE</version>
</dependency>
<dependency>
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
</dependency>
<dependency>
@@ -413,21 +410,15 @@
<dependency>
<groupId>com.fasterxml.uuid</groupId>
<artifactId>java-uuid-generator</artifactId>
<version>3.2.0</version>
<version>4.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>DynamoDBLocal</artifactId>
<version>1.16.0</version>
<version>1.20.0</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.antlr</groupId>
<artifactId>antlr4-runtime</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
@@ -441,11 +432,16 @@
</dependency>
<dependency>
<groupId>pl.pragmatists</groupId>
<artifactId>JUnitParams</artifactId>
<version>1.1.1</version>
<scope>test</scope>
<groupId>com.braintreepayments.gateway</groupId>
<artifactId>braintree-java</artifactId>
</dependency>
<dependency>
<groupId>com.apollographql.apollo3</groupId>
<artifactId>apollo-api-jvm</artifactId>
<version>3.7.1</version>
</dependency>
</dependencies>
<profiles>
@@ -601,6 +597,31 @@
</arguments>
</configuration>
</plugin>
<plugin>
<groupId>com.github.aoudiamoncef</groupId>
<artifactId>apollo-client-maven-plugin</artifactId>
<version>5.0.0</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<services>
<braintree>
<compilationUnit>
<name>braintree</name>
<compilerParams>
<schemaPackageName>com.braintree.graphql.client</schemaPackageName>
</compilerParams>
</compilationUnit>
</braintree>
</services>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,9 @@
# https://graphql.braintreepayments.com/reference/#Mutation--chargePaymentMethod
mutation ChargePayPalOneTimePayment($input: ChargePaymentMethodInput!) {
chargePaymentMethod(input: $input) {
transaction {
id,
status
}
}
}

View File

@@ -0,0 +1,6 @@
mutation CreatePayPalBillingAgreement($input: CreatePayPalBillingAgreementInput!) {
createPayPalBillingAgreement(input: $input) {
approvalUrl,
billingAgreementToken
}
}

View File

@@ -0,0 +1,7 @@
# https://graphql.braintreepayments.com/reference/#Mutation--createPayPalOneTimePayment
mutation CreatePayPalOneTimePayment($input: CreatePayPalOneTimePaymentInput!) {
createPayPalOneTimePayment(input: $input) {
approvalUrl,
paymentId
}
}

View File

@@ -0,0 +1,7 @@
mutation TokenizePayPalBillingAgreement($input: TokenizePayPalBillingAgreementInput!) {
tokenizePayPalBillingAgreement(input: $input) {
paymentMethod {
id
}
}
}

View File

@@ -0,0 +1,8 @@
# https://graphql.braintreepayments.com/reference/#Mutation--tokenizePayPalOneTimePayment
mutation TokenizePayPalOneTimePayment($input: TokenizePayPalOneTimePaymentInput!) {
tokenizePayPalOneTimePayment(input: $input) {
paymentMethod {
id
}
}
}

View File

@@ -0,0 +1,7 @@
mutation VaultPaymentMethod($input: VaultPaymentMethodInput!) {
vaultPaymentMethod(input: $input) {
paymentMethod {
id
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,11 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.dropwizard.Configuration;
import io.dropwizard.client.JerseyClientConfiguration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@@ -15,30 +14,31 @@ 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.AdminEventLoggingConfiguration;
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.BraintreeConfiguration;
import org.whispersystems.textsecuregcm.configuration.CdnConfiguration;
import org.whispersystems.textsecuregcm.configuration.DatabaseConfiguration;
import org.whispersystems.textsecuregcm.configuration.DatadogConfiguration;
import org.whispersystems.textsecuregcm.configuration.DirectoryConfiguration;
import org.whispersystems.textsecuregcm.configuration.DirectoryV2Configuration;
import org.whispersystems.textsecuregcm.configuration.DonationConfiguration;
import org.whispersystems.textsecuregcm.configuration.DynamoDbClientConfiguration;
import org.whispersystems.textsecuregcm.configuration.DynamoDbTables;
import org.whispersystems.textsecuregcm.configuration.GcmConfiguration;
import org.whispersystems.textsecuregcm.configuration.FcmConfiguration;
import org.whispersystems.textsecuregcm.configuration.GcpAttachmentsConfiguration;
import org.whispersystems.textsecuregcm.configuration.HCaptchaConfiguration;
import org.whispersystems.textsecuregcm.configuration.MaxDeviceConfiguration;
import org.whispersystems.textsecuregcm.configuration.MessageCacheConfiguration;
import org.whispersystems.textsecuregcm.configuration.OneTimeDonationConfiguration;
import org.whispersystems.textsecuregcm.configuration.PaymentsServiceConfiguration;
import org.whispersystems.textsecuregcm.configuration.PushConfiguration;
import org.whispersystems.textsecuregcm.configuration.ArtServiceConfiguration;
import org.whispersystems.textsecuregcm.configuration.RateLimitsConfiguration;
import org.whispersystems.textsecuregcm.configuration.RecaptchaConfiguration;
import org.whispersystems.textsecuregcm.configuration.RecaptchaV2Configuration;
import org.whispersystems.textsecuregcm.configuration.RedisClusterConfiguration;
import org.whispersystems.textsecuregcm.configuration.RedisConfiguration;
import org.whispersystems.textsecuregcm.configuration.RegistrationServiceConfiguration;
import org.whispersystems.textsecuregcm.configuration.RemoteConfigConfiguration;
import org.whispersystems.textsecuregcm.configuration.ReportMessageConfiguration;
import org.whispersystems.textsecuregcm.configuration.SecureBackupServiceConfiguration;
@@ -46,9 +46,8 @@ import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfig
import org.whispersystems.textsecuregcm.configuration.StripeConfiguration;
import org.whispersystems.textsecuregcm.configuration.SubscriptionConfiguration;
import org.whispersystems.textsecuregcm.configuration.TestDeviceConfiguration;
import org.whispersystems.textsecuregcm.configuration.TurnConfiguration;
import org.whispersystems.textsecuregcm.configuration.TwilioConfiguration;
import org.whispersystems.textsecuregcm.configuration.UnidentifiedDeliveryConfiguration;
import org.whispersystems.textsecuregcm.configuration.UsernameConfiguration;
import org.whispersystems.textsecuregcm.configuration.VoiceVerificationConfiguration;
import org.whispersystems.textsecuregcm.configuration.ZkConfig;
import org.whispersystems.websocket.configuration.WebSocketConfiguration;
@@ -56,11 +55,21 @@ import org.whispersystems.websocket.configuration.WebSocketConfiguration;
/** @noinspection MismatchedQueryAndUpdateOfCollection, WeakerAccess */
public class WhisperServerConfiguration extends Configuration {
@NotNull
@Valid
@JsonProperty
private AdminEventLoggingConfiguration adminEventLoggingConfiguration;
@NotNull
@Valid
@JsonProperty
private StripeConfiguration stripe;
@NotNull
@Valid
@JsonProperty
private BraintreeConfiguration braintree;
@NotNull
@Valid
@JsonProperty
@@ -71,16 +80,6 @@ public class WhisperServerConfiguration extends Configuration {
@JsonProperty
private DynamoDbTables dynamoDbTables;
@NotNull
@Valid
@JsonProperty
private TwilioConfiguration twilio;
@NotNull
@Valid
@JsonProperty
private PushConfiguration push;
@NotNull
@Valid
@JsonProperty
@@ -151,11 +150,6 @@ public class WhisperServerConfiguration extends Configuration {
@JsonProperty
private RedisClusterConfiguration clientPresenceCluster;
@Valid
@NotNull
@JsonProperty
private DatabaseConfiguration abuseDatabase;
@Valid
@NotNull
@JsonProperty
@@ -171,11 +165,6 @@ public class WhisperServerConfiguration extends Configuration {
@JsonProperty
private RateLimitsConfiguration limits = new RateLimitsConfiguration();
@Valid
@NotNull
@JsonProperty
private JerseyClientConfiguration httpClient = new JerseyClientConfiguration();
@Valid
@NotNull
@JsonProperty
@@ -184,12 +173,7 @@ public class WhisperServerConfiguration extends Configuration {
@Valid
@NotNull
@JsonProperty
private TurnConfiguration turn;
@Valid
@NotNull
@JsonProperty
private GcmConfiguration gcm;
private FcmConfiguration fcm;
@Valid
@NotNull
@@ -214,7 +198,7 @@ public class WhisperServerConfiguration extends Configuration {
@Valid
@NotNull
@JsonProperty
private RecaptchaV2Configuration recaptchaV2;
private HCaptchaConfiguration hCaptcha;
@Valid
@NotNull
@@ -231,6 +215,11 @@ public class WhisperServerConfiguration extends Configuration {
@JsonProperty
private PaymentsServiceConfiguration paymentsService;
@Valid
@NotNull
@JsonProperty
private ArtServiceConfiguration artService;
@Valid
@NotNull
@JsonProperty
@@ -246,11 +235,6 @@ public class WhisperServerConfiguration extends Configuration {
@JsonProperty
private AppConfigConfiguration appConfig;
@Valid
@NotNull
@JsonProperty
private DonationConfiguration donation;
@Valid
@NotNull
@JsonProperty
@@ -264,23 +248,39 @@ public class WhisperServerConfiguration extends Configuration {
@Valid
@JsonProperty
@NotNull
private BoostConfiguration boost;
private OneTimeDonationConfiguration oneTimeDonations;
@Valid
@NotNull
@JsonProperty
private ReportMessageConfiguration reportMessage = new ReportMessageConfiguration();
@Valid
@NotNull
@JsonProperty
private UsernameConfiguration username = new UsernameConfiguration();
@Valid
@JsonProperty
private AbusiveMessageFilterConfiguration abusiveMessageFilter;
private Map<String, String> transparentDataIndex = new HashMap<>();
@Valid
@NotNull
@JsonProperty
private RegistrationServiceConfiguration registrationService;
public AdminEventLoggingConfiguration getAdminEventLoggingConfiguration() {
return adminEventLoggingConfiguration;
}
public StripeConfiguration getStripe() {
return stripe;
}
public BraintreeConfiguration getBraintree() {
return braintree;
}
public DynamoDbClientConfiguration getDynamoDbClientConfiguration() {
return dynamoDbClientConfiguration;
}
@@ -293,8 +293,8 @@ public class WhisperServerConfiguration extends Configuration {
return recaptcha;
}
public RecaptchaV2Configuration getRecaptchaV2Configuration() {
return recaptchaV2;
public HCaptchaConfiguration getHCaptchaConfiguration() {
return hCaptcha;
}
public VoiceVerificationConfiguration getVoiceVerificationConfiguration() {
@@ -305,18 +305,6 @@ public class WhisperServerConfiguration extends Configuration {
return webSocket;
}
public TwilioConfiguration getTwilioConfiguration() {
return twilio;
}
public PushConfiguration getPushConfiguration() {
return push;
}
public JerseyClientConfiguration getJerseyClientConfiguration() {
return httpClient;
}
public AwsAttachmentsConfiguration getAwsAttachmentsConfiguration() {
return awsAttachments;
}
@@ -369,20 +357,12 @@ public class WhisperServerConfiguration extends Configuration {
return rateLimitersCluster;
}
public DatabaseConfiguration getAbuseDatabaseConfiguration() {
return abuseDatabase;
}
public RateLimitsConfiguration getLimitsConfiguration() {
return limits;
}
public TurnConfiguration getTurnConfiguration() {
return turn;
}
public GcmConfiguration getGcmConfiguration() {
return gcm;
public FcmConfiguration getFcmConfiguration() {
return fcm;
}
public ApnConfiguration getApnConfiguration() {
@@ -423,10 +403,6 @@ public class WhisperServerConfiguration extends Configuration {
return results;
}
public Map<String, String> getTransparentDataIndex() {
return transparentDataIndex;
}
public SecureBackupServiceConfiguration getSecureBackupServiceConfiguration() {
return backupService;
}
@@ -435,6 +411,10 @@ public class WhisperServerConfiguration extends Configuration {
return paymentsService;
}
public ArtServiceConfiguration getArtServiceConfiguration() {
return artService;
}
public ZkConfig getZkConfig() {
return zkConfig;
}
@@ -447,10 +427,6 @@ public class WhisperServerConfiguration extends Configuration {
return appConfig;
}
public DonationConfiguration getDonationConfiguration() {
return donation;
}
public BadgesConfiguration getBadges() {
return badges;
}
@@ -459,8 +435,8 @@ public class WhisperServerConfiguration extends Configuration {
return subscription;
}
public BoostConfiguration getBoost() {
return boost;
public OneTimeDonationConfiguration getOneTimeDonations() {
return oneTimeDonations;
}
public ReportMessageConfiguration getReportMessageConfiguration() {
@@ -470,4 +446,12 @@ public class WhisperServerConfiguration extends Configuration {
public AbusiveMessageFilterConfiguration getAbusiveMessageFilterConfiguration() {
return abusiveMessageFilter;
}
public UsernameConfiguration getUsername() {
return username;
}
public RegistrationServiceConfiguration getRegistrationServiceConfiguration() {
return registrationService;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* Copyright 2013-2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm;
@@ -11,10 +11,11 @@ import com.amazonaws.auth.InstanceProfileCredentialsProvider;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.codahale.metrics.SharedMetricRegistries;
import com.codahale.metrics.jdbi3.strategies.DefaultNameStrategy;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.logging.LoggingOptions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
@@ -24,10 +25,10 @@ import io.dropwizard.auth.PolymorphicAuthDynamicFeature;
import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider;
import io.dropwizard.auth.basic.BasicCredentialAuthFilter;
import io.dropwizard.auth.basic.BasicCredentials;
import io.dropwizard.db.PooledDataSourceFactory;
import io.dropwizard.jdbi3.JdbiFactory;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import io.lettuce.core.metrics.MicrometerCommandLatencyRecorder;
import io.lettuce.core.metrics.MicrometerOptions;
import io.lettuce.core.resource.ClientResources;
import io.micrometer.core.instrument.Meter.Id;
import io.micrometer.core.instrument.Metrics;
@@ -35,7 +36,9 @@ import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.config.MeterFilter;
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
import io.micrometer.datadog.DatadogMeterRegistry;
import java.io.ByteArrayInputStream;
import java.net.http.HttpClient;
import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.time.Duration;
import java.util.ArrayList;
@@ -47,20 +50,23 @@ import java.util.ServiceLoader;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletRegistration;
import org.eclipse.jetty.servlets.CrossOriginFilter;
import org.glassfish.jersey.server.ServerProperties;
import org.jdbi.v3.core.Jdbi;
import org.signal.event.AdminEventLogger;
import org.signal.event.GoogleCloudAdminEventLogger;
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.signal.libsignal.zkgroup.ServerSecretParams;
import org.signal.libsignal.zkgroup.auth.ServerZkAuthOperations;
import org.signal.libsignal.zkgroup.profiles.ServerZkProfileOperations;
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialPresentation;
import org.signal.libsignal.zkgroup.receipts.ServerZkReceiptOperations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.dispatch.DispatchManager;
@@ -77,11 +83,11 @@ 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.captcha.CaptchaChecker;
import org.whispersystems.textsecuregcm.captcha.HCaptchaClient;
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;
@@ -100,30 +106,27 @@ 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.ArtController;
import org.whispersystems.textsecuregcm.controllers.SubscriptionController;
import org.whispersystems.textsecuregcm.controllers.VoiceVerificationController;
import org.whispersystems.textsecuregcm.currency.CoinMarketCapClient;
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.RequestStatisticsFilter;
import org.whispersystems.textsecuregcm.filters.RemoteDeprecationFilter;
import org.whispersystems.textsecuregcm.filters.TimestampResponseFilter;
import org.whispersystems.textsecuregcm.limits.DynamicRateLimiters;
import org.whispersystems.textsecuregcm.limits.PushChallengeManager;
import org.whispersystems.textsecuregcm.limits.RateLimitChallengeManager;
import org.whispersystems.textsecuregcm.limits.RateLimitChallengeOptionManager;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
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;
@@ -131,45 +134,45 @@ import org.whispersystems.textsecuregcm.metrics.CpuUsageGauge;
import org.whispersystems.textsecuregcm.metrics.FileDescriptorGauge;
import org.whispersystems.textsecuregcm.metrics.FreeMemoryGauge;
import org.whispersystems.textsecuregcm.metrics.GarbageCollectionGauges;
import org.whispersystems.textsecuregcm.metrics.LettuceMetricsMeterFilter;
import org.whispersystems.textsecuregcm.metrics.MaxFileDescriptorGauge;
import org.whispersystems.textsecuregcm.metrics.MetricsApplicationEventListener;
import org.whispersystems.textsecuregcm.metrics.MetricsRequestEventListener;
import org.whispersystems.textsecuregcm.metrics.MicrometerRegistryManager;
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.ReportedMessageMetricsListener;
import org.whispersystems.textsecuregcm.metrics.TrafficSource;
import org.whispersystems.textsecuregcm.providers.MultiRecipientMessageProvider;
import org.whispersystems.textsecuregcm.providers.RedisClientFactory;
import org.whispersystems.textsecuregcm.providers.RedisClusterHealthCheck;
import org.whispersystems.textsecuregcm.push.APNSender;
import org.whispersystems.textsecuregcm.push.ApnFallbackManager;
import org.whispersystems.textsecuregcm.push.ApnPushNotificationScheduler;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.GCMSender;
import org.whispersystems.textsecuregcm.push.FcmSender;
import org.whispersystems.textsecuregcm.push.MessageSender;
import org.whispersystems.textsecuregcm.push.ProvisioningManager;
import org.whispersystems.textsecuregcm.push.PushLatencyManager;
import org.whispersystems.textsecuregcm.push.PushNotificationManager;
import org.whispersystems.textsecuregcm.push.ReceiptSender;
import org.whispersystems.textsecuregcm.recaptcha.EnterpriseRecaptchaClient;
import org.whispersystems.textsecuregcm.recaptcha.LegacyRecaptchaClient;
import org.whispersystems.textsecuregcm.recaptcha.TransitionalRecaptchaClient;
import org.whispersystems.textsecuregcm.captcha.RecaptchaClient;
import org.whispersystems.textsecuregcm.redis.ConnectionEventLogger;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
import org.whispersystems.textsecuregcm.redis.ReplicatedJedisPool;
import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient;
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.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.ChangeNumberManager;
import org.whispersystems.textsecuregcm.storage.ContactDiscoveryWriter;
import org.whispersystems.textsecuregcm.storage.DeletedAccounts;
import org.whispersystems.textsecuregcm.storage.DeletedAccountsDirectoryReconciler;
@@ -178,7 +181,6 @@ 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.IssuedReceiptsManager;
import org.whispersystems.textsecuregcm.storage.Keys;
import org.whispersystems.textsecuregcm.storage.MessagePersister;
@@ -189,6 +191,7 @@ import org.whispersystems.textsecuregcm.storage.NonNormalizedAccountCrawlerListe
import org.whispersystems.textsecuregcm.storage.PhoneNumberIdentifiers;
import org.whispersystems.textsecuregcm.storage.Profiles;
import org.whispersystems.textsecuregcm.storage.ProfilesManager;
import org.whispersystems.textsecuregcm.storage.ProhibitedUsernames;
import org.whispersystems.textsecuregcm.storage.PubSubManager;
import org.whispersystems.textsecuregcm.storage.PushChallengeDynamoDb;
import org.whispersystems.textsecuregcm.storage.PushFeedbackProcessor;
@@ -197,18 +200,18 @@ 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.VerificationCodeStore;
import org.whispersystems.textsecuregcm.stripe.StripeManager;
import org.whispersystems.textsecuregcm.subscriptions.BraintreeManager;
import org.whispersystems.textsecuregcm.subscriptions.StripeManager;
import org.whispersystems.textsecuregcm.util.Constants;
import org.whispersystems.textsecuregcm.util.DynamoDbFromConfig;
import org.whispersystems.textsecuregcm.util.HostnameUtil;
import org.whispersystems.textsecuregcm.util.UsernameGenerator;
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.AssignUsernameCommand;
@@ -223,6 +226,7 @@ import org.whispersystems.textsecuregcm.workers.SetUserDiscoverabilityCommand;
import org.whispersystems.textsecuregcm.workers.ZkParamsCommand;
import org.whispersystems.websocket.WebSocketResourceProviderFactory;
import org.whispersystems.websocket.setup.WebSocketEnvironment;
import reactor.core.scheduler.Schedulers;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
@@ -244,13 +248,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
bootstrap.addCommand(new SetUserDiscoverabilityCommand());
bootstrap.addCommand(new ReserveUsernameCommand());
bootstrap.addCommand(new AssignUsernameCommand());
bootstrap.addBundle(new NameableMigrationsBundle<WhisperServerConfiguration>("abusedb", "abusedb.xml") {
@Override
public PooledDataSourceFactory getDataSourceFactory(WhisperServerConfiguration configuration) {
return configuration.getAbuseDatabaseConfiguration();
}
});
}
@Override
@@ -276,15 +273,16 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
config.getDatadogConfiguration(), io.micrometer.core.instrument.Clock.SYSTEM);
datadogMeterRegistry.config().commonTags(
Tags.of(
"service", "chat",
"host", HostnameUtil.getLocalHostname(),
"version", WhisperServerVersion.getServerVersion(),
"env", config.getDatadogConfiguration().getEnvironment()))
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 LettuceMetricsMeterFilter())
.meterFilter(new MeterFilter() {
@Override
public DistributionStatisticConfig configure(final Id id, final DistributionStatisticConfig config) {
@@ -295,6 +293,8 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
Metrics.addRegistry(datadogMeterRegistry);
}
environment.lifecycle().manage(new MicrometerRegistryManager(Metrics.globalRegistry));
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);
@@ -306,12 +306,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
ResourceBundleLevelTranslator resourceBundleLevelTranslator = new ResourceBundleLevelTranslator(
headerControlledResourceBundleLookup);
JdbiFactory jdbiFactory = new JdbiFactory(DefaultNameStrategy.CHECK_EMPTY);
Jdbi abuseJdbi = jdbiFactory.build(environment, config.getAbuseDatabaseConfiguration(), "abusedb");
FaultTolerantDatabase abuseDatabase = new FaultTolerantDatabase("abuse_database", abuseJdbi,
config.getAbuseDatabaseConfiguration().getCircuitBreakerConfiguration());
DynamoDbAsyncClient dynamoDbAsyncClient = DynamoDbFromConfig.asyncClient(
config.getDynamoDbClientConfiguration(),
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create());
@@ -339,7 +333,16 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
config.getAppConfig().getConfigurationName(),
DynamicConfiguration.class);
Accounts accounts = new Accounts(dynamoDbClient,
BlockingQueue<Runnable> messageDeletionQueue = new LinkedBlockingQueue<>();
Metrics.gaugeCollectionSize(name(getClass(), "messageDeletionQueueSize"), Collections.emptyList(),
messageDeletionQueue);
ExecutorService messageDeletionAsyncExecutor = environment.lifecycle()
.executorService(name(getClass(), "messageDeletionAsyncExecutor-%d")).maxThreads(16)
.workQueue(messageDeletionQueue).build();
Accounts accounts = new Accounts(
dynamoDbClient,
dynamoDbAsyncClient,
config.getDynamoDbTables().getAccounts().getTableName(),
config.getDynamoDbTables().getAccounts().getPhoneNumberTableName(),
config.getDynamoDbTables().getAccounts().getPhoneNumberIdentifierTableName(),
@@ -347,15 +350,15 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
config.getDynamoDbTables().getAccounts().getScanPageSize());
PhoneNumberIdentifiers phoneNumberIdentifiers = new PhoneNumberIdentifiers(dynamoDbClient,
config.getDynamoDbTables().getPhoneNumberIdentifiers().getTableName());
ReservedUsernames reservedUsernames = new ReservedUsernames(dynamoDbClient,
ProhibitedUsernames prohibitedUsernames = new ProhibitedUsernames(dynamoDbClient,
config.getDynamoDbTables().getReservedUsernames().getTableName());
Profiles profiles = new Profiles(dynamoDbClient, dynamoDbAsyncClient,
config.getDynamoDbTables().getProfiles().getTableName());
Keys keys = new Keys(dynamoDbClient, config.getDynamoDbTables().getKeys().getTableName());
MessagesDynamoDb messagesDynamoDb = new MessagesDynamoDb(dynamoDbClient,
MessagesDynamoDb messagesDynamoDb = new MessagesDynamoDb(dynamoDbClient, dynamoDbAsyncClient,
config.getDynamoDbTables().getMessages().getTableName(),
config.getDynamoDbTables().getMessages().getExpiration());
AbusiveHostRules abusiveHostRules = new AbusiveHostRules(abuseDatabase);
config.getDynamoDbTables().getMessages().getExpiration(),
messageDeletionAsyncExecutor);
RemoteConfigs remoteConfigs = new RemoteConfigs(dynamoDbClient,
config.getDynamoDbTables().getRemoteConfig().getTableName());
PushChallengeDynamoDb pushChallengeDynamoDb = new PushChallengeDynamoDb(dynamoDbClient,
@@ -368,10 +371,17 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
VerificationCodeStore pendingDevices = new VerificationCodeStore(dynamoDbClient,
config.getDynamoDbTables().getPendingDevices().getTableName());
RedisClientFactory pubSubClientFactory = new RedisClientFactory("pubsub_cache", config.getPubsubCacheConfiguration().getUrl(), config.getPubsubCacheConfiguration().getReplicaUrls(), config.getPubsubCacheConfiguration().getCircuitBreakerConfiguration());
ReplicatedJedisPool pubsubClient = pubSubClientFactory.getRedisClientPool();
reactor.util.Metrics.MicrometerConfiguration.useRegistry(Metrics.globalRegistry);
Schedulers.enableMetrics();
ClientResources redisClientResources = ClientResources.builder().build();
RedisClientFactory pubSubClientFactory = new RedisClientFactory("pubsub_cache",
config.getPubsubCacheConfiguration().getUrl(), config.getPubsubCacheConfiguration().getReplicaUrls(),
config.getPubsubCacheConfiguration().getCircuitBreakerConfiguration());
ReplicatedJedisPool pubsubClient = pubSubClientFactory.getRedisClientPool();
MicrometerOptions options = MicrometerOptions.builder().build();
ClientResources redisClientResources = ClientResources.builder()
.commandLatencyRecorder(new MicrometerCommandLatencyRecorder(Metrics.globalRegistry, options)).build();
ConnectionEventLogger.logConnectionEvents(redisClientResources);
FaultTolerantRedisCluster cacheCluster = new FaultTolerantRedisCluster("main_cache_cluster", config.getCacheClusterConfiguration(), redisClientResources);
@@ -381,71 +391,115 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
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);
final BlockingQueue<Runnable> keyspaceNotificationDispatchQueue = new ArrayBlockingQueue<>(100_000);
Metrics.gaugeCollectionSize(name(getClass(), "keyspaceNotificationDispatchQueueSize"), Collections.emptyList(), keyspaceNotificationDispatchQueue);
final BlockingQueue<Runnable> receiptSenderQueue = new LinkedBlockingQueue<>();
Metrics.gaugeCollectionSize(name(getClass(), "receiptSenderQueue"), Collections.emptyList(), receiptSenderQueue);
final BlockingQueue<Runnable> fcmSenderQueue = new LinkedBlockingQueue<>();
Metrics.gaugeCollectionSize(name(getClass(), "fcmSenderQueue"), Collections.emptyList(), fcmSenderQueue);
ScheduledExecutorService recurringJobExecutor = environment.lifecycle()
.scheduledExecutorService(name(getClass(), "recurringJob-%d")).threads(6).build();
ScheduledExecutorService retrySchedulingExecutor = environment.lifecycle().scheduledExecutorService(name(getClass(), "retry-%d")).threads(2).build();
ScheduledExecutorService websocketScheduledExecutor = environment.lifecycle().scheduledExecutorService(name(getClass(), "websocket-%d")).threads(8).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 fcmSenderExecutor = environment.lifecycle().executorService(name(getClass(), "fcmSender-%d")).maxThreads(32).minThreads(32).workQueue(fcmSenderQueue).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 accountDeletionExecutor = environment.lifecycle().executorService(name(getClass(), "accountCleaner-%d")).maxThreads(16).minThreads(16).build();
// TODO: generally speaking this is a DynamoDB I/O executor for the accounts table; we should eventually have a general executor for speaking to the accounts table, but most of the server is still synchronous so this isn't widely useful yet
ExecutorService batchIdentityCheckExecutor = environment.lifecycle().executorService(name(getClass(), "batchIdentityCheck-%d")).minThreads(32).maxThreads(32).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).
ExecutorService subscriptionProcessorExecutor = environment.lifecycle()
.executorService(name(getClass(), "subscriptionProcessor-%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();
ExecutorService receiptSenderExecutor = environment.lifecycle()
.executorService(name(getClass(), "receiptSender-%d"))
.maxThreads(2)
.minThreads(2)
.workQueue(receiptSenderQueue)
.rejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy())
.build();
ExecutorService registrationCallbackExecutor = environment.lifecycle()
.executorService(name(getClass(), "registration-%d"))
.maxThreads(2)
.minThreads(2)
.build();
StripeManager stripeManager = new StripeManager(config.getStripe().getApiKey(), stripeExecutor,
config.getStripe().getIdempotencyKeyGenerator(), config.getStripe().getBoostDescription());
final AdminEventLogger adminEventLogger = new GoogleCloudAdminEventLogger(
LoggingOptions.newBuilder().setProjectId(config.getAdminEventLoggingConfiguration().projectId())
.setCredentials(GoogleCredentials.fromStream(new ByteArrayInputStream(
config.getAdminEventLoggingConfiguration().credentials().getBytes(StandardCharsets.UTF_8))))
.build().getService(),
config.getAdminEventLoggingConfiguration().projectId(),
config.getAdminEventLoggingConfiguration().logName());
StripeManager stripeManager = new StripeManager(config.getStripe().apiKey(), subscriptionProcessorExecutor,
config.getStripe().idempotencyKeyGenerator(), config.getStripe().boostDescription(), config.getStripe()
.supportedCurrencies());
BraintreeManager braintreeManager = new BraintreeManager(config.getBraintree().merchantId(),
config.getBraintree().publicKey(), config.getBraintree().privateKey(), config.getBraintree().environment(),
config.getBraintree().supportedCurrencies(), config.getBraintree().merchantAccounts(),
config.getBraintree().graphqlUrl(), config.getBraintree().circuitBreaker(), subscriptionProcessorExecutor);
ExternalServiceCredentialGenerator directoryCredentialsGenerator = new ExternalServiceCredentialGenerator(
config.getDirectoryConfiguration().getDirectoryClientConfiguration().getUserAuthenticationTokenSharedSecret(),
config.getDirectoryConfiguration().getDirectoryClientConfiguration().getUserAuthenticationTokenUserIdSecret());
ExternalServiceCredentialGenerator directoryV2CredentialsGenerator = new ExternalServiceCredentialGenerator(
config.getDirectoryV2Configuration().getDirectoryV2ClientConfiguration()
.getUserAuthenticationTokenSharedSecret(), false);
config.getDirectoryV2Configuration().getDirectoryV2ClientConfiguration().getUserAuthenticationTokenSharedSecret(),
config.getDirectoryV2Configuration().getDirectoryV2ClientConfiguration().getUserIdTokenSharedSecret(),
true, false);
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);
ExternalServiceCredentialGenerator artCredentialsGenerator = new ExternalServiceCredentialGenerator(
config.getArtServiceConfiguration().getUserAuthenticationTokenSharedSecret(),
config.getArtServiceConfiguration().getUserAuthenticationTokenUserIdSecret(),
true, false, false);
RegistrationServiceClient registrationServiceClient = new RegistrationServiceClient(config.getRegistrationServiceConfiguration().getHost(), config.getRegistrationServiceConfiguration().getPort(), config.getRegistrationServiceConfiguration().getApiKey(), config.getRegistrationServiceConfiguration().getRegistrationCaCertificate(), registrationCallbackExecutor);
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());
StoredVerificationCodeManager pendingAccountsManager = new StoredVerificationCodeManager(pendingAccounts);
StoredVerificationCodeManager pendingDevicesManager = new StoredVerificationCodeManager(pendingDevices);
ProfilesManager profilesManager = new ProfilesManager(profiles, cacheCluster);
MessagesCache messagesCache = new MessagesCache(messagesCluster, messagesCluster, keyspaceNotificationDispatchExecutor);
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);
ProfilesManager profilesManager = new ProfilesManager(profiles, cacheCluster);
MessagesCache messagesCache = new MessagesCache(messagesCluster, messagesCluster, Clock.systemUTC(),
keyspaceNotificationDispatchExecutor, messageDeletionAsyncExecutor);
PushLatencyManager pushLatencyManager = new PushLatencyManager(metricsCluster, dynamicConfigurationManager);
ReportMessageManager reportMessageManager = new ReportMessageManager(reportMessageDynamoDb, rateLimitersCluster,
config.getReportMessageConfiguration().getCounterTtl());
MessagesManager messagesManager = new MessagesManager(messagesDynamoDb, messagesCache, reportMessageManager,
messageDeletionAsyncExecutor);
UsernameGenerator usernameGenerator = new UsernameGenerator(config.getUsername());
DeletedAccountsManager deletedAccountsManager = new DeletedAccountsManager(deletedAccounts,
deletedAccountsLockDynamoDbClient, config.getDynamoDbTables().getDeletedAccounts().getTableName());
deletedAccountsLockDynamoDbClient, config.getDynamoDbTables().getDeletedAccountsLock().getTableName());
AccountsManager accountsManager = new AccountsManager(accounts, phoneNumberIdentifiers, cacheCluster,
deletedAccountsManager, directoryQueue, keys, messagesManager, reservedUsernames, profilesManager,
pendingAccountsManager, secureStorageClient, secureBackupClient, clientPresenceManager, clock);
deletedAccountsManager, directoryQueue, keys, messagesManager, prohibitedUsernames, profilesManager,
pendingAccountsManager, secureStorageClient, secureBackupClient, clientPresenceManager, usernameGenerator,
experimentEnrollmentManager, clock);
RemoteConfigsManager remoteConfigsManager = new RemoteConfigsManager(remoteConfigs);
DeadLetterHandler deadLetterHandler = new DeadLetterHandler(accountsManager, messagesManager);
DispatchManager dispatchManager = new DispatchManager(pubSubClientFactory, Optional.of(deadLetterHandler));
DispatchManager dispatchManager = new DispatchManager(pubSubClientFactory, Optional.empty());
PubSubManager pubSubManager = new PubSubManager(pubsubClient, dispatchManager);
APNSender apnSender = new APNSender(apnSenderExecutor, accountsManager, config.getApnConfiguration());
GCMSender gcmSender = new GCMSender(gcmSenderExecutor, accountsManager, config.getGcmConfiguration().getApiKey());
APNSender apnSender = new APNSender(apnSenderExecutor, config.getApnConfiguration());
FcmSender fcmSender = new FcmSender(fcmSenderExecutor, config.getFcmConfiguration().credentials());
ApnPushNotificationScheduler apnPushNotificationScheduler = new ApnPushNotificationScheduler(pushSchedulerCluster, apnSender, accountsManager);
PushNotificationManager pushNotificationManager = new PushNotificationManager(accountsManager, apnSender, fcmSender, apnPushNotificationScheduler, pushLatencyManager, dynamicConfigurationManager);
RateLimiters rateLimiters = new RateLimiters(config.getLimitsConfiguration(), rateLimitersCluster);
DynamicRateLimiters dynamicRateLimiters = new DynamicRateLimiters(rateLimitersCluster, dynamicConfigurationManager);
ProvisioningManager provisioningManager = new ProvisioningManager(pubSubManager);
@@ -462,29 +516,30 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
SubscriptionManager subscriptionManager = new SubscriptionManager(
config.getDynamoDbTables().getSubscriptions().getTableName(), dynamoDbAsyncClient);
ReportedMessageMetricsListener reportedMessageMetricsListener = new ReportedMessageMetricsListener(accountsManager);
reportMessageManager.addListener(reportedMessageMetricsListener);
AccountAuthenticator accountAuthenticator = new AccountAuthenticator(accountsManager);
DisabledPermittedAccountAuthenticator disabledPermittedAccountAuthenticator = new DisabledPermittedAccountAuthenticator(accountsManager);
ApnFallbackManager apnFallbackManager = new ApnFallbackManager(pushSchedulerCluster, apnSender, accountsManager);
TwilioSmsSender twilioSmsSender = new TwilioSmsSender(config.getTwilioConfiguration(), dynamicConfigurationManager);
SmsSender smsSender = new SmsSender(twilioSmsSender);
MessageSender messageSender = new MessageSender(apnFallbackManager, clientPresenceManager, messagesManager, gcmSender, apnSender, pushLatencyManager);
ReceiptSender receiptSender = new ReceiptSender(accountsManager, messageSender);
TurnTokenGenerator turnTokenGenerator = new TurnTokenGenerator(config.getTurnConfiguration());
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);
MessageSender messageSender = new MessageSender(clientPresenceManager, messagesManager, pushNotificationManager, pushLatencyManager);
ReceiptSender receiptSender = new ReceiptSender(accountsManager, messageSender, receiptSenderExecutor);
TurnTokenGenerator turnTokenGenerator = new TurnTokenGenerator(dynamicConfigurationManager);
RecaptchaClient recaptchaClient = new RecaptchaClient(
config.getRecaptchaConfiguration().getProjectPath(),
config.getRecaptchaConfiguration().getCredentialConfigurationJson(),
dynamicConfigurationManager);
HttpClient hcaptchaHttpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).connectTimeout(Duration.ofSeconds(10)).build();
HCaptchaClient hCaptchaClient = new HCaptchaClient(config.getHCaptchaConfiguration().apiKey(), hcaptchaHttpClient, dynamicConfigurationManager);
CaptchaChecker captchaChecker = new CaptchaChecker(List.of(recaptchaClient, hCaptchaClient));
PushChallengeManager pushChallengeManager = new PushChallengeManager(pushNotificationManager, pushChallengeDynamoDb);
RateLimitChallengeManager rateLimitChallengeManager = new RateLimitChallengeManager(pushChallengeManager,
transitionalRecaptchaClient, dynamicRateLimiters);
RateLimitChallengeOptionManager rateLimitChallengeOptionManager =
new RateLimitChallengeOptionManager(dynamicRateLimiters, dynamicConfigurationManager);
captchaChecker, dynamicRateLimiters);
MessagePersister messagePersister = new MessagePersister(messagesCache, messagesManager, accountsManager, dynamicConfigurationManager, Duration.ofMinutes(config.getMessageCacheConfiguration().getPersistDelayMinutes()));
ChangeNumberManager changeNumberManager = new ChangeNumberManager(messageSender, accountsManager);
final List<AccountDatabaseCrawlerListener> directoryReconciliationAccountDatabaseCrawlerListeners = new ArrayList<>();
final List<DeletedAccountsDirectoryReconciler> deletedAccountsDirectoryReconcilers = new ArrayList<>();
@@ -513,15 +568,21 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
config.getAccountDatabaseCrawlerConfiguration().getChunkIntervalMs()
);
AccountDatabaseCrawlerCache accountCleanerAccountDatabaseCrawlerCache =
new AccountDatabaseCrawlerCache(cacheCluster, AccountDatabaseCrawlerCache.ACCOUNT_CLEANER_PREFIX);
AccountDatabaseCrawler accountCleanerAccountDatabaseCrawler = new AccountDatabaseCrawler("Account cleaner crawler",
accountsManager,
accountCleanerAccountDatabaseCrawlerCache, List.of(new AccountCleaner(accountsManager, accountDeletionExecutor)),
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)
);
new PushFeedbackProcessor(accountsManager));
AccountDatabaseCrawlerCache accountDatabaseCrawlerCache = new AccountDatabaseCrawlerCache(cacheCluster,
AccountDatabaseCrawlerCache.GENERAL_PURPOSE_PREFIX);
@@ -534,24 +595,25 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
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());
HttpClient currencyClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).connectTimeout(Duration.ofSeconds(10)).build();
FixerClient fixerClient = new FixerClient(currencyClient, config.getPaymentsServiceConfiguration().getFixerApiKey());
CoinMarketCapClient coinMarketCapClient = new CoinMarketCapClient(currencyClient, config.getPaymentsServiceConfiguration().getCoinMarketCapApiKey(), config.getPaymentsServiceConfiguration().getCoinMarketCapCurrencyIds());
CurrencyConversionManager currencyManager = new CurrencyConversionManager(fixerClient, coinMarketCapClient,
cacheCluster, config.getPaymentsServiceConfiguration().getPaymentCurrencies(), Clock.systemUTC());
apnSender.setApnFallbackManager(apnFallbackManager);
environment.lifecycle().manage(new ApplicationShutdownMonitor());
environment.lifecycle().manage(apnFallbackManager);
environment.lifecycle().manage(apnSender);
environment.lifecycle().manage(apnPushNotificationScheduler);
environment.lifecycle().manage(pubSubManager);
environment.lifecycle().manage(messageSender);
environment.lifecycle().manage(accountDatabaseCrawler);
environment.lifecycle().manage(directoryReconciliationAccountDatabaseCrawler);
environment.lifecycle().manage(accountCleanerAccountDatabaseCrawler);
environment.lifecycle().manage(deletedAccountsTableCrawler);
environment.lifecycle().manage(messagesCache);
environment.lifecycle().manage(messagePersister);
environment.lifecycle().manage(clientPresenceManager);
environment.lifecycle().manage(currencyManager);
environment.lifecycle().manage(directoryQueue);
environment.lifecycle().manage(registrationServiceClient);
StaticCredentialsProvider cdnCredentialsProvider = StaticCredentialsProvider
.create(AwsBasicCredentials.create(
@@ -580,12 +642,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
.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(new RequestStatisticsFilter(TrafficSource.HTTP));
environment.jersey().register(MultiRecipientMessageProvider.class);
environment.jersey().register(new MetricsApplicationEventListener(TrafficSource.HTTP));
environment.jersey()
@@ -603,49 +660,53 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
config.getWebSocketConfiguration(), 90000);
webSocketEnvironment.setAuthenticator(new WebSocketAccountAuthenticator(accountAuthenticator));
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);
new AuthenticatedConnectListener(receiptSender, messagesManager, pushNotificationManager,
clientPresenceManager, websocketScheduledExecutor));
webSocketEnvironment.jersey()
.register(new WebsocketRefreshApplicationEventListener(accountsManager, clientPresenceManager));
webSocketEnvironment.jersey().register(new RequestStatisticsFilter(TrafficSource.WEBSOCKET));
webSocketEnvironment.jersey().register(MultiRecipientMessageProvider.class);
webSocketEnvironment.jersey().register(new MetricsApplicationEventListener(TrafficSource.WEBSOCKET));
webSocketEnvironment.jersey().register(new KeepAliveController(clientPresenceManager));
// these should be common, but use @Auth DisabledPermittedAccount, which isnt supported yet on websocket
environment.jersey().register(
new AccountController(pendingAccountsManager, accountsManager, abusiveHostRules, rateLimiters,
smsSender, dynamicConfigurationManager, turnTokenGenerator, config.getTestDevices(),
transitionalRecaptchaClient, gcmSender, apnSender, backupCredentialsGenerator,
verifyExperimentEnrollmentManager));
new AccountController(pendingAccountsManager, accountsManager, rateLimiters,
registrationServiceClient, dynamicConfigurationManager, turnTokenGenerator, config.getTestDevices(),
captchaChecker, pushNotificationManager, changeNumberManager, backupCredentialsGenerator,
clientPresenceManager, clock));
environment.jersey().register(new KeysController(rateLimiters, keys, accountsManager));
final List<Object> commonControllers = Lists.newArrayList(
new AttachmentControllerV1(rateLimiters, config.getAwsAttachmentsConfiguration().getAccessKey(), config.getAwsAttachmentsConfiguration().getAccessSecret(), config.getAwsAttachmentsConfiguration().getBucket()),
new ArtController(rateLimiters, artCredentialsGenerator),
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 CertificateController(new CertificateGenerator(config.getDeliveryCertificate().getCertificate(), config.getDeliveryCertificate().getPrivateKey(), config.getDeliveryCertificate().getExpiresDays()), zkAuthOperations, clock),
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, apnFallbackManager,
reportMessageManager, multiRecipientMessageExecutor),
ReceiptCredentialPresentation::new),
new MessageController(rateLimiters, messageSender, receiptSender, accountsManager, deletedAccountsManager, messagesManager, pushNotificationManager, reportMessageManager, multiRecipientMessageExecutor),
new PaymentsController(currencyManager, paymentsCredentialsGenerator),
new ProfileController(clock, rateLimiters, accountsManager, profilesManager, dynamicConfigurationManager, profileBadgeConverter, config.getBadges(), cdnS3Client, profileCdnPolicyGenerator, profileCdnPolicySigner, config.getCdnConfiguration().getBucket(), zkProfileOperations),
new ProfileController(clock, rateLimiters, accountsManager, profilesManager, dynamicConfigurationManager,
profileBadgeConverter, config.getBadges(), cdnS3Client, profileCdnPolicyGenerator, profileCdnPolicySigner,
config.getCdnConfiguration().getBucket(), zkProfileOperations, batchIdentityCheckExecutor),
new ProvisioningController(rateLimiters, provisioningManager),
new RemoteConfigController(remoteConfigsManager, config.getRemoteConfigConfiguration().getAuthorizedTokens(), config.getRemoteConfigConfiguration().getGlobalConfig()),
new RemoteConfigController(remoteConfigsManager, adminEventLogger,
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,
if (config.getSubscription() != null && config.getOneTimeDonations() != null) {
commonControllers.add(new SubscriptionController(clock, config.getSubscription(), config.getOneTimeDonations(),
subscriptionManager, stripeManager, braintreeManager, zkReceiptOperations, issuedReceiptsManager, profileBadgeConverter,
resourceBundleLevelTranslator));
}
@@ -695,13 +756,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
registerCorsFilter(environment);
registerExceptionMappers(environment, webSocketEnvironment, provisioningEnvironment);
RateLimitChallengeExceptionMapper rateLimitChallengeExceptionMapper =
new RateLimitChallengeExceptionMapper(rateLimitChallengeOptionManager);
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);
@@ -725,6 +779,8 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
environment.healthChecks().register("cacheCluster", new RedisClusterHealthCheck(cacheCluster));
environment.lifecycle().manage(new ApplicationShutdownMonitor(Metrics.globalRegistry));
environment.metrics().register(name(CpuUsageGauge.class, "cpu"), new CpuUsageGauge(3, TimeUnit.SECONDS));
environment.metrics().register(name(FreeMemoryGauge.class, "free_memory"), new FreeMemoryGauge());
environment.metrics().register(name(NetworkSentGauge.class, "bytes_sent"), new NetworkSentGauge());
@@ -751,7 +807,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
new RateLimitExceededExceptionMapper(),
new InvalidWebsocketAddressExceptionMapper(),
new DeviceLimitExceededExceptionMapper(),
new RetryLaterExceptionMapper(),
new ServerRejectedExceptionMapper(),
new ImpossiblePhoneNumberExceptionMapper(),
new NonNormalizedPhoneNumberExceptionMapper()

View File

@@ -7,32 +7,19 @@ package org.whispersystems.textsecuregcm.auth;
import io.dropwizard.auth.Authenticator;
import io.dropwizard.auth.basic.BasicCredentials;
import java.util.Optional;
import io.micrometer.core.instrument.Metrics;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import static com.codahale.metrics.MetricRegistry.name;
public class AccountAuthenticator extends BaseAccountAuthenticator implements
Authenticator<BasicCredentials, AuthenticatedAccount> {
private static final String AUTHENTICATION_COUNTER_NAME = name(AccountAuthenticator.class, "authenticate");
public AccountAuthenticator(AccountsManager accountsManager) {
super(accountsManager);
}
@Override
public Optional<AuthenticatedAccount> authenticate(BasicCredentials basicCredentials) {
final Optional<AuthenticatedAccount> maybeAuthenticatedAccount = super.authenticate(basicCredentials, true);
// TODO Remove after announcement groups have launched
maybeAuthenticatedAccount.ifPresent(authenticatedAccount ->
Metrics.counter(AUTHENTICATION_COUNTER_NAME,
"supportsAnnouncementGroups",
String.valueOf(authenticatedAccount.getAccount().isAnnouncementGroupSupported()))
.increment());
return maybeAuthenticatedAccount;
return super.authenticate(basicCredentials, true);
}
}

View File

@@ -4,7 +4,10 @@
*/
package org.whispersystems.textsecuregcm.auth;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.codec.binary.Hex;
import org.signal.libsignal.protocol.kdf.HKDF;
import org.whispersystems.textsecuregcm.util.Util;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
@@ -12,18 +15,39 @@ import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public class AuthenticationCredentials {
private static final String V2_PREFIX = "2.";
private final String hashedAuthenticationToken;
private final String salt;
public enum Version {
V1,
V2,
}
public static final Version CURRENT_VERSION = Version.V2;
public AuthenticationCredentials(String hashedAuthenticationToken, String salt) {
this.hashedAuthenticationToken = hashedAuthenticationToken;
this.salt = salt;
}
public AuthenticationCredentials(String authenticationToken) {
this.salt = String.valueOf(Math.abs(new SecureRandom().nextInt()));
this.hashedAuthenticationToken = getHashedValue(salt, authenticationToken);
this.salt = String.valueOf(Util.ensureNonNegativeInt(new SecureRandom().nextInt()));
this.hashedAuthenticationToken = getV2HashedValue(salt, authenticationToken);
}
@VisibleForTesting
public AuthenticationCredentials v1ForTesting(String authenticationToken) {
String salt = String.valueOf(Util.ensureNonNegativeInt(new SecureRandom().nextInt()));
return new AuthenticationCredentials(getV1HashedValue(salt, authenticationToken), salt);
}
public Version getVersion() {
if (this.hashedAuthenticationToken.startsWith(V2_PREFIX)) {
return Version.V2;
}
return Version.V1;
}
public String getHashedAuthenticationToken() {
@@ -35,11 +59,14 @@ public class AuthenticationCredentials {
}
public boolean verify(String authenticationToken) {
String theirValue = getHashedValue(salt, authenticationToken);
final String theirValue = switch (getVersion()) {
case V1 -> getV1HashedValue(salt, authenticationToken);
case V2 -> getV2HashedValue(salt, authenticationToken);
};
return MessageDigest.isEqual(theirValue.getBytes(StandardCharsets.UTF_8), this.hashedAuthenticationToken.getBytes(StandardCharsets.UTF_8));
}
private static String getHashedValue(String salt, String token) {
private static String getV1HashedValue(String salt, String token) {
try {
return new String(Hex.encodeHex(MessageDigest.getInstance("SHA1").digest((salt + token).getBytes(StandardCharsets.UTF_8))));
} catch (NoSuchAlgorithmException e) {
@@ -47,4 +74,13 @@ public class AuthenticationCredentials {
}
}
private static final byte[] AUTH_TOKEN_HKDF_INFO = "authtoken".getBytes(StandardCharsets.UTF_8);
private static String getV2HashedValue(String salt, String token) {
byte[] secret = HKDF.deriveSecrets(
token.getBytes(StandardCharsets.UTF_8), // key
salt.getBytes(StandardCharsets.UTF_8), // salt
AUTH_TOKEN_HKDF_INFO,
32);
return V2_PREFIX + Hex.encodeHexString(secret);
}
}

View File

@@ -27,13 +27,21 @@ import org.whispersystems.textsecuregcm.util.Util;
public class BaseAccountAuthenticator {
private static final String AUTHENTICATION_COUNTER_NAME = name(BaseAccountAuthenticator.class, "authentication");
private static final String ENABLED_NOT_REQUIRED_AUTHENTICATION_COUNTER_NAME = name(BaseAccountAuthenticator.class,
"enabledNotRequiredAuthentication");
private static final String AUTHENTICATION_SUCCEEDED_TAG_NAME = "succeeded";
private static final String AUTHENTICATION_FAILURE_REASON_TAG_NAME = "reason";
private static final String AUTHENTICATION_ENABLED_REQUIRED_TAG_NAME = "enabledRequired";
private static final String ENABLED_TAG_NAME = "enabled";
private static final String AUTHENTICATION_HAS_STORY_CAPABILITY = "hasStoryCapability";
private static final String STORY_ADOPTION_COUNTER_NAME = name(BaseAccountAuthenticator.class, "storyAdoption");
private static final String DAYS_SINCE_LAST_SEEN_DISTRIBUTION_NAME = name(BaseAccountAuthenticator.class, "daysSinceLastSeen");
private static final String IS_PRIMARY_DEVICE_TAG = "isPrimary";
@VisibleForTesting
static final char DEVICE_ID_SEPARATOR = '.';
private final AccountsManager accountsManager;
private final Clock clock;
@@ -43,15 +51,15 @@ public class BaseAccountAuthenticator {
@VisibleForTesting
public BaseAccountAuthenticator(AccountsManager accountsManager, Clock clock) {
this.accountsManager = accountsManager;
this.clock = clock;
this.accountsManager = accountsManager;
this.clock = clock;
}
static Pair<String, Long> getIdentifierAndDeviceId(final String basicUsername) {
final String identifier;
final long deviceId;
final int deviceIdSeparatorIndex = basicUsername.indexOf('.');
final int deviceIdSeparatorIndex = basicUsername.indexOf(DEVICE_ID_SEPARATOR);
if (deviceIdSeparatorIndex == -1) {
identifier = basicUsername;
@@ -67,6 +75,7 @@ public class BaseAccountAuthenticator {
public Optional<AuthenticatedAccount> authenticate(BasicCredentials basicCredentials, boolean enabledRequired) {
boolean succeeded = false;
String failureReason = null;
boolean hasStoryCapability = false;
try {
final UUID accountUuid;
@@ -85,6 +94,8 @@ public class BaseAccountAuthenticator {
return Optional.empty();
}
hasStoryCapability = account.map(Account::isStoriesSupported).orElse(false);
Optional<Device> device = account.get().getDevice(deviceId);
if (device.isEmpty()) {
@@ -93,20 +104,35 @@ public class BaseAccountAuthenticator {
}
if (enabledRequired) {
if (!device.get().isEnabled()) {
final boolean deviceDisabled = !device.get().isEnabled();
if (deviceDisabled) {
failureReason = "deviceDisabled";
return Optional.empty();
}
if (!account.get().isEnabled()) {
final boolean accountDisabled = !account.get().isEnabled();
if (accountDisabled) {
failureReason = "accountDisabled";
}
if (accountDisabled || deviceDisabled) {
return Optional.empty();
}
} else {
Metrics.counter(ENABLED_NOT_REQUIRED_AUTHENTICATION_COUNTER_NAME,
ENABLED_TAG_NAME, String.valueOf(device.get().isEnabled() && account.get().isEnabled()),
IS_PRIMARY_DEVICE_TAG, String.valueOf(device.get().isMaster()))
.increment();
}
if (device.get().getAuthenticationCredentials().verify(basicCredentials.getPassword())) {
AuthenticationCredentials deviceAuthenticationCredentials = device.get().getAuthenticationCredentials();
if (deviceAuthenticationCredentials.verify(basicCredentials.getPassword())) {
succeeded = true;
final Account authenticatedAccount = updateLastSeen(account.get(), device.get());
Account authenticatedAccount = updateLastSeen(account.get(), device.get());
if (deviceAuthenticationCredentials.getVersion() != AuthenticationCredentials.CURRENT_VERSION) {
authenticatedAccount = accountsManager.updateDeviceAuthentication(
authenticatedAccount,
device.get(),
new AuthenticationCredentials(basicCredentials.getPassword())); // new credentials have current version
}
return Optional.of(new AuthenticatedAccount(
new RefreshingAccountAndDeviceSupplier(authenticatedAccount, device.get().getId(), accountsManager)));
}
@@ -117,22 +143,33 @@ public class BaseAccountAuthenticator {
return Optional.empty();
} finally {
Tags tags = Tags.of(
AUTHENTICATION_SUCCEEDED_TAG_NAME, String.valueOf(succeeded),
AUTHENTICATION_ENABLED_REQUIRED_TAG_NAME, String.valueOf(enabledRequired));
AUTHENTICATION_SUCCEEDED_TAG_NAME, String.valueOf(succeeded));
if (StringUtils.isNotBlank(failureReason)) {
tags = tags.and(AUTHENTICATION_FAILURE_REASON_TAG_NAME, failureReason);
}
Metrics.counter(AUTHENTICATION_COUNTER_NAME, tags).increment();
Tags storyTags = Tags.of(AUTHENTICATION_HAS_STORY_CAPABILITY, String.valueOf(hasStoryCapability));
Metrics.counter(STORY_ADOPTION_COUNTER_NAME, storyTags).increment();
}
}
@VisibleForTesting
public Account updateLastSeen(Account account, Device device) {
final long lastSeenOffsetSeconds = Math.abs(account.getUuid().getLeastSignificantBits()) % ChronoUnit.DAYS.getDuration().toSeconds();
// compute a non-negative integer between 0 and 86400.
long n = Util.ensureNonNegativeLong(account.getUuid().getLeastSignificantBits());
final long lastSeenOffsetSeconds = n % ChronoUnit.DAYS.getDuration().toSeconds();
// produce a truncated timestamp which is either today at UTC midnight
// or yesterday at UTC midnight, based on per-user randomized offset used.
final long todayInMillisWithOffset = Util.todayInMillisGivenOffsetFromNow(clock, Duration.ofSeconds(lastSeenOffsetSeconds).negated());
// only update the device's last seen time when it falls behind the truncated timestamp.
// this ensure a few things:
// (1) each account will only update last-seen at most once per day
// (2) these updates will occur throughout the day rather than all occurring at UTC midnight.
if (device.getLastSeen() < todayInMillisWithOffset) {
Metrics.summary(DAYS_SINCE_LAST_SEEN_DISTRIBUTION_NAME, IS_PRIMARY_DEVICE_TAG, String.valueOf(device.isMaster()))
.record(Duration.ofMillis(todayInMillisWithOffset - device.getLastSeen()).toDays());
@@ -142,5 +179,4 @@ public class BaseAccountAuthenticator {
return account;
}
}

View File

@@ -5,9 +5,7 @@
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 {

View File

@@ -7,18 +7,16 @@ package org.whispersystems.textsecuregcm.auth;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import org.whispersystems.textsecuregcm.crypto.Curve;
import org.whispersystems.textsecuregcm.crypto.ECPrivateKey;
import java.security.InvalidKeyException;
import java.util.Base64;
import java.util.concurrent.TimeUnit;
import org.signal.libsignal.protocol.ecc.Curve;
import org.signal.libsignal.protocol.ecc.ECPrivateKey;
import org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate;
import org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Device;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.util.Base64;
import java.util.concurrent.TimeUnit;
public class CertificateGenerator {
private final ECPrivateKey privateKey;
@@ -46,7 +44,12 @@ public class CertificateGenerator {
}
byte[] certificate = builder.build().toByteArray();
byte[] signature = Curve.calculateSignature(privateKey, certificate);
byte[] signature;
try {
signature = Curve.calculateSignature(privateKey, certificate);
} catch (org.signal.libsignal.protocol.InvalidKeyException e) {
throw new InvalidKeyException(e);
}
return SenderCertificate.newBuilder()
.setCertificate(ByteString.copyFrom(certificate))

View File

@@ -8,6 +8,7 @@ package org.whispersystems.textsecuregcm.auth;
import io.dropwizard.auth.Authenticator;
import io.dropwizard.auth.basic.BasicCredentials;
import java.util.Optional;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
public class DisabledPermittedAccountAuthenticator extends BaseAccountAuthenticator implements

View File

@@ -9,9 +9,9 @@ import com.google.common.annotations.VisibleForTesting;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Clock;
import java.util.HexFormat;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Hex;
import org.whispersystems.textsecuregcm.util.Util;
public class ExternalServiceCredentialGenerator {
@@ -20,33 +20,50 @@ public class ExternalServiceCredentialGenerator {
private final byte[] userIdKey;
private final boolean usernameDerivation;
private final boolean prependUsername;
private final boolean truncateKey;
private final Clock clock;
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey) {
this(key, userIdKey, true, true);
this(key, userIdKey, true, true, true);
}
public ExternalServiceCredentialGenerator(byte[] key, boolean prependUsername) {
this(key, new byte[0], false, prependUsername);
this(key, prependUsername, true);
}
public ExternalServiceCredentialGenerator(byte[] key, boolean prependUsername, boolean truncateKey) {
this(key, new byte[0], false, prependUsername, truncateKey);
}
@VisibleForTesting
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey, boolean usernameDerivation) {
this(key, userIdKey, usernameDerivation, true);
this(key, userIdKey, usernameDerivation, true, true);
}
private ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey, boolean usernameDerivation,
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey, boolean usernameDerivation,
boolean prependUsername) {
this(key, userIdKey, usernameDerivation, prependUsername, Clock.systemUTC());
this(key, userIdKey, usernameDerivation, prependUsername, true, Clock.systemUTC());
}
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey, boolean usernameDerivation,
boolean prependUsername, boolean truncateKey) {
this(key, userIdKey, usernameDerivation, prependUsername, truncateKey, Clock.systemUTC());
}
@VisibleForTesting
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey, boolean usernameDerivation,
boolean prependUsername, Clock clock) {
this(key, userIdKey, usernameDerivation, prependUsername, true, clock);
}
@VisibleForTesting
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey, boolean usernameDerivation,
boolean prependUsername, boolean truncateKey, Clock clock) {
this.key = key;
this.userIdKey = userIdKey;
this.usernameDerivation = usernameDerivation;
this.prependUsername = prependUsername;
this.truncateKey = truncateKey;
this.clock = clock;
}
@@ -55,14 +72,17 @@ public class ExternalServiceCredentialGenerator {
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));
byte[] prefixMac = getHmac(key, prefix.getBytes(), mac);
final HexFormat hex = HexFormat.of();
String output = hex.formatHex(truncateKey ? Util.truncate(prefixMac, 10) : prefixMac);
String token = (prependUsername ? prefix : currentTimeSeconds) + ":" + output;
return new ExternalServiceCredentials(username, token);
}
private String getUserId(String number, Mac mac, boolean usernameDerivation) {
if (usernameDerivation) return Hex.encodeHexString(Util.truncate(getHmac(userIdKey, number.getBytes(), mac), 10));
final HexFormat hex = HexFormat.of();
if (usernameDerivation) return hex.formatHex(Util.truncate(getHmac(userIdKey, number.getBytes(), mac), 10));
else return number;
}

View File

@@ -6,28 +6,6 @@
package org.whispersystems.textsecuregcm.auth;
import com.fasterxml.jackson.annotation.JsonProperty;
public record ExternalServiceCredentials(String username, String password) {
public class ExternalServiceCredentials {
@JsonProperty
private String username;
@JsonProperty
private String password;
public ExternalServiceCredentials(String username, String password) {
this.username = username;
this.password = password;
}
public ExternalServiceCredentials() {}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
}

View File

@@ -8,6 +8,8 @@ package org.whispersystems.textsecuregcm.auth;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Device;
import javax.ws.rs.NotAuthorizedException;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import java.security.MessageDigest;
@@ -36,9 +38,9 @@ public class OptionalAccess {
}
if (requestAccount.isPresent()) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
throw new NotFoundException();
} else {
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
throw new NotAuthorizedException(Response.Status.UNAUTHORIZED);
}
}
} catch (NumberFormatException e) {
@@ -56,7 +58,7 @@ public class OptionalAccess {
//noinspection ConstantConditions
if (requestAccount.isPresent() && (targetAccount.isEmpty() || (targetAccount.isPresent() && !targetAccount.get().isEnabled()))) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
throw new NotFoundException();
}
if (accessKey.isPresent() && targetAccount.isPresent() && targetAccount.get().isEnabled() && targetAccount.get().isUnrestrictedUnidentifiedAccess()) {
@@ -72,7 +74,7 @@ public class OptionalAccess {
return;
}
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
throw new NotAuthorizedException(Response.Status.UNAUTHORIZED);
}
}

View File

@@ -10,7 +10,6 @@ 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;

View File

@@ -21,6 +21,20 @@ public class StoredRegistrationLock {
private final long lastSeen;
/**
* @return milliseconds since the last time the account was seen.
*/
private long timeSinceLastSeen() {
return System.currentTimeMillis() - lastSeen;
}
/**
* @return true if the registration lock and salt are both set.
*/
private boolean hasLockAndSalt() {
return registrationLock.isPresent() && registrationLockSalt.isPresent();
}
public StoredRegistrationLock(Optional<String> registrationLock, Optional<String> registrationLockSalt, long lastSeen) {
this.registrationLock = registrationLock;
this.registrationLockSalt = registrationLockSalt;
@@ -28,24 +42,22 @@ public class StoredRegistrationLock {
}
public boolean requiresClientRegistrationLock() {
return registrationLock.isPresent() && registrationLockSalt.isPresent() && System.currentTimeMillis() - lastSeen < TimeUnit.DAYS.toMillis(7);
boolean hasTimeRemaining = getTimeRemaining() >= 0;
return hasLockAndSalt() && hasTimeRemaining;
}
public boolean needsFailureCredentials() {
return registrationLock.isPresent() && registrationLockSalt.isPresent();
return hasLockAndSalt();
}
public long getTimeRemaining() {
return TimeUnit.DAYS.toMillis(7) - (System.currentTimeMillis() - lastSeen);
return TimeUnit.DAYS.toMillis(7) - timeSinceLastSeen();
}
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);
if (hasLockAndSalt() && Util.nonEmpty(clientRegistrationLock)) {
AuthenticationCredentials credentials = new AuthenticationCredentials(registrationLock.get(), registrationLockSalt.get());
return credentials.verify(clientRegistrationLock);
} else {
return false;
}

View File

@@ -5,60 +5,18 @@
package org.whispersystems.textsecuregcm.auth;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.security.MessageDigest;
import java.time.Duration;
import java.util.Optional;
import javax.annotation.Nullable;
import org.whispersystems.textsecuregcm.util.Util;
public class StoredVerificationCode {
@JsonProperty
private final String code;
@JsonProperty
private final long timestamp;
@JsonProperty
private final String pushCode;
@JsonProperty
@Nullable
private final String twilioVerificationSid;
public record StoredVerificationCode(String code,
long timestamp,
String pushCode,
@Nullable byte[] sessionId) {
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.twilioVerificationSid = twilioVerificationSid;
}
public String getCode() {
return code;
}
public long getTimestamp() {
return timestamp;
}
public String getPushCode() {
return pushCode;
}
public Optional<String> getTwilioVerificationSid() {
return Optional.ofNullable(twilioVerificationSid);
}
public boolean isValid(String theirCodeString) {
if (Util.isEmpty(code) || Util.isEmpty(theirCodeString)) {
return false;

View File

@@ -6,6 +6,7 @@
package org.whispersystems.textsecuregcm.auth;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;
import java.util.List;
@@ -25,4 +26,9 @@ public class TurnToken {
this.password = password;
this.urls = urls;
}
@VisibleForTesting
List<String> getUrls() {
return urls;
}
}

View File

@@ -5,7 +5,13 @@
package org.whispersystems.textsecuregcm.auth;
import org.whispersystems.textsecuregcm.configuration.TurnConfiguration;
import org.whispersystems.textsecuregcm.configuration.TurnUriConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicTurnConfiguration;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import org.whispersystems.textsecuregcm.util.Pair;
import org.whispersystems.textsecuregcm.util.Util;
import org.whispersystems.textsecuregcm.util.WeightedRandomSelect;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
@@ -14,23 +20,24 @@ import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
public class TurnTokenGenerator {
private final byte[] key;
private final List<String> urls;
private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfiguration;
public TurnTokenGenerator(TurnConfiguration configuration) {
this.key = configuration.getSecret().getBytes();
this.urls = configuration.getUris();
public TurnTokenGenerator(final DynamicConfigurationManager<DynamicConfiguration> config) {
this.dynamicConfiguration = config;
}
public TurnToken generate() {
public TurnToken generate(final String e164) {
try {
byte[] key = dynamicConfiguration.getConfiguration().getTurnConfiguration().getSecret().getBytes();
List<String> urls = urls(e164);
Mac mac = Mac.getInstance("HmacSHA1");
long validUntilSeconds = (System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1)) / 1000;
long user = Math.abs(new SecureRandom().nextInt());
long user = Util.ensureNonNegativeInt(new SecureRandom().nextInt());
String userTime = validUntilSeconds + ":" + user;
mac.init(new SecretKeySpec(key, "HmacSHA1"));
@@ -41,4 +48,22 @@ public class TurnTokenGenerator {
throw new AssertionError(e);
}
}
private List<String> urls(final String e164) {
final DynamicTurnConfiguration turnConfig = dynamicConfiguration.getConfiguration().getTurnConfiguration();
// Check if number is enrolled to test out specific turn servers
final Optional<TurnUriConfiguration> enrolled = turnConfig.getUriConfigs().stream()
.filter(config -> config.getEnrolledNumbers().contains(e164))
.findFirst();
if (enrolled.isPresent()) {
return enrolled.get().getUris();
}
// Otherwise, select from turn server sets by weighted choice
return WeightedRandomSelect.select(turnConfig
.getUriConfigs()
.stream()
.map(c -> new Pair<List<String>, Long>(c.getUris(), c.getWeight())).toList());
}
}

View File

@@ -7,7 +7,6 @@ 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;

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.captcha;
/**
* A captcha assessment
*
* @param valid whether the captcha was passed
* @param score string representation of the risk level
*/
public record AssessmentResult(boolean valid, String score) {
public static AssessmentResult invalid() {
return new AssessmentResult(false, "");
}
/**
* Map a captcha score in [0.0, 1.0] to a low cardinality discrete space in [0, 100] suitable for use in metrics
*/
static String scoreString(final float score) {
final int x = Math.round(score * 10); // [0, 10]
return Integer.toString(x * 10); // [0, 100] in increments of 10
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.captcha;
import com.google.common.annotations.VisibleForTesting;
import io.micrometer.core.instrument.Metrics;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.ws.rs.BadRequestException;
import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
public class CaptchaChecker {
private static final String ASSESSMENTS_COUNTER_NAME = name(RecaptchaClient.class, "assessments");
@VisibleForTesting
static final String SEPARATOR = ".";
private final Map<String, CaptchaClient> captchaClientMap;
public CaptchaChecker(final List<CaptchaClient> captchaClients) {
this.captchaClientMap = captchaClients.stream()
.collect(Collectors.toMap(CaptchaClient::scheme, Function.identity()));
}
/**
* Check if a solved captcha should be accepted
* <p>
*
* @param input expected to contain a prefix indicating the captcha scheme, sitekey, token, and action. The expected
* format is {@code version-prefix.sitekey.[action.]token}
* @param ip IP of the solver
* @return An {@link AssessmentResult} indicating whether the solution should be accepted, and a score that can be
* used for metrics
* @throws IOException if there is an error validating the captcha with the underlying service
* @throws BadRequestException if input is not in the expected format
*/
public AssessmentResult verify(final String input, final String ip) throws IOException {
/*
* For action to be optional, there is a strong assumption that the token will never contain a {@value SEPARATOR}.
* Observation suggests {@code token} is base-64 encoded. In practice, an action should always be present, but we
* dont need to be strict.
*/
final String[] parts = input.split("\\" + SEPARATOR, 4);
// we allow missing actions, if we're missing 1 part, assume it's the action
if (parts.length < 3) {
throw new BadRequestException("too few parts");
}
int idx = 0;
final String prefix = parts[idx++];
final String siteKey = parts[idx++];
final String action = parts.length == 3 ? null : parts[idx++];
final String token = parts[idx];
final CaptchaClient client = this.captchaClientMap.get(prefix);
if (client == null) {
throw new BadRequestException("invalid captcha scheme");
}
final AssessmentResult result = client.verify(siteKey, action, token, ip);
Metrics.counter(ASSESSMENTS_COUNTER_NAME,
"action", String.valueOf(action),
"valid", String.valueOf(result.valid()),
"score", result.score(),
"provider", prefix)
.increment();
return result;
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.captcha;
import javax.annotation.Nullable;
import java.io.IOException;
public interface CaptchaClient {
/**
* @return the identifying captcha scheme that this CaptchaClient handles
*/
String scheme();
/**
* Verify a provided captcha solution
*
* @param siteKey identifying string for the captcha service
* @param action an optional action indicating the purpose of the captcha
* @param token the captcha solution that will be verified
* @param ip the ip of the captcha solve
* @return An {@link AssessmentResult} indicating whether the solution should be accepted
* @throws IOException if the underlying captcha provider returns an error
*/
AssessmentResult verify(
final String siteKey,
final @Nullable String action,
final String token,
final String ip) throws IOException;
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright 2021-2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.captcha;
import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
import io.micrometer.core.instrument.Metrics;
import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import javax.annotation.Nullable;
import javax.ws.rs.core.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicCaptchaConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import org.whispersystems.textsecuregcm.util.SystemMapper;
public class HCaptchaClient implements CaptchaClient {
private static final Logger logger = LoggerFactory.getLogger(HCaptchaClient.class);
private static final String PREFIX = "signal-hcaptcha";
private static final String ASSESSMENT_REASON_COUNTER_NAME = name(HCaptchaClient.class, "assessmentReason");
private static final String INVALID_REASON_COUNTER_NAME = name(HCaptchaClient.class, "invalidReason");
private final String apiKey;
private final HttpClient client;
private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager;
public HCaptchaClient(
final String apiKey,
final HttpClient client,
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager) {
this.apiKey = apiKey;
this.client = client;
this.dynamicConfigurationManager = dynamicConfigurationManager;
}
@Override
public String scheme() {
return PREFIX;
}
@Override
public AssessmentResult verify(final String siteKey, final @Nullable String action, final String token,
final String ip)
throws IOException {
final DynamicCaptchaConfiguration config = dynamicConfigurationManager.getConfiguration().getCaptchaConfiguration();
if (!config.isAllowHCaptcha()) {
logger.warn("Received request to verify an hCaptcha, but hCaptcha is not enabled");
return AssessmentResult.invalid();
}
final String body = String.format("response=%s&secret=%s&remoteip=%s",
URLEncoder.encode(token, StandardCharsets.UTF_8),
URLEncoder.encode(this.apiKey, StandardCharsets.UTF_8),
ip);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://hcaptcha.com/siteverify"))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response;
try {
response = this.client.send(request, HttpResponse.BodyHandlers.ofString());
} catch (InterruptedException e) {
throw new IOException(e);
}
if (response.statusCode() != Response.Status.OK.getStatusCode()) {
logger.warn("failure submitting token to hCaptcha (code={}): {}", response.statusCode(), response);
throw new IOException("hCaptcha http failure : " + response.statusCode());
}
final HCaptchaResponse hCaptchaResponse = SystemMapper.getMapper()
.readValue(response.body(), HCaptchaResponse.class);
logger.debug("received hCaptcha response: {}", hCaptchaResponse);
if (!hCaptchaResponse.success) {
for (String errorCode : hCaptchaResponse.errorCodes) {
Metrics.counter(INVALID_REASON_COUNTER_NAME,
"action", String.valueOf(action),
"reason", errorCode).increment();
}
return AssessmentResult.invalid();
}
// hcaptcha uses the inverse scheme of recaptcha (for hcaptcha, a low score is less risky)
float score = 1.0f - hCaptchaResponse.score;
if (score < 0.0f || score > 1.0f) {
logger.error("Invalid score {} from hcaptcha response {}", hCaptchaResponse.score, hCaptchaResponse);
return AssessmentResult.invalid();
}
final String scoreString = AssessmentResult.scoreString(score);
for (String reason : hCaptchaResponse.scoreReasons) {
Metrics.counter(ASSESSMENT_REASON_COUNTER_NAME,
"action", String.valueOf(action),
"reason", reason,
"score", scoreString).increment();
}
return new AssessmentResult(score >= config.getScoreFloor().floatValue(), scoreString);
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.captcha;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
/**
* Verify response returned by hcaptcha
* <p>
* see <a href="https://docs.hcaptcha.com/#verify-the-user-response-server-side">...</a>
*/
public class HCaptchaResponse {
@JsonProperty
boolean success;
@JsonProperty(value = "challenge-ts")
Duration challengeTs;
@JsonProperty
String hostname;
@JsonProperty
boolean credit;
@JsonProperty(value = "error-codes")
List<String> errorCodes = Collections.emptyList();
@JsonProperty
float score;
@JsonProperty(value = "score-reasons")
List<String> scoreReasons = Collections.emptyList();
public HCaptchaResponse() {
}
@Override
public String toString() {
return "HCaptchaResponse{" +
"success=" + success +
", challengeTs=" + challengeTs +
", hostname='" + hostname + '\'' +
", credit=" + credit +
", errorCodes=" + errorCodes +
", score=" + score +
", scoreReasons=" + scoreReasons +
'}';
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright 2021-2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.captcha;
import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
import com.google.api.gax.core.FixedCredentialsProvider;
import com.google.api.gax.rpc.ApiException;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.recaptchaenterprise.v1.RecaptchaEnterpriseServiceClient;
import com.google.cloud.recaptchaenterprise.v1.RecaptchaEnterpriseServiceSettings;
import com.google.recaptchaenterprise.v1.Assessment;
import com.google.recaptchaenterprise.v1.Event;
import com.google.recaptchaenterprise.v1.RiskAnalysis;
import io.micrometer.core.instrument.Metrics;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicCaptchaConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
public class RecaptchaClient implements CaptchaClient {
private static final Logger log = LoggerFactory.getLogger(RecaptchaClient.class);
private static final String V2_PREFIX = "signal-recaptcha-v2";
private static final String INVALID_REASON_COUNTER_NAME = name(RecaptchaClient.class, "invalidReason");
private static final String ASSESSMENT_REASON_COUNTER_NAME = name(RecaptchaClient.class, "assessmentReason");
private final String projectPath;
private final RecaptchaEnterpriseServiceClient client;
private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager;
public RecaptchaClient(
@Nonnull final String projectPath,
@Nonnull final String recaptchaCredentialConfigurationJson,
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager) {
try {
this.projectPath = Objects.requireNonNull(projectPath);
this.client = RecaptchaEnterpriseServiceClient.create(RecaptchaEnterpriseServiceSettings.newBuilder()
.setCredentialsProvider(FixedCredentialsProvider.create(GoogleCredentials.fromStream(
new ByteArrayInputStream(recaptchaCredentialConfigurationJson.getBytes(StandardCharsets.UTF_8)))))
.build());
this.dynamicConfigurationManager = dynamicConfigurationManager;
} catch (IOException e) {
throw new AssertionError(e);
}
}
@Override
public String scheme() {
return V2_PREFIX;
}
@Override
public org.whispersystems.textsecuregcm.captcha.AssessmentResult verify(final String sitekey,
final @Nullable String expectedAction,
final String token, final String ip) throws IOException {
final DynamicCaptchaConfiguration config = dynamicConfigurationManager.getConfiguration().getCaptchaConfiguration();
if (!config.isAllowRecaptcha()) {
log.warn("Received request to verify a recaptcha, but recaptcha is not enabled");
return AssessmentResult.invalid();
}
Event.Builder eventBuilder = Event.newBuilder()
.setSiteKey(sitekey)
.setToken(token)
.setUserIpAddress(ip);
if (expectedAction != null) {
eventBuilder.setExpectedAction(expectedAction);
}
final Event event = eventBuilder.build();
final Assessment assessment;
try {
assessment = client.createAssessment(projectPath, Assessment.newBuilder().setEvent(event).build());
} catch (ApiException e) {
throw new IOException(e);
}
if (assessment.getTokenProperties().getValid()) {
final float score = assessment.getRiskAnalysis().getScore();
log.debug("assessment for {} was valid, score: {}", expectedAction, score);
for (RiskAnalysis.ClassificationReason reason : assessment.getRiskAnalysis().getReasonsList()) {
Metrics.counter(ASSESSMENT_REASON_COUNTER_NAME,
"action", String.valueOf(expectedAction),
"score", AssessmentResult.scoreString(score),
"reason", reason.name())
.increment();
}
return new AssessmentResult(
score >= config.getScoreFloor().floatValue(),
AssessmentResult.scoreString(score));
} else {
Metrics.counter(INVALID_REASON_COUNTER_NAME,
"action", String.valueOf(expectedAction),
"reason", assessment.getTokenProperties().getInvalidReason().name())
.increment();
return AssessmentResult.invalid();
}
}
}

View File

@@ -0,0 +1,14 @@
/*
* Copyright 2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import javax.validation.constraints.NotEmpty;
public record AdminEventLoggingConfiguration(
@NotEmpty String credentials,
@NotEmpty String projectId,
@NotEmpty String logName) {
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import java.time.Duration;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
public class ArtServiceConfiguration {
@NotEmpty
@JsonProperty
private String userAuthenticationTokenSharedSecret;
@NotEmpty
@JsonProperty
private String userAuthenticationTokenUserIdSecret;
@JsonProperty
@NotNull
private Duration tokenExpiration = Duration.ofDays(1);
public byte[] getUserAuthenticationTokenSharedSecret() throws DecoderException {
return Hex.decodeHex(userAuthenticationTokenSharedSecret.toCharArray());
}
public byte[] getUserAuthenticationTokenUserIdSecret() throws DecoderException {
return Hex.decodeHex(userAuthenticationTokenUserIdSecret.toCharArray());
}
public Duration getTokenExpiration() {
return tokenExpiration;
}
}

View File

@@ -1,58 +0,0 @@
/*
* 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

@@ -0,0 +1,44 @@
/*
* Copyright 2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import java.util.Map;
import java.util.Set;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
* @param merchantId the Braintree merchant ID
* @param publicKey the Braintree API public key
* @param privateKey the Braintree API private key
* @param environment the Braintree environment ("production" or "sandbox")
* @param supportedCurrencies the set of supported currencies
* @param graphqlUrl the Braintree GraphQL URl to use (this must match the environment)
* @param merchantAccounts merchant account within the merchant for processing individual currencies
* @param circuitBreaker configuration for the circuit breaker used by the GraphQL HTTP client
*/
public record BraintreeConfiguration(@NotBlank String merchantId,
@NotBlank String publicKey,
@NotBlank String privateKey,
@NotBlank String environment,
@NotEmpty Set<@NotBlank String> supportedCurrencies,
@NotBlank String graphqlUrl,
@NotEmpty Map<String, String> merchantAccounts,
@NotNull
@Valid
CircuitBreakerConfiguration circuitBreaker) {
public BraintreeConfiguration {
if (circuitBreaker == null) {
// Its a little counter-intuitive, but this compact constructor allows a default value
// to be used when one isnt specified (e.g. in YAML), allowing the field to still be
// validated as @NotNull
circuitBreaker = new CircuitBreakerConfiguration();
}
}
}

View File

@@ -27,12 +27,17 @@ public class CircuitBreakerConfiguration {
@JsonProperty
@NotNull
@Min(1)
private int ringBufferSizeInHalfOpenState = 10;
private int permittedNumberOfCallsInHalfOpenState = 10;
@JsonProperty
@NotNull
@Min(1)
private int ringBufferSizeInClosedState = 100;
private int slidingWindowSize = 100;
@JsonProperty
@NotNull
@Min(1)
private int slidingWindowMinimumNumberOfCalls = 100;
@JsonProperty
@NotNull
@@ -47,28 +52,32 @@ public class CircuitBreakerConfiguration {
return failureRateThreshold;
}
public int getRingBufferSizeInHalfOpenState() {
return ringBufferSizeInHalfOpenState;
public int getPermittedNumberOfCallsInHalfOpenState() {
return permittedNumberOfCallsInHalfOpenState;
}
public int getRingBufferSizeInClosedState() {
return ringBufferSizeInClosedState;
public int getSlidingWindowSize() {
return slidingWindowSize;
}
public int getSlidingWindowMinimumNumberOfCalls() {
return slidingWindowMinimumNumberOfCalls;
}
public long getWaitDurationInOpenStateInSeconds() {
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());
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
@@ -77,13 +86,18 @@ public class CircuitBreakerConfiguration {
}
@VisibleForTesting
public void setRingBufferSizeInClosedState(int size) {
this.ringBufferSizeInClosedState = size;
public void setSlidingWindowSize(int size) {
this.slidingWindowSize = size;
}
@VisibleForTesting
public void setRingBufferSizeInHalfOpenState(int size) {
this.ringBufferSizeInHalfOpenState = size;
public void setSlidingWindowMinimumNumberOfCalls(int size) {
this.slidingWindowMinimumNumberOfCalls = size;
}
@VisibleForTesting
public void setPermittedNumberOfCallsInHalfOpenState(int size) {
this.permittedNumberOfCallsInHalfOpenState = size;
}
@VisibleForTesting
@@ -98,11 +112,12 @@ public class CircuitBreakerConfiguration {
public CircuitBreakerConfig toCircuitBreakerConfig() {
return CircuitBreakerConfig.custom()
.failureRateThreshold(getFailureRateThreshold())
.ignoreExceptions(getIgnoredExceptions().toArray(new Class[0]))
.ringBufferSizeInHalfOpenState(getRingBufferSizeInHalfOpenState())
.waitDurationInOpenState(Duration.ofSeconds(getWaitDurationInOpenStateInSeconds()))
.ringBufferSizeInClosedState(getRingBufferSizeInClosedState())
.build();
.failureRateThreshold(getFailureRateThreshold())
.ignoreExceptions(getIgnoredExceptions().toArray(new Class[0]))
.permittedNumberOfCallsInHalfOpenState(getPermittedNumberOfCallsInHalfOpenState())
.waitDurationInOpenState(Duration.ofSeconds(getWaitDurationInOpenStateInSeconds()))
.slidingWindow(getSlidingWindowSize(), getSlidingWindowMinimumNumberOfCalls(),
CircuitBreakerConfig.SlidingWindowType.COUNT_BASED)
.build();
}
}

View File

@@ -5,7 +5,9 @@
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import java.util.List;
public class DirectoryServerConfiguration {
@@ -23,7 +25,7 @@ public class DirectoryServerConfiguration {
@NotEmpty
@JsonProperty
private String replicationCaCertificate;
private List<@NotBlank String> replicationCaCertificates;
public String getReplicationName() {
return replicationName;
@@ -37,8 +39,8 @@ public class DirectoryServerConfiguration {
return replicationPassword;
}
public String getReplicationCaCertificate() {
return replicationCaCertificate;
public List<String> getReplicationCaCertificates() {
return replicationCaCertificates;
}
}

View File

@@ -11,11 +11,14 @@ import org.whispersystems.textsecuregcm.util.ExactlySize;
public class DirectoryV2ClientConfiguration {
private final byte[] userAuthenticationTokenSharedSecret;
private final byte[] userIdTokenSharedSecret;
@JsonCreator
public DirectoryV2ClientConfiguration(
@JsonProperty("userAuthenticationTokenSharedSecret") final byte[] userAuthenticationTokenSharedSecret) {
@JsonProperty("userAuthenticationTokenSharedSecret") final byte[] userAuthenticationTokenSharedSecret,
@JsonProperty("userIdTokenSharedSecret") final byte[] userIdTokenSharedSecret) {
this.userAuthenticationTokenSharedSecret = userAuthenticationTokenSharedSecret;
this.userIdTokenSharedSecret = userIdTokenSharedSecret;
}
@ExactlySize({32})
@@ -23,4 +26,8 @@ public class DirectoryV2ClientConfiguration {
return userAuthenticationTokenSharedSecret;
}
@ExactlySize({32})
public byte[] getUserIdTokenSharedSecret() {
return userIdTokenSharedSecret;
}
}

View File

@@ -1,78 +0,0 @@
/*
* 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,11 @@
/*
* Copyright 2013-2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import javax.validation.constraints.NotBlank;
public record FcmConfiguration(@NotBlank String credentials) {
}

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.NotNull;
public class GcmConfiguration {
@NotNull
@JsonProperty
private long senderId;
@NotEmpty
@JsonProperty
private String apiKey;
public String getApiKey() {
return apiKey;
}
public long getSenderId() {
return senderId;
}
}

View File

@@ -0,0 +1,11 @@
/*
* Copyright 2021-2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import javax.validation.constraints.NotBlank;
public record HCaptchaConfiguration(@NotBlank String apiKey) {
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import java.time.Duration;
import java.util.Map;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Positive;
/**
* @param boost configuration for individual donations
* @param gift configuration for gift donations
* @param currencies map of lower-cased ISO 3 currency codes and the suggested donation amounts in that currency
*/
public record OneTimeDonationConfiguration(@Valid ExpiringLevelConfiguration boost,
@Valid ExpiringLevelConfiguration gift,
Map<String, @Valid OneTimeDonationCurrencyConfiguration> currencies) {
/**
* @param badge the numeric donation level ID
* @param level the badge ID associated with the level
* @param expiration the duration after which the level expires
*/
public record ExpiringLevelConfiguration(@NotEmpty String badge, @Positive long level, Duration expiration) {
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import java.math.BigDecimal;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.util.ExactlySize;
/**
* One-time donation configuration for a given currency
*
* @param minimum the minimum amount permitted to be charged in this currency
* @param gift the suggested gift donation amount
* @param boosts the list of suggested one-time donation amounts
*/
public record OneTimeDonationCurrencyConfiguration(
@NotNull @DecimalMin("0.01") BigDecimal minimum,
@NotNull @DecimalMin("0.01") BigDecimal gift,
@Valid
@ExactlySize(6)
@NotNull
List<@NotNull @DecimalMin("0.01") BigDecimal> boosts) {
}

View File

@@ -9,8 +9,10 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import java.util.List;
import java.util.Map;
public class PaymentsServiceConfiguration {
@@ -18,6 +20,14 @@ public class PaymentsServiceConfiguration {
@JsonProperty
private String userAuthenticationTokenSharedSecret;
@NotBlank
@JsonProperty
private String coinMarketCapApiKey;
@JsonProperty
@NotEmpty
private Map<@NotBlank String, Integer> coinMarketCapCurrencyIds;
@NotEmpty
@JsonProperty
private String fixerApiKey;
@@ -30,6 +40,14 @@ public class PaymentsServiceConfiguration {
return Hex.decodeHex(userAuthenticationTokenSharedSecret.toCharArray());
}
public String getCoinMarketCapApiKey() {
return coinMarketCapApiKey;
}
public Map<String, Integer> getCoinMarketCapCurrencyIds() {
return coinMarketCapCurrencyIds;
}
public String getFixerApiKey() {
return fixerApiKey;
}

View File

@@ -1,20 +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.Min;
public class PushConfiguration {
@JsonProperty
@Min(0)
private int queueSize = 200;
public int getQueueSize() {
return queueSize;
}
}

View File

@@ -5,7 +5,6 @@
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.time.Duration;
public class RateLimitsConfiguration {
@@ -37,7 +36,7 @@ public class RateLimitsConfiguration {
private RateLimitConfiguration attachments = new RateLimitConfiguration(50, 50);
@JsonProperty
private RateLimitConfiguration prekeys = new RateLimitConfiguration(3, 1.0 / 10.0);
private RateLimitConfiguration prekeys = new RateLimitConfiguration(6, 1.0 / 10.0);
@JsonProperty
private RateLimitConfiguration messages = new RateLimitConfiguration(60, 60);
@@ -46,7 +45,7 @@ public class RateLimitsConfiguration {
private RateLimitConfiguration allocateDevice = new RateLimitConfiguration(2, 1.0 / 2.0);
@JsonProperty
private RateLimitConfiguration verifyDevice = new RateLimitConfiguration(2, 2);
private RateLimitConfiguration verifyDevice = new RateLimitConfiguration(6, 1.0 / 10.0);
@JsonProperty
private RateLimitConfiguration turnAllocations = new RateLimitConfiguration(60, 60);
@@ -57,15 +56,24 @@ public class RateLimitsConfiguration {
@JsonProperty
private RateLimitConfiguration stickerPack = new RateLimitConfiguration(50, 20 / (24.0 * 60.0));
@JsonProperty
private RateLimitConfiguration artPack = new RateLimitConfiguration(50, 20 / (24.0 * 60.0));
@JsonProperty
private RateLimitConfiguration usernameLookup = new RateLimitConfiguration(100, 100 / (24.0 * 60.0));
@JsonProperty
private RateLimitConfiguration usernameSet = new RateLimitConfiguration(100, 100 / (24.0 * 60.0));
@JsonProperty
private RateLimitConfiguration usernameReserve = new RateLimitConfiguration(100, 100 / (24.0 * 60.0));
@JsonProperty
private RateLimitConfiguration checkAccountExistence = new RateLimitConfiguration(1_000, 1_000 / 60.0);
@JsonProperty
private RateLimitConfiguration stories = new RateLimitConfiguration(10_000, 10_000 / (24.0 * 60.0));
public RateLimitConfiguration getAutoBlock() {
return autoBlock;
}
@@ -130,6 +138,10 @@ public class RateLimitsConfiguration {
return stickerPack;
}
public RateLimitConfiguration getArtPack() {
return artPack;
}
public RateLimitConfiguration getUsernameLookup() {
return usernameLookup;
}
@@ -138,10 +150,16 @@ public class RateLimitsConfiguration {
return usernameSet;
}
public RateLimitConfiguration getUsernameReserve() {
return usernameReserve;
}
public RateLimitConfiguration getCheckAccountExistence() {
return checkAccountExistence;
}
public RateLimitConfiguration getStories() { return stories; }
public static class RateLimitConfiguration {
@JsonProperty
private int bucketSize;
@@ -164,28 +182,4 @@ public class RateLimitsConfiguration {
return leakRatePerMinute;
}
}
public static class CardinalityRateLimitConfiguration {
@JsonProperty
private int maxCardinality;
@JsonProperty
private Duration ttl;
public CardinalityRateLimitConfiguration() {
}
public CardinalityRateLimitConfiguration(int maxCardinality, Duration ttl) {
this.maxCardinality = maxCardinality;
this.ttl = ttl;
}
public int getMaxCardinality() {
return maxCardinality;
}
public Duration getTtl() {
return ttl;
}
}
}

View File

@@ -1,21 +1,24 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* Copyright 2021-2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import javax.validation.constraints.NotEmpty;
public class RecaptchaConfiguration {
@JsonProperty
@NotEmpty
private String secret;
private String projectPath;
private String credentialConfigurationJson;
public String getSecret() {
return secret;
@NotEmpty
public String getProjectPath() {
return projectPath;
}
@NotEmpty
public String getCredentialConfigurationJson() {
return credentialConfigurationJson;
}
}

View File

@@ -1,42 +0,0 @@
/*
* 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

@@ -0,0 +1,49 @@
package org.whispersystems.textsecuregcm.configuration;
import javax.validation.constraints.NotBlank;
public class RegistrationServiceConfiguration {
@NotBlank
private String host;
private int port = 443;
@NotBlank
private String apiKey;
@NotBlank
private String registrationCaCertificate;
public String getHost() {
return host;
}
public void setHost(final String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(final int port) {
this.port = port;
}
public String getApiKey() {
return apiKey;
}
public void setApiKey(final String apiKey) {
this.apiKey = apiKey;
}
public String getRegistrationCaCertificate() {
return registrationCaCertificate;
}
public void setRegistrationCaCertificate(final String registrationCaCertificate) {
this.registrationCaCertificate = registrationCaCertificate;
}
}

View File

@@ -13,6 +13,7 @@ import javax.validation.constraints.NotNull;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import java.util.List;
public class SecureBackupServiceConfiguration {
@@ -24,9 +25,9 @@ public class SecureBackupServiceConfiguration {
@JsonProperty
private String uri;
@NotBlank
@NotEmpty
@JsonProperty
private String backupCaCertificate;
private List<@NotBlank String> backupCaCertificates;
@NotNull
@Valid
@@ -52,12 +53,12 @@ public class SecureBackupServiceConfiguration {
}
@VisibleForTesting
public void setBackupCaCertificate(final String backupCaCertificate) {
this.backupCaCertificate = backupCaCertificate;
public void setBackupCaCertificates(final List<String> backupCaCertificates) {
this.backupCaCertificates = backupCaCertificates;
}
public String getBackupCaCertificate() {
return backupCaCertificate;
public List<String> getBackupCaCertificates() {
return backupCaCertificates;
}
public CircuitBreakerConfiguration getCircuitBreakerConfiguration() {

View File

@@ -13,6 +13,7 @@ import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import java.util.List;
public class SecureStorageServiceConfiguration {
@@ -24,9 +25,9 @@ public class SecureStorageServiceConfiguration {
@JsonProperty
private String uri;
@NotBlank
@NotEmpty
@JsonProperty
private String storageCaCertificate;
private List<@NotBlank String> storageCaCertificates;
@NotNull
@Valid
@@ -52,12 +53,12 @@ public class SecureStorageServiceConfiguration {
}
@VisibleForTesting
public void setStorageCaCertificate(final String certificatePem) {
this.storageCaCertificate = certificatePem;
public void setStorageCaCertificates(final List<String> certificatePem) {
this.storageCaCertificates = certificatePem;
}
public String getStorageCaCertificate() {
return storageCaCertificate;
public List<String> getStorageCaCertificates() {
return storageCaCertificates;
}
public CircuitBreakerConfiguration getCircuitBreakerConfiguration() {

View File

@@ -5,38 +5,13 @@
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Set;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
public class StripeConfiguration {
public record StripeConfiguration(@NotBlank String apiKey,
@NotEmpty byte[] idempotencyKeyGenerator,
@NotBlank String boostDescription,
@NotEmpty Set<@NotBlank String> supportedCurrencies) {
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

@@ -1,5 +1,5 @@
/*
* Copyright 2021 Signal Messenger, LLC
* Copyright 2021-2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
@@ -15,16 +15,13 @@ 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;
}
@@ -32,10 +29,6 @@ public class SubscriptionLevelConfiguration {
return badge;
}
public String getProduct() {
return product;
}
public Map<String, SubscriptionPriceConfiguration> getPrices() {
return prices;
}

View File

@@ -5,31 +5,16 @@
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.math.BigDecimal;
import java.util.Map;
import javax.validation.Valid;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.subscriptions.SubscriptionProcessor;
public class SubscriptionPriceConfiguration {
public record SubscriptionPriceConfiguration(@Valid @NotEmpty Map<SubscriptionProcessor, @NotBlank String> processorIds,
@NotNull @DecimalMin("0.01") BigDecimal amount) {
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;
}
}

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