Compare commits

...

1182 Commits

Author SHA1 Message Date
Jon Chambers
7e353f8ea0 Update to the latest version of the spam filter 2024-08-14 12:52:57 -04:00
Jon Chambers
0075e94a42 Rename AuthenticatedAccount to AuthenticatedDevice 2024-08-14 12:44:48 -04:00
Katherine
1ea9e38fea Use a separate virtual executor instead of the one used for async jersey tasks 2024-08-14 12:28:16 -04:00
Jon Chambers
3b405a53d0 Move "push notifications on close" logic to WebSocketConnection 2024-08-14 12:24:49 -04:00
Katherine
84c329e911 Key transparency search and monitor endpoints 2024-08-12 13:14:42 -07:00
Jonathan Klabunde Tomer
4349ceaf0e Update to the latest version of the spam filter 2024-08-12 11:49:18 -07:00
Ravi Khadiwala
acdf37561f Count requests that supply auth to boost endpoints 2024-08-12 13:40:58 -05:00
Jon Chambers
9128d4cc49 Correct a counter name 2024-08-12 14:39:31 -04:00
Jon Chambers
206e97d374 Use queries instead of scans when fetching experiment samples (again) 2024-08-12 14:39:22 -04:00
Jonathan Klabunde Tomer
f682af2fe0 build a multi-architecture docker manifest list 2024-08-12 11:38:59 -07:00
Jon Chambers
ecf7e60d98 Add an experiment for sending push notifications to idle devices that DO have pending messages 2024-08-07 16:41:19 -04:00
Ravi Khadiwala
68ddc070ca Count old version authentication tokens 2024-08-07 15:41:08 -05:00
Ameya Lokare
d661da8d7e Update to the latest version of the spam filter 2024-08-06 15:37:09 -07:00
Jonathan Klabunde Tomer
5d2e8cb000 update to libsignal 0.54.2 2024-08-06 14:18:14 -07:00
Ravi Khadiwala
096bb8e6e5 Remove deprecated SubscriptionController methods 2024-08-06 16:09:44 -05:00
Jon Chambers
1af8bb494e Generalize "is idle?" check in idle device notification scheduler 2024-08-05 15:19:39 -04:00
Jon Chambers
46d04d9d1a Add a command to schedule notifications for idle devices without messages 2024-08-05 15:19:39 -04:00
Jon Chambers
c0ca4ffbcc Retire "notify idle devices without messages" push notification experiment 2024-08-05 15:19:39 -04:00
Katherine Yen
8720b6db95 Update to the latest version of the spam filter 2024-08-05 12:11:40 -07:00
Jon Chambers
8c61d45206 Tune the "finish push notification experiment" command 2024-08-05 15:02:24 -04:00
Katherine
0e4625ef88 Migrate to 429 for all ratelimit responses 2024-08-05 12:02:11 -07:00
Ravi Khadiwala
10d559bbb5 Return backup info at /v1/subscription/configuration
- Return the free tier media duration and storage allowance for backups
- Add openapi annotations
- Update default media storage allowance
2024-08-02 14:08:00 -05:00
Jonathan Klabunde Tomer
65b2892de5 Simplify unlink-device-on-full-DB process 2024-08-02 12:03:43 -07:00
Chris Eager
6fa6c3c81c Ensure multi-recipient messages are addressed to ACI service IDs 2024-08-01 12:31:27 -05:00
Chris Eager
e4ffc932a9 Check for IdentityType.PNI in OptionalAccess#verify 2024-08-01 12:31:27 -05:00
Ameya Lokare
8afc0e6ab2 Update to the latest version of the spam filter 2024-07-31 12:34:54 -07:00
Jon Chambers
822092044b Add a method to check for the presence of persisted messages, skipping the cache 2024-07-31 10:50:47 -04:00
Jon Chambers
f1c153f39f Log max concurrency when starting/finishing experiments 2024-07-31 10:50:31 -04:00
Jon Chambers
7e62dc64dc Replace filterWhen with flatMap 2024-07-31 10:50:31 -04:00
Jon Chambers
2104a60703 Also check legacy parittion keys for message presence 2024-07-31 10:50:19 -04:00
Jon Chambers
97785fa570 Remove unused metrics and arguments 2024-07-31 10:50:19 -04:00
Chris Eager
9341fe9584 Add endpoint tag and story to auth type tag values 2024-07-31 09:38:14 -05:00
Ravi Khadiwala
3a582721cf Throw error for oversized inbound noise messages 2024-07-30 11:25:09 -05:00
Ravi Khadiwala
3d96d73169 Break up large outbound noise messages 2024-07-30 11:25:09 -05:00
Ravi Khadiwala
542422b7b8 Replace XX/NX handshakes with IK/NK 2024-07-30 11:25:09 -05:00
Jon Chambers
c835d85256 Drop opentest4j from dependency management 2024-07-30 12:24:26 -04:00
Jon Chambers
56ada7f0e9 Add a "dry run" mode for the "start push notification experiment" command 2024-07-30 12:19:00 -04:00
Jon Chambers
56fdebde75 Check for cached/persisted messages in parallel 2024-07-30 12:18:46 -04:00
Jon Chambers
4ee67064bb Remove a pair of unused methods 2024-07-30 12:18:34 -04:00
Jon Chambers
045ec9689d Introduce a job scheduler and experiment for sending notifications to idle devices 2024-07-29 11:16:53 -04:00
Jon Chambers
4ebad2c473 Add a framework for running experiments to improve push notification reliability 2024-07-25 11:36:05 -04:00
Jonathan Klabunde Tomer
1fe6dac760 read old new key and new new key 2024-07-24 07:44:44 -07:00
Jonathan Klabunde Tomer
f12a6ff73f Remove migration paths for lazy message deletion 2024-07-23 14:07:19 -07:00
Chris Eager
6eed458ceb Use server timestamp for delivery duration metrics 2024-07-18 12:22:59 -05:00
Jon Chambers
54fb0a6acb Add a general job scheduler 2024-07-18 13:22:31 -04:00
Jon Chambers
5147d9cb6d Mark old attachment endpoints as @Deprecated 2024-07-17 15:01:44 -04:00
Jon Chambers
37369929f3 Retire PushLatencyManager 2024-07-17 15:01:23 -04:00
Jon Chambers
4f10014902 Add an "is primary" dimension to message latency measurements 2024-07-17 15:01:08 -04:00
Ravi Khadiwala
0ef3e00ba7 Use non-legacy rate limit error in BackupAuthManager 2024-07-11 16:39:11 -05:00
Chris Eager
2408590430 Update GitHub Actions 2024-07-11 16:38:15 -05:00
Chris Eager
b7f4fe4d73 Update various dependencies 2024-07-11 16:38:15 -05:00
Chris Eager
b811492acd Update maven-wrapper.properties 2024-07-11 16:38:15 -05:00
Chris Eager
a63e0e0390 Remove unused RateLimiters 2024-07-11 16:37:55 -05:00
Jon Chambers
5e8a0b2cfa Introduce a utility class for finding reasonable times to send push notifications 2024-07-11 17:36:54 -04:00
Chris Eager
eac75aad03 Add distribution of multi-recipient message recipient counts 2024-07-09 15:04:47 -05:00
Chris Eager
b05fbc2102 Add metric for group send token adoption 2024-07-09 15:04:12 -05:00
Jon Chambers
6d166fdfc5 Return futures from "send push notification" operations 2024-07-08 15:36:17 -04:00
Ravi Khadiwala
2e36673702 Add warn log if changeNumber messages fail 2024-07-08 15:28:08 -04:00
ravi-signal
0c81ffe8b7 Count unregistered APNS tokens with a recent update 2024-07-08 15:27:48 -04:00
Jon Chambers
02b9ceb4c7 Discard APNs tokens if the APNs server reports that the token is expired 2024-07-08 15:27:23 -04:00
Chris Eager
775889c0b6 Remove deprecated PUT /v2/keys/signed endpoint 2024-07-08 15:26:26 -04:00
Chris Eager
98f2cdaf5a Reduce DynamoDB test client API timeouts 2024-07-08 15:26:11 -04:00
Jon Chambers
ff5cc3cb4f Avoid duplicate metric registry configuration in commands 2024-07-08 15:25:16 -04:00
Jon Chambers
ebecb1caec Throw an exception if metric registries are configured more than once 2024-07-08 15:25:16 -04:00
Jon Chambers
73e0aea85c Retire Device#hasMessageDeliveryChannel() 2024-06-26 14:46:39 -04:00
Ameya Lokare
1a09f5807b Update to the latest version of the spam filter 2024-06-26 11:45:58 -07:00
Ameya Lokare
ec009a2bba Pass AccountAndAuthenticatedDeviceHolder to spam filter 2024-06-26 11:32:49 -07:00
Jon Chambers
f52c40a492 Update to the latest version of the spam filter 2024-06-25 11:57:37 -04:00
Jon Chambers
1959c059ed Rename AuthEnablementRefreshRequirementProvider to LinkedDeviceRefreshRequirementProvider 2024-06-25 11:57:25 -04:00
Jon Chambers
2d1610b075 Stop monitoring device "enabled" state changes from auth enablement refresh requirement provider
Device enabled states no longer affect anything at an authentication level
2024-06-25 11:57:25 -04:00
Jon Chambers
2f76738b50 Expire APNs tokens if they haven't been updated since the expiration timestamp 2024-06-25 11:53:23 -04:00
Jon Chambers
1cf174a613 Include "token invalidation timestamp" in push notification responses 2024-06-25 11:53:23 -04:00
Jon Chambers
d743454d07 Store updated APNs tokens unconditionally 2024-06-25 11:53:23 -04:00
Jon Chambers
1cd16eaa08 Delete FCM tokens immediately if FCM reports that they're no longer active 2024-06-25 11:53:23 -04:00
Jon Chambers
90e622b307 Require that message bundles include all linked devices 2024-06-25 11:51:09 -04:00
Jon Chambers
cb5cd64c05 Gracefully handle NotPushRegisteredException 2024-06-25 11:23:16 -04:00
Jon Chambers
2619569549 Update to the latest version of the spam filter 2024-06-25 09:57:32 -04:00
Jon Chambers
d306cafbcc Allow, but do not require, message delivery to devices without active delivery channels 2024-06-25 09:53:31 -04:00
Jon Chambers
f5ce34fb69 Update to the latest version of the spam filter 2024-06-24 15:41:11 -04:00
Jon Chambers
dbeba4f173 Reduce "delete after idle" time for accounts from 180 days to 120 days 2024-06-24 15:20:35 -04:00
Jon Chambers
86f83635bc Document a missing "capability downgrade" response 2024-06-24 15:20:06 -04:00
Jon Chambers
fceda00d83 Consider all device when checking device capabilities 2024-06-24 15:20:06 -04:00
Jon Chambers
9b7af00cf5 Add methods to test whether a device may have unread messages 2024-06-24 15:19:48 -04:00
Chris Eager
fa1281ae86 Update maven plugin versions 2024-06-24 14:16:05 -05:00
Chris Eager
f5de4d7b71 Update various library dependencies 2024-06-24 14:16:05 -05:00
Chris Eager
1134df88e2 Remove obsolete "recaptcha" 2024-06-24 14:15:27 -05:00
ravi-signal
4aadabfac0 Make copy/delete streaming friendly 2024-06-20 16:00:09 -05:00
Jon Chambers
c27898a993 Update to the latest version of the spam filter 2024-06-20 11:36:00 -04:00
Ravi Khadiwala
daa897db93 Add some extra validation in integration tests 2024-06-18 15:29:58 -05:00
Jon Chambers
7a907bb44d Update to the latest version of the spam filter 2024-06-14 15:01:37 -04:00
Jon Chambers
d7cb219577 Update to the latest version of the spam filter 2024-06-14 11:03:42 -04:00
Jon Chambers
b28f8b0e7f Update to the latest version of the spam filter 2024-06-14 10:34:37 -04:00
Jon Chambers
51721dde50 Update to the latest version of the spam filter 2024-06-13 14:40:35 -04:00
Jon Chambers
09547ba788 Update to the latest version of the spam filter 2024-06-13 10:39:11 -04:00
Jon Chambers
3dc8acc385 Update to the latest version of the spam filter 2024-06-13 09:10:30 -04:00
Katherine
0414da8c32 Add delete sync capability 2024-06-12 13:54:06 -04:00
Jon Chambers
155450380e Fix weird indentation 2024-06-12 13:53:48 -04:00
Jon Chambers
09bc4ef1d6 Measure sizes of group messages 2024-06-12 13:53:48 -04:00
Jon Chambers
3aa4d8713c Switch to a remote-aggregated distribution for message size metrics 2024-06-12 13:53:48 -04:00
Jon Chambers
5fc926271f Remove a metrics tag for "has spam reporting token" 2024-06-12 13:53:33 -04:00
Katherine
f435b612c9 Remove stale pni, pnp, and giftBadges capabilities 2024-06-12 13:42:18 -04:00
Jon Chambers
5b78c0d3e0 Update to the latest version of the spam filter 2024-06-10 17:21:04 -04:00
Jon Chambers
6a14bf70e0 Extend push token removal grace period from 3 days to 14 2024-06-10 17:16:49 -04:00
Jon Chambers
138b368951 Add an explicit "incorrect password" authentication failure reason 2024-06-10 17:16:32 -04:00
Jon Chambers
0871d6ebc1 Add a log filter for spurious warnings about unsupported channel options 2024-06-10 17:16:10 -04:00
Jon Chambers
ad5ef76e8e Update to the latest version of the spam filter 2024-06-10 11:20:52 -04:00
Jon Chambers
2f55747601 Remove expiration check from Device#isEnabled() 2024-06-07 10:39:11 -07:00
Jonathan Klabunde Tomer
b376458963 include deleted-messages counter on both message deletion paths 2024-06-07 10:24:42 -07:00
Jon Chambers
64ac22a918 Resolve warnings/suggestions throughout MessageControllerTest 2024-06-06 14:11:44 -04:00
Alan Liu
ffb81e4ff7 Retrieve Cloudflare Turn Credentials from Cloudflare 2024-06-05 12:03:40 -04:00
Jonathan Klabunde Tomer
01743e5c88 Delete messages lazily on account and device deletion to prevent timeouts when deleting accounts/devices with large queues 2024-06-04 12:16:43 -07:00
Ameya Lokare
4ef6266e8f Update spam-filter to the latest version 2024-06-03 15:33:25 -07:00
Ameya Lokare
478a8362b8 Update to the latest version of the spam filter 2024-06-03 12:17:02 -07:00
ravi-signal
afa1899dc9 Add a require.proto presence annotation 2024-06-03 14:07:02 -05:00
Ravi Khadiwala
cea2abcf6e Fix some accidentally sync async methods 2024-06-03 14:07:02 -05:00
Jonathan Klabunde Tomer
c7d1ad56ff support local-filesystem-based dynamic config for tests 2024-05-31 15:25:47 -07:00
ravi-signal
a5f490cc53 return nonzero exit code when check-dynamic-config fails 2024-05-30 16:21:51 -05:00
ravi-signal
abe29fa6ee document gRPC error strategy 2024-05-30 16:20:34 -05:00
Chris Eager
f6d1e566e7 Add validation annotations to DynamicExperimentEnrollmentConfiguration 2024-05-24 09:11:48 -04:00
Jon Chambers
9ec4f0b2f5 Gracefully handle proxy protocol messages at the beginning of TCP connections 2024-05-24 09:11:19 -04:00
Ameya Lokare
1678045ce4 Update to the latest version of the spam filter 2024-05-23 10:29:53 -07:00
Chris Eager
7286e724dc Add SIGNAL_SERVER_CONFIG override to LocalWhisperServerService 2024-05-23 12:08:48 -05:00
Jon Chambers
e59a1e9efd Add support for TLS 1.2 for the benefit of load balancers performing health checks 2024-05-23 08:31:23 -04:00
Jon Chambers
097bedcb9b Add a health check handler to the Noise-over-WebSocket pipeline 2024-05-22 14:46:05 -04:00
Jon Chambers
907ff89011 Use complete certificate chains from the TLS keystore 2024-05-22 12:38:29 -04:00
Chris Eager
08faa0c009 Only register metrics for connections to upstream nodes 2024-05-22 11:17:52 -05:00
Ravi Khadiwala
dd4759487b Specify AuthCheckResponseV3 serialization 2024-05-22 09:42:05 -04:00
Jon Chambers
7980da9ce5 Set client public keys in the scope of a pessimistic account lock 2024-05-22 09:40:48 -04:00
Jon Chambers
0e43524dac Remove client public keys when deleting accounts/devices 2024-05-22 09:40:48 -04:00
Jon Chambers
c5c5f642e8 Configure and instantiate a Noise-over-WebSocket tunnel 2024-05-22 09:37:25 -04:00
Jon Chambers
e096c608ee Make Noise-over-WebSocket component names more consistent 2024-05-22 09:37:25 -04:00
Jon Chambers
9a2bfe1180 Add a plaintext mode to the Noise-over-WebSocket server for local testing 2024-05-22 09:25:28 -04:00
Ravi Khadiwala
9e36cabef0 Update to the latest version of the spam filter 2024-05-17 11:20:27 -05:00
ravi-signal
ce1c5be940 Add svr3 share-set store/retrieve 2024-05-17 10:45:18 -05:00
Ravi Khadiwala
1182d159aa Move command boilerplate into a base class 2024-05-17 10:44:58 -05:00
Ravi Khadiwala
7d95926f02 Add a crawler for backup usage metrics 2024-05-17 10:38:00 -05:00
Jon Chambers
101ecf342f Remove now-unused rate limiters 2024-05-16 16:56:42 -05:00
Jon Chambers
4efba94662 Add an API endpoint for storing public keys 2024-05-16 17:53:16 -04:00
Ravi Khadiwala
1855d661e8 Add maximum length to ProvisioningMessage 2024-05-16 16:47:47 -05:00
Ravi Khadiwala
438abc4cf9 Remove unused entity 2024-05-16 16:47:47 -05:00
Ravi Khadiwala
40639f70f4 Fix flaky MessageMetricsTest
Make the MeterRegistry in MessageMetrics configurable
2024-05-16 13:39:17 -05:00
Ravi Khadiwala
a80c020146 Remove AssignUsernameCommand 2024-05-13 16:43:02 -05:00
Ravi Khadiwala
2ce3270d21 Update docker container image 2024-05-13 16:39:54 -05:00
Ravi Khadiwala
4d8fe0b6b2 Fix a flaky test 2024-05-07 13:52:35 -05:00
Max Moiseev
411087ff1a Add a testcase with real libsignal User Agent 2024-05-07 14:51:01 -04:00
ravi-signal
10bb2a6a10 Add finer grain rollouts to experiments 2024-05-06 13:28:32 -05:00
Ravi Khadiwala
7aff81547a Manage some unmanaged executors 2024-05-06 13:25:18 -05:00
Ravi Khadiwala
fc097db2a0 Use storage-manager's copy implementation 2024-05-06 13:15:42 -05:00
Alan Liu
843151859d Adding hostname property to cloudflare turn config 2024-05-02 12:35:32 -07:00
adel-signal
854ab353b3 calling: update TurnCallRouter to shuffle instance IPs to prevent allocation skew
Co-authored-by: Jonathan Klabunde Tomer <125505367+jkt-signal@users.noreply.github.com>
2024-05-02 12:34:34 -07:00
Ameya Lokare
cc6ec1d351 Update to the latest version of the spam filter 2024-05-02 12:24:54 -07:00
Chris Eager
cf307db31d Update JsonMappingExceptionMapper “Early EOF” logic 2024-05-01 10:31:11 -05:00
Chris Eager
dcfca4d95e Update to the latest version of the spam filter 2024-05-01 10:30:53 -05:00
Chris Eager
567c368a81 Add DiscoverableSubtypeResolver to static YAML_MAPPER 2024-05-01 10:21:55 -05:00
Chris Eager
223b2fc263 Add javax.validation.Validator to SpamFilter#configure 2024-05-01 10:21:55 -05:00
Chris Eager
4a28ab6317 Add support to trial Cloudflare TURN beta 2024-05-01 10:15:01 -05:00
Chris Eager
0986ce12e6 Validate integration test Config 2024-05-01 10:11:15 -05:00
Ravi Khadiwala
37aa3b8e49 Default to 0 usedBytes in GET /v1/archives 2024-05-01 10:10:46 -05:00
Chris Eager
d7f14339fe Update to the latest version of the spam filter 2024-04-29 11:18:23 -05:00
Chris Eager
0e4be0c85a Add tests for WhisperServerService#run
Additionally, `LocalWhisperServerService` may be used for integration testing.
2024-04-29 11:05:35 -05:00
Jon Chambers
b6f8bca361 Update to the latest version of the spam filter 2024-04-26 15:59:32 -04:00
Jon Chambers
354c72968e Update to the latest version of the spam filter 2024-04-26 15:37:04 -04:00
Jon Chambers
9d3e3c7312 Use a common utility for turning Google API futures into CompletableFutures 2024-04-26 15:27:59 -04:00
Ravi Khadiwala
88e2687e23 Add a check for missing uak in OptionalAccess 2024-04-26 15:24:54 -04:00
Ravi Khadiwala
19944bfdb2 Update to libsignal 0.45 and use libsignal's BackupLevel 2024-04-25 16:54:41 -05:00
Ravi Khadiwala
c8efcf5105 Don't map a Mono<Void> 2024-04-25 16:47:34 -05:00
Jon Chambers
7f6da52349 Publish donation events for immediately-successful donations 2024-04-24 13:16:37 -04:00
Jon Chambers
8999f0104f Trigger pub/sub events for one-time donations via Braintree (PayPal) 2024-04-24 09:19:21 -04:00
Jon Chambers
516c481e94 Pass a CurrencyConversionManager to BraintreeManager 2024-04-24 09:19:21 -04:00
Jon Chambers
3266c2cd8f Add cloud pubsub as a dependency 2024-04-24 09:19:21 -04:00
Jonathan Klabunde Tomer
f0dcd8e07b Group Send Endorsement support for unversioned profile fetch 2024-04-23 14:58:19 -07:00
ravi-signal
9ef1fee172 Add DELETE v1/archives 2024-04-23 16:50:11 -05:00
Jonathan Klabunde Tomer
b3bd4ccc17 simplify profile auth 2024-04-23 14:49:04 -07:00
Chris Eager
fba7686390 Remove shard tag from Lettuce circuit breaker metrics 2024-04-23 16:00:46 -05:00
Ameya Lokare
2d314e5309 Minor: Return exception instead of throwing it in .orElseThrow()
Gets rid of an IntelliJ warning about it. No difference in behavior, there is
a test that already covers this path.
2024-04-22 09:32:12 -04:00
Jon Chambers
ed72d7f9ec Attach client platforms when creating donations 2024-04-22 09:31:57 -04:00
Jonathan Klabunde Tomer
b8f64fe3d4 Group Send Endorsement support for pre-key fetch endpoint 2024-04-19 15:40:46 -07:00
Chris Eager
ab64828661 Update custom Gauges to Micrometer MeterBinders 2024-04-17 17:43:34 -05:00
Chris Eager
10dfa18e81 Update to the latest version of the spam filter 2024-04-17 15:53:12 -05:00
Chris Eager
a38bf25e68 Migrate remaining custom metrics from Dropwizard to Micrometer
And remove some that are obsolete or duplicative.
2024-04-17 15:35:04 -05:00
Ameya Lokare
419ec6e308 Update to the latest version of the spam filter 2024-04-17 10:14:45 -07:00
Jonathan Klabunde Tomer
ada589d0c3 accept Group Send Endorsements for single-recipient message send
Co-authored-by: Jon Chambers <63609320+jon-signal@users.noreply.github.com>
2024-04-16 15:06:40 -07:00
Chris Eager
7068d27a8b Update to the latest version of the spam filter 2024-04-16 12:30:14 -05:00
Chris Eager
a302275187 Use a single cluster instance in MessagesCache 2024-04-16 12:04:18 -05:00
Chris Eager
b734d58ab7 Coalesce all Redis clusters to per-shard circuit breakers 2024-04-16 12:04:18 -05:00
Ravi Khadiwala
2046b02bd8 Rename RedeemReceiptRequest to help openapi 2024-04-16 11:16:18 -05:00
Chris Eager
1df824db7c Remap some JsonMappingExceptions to 408 and 400 2024-04-15 16:01:09 -05:00
Chris Eager
9cad2c6b7d Improve test Redis cluster setup and teardown 2024-04-15 15:58:23 -05:00
Chris Eager
82881c030a Update to the latest version of the spam filter 2024-04-15 14:13:09 -05:00
ravi-signal
00ca7d5942 Add cdn number query parameter to /archives/auth/read 2024-04-15 13:59:14 -05:00
ravi-signal
d36df3eaa9 Add new upload-for-copy backup endpoint 2024-04-15 13:47:46 -05:00
ravi-signal
e5d654f0c7 Add /v1/archives/redeem-receipt 2024-04-15 13:47:02 -05:00
Chris Eager
fc1f471369 Use per-shard circuit breakers for additional Redis clusters 2024-04-15 13:45:24 -05:00
Ameya Lokare
be6f4e38b8 Update to the latest version of the spam filter 2024-04-12 11:29:18 -07:00
Chris Eager
faa8674f39 Update to the latest version of the spam filter 2024-04-12 11:34:39 -05:00
Chris Eager
2dc707d86e Add per-shard Redis circuit breakers 2024-04-12 11:22:41 -05:00
Chris Eager
05a92494bb Remove X-Forwarded-For from RemoteAddressFilter 2024-04-11 11:03:37 -05:00
Alex Konradi
39fd955f13 Allow clients to request zkc-based auth creds
Allow clients to pass a zkcCredential=true query parameter to request the new
auth credential format implemented with the zkcredential Rust library.
2024-04-11 11:00:10 -05:00
ravi-signal
4863e1d227 Add backup levels to subscription configuration response 2024-04-11 10:58:40 -05:00
Jonathan Klabunde Tomer
44ad9d4f5f Update to the latest version of the spam filter 2024-04-10 16:52:28 -07:00
Jonathan Klabunde Tomer
2b652fe2a9 accept group send endorsements for multi-recipient sends 2024-04-10 16:51:09 -07:00
Ravi Khadiwala
cdd2082b07 Decrease logging level in RemoveExpiredBackupsCommand 2024-04-10 18:41:29 -05:00
Chris Eager
5c74aed8f6 Update to the latest version of the spam filter 2024-04-04 16:37:27 -05:00
Chris Eager
5b97bc04e0 Add ExternalRequestFilter 2024-04-04 16:24:20 -05:00
Ravi Khadiwala
63c8b275d1 Return 401 instead of 404 on unknown backup-ids 2024-04-04 10:56:48 -05:00
Ravi Khadiwala
1ebc17352f Check presentation before verifying the signature 2024-04-04 10:56:48 -05:00
Ravi Khadiwala
268c8382ee Fix Backup expiration purge time 2024-04-04 10:55:55 -05:00
ravi-signal
498dcbbfe8 Make media encrypter stream ordered
Co-authored-by: Chris Eager <79161849+eager-signal@users.noreply.github.com>
2024-04-04 10:53:45 -05:00
Ravi Khadiwala
3a1ecb342f allow striping clients in FaultTolerantHttpClient 2024-04-04 10:47:34 -05:00
ravi-signal
bb0da69c9e Set the shutdown gauge earlier in the shutdown process
Co-authored-by: Chris Eager <79161849+eager-signal@users.noreply.github.com>
2024-04-02 09:39:55 -05:00
Jon Chambers
796dce3cd3 Always use the "peek" strategy for counting one-time pre-keys 2024-04-02 10:31:20 -04:00
Jon Chambers
f59c34004d De-idiom-ize a "wait for everything to finish" idiom 2024-04-02 10:30:44 -04:00
Ameya Lokare
c4cbf0d618 Update to the latest version of the spam filter 2024-04-01 15:09:44 -07:00
Katherine
d002e5dda8 Hardcode paymentActivation flag to true 2024-04-01 15:07:11 -07:00
Chris Eager
a9d0ab271d Update embedded-redis to 0.9.0 2024-04-01 17:06:55 -05:00
Chris Eager
89cb821c97 Remove vavr dependency 2024-04-01 17:06:55 -05:00
Chris Eager
ef8c520b59 Update reactor-bom to 2023.0.4 2024-04-01 17:06:55 -05:00
Chris Eager
8897fd75ad Update push to 0.15.4 2024-04-01 17:06:55 -05:00
Chris Eager
fd748c1dc3 Update netty to 4.1.108.Final 2024-04-01 17:06:55 -05:00
Chris Eager
c95dbf7508 Update resilience4j to 2.2.0 2024-04-01 17:06:55 -05:00
Chris Eager
ed64c38950 Update Micrometer to 1.12.4 2024-04-01 17:06:55 -05:00
Chris Eager
0b5be8cdcd Update Lettuce to 6.3.2.RELEASE 2024-04-01 17:06:55 -05:00
Chris Eager
fcc77052a6 Add org.eclipse.jetty.io.EofException to expected measureSendMessageErrors 2024-04-01 17:06:29 -05:00
Ravi Khadiwala
831c9ff5bf Make backupDir/mediaDir indirect 2024-04-01 13:45:21 -05:00
Ravi Khadiwala
de37141812 Add a crawler that expires old backups 2024-04-01 13:45:21 -05:00
Ameya Lokare
c35a648734 Update to the latest version of the spam filter 2024-03-27 15:44:31 -07:00
Ravi Khadiwala
a550caf63f Make sure we close the HTTP/2 stream after cdn read errors 2024-03-27 17:00:37 -05:00
Ravi Khadiwala
de9eaa98db 404 instead of 400 on unknown source cdn 2024-03-27 17:00:37 -05:00
ravi-signal
37b657cbbd avoid baos::writeTo on virtual threads 2024-03-27 16:58:38 -05:00
Jon Chambers
a733f5c615 Add debugging context to signature validation failures 2024-03-27 17:58:02 -04:00
Jon Chambers
8a587d1d12 Rename NoiseStreamHandler to NoiseTransportHandler for consistency with Noise specification terminology 2024-03-27 17:57:46 -04:00
Chris Eager
75bb22f08b Include HTTP2ServerConnectionFactory in JettyHttpConfigurationCustomizer 2024-03-27 16:56:19 -05:00
Jon Chambers
d10da39e5b Pare back debug-oriented metrics around fetching pre-keys 2024-03-26 16:44:34 -04:00
adel-signal
54e9b839bd update TurnTokenGenerator to add whether this is with ip or url turn allocation 2024-03-26 13:40:53 -07:00
Jon Chambers
aec6ac019f Introduce a Noise-over-WebSocket client connection manager 2024-03-22 15:20:55 -04:00
Ravi Khadiwala
075a08884b Preserve backupCredentialRequest across rereg 2024-03-22 14:19:35 -05:00
Jon Chambers
6fcb2ab5dd Remove username phased rollout plumbing (usernames are now available to everybody) 2024-03-21 13:42:20 -04:00
ameya-signal
7f0f045f29 Minor cleanup of report spam endpoint (#1979) 2024-03-21 10:41:25 -07:00
Ravi Khadiwala
e7d1eadf8e Fix experiments in BackupAuthManager 2024-03-20 11:43:07 -05:00
Chris Eager
a9b5359f7c Update to the latest version of the spam filter 2024-03-19 13:32:33 -05:00
Chris Eager
9df6e19204 Ignored EofException response failure in MetricsHttpChannelListener 2024-03-18 17:38:19 -05:00
Chris Eager
5eaae184c9 Set request failure to debug in MetricsHttpChannelListener 2024-03-18 17:38:19 -05:00
Chris Eager
459882e6fa Add JettyHttpConfigurationCustomizer 2024-03-18 17:38:19 -05:00
Chris Eager
2c2b5d555e Rename obsolete uses of recaptcha 2024-03-13 16:40:32 -05:00
Alex Konradi
0ab2428d87 Don't produce zkgroup auth creds with PNI as ACI 2024-03-13 16:22:45 -05:00
Chris Eager
8574494573 Support "captcha" rename in AnswerChallengeRequest.type 2024-03-13 16:19:05 -05:00
Chris Eager
a4d4a9c686 Remove RecaptchaClient 2024-03-13 16:19:05 -05:00
Chris Eager
3d32b68bb2 Move WebSocketClient SecureRandom to a static field 2024-03-12 16:54:38 -05:00
Chris Eager
fd9eb462cc Replace extraneous SecureRandom with empty byte array 2024-03-12 16:49:57 -05:00
Ravi Khadiwala
f9533e016f Adjust metrics in RemoveExpiredUsernameHoldsCommand 2024-03-11 13:50:08 -05:00
Jon Chambers
85b15fa63b Actually increment the "get group credentials" counter 2024-03-08 17:07:16 -05:00
Chris Eager
e236842888 Update to the latest version of the spam filter 2024-03-08 14:11:56 -06:00
Chris Eager
3dadaf9334 Migrate DynamicConfigurationManager to use java.util.concurrent 2024-03-08 13:53:28 -06:00
Ravi Khadiwala
9e510a678c disable response buffering on the websocket
Jersey buffers responses (by default up to 8192 bytes) just so it can
add a content length to responses. We already buffer our responses to
serialize them as protos, so we can compute the content length
ourselves. Setting the buffer to zero disables buffering.
2024-03-08 13:46:00 -06:00
Chris Eager
2dc0ea2b89 Address potential NullPointerException when calling Collection#contains 2024-03-08 13:43:31 -06:00
Alex Konradi
7d364ca7ce Count group auth reqs without pniAsServiceId=true 2024-03-06 15:41:48 -05:00
Chris Eager
9f6a6d7f5b Include HTTP/2 stream idle timeouts in IOExceptionMapper 2024-03-06 11:11:39 -06:00
Ravi Khadiwala
3cc740cda3 Temporarily hold a username after an account releases it 2024-03-06 11:09:25 -06:00
Ravi Khadiwala
47b24b5dff Simplify username operations in Accounts
- Group username table constants together
- Rethrow JsonProcessingException earlier
- Use UpdateAccountSpec.forAccount in username operations
- Inline confirm/clear transaction helpers
2024-03-06 11:09:25 -06:00
adel-signal
8f100a792e calling: update TurnCallRouter to reduce returned options 2024-03-05 11:26:19 -08:00
Chris Eager
84c6731ddf Update protobuf.version to match upstream 2024-02-29 16:56:25 -06:00
Chris Eager
1f1de353de Reapply "Update to Dropwizard 3.0.6"
This reverts commit 95e83c52fa.
2024-02-29 16:56:25 -06:00
Chris Eager
40eb82adbe Update actions/checkout and actions/setup-java to 4.x 2024-02-29 15:56:55 -06:00
Ehren Kret
d9240e1e2e use consistent abbreviation for GNU AGPLv3 2024-02-28 18:09:27 -06:00
Ehren Kret
9abaed8385 update README copyright year 2024-02-28 17:51:05 -06:00
Ravi Khadiwala
95e83c52fa Revert "Update to Dropwizard 3.0.6"
This reverts commit 2ad5d33251.
2024-02-28 15:58:55 -06:00
Ravi Khadiwala
be377dcda8 Update to the latest version of the spam filter 2024-02-28 15:34:32 -06:00
Chris Eager
88a68e883e Update various <exclusions> in POM 2024-02-28 15:25:33 -06:00
Chris Eager
2ad5d33251 Update to Dropwizard 3.0.6 2024-02-28 15:25:33 -06:00
Ravi Khadiwala
4e5dd914dd Add varargs variant to HmacUtil 2024-02-28 15:18:39 -06:00
ravi-signal
2adf1e5017 Avoid modification of Account from @ReadOnly endpoint 2024-02-28 15:18:12 -06:00
Jon Chambers
55ca4e93c4 Update to noise-java 0.1.1 2024-02-28 16:16:46 -05:00
Jon Chambers
d1d03f45c5 Resolve warnings throughout AccountControllerTest 2024-02-26 16:11:03 -05:00
Ravi Khadiwala
436bd891bd Don't register SpamFilter as a request filter 2024-02-26 09:33:36 -06:00
Chris Eager
a7c28fe5ed Rename "name" tags to be more distinguishing 2024-02-26 09:32:50 -06:00
Chris Eager
60814d1ff0 Configure metrics registries earlier in background command setup 2024-02-26 09:32:01 -06:00
Jon Chambers
d018efe2a5 Require non-null proofs in "confirm username hash" requests 2024-02-26 10:30:52 -05:00
Jon Chambers
6fd0cba06a Temporarily restore the giftBadges capability for backward compatibility 2024-02-26 10:30:29 -05:00
Ravi Khadiwala
86f9322036 Update to the latest version of the spam filter 2024-02-23 16:08:41 -06:00
Katherine
12c6af23ee Map TransactionConflict to ContestedOptimisticLockException in username flows 2024-02-23 14:06:03 -08:00
Ravi Khadiwala
69330f47fd Explicitly call spam-filter for verification session updates
Pass in the same information to the spam-filter, but just use explicit
method calls rather than jersey request filters.
2024-02-23 16:04:24 -06:00
Ravi Khadiwala
4f40c128bf Explicitly call spam-filter for challenges
Pass in the same information to the spam-filter, but just use explicit
method calls rather than jersey request filters.
2024-02-23 15:58:52 -06:00
Jon Chambers
30b5ad1515 Fix an incorrectly-named "check keys" method 2024-02-23 13:17:10 -05:00
adel-signal
665a26d164 update call routing to return ipv6 ips in RFC3986 format 2024-02-23 11:57:58 -05:00
Jon Chambers
a5774bf6ff Introduce a (dormant) Noise/WebSocket for future client/server communication 2024-02-23 11:42:42 -05:00
Jon Chambers
d2716fe5cf Add an endpoint for checking that clients and the server have a common view of the client's repeated-use keys 2024-02-23 11:41:58 -05:00
Jon Chambers
279f877bf2 Validate pre-key signatures via the legacy "set signed pre-key" endpoint 2024-02-23 11:33:41 -05:00
Ravi Khadiwala
d51e6a43e7 Update to the latest version of the spam filter 2024-02-22 12:12:42 -06:00
Ravi Khadiwala
6a96756c87 Update to the latest version of the spam filter 2024-02-22 12:09:37 -06:00
Ravi Khadiwala
df69d9f195 Annotate authenticated endpoints with @ReadOnly or @Mutable 2024-02-22 12:05:32 -06:00
Ravi Khadiwala
26ffa19f36 Lifecycle management for Account objects reused accross websocket requests 2024-02-22 12:05:32 -06:00
Jon Chambers
29ef3f0b41 Add tests and metrics for parsing invalid keys 2024-02-21 15:32:25 -05:00
Jon Chambers
106d5e54c7 Extract a common base class for public key serializers/deserializers 2024-02-21 15:32:25 -05:00
Jon Chambers
6ac2460eb0 Drop the "key without version byte" counter 2024-02-21 15:32:25 -05:00
dependabot[bot]
79c030b138 Bump org.apache.commons:commons-compress from 1.24.0 to 1.26.0 in /service 2024-02-21 11:52:41 -08:00
Chris Eager
c8d649e8c2 Update MetricsHttpChannelListener to share MetricsRequestEventListener namespace 2024-02-21 13:32:47 -06:00
Ravi Khadiwala
1fdf82dd6c Remove unused ChangePhoneNumberRequest object
Actual request object is ChangeNumberRequest
2024-02-21 13:32:27 -06:00
Jon Chambers
4aa4246695 Clear account records from the account cache after username operations 2024-02-20 12:11:29 -05:00
Chris Eager
1bebceb29c Actually fix flaky test in MetricsHttpChannelListenerIntegrationTest 2024-02-20 10:52:04 -06:00
Jon Chambers
a2139ee236 Revert "Add diagnostic dimensions to the "get keys" counter"
This reverts commit cd64390141.
2024-02-18 20:14:05 -05:00
Jon Chambers
8c55f39cdf Revert "Use a phased enrollment strategy for the pnp compatibility flag"
This reverts commit 3e12a8780d.
2024-02-18 20:08:39 -05:00
Chris Eager
0329184c94 Fix flaky test in MetricsHttpChannelListenerIntegrationTest 2024-02-18 20:08:29 -05:00
Jon Chambers
cd64390141 Add diagnostic dimensions to the "get keys" counter 2024-02-18 18:10:58 -05:00
Jon Chambers
3e12a8780d Use a phased enrollment strategy for the pnp compatibility flag 2024-02-18 17:59:00 -05:00
Jon Chambers
11e6ff1bbe Introduce a pnp capability for backward compatibility 2024-02-18 17:59:00 -05:00
Jon Chambers
36f85fc97e Fix an inverted boolean in a counter 2024-02-16 15:18:18 -05:00
Jon Chambers
9040cfd200 Count "store key" attempts by key type/platform 2024-02-16 13:09:55 -05:00
Jon Chambers
757da3b15a Fully-qualify OpenAPI Tag, allowing for imported Micrometer Tag 2024-02-16 13:09:55 -05:00
Jon Chambers
d162590a32 Record a remote-aggregated distribution of one-time pre-key counts 2024-02-16 13:09:55 -05:00
Jon Chambers
f41e1716c6 Combine/expand existing "get keys" counter 2024-02-16 13:09:55 -05:00
Jon Chambers
4dce0f1b9d Add identity type/"stale" dimensions to the "pre-key unavailable" counter 2024-02-16 13:09:55 -05:00
Jonathan Klabunde Tomer
fef57dce0d use explicit Timer rather than micrometer annotation for send-message latency distribution 2024-02-15 14:58:43 -08:00
Jon Chambers
d884700b61 Significantly reduce default Redis command timeout 2024-02-15 17:57:17 -05:00
Jonathan Klabunde Tomer
ff9ad4bd1d export 1:1 message send latency as a full distribution 2024-02-15 10:33:02 -08:00
Chris Eager
9ce2b7555c Add static servlet paths to MetricsHttpChannelListener 2024-02-14 17:09:30 -06:00
Jon Chambers
f90ccd3391 Count attempts to fetch a one-time EC pre-key that result in a "key unavailable" response 2024-02-14 18:04:31 -05:00
Jon Chambers
5ff092e541 Retire the pni device capability 2024-02-14 18:04:23 -05:00
Ravi Khadiwala
dcdf401f64 Make DELETE /v1/device synchronous 2024-02-14 15:30:10 -05:00
Chris Eager
e4fb80b39b Add onResponseFailure handler to MetricsHttpChannelListener 2024-02-14 13:43:23 -06:00
Chris Eager
9745854ab8 Switch MetricsHttpChannelListener to ContainerResponseFilter 2024-02-14 13:43:23 -06:00
Jon Chambers
7124621f66 Use pre-calculated pre-key counts when possible 2024-02-14 14:28:33 -05:00
Jon Chambers
47fd8f5793 Assume that all devices are PNI-capable 2024-02-14 14:27:48 -05:00
Jonathan Klabunde Tomer
40d698f2db Remove last vestiges of stale capabilities.
Not for deployment until 2024-02-06!
2024-02-14 14:23:05 -05:00
adel-signal
74abe98706 initialize call routing data syncs 2024-02-13 17:05:08 -08:00
Chris Eager
86787f3bc8 Start DynamicConfigurationManager directly after construction 2024-02-13 13:08:20 -06:00
Chris Eager
699b0c775a Add dynamic configuration to enable detailed Lettuce metrics 2024-02-13 10:33:43 -06:00
Chris Eager
ff59ef8094 Add MetricHttpChannelListener 2024-02-13 10:29:03 -06:00
Jonathan Klabunde Tomer
089af7cc1f correct metric name in MaxMindDatabaseManager timer 2024-02-09 13:37:39 -08:00
Jonathan Klabunde Tomer
1591a2d9a3 CallRoutingTableManager: fix crash on startup due to typo in metric naming 2024-02-09 12:27:51 -08:00
Jonathan Klabunde Tomer
f7984ed642 CallDnsRecordsManager: fix crash on startup due to typo in metric naming 2024-02-09 12:14:50 -08:00
Jonathan Klabunde Tomer
be634c6043 Update to the latest version of the spam filter 2024-02-09 11:38:18 -08:00
adel-signal
d1f68eacd9 Add call routing API endpoint for turn servers 2024-02-09 11:12:23 -08:00
Jon Chambers
4f45f23094 Prevent old versions of the Android app from carrying out "change phone number" requests 2024-02-09 09:58:38 -05:00
Jon Chambers
c5dc01ee11 Restore high-cardinality Lettuce metrics for debugging 2024-02-08 19:27:56 -05:00
Ravi Khadiwala
587c385936 Update to the latest version of the spam filter 2024-02-07 17:19:59 -06:00
Ravi Khadiwala
3a641a58b0 Update to the latest version of the spam filter 2024-02-07 14:38:35 -06:00
Chris Eager
e944306a28 Remove obsolete dropwizard-db dependency 2024-02-07 14:35:23 -06:00
Ravi Khadiwala
3b44ed6d16 Explicitly call spam-filter for messages
Pass in the same information to the spam-filter, but just use explicit
method calls rather than jersey request filters.
2024-02-07 14:30:32 -06:00
Chris Eager
0965ab8063 Move HeaderUtils#getMostRecentyProxy to RemoteAddressFilter 2024-02-07 14:28:37 -06:00
Chris Eager
fcae100df1 Add dropwizard-http2 dependency 2024-02-07 14:28:20 -06:00
Fedor Indutny
24a7762873 Add identityType tag to SENT_MESSAGE_COUNTER_NAME 2024-02-06 17:39:00 -06:00
Chris Eager
e441ab60a2 Add metric for IPv4/IPv6 requests count 2024-02-06 17:38:25 -06:00
Chris Eager
50c2bc5edb Update to the latest version of the spam filter 2024-02-05 13:45:50 -06:00
Chris Eager
2ab14ca59e Refactor remote address/X-Forwarded-For handling 2024-02-05 13:37:06 -06:00
Chris Eager
4475d65780 Make Accounts#changeNumber exception handling more resilient to future changes 2024-02-01 15:14:15 -06:00
Ravi Khadiwala
b1d10f5817 Add lifecycle management for AwsCrt for commands 2024-02-01 15:08:45 -06:00
Chris Eager
36664f37de Update to the latest version of the spam filter 2024-01-31 18:04:03 -06:00
Chris Eager
c838df90ef Add HttpServletRequestUtil 2024-01-31 17:53:47 -06:00
Chris Eager
fb39af67e5 Allow for more TransactWriteItems when inspecting TransactionCanceledException 2024-01-31 17:46:32 -06:00
Chris Eager
2d4d37f96a Translate TransactionCanceledException to ContestedOptimisticLockException in Accounts#changeNumber 2024-01-31 17:27:16 -06:00
Jonathan Klabunde Tomer
84af984c4b remove unversioned and libsignal-0.36 versioned zk secrets from sample bundle 2024-01-31 15:25:23 -08:00
Jon Chambers
26adf20ee8 Make "fetch profile" endpoints @ManagedAsync 2024-01-31 14:38:50 -06:00
Jon Chambers
72668ed0a2 Make "send individual message" endpoint @ManagedAsync 2024-01-31 14:38:50 -06:00
Ravi Khadiwala
50f1ed7851 Add a virtual-thread backed @ManagedAsyncExecutor 2024-01-31 14:38:50 -06:00
ravi-signal
cf8f2a3463 remove synchronized locks that may be held while blocking 2024-01-31 14:29:15 -06:00
ravi-signal
b483159b3a reintroduce VirtualThreadPinEventMonitor 2024-01-31 14:28:12 -06:00
ravi-signal
480abebf7e Offload account lock updates to accountLockExecutor 2024-01-31 14:27:16 -06:00
Ravi Khadiwala
b924dea045 Remove VirtualThreadPinEventMonitor 2024-01-30 13:49:39 -06:00
Ravi Khadiwala
2c1e7e5ed6 Disable flaky VirtualThreadPinEventMonitorTest 2024-01-30 13:29:58 -06:00
Ravi Khadiwala
4dfd74906c Add timeouts to VirtualThreadPinEventMonitorTest 2024-01-30 13:10:43 -06:00
Chris Eager
fdae6ad94f Update to the latest version of the spam filter 2024-01-30 12:53:01 -06:00
Chris Eager
c80225a18c Remove NoneHealthResponder 2024-01-30 12:51:43 -06:00
ravi-signal
0e6242373e Add a monitor for virtual thread pin events 2024-01-30 12:48:07 -06:00
ravi-signal
4305db5579 Update to aws sdk 2.23.8 and use AwsCrtHttpClient 2024-01-30 12:46:27 -06:00
ravi-signal
36e7772f74 Put some validation on to profile version strings
Co-authored-by: Jon Chambers <63609320+jon-signal@users.noreply.github.com>
2024-01-26 15:24:38 -06:00
Chris Eager
ca05df5172 Revert "Add support for secondary credentials for registration service"
This reverts commit 4b8fc2950f.
2024-01-26 11:42:35 -06:00
Chris Eager
422e8e6f3e Remove CommandStopListener 2024-01-26 11:31:04 -06:00
Jon Chambers
852b285d84 Configure and instantiate a ClientPublicKeys data store/manager 2024-01-26 10:29:59 -05:00
Jon Chambers
6c13193623 Add a data store for client public keys for transport-level authentication/encryption 2024-01-26 10:29:59 -05:00
Ravi Khadiwala
61809107c8 Update to the latest version of the spam filter 2024-01-25 13:27:13 -06:00
Ravi Khadiwala
6bda9d8604 Set destination account on the request context 2024-01-25 13:02:28 -06:00
Ravi Khadiwala
1428ca73de Make identity token fetcher more async friendly
After the identity token expires a subsequent call would do a blocking
operation to retrieve the new token. Since we're making use of an async
gRPC client, this tends to block a thread we don't want to be blocking
on.

Instead, switch to periodically refreshing the token on a dedicated
thread.
2024-01-25 13:01:21 -06:00
Katherine
498ace0488 Remove iOS special case handling logic for SEPA/iDEAL 2024-01-25 10:52:17 -08:00
Chris Eager
f082b95efb Update to the latest version of the spam filter 2024-01-24 15:01:35 -06:00
Chris Eager
4b8fc2950f Add support for secondary credentials for registration service 2024-01-24 13:52:23 -06:00
Jon Chambers
595cc55578 Retire the returnPqKey flag when fetching pre-keys 2024-01-23 16:01:40 -05:00
Ravi Khadiwala
91b0c368b4 Remove unused table configuration parameter 2024-01-23 14:48:52 -06:00
Jonathan Klabunde Tomer
21d0ffc990 Allow "reserving" one's own committed username hash 2024-01-23 12:44:55 -08:00
Chris Eager
55b9d84956 Add host to DogstatsdConfiguration 2024-01-22 15:59:52 -06:00
Chris Eager
ffdb0db6c6 Migrate from host tag to dd.internal.entity_id 2024-01-22 15:59:52 -06:00
Chris Eager
a5ed07a666 Support environment variable substitution in configuration 2024-01-22 11:25:18 -06:00
Chris Eager
da02c90bad Remove AWS instance profile credentials provider 2024-01-22 11:24:03 -06:00
Ravi Khadiwala
3820a231ec Strictly enforce ACI service identifier strings have no prefix 2024-01-22 11:23:33 -06:00
Dimitris Apostolou
408b065b9e Fix typos 2024-01-20 12:40:08 -05:00
hduelme
238ab84749 use StandardCharsets UTF_8 instead of String 2024-01-20 12:39:43 -05:00
Ravi Khadiwala
6894015986 Update to the latest version of the spam filter 2024-01-19 14:32:15 -06:00
Ravi Khadiwala
f5080f9bd6 Support configurable verification code sender overrides 2024-01-19 13:58:17 -06:00
Ravi Khadiwala
db4aa99ce0 Make the ServiceIdentifier interface sealed 2024-01-19 13:57:20 -06:00
ravi-signal
70134507f8 Add metric for ServiceId string parsing 2024-01-19 13:56:47 -06:00
Ravi Khadiwala
360a4793ae Remove global lock in RateLimitChallengeManager 2024-01-19 13:56:09 -06:00
Jon Chambers
47bfb25f2c Retire a now-unused transacational update method for accounts 2024-01-18 16:40:21 -05:00
Jon Chambers
b048b0bf65 Remove signed pre-keys from Device entities 2024-01-18 12:13:00 -05:00
Jonathan Klabunde Tomer
394f9929ad limit size of multi-recipient messages 2024-01-11 16:31:37 -08:00
ravi-signal
bf39be3320 Add Content-Type header for copy uploads 2024-01-11 14:59:35 -06:00
Chris Eager
4a2cbb9ec7 Stop timers on Flux termination 2024-01-11 14:57:31 -06:00
Ravi Khadiwala
cc6cf8194f Add media deletion endpoint 2024-01-11 10:19:06 -06:00
Ravi Khadiwala
e934ead85c Fix incorrect comparison in archive usage metric 2024-01-11 10:19:06 -06:00
Ravi Khadiwala
323bfd9a6e Remove some secondary keys from account cache
Remove e164, usernameHash, and usernameLink secondary mappings from the
accounts redis cache.
2024-01-11 10:10:32 -06:00
Jon Chambers
bf05e47e26 Get accounts asynchronously when checking SVR credentials via gRPC 2024-01-11 11:09:49 -05:00
Jon Chambers
d18f576239 Revert "Revert "Treat the stand-alone signed pre-keys table as the source of truth for signed pre-keys""
This reverts commit 3f9edfe597.
2024-01-10 11:22:10 -05:00
Jon Chambers
7d483c711a Revert "Revert "Retire "migrate signed pre-keys" configuration""
This reverts commit a024949311.
2024-01-10 11:22:10 -05:00
Jon Chambers
61256d49cd Revert "Revert "Retire the "migrate signed pre-keys" command""
This reverts commit f738bc97e7.
2024-01-10 11:22:10 -05:00
Jonathan Klabunde Tomer
184cdc0331 Remove two-stage check of username availability in reserve/confirm 2024-01-09 14:01:42 -08:00
Ravi Khadiwala
ed972a0037 Fix archive listing directory prefix order 2024-01-09 16:01:11 -06:00
Ravi Khadiwala
a62a6c1cb6 Change type of Cdn3StorageManagerConfiguration.clientSecret to SecretString 2024-01-08 16:06:54 -06:00
Ravi Khadiwala
ba0c6be3e3 Update to the latest version of the spam filter 2024-01-08 15:00:29 -06:00
Ravi Khadiwala
f66566aa17 Fix default configuratiton in MonitoredS3ObjectConfiguration 2024-01-08 14:52:14 -06:00
ravi-signal
b6ecfc7131 Add archive listing 2024-01-08 13:54:57 -06:00
adel-signal
460dc6224c Update to the latest version of the spam filter 2024-01-08 10:56:43 -08:00
adel-signal
2b688b1a60 Refactor spam filter's S3MonitoredObject to server parent module.
Allows other parts of server to use S3MonitoredObjects.
2024-01-08 10:34:12 -08:00
Jon Chambers
3c64d9292f Revert "Expand the default max packet size for Dogstatsd"
This reverts commit 0f52d2e464.
2024-01-08 12:23:16 -05:00
Jon Chambers
0f52d2e464 Expand the default max packet size for Dogstatsd 2024-01-08 11:43:50 -05:00
Jonathan Klabunde Tomer
1e5fadc440 Allow reserving a hash previously reserved (but not committed) by the same user 2024-01-05 15:38:45 -08:00
Chris Eager
f495ff483a Update RemoveExpiredLinkedDevicesCommand to retry failures 2024-01-05 15:38:34 -08:00
Jonathan Klabunde Tomer
4e3b1509a8 Update to the latest version of the spam filter 2024-01-04 12:20:50 -08:00
Jonathan Klabunde Tomer
d1a80cc880 fix build-breaking typo 2024-01-04 11:46:07 -08:00
Jonathan Klabunde Tomer
e1ad25cee0 Group Send Credential support in chat 2024-01-04 11:38:57 -08:00
Chris Eager
195f23c347 Add /v1/accounts and /v2/keys to the WebSocket 2024-01-02 15:51:57 -06:00
Chris Eager
ad6b99be6a Dispatch client presence operations after device deletion to a dedicated executor 2023-12-22 10:51:17 -06:00
Chris Eager
b9dd9fc47d Reduce fan-out by processing a single stream of expired linked devices 2023-12-22 10:51:06 -06:00
Ravi Khadiwala
19a8a80a30 Update to the latest version of the spam filter 2023-12-21 16:29:54 -06:00
Chris Eager
637792c6d4 Move RemoveExpiredLinkedDevicesCommand error handling for more accurate metrics 2023-12-21 16:06:41 -06:00
Jon Chambers
4d1bca2d97 Dispatch client presence operations after account deletion to a dedicated executor 2023-12-21 13:40:49 -05:00
Chris Eager
f33a2eba50 Add buffer + shuffle and configurable concurrency to RemoveExpiredLinkedDevicesCommand 2023-12-21 11:14:02 -06:00
Jon Chambers
5d6bea5ec9 Clear Redis cache entries strictly after removing accounts from DynamoDB 2023-12-21 12:08:03 -05:00
Jon Chambers
057d1f07a8 Remove bulk "set repeated-use signed pre-keys" methods because they were only ever used for single devices 2023-12-21 12:07:42 -05:00
Ravi Khadiwala
25c3f55672 Update documentation/integration-tests actions to java 21 2023-12-20 16:41:16 -06:00
Ravi Khadiwala
c9d4091c1e Update to the latest version of the spam filter 2023-12-20 16:38:20 -06:00
Ravi Khadiwala
1d55562dc3 Update to Java 21 2023-12-20 16:37:14 -06:00
Jon Chambers
95bb9a9780 Log signed pre-key retrieval errors and mismatches 2023-12-20 14:55:12 -05:00
Chris Eager
06c391cbf6 Add counter for failed account updates to RemoveExpiredLinkedDevicesCommand 2023-12-20 13:51:28 -06:00
Chris Eager
d90dff95b1 Move MeterRegistry#close calls to after the lifecycle has fully stopped 2023-12-20 13:51:16 -06:00
Ravi Khadiwala
c93972a322 Update to the latest version of the spam filter 2023-12-19 18:33:45 -06:00
Ravi Khadiwala
ca47a7b663 handle new RegistrationService proto error 2023-12-19 18:19:26 -06:00
Jonathan Klabunde Tomer
9d3d4a3698 Add phone-number-sharing field to versioned profile
Co-authored-by: Katherine <katherine@signal.org>
2023-12-19 11:20:04 -08:00
Chris Eager
3b509bf820 Add command to remove expired linked devices 2023-12-19 13:11:26 -06:00
Jon Chambers
5b7f91827a Remove signed pre-keys transactionally when removing devices 2023-12-19 14:11:05 -05:00
Chris Eager
a44491714c Update to the latest version of the spam filter 2023-12-18 14:55:39 -06:00
Chris Eager
06800043a9 Set TLS keystore password in secondary persistent services 2023-12-15 13:39:58 -06:00
Chris Eager
3090de56b8 Set TLS keystore password from secrets configuration 2023-12-15 12:47:27 -06:00
Katherine
a37acd1f42 Add ttl for braintree writes to onetime donation table 2023-12-15 13:37:35 -05:00
Jonathan Klabunde Tomer
372e3f83d2 Update libsignal to 0.37.0 2023-12-15 10:36:59 -08:00
Chris Eager
de260a2bef Update to the latest version of the spam filter 2023-12-15 12:23:01 -06:00
Jonathan Klabunde Tomer
e9a130f976 add versioned zkconfig secrets to sample secrets bundle 2023-12-14 17:38:49 -08:00
Jonathan Klabunde Tomer
43f17414ff Make key-setting methods asynchronous again 2023-12-14 16:49:16 -08:00
Jon Chambers
b259eea8ce Refactor/clarify account creation/reclamation process 2023-12-14 16:48:57 -08:00
Katherine
9cfc2ba09a Persist onetime donation payment success timestamps for Braintree transactions 2023-12-14 16:48:29 -08:00
Chris Eager
bb347999ce Propagate another subscription processor error to clients 2023-12-14 15:40:08 -05:00
Katherine
3548c3df15 Calculate onetime badge expiration from payment success timestamp 2023-12-14 15:39:46 -05:00
Jon Chambers
1167d0ac2e Make key-setting methods synchronous 2023-12-13 17:49:55 -05:00
Jon Chambers
f738bc97e7 Revert "Retire the "migrate signed pre-keys" command"
This reverts commit c7cc3002d5.
2023-12-13 17:49:55 -05:00
Jon Chambers
3f9edfe597 Revert "Treat the stand-alone signed pre-keys table as the source of truth for signed pre-keys"
This reverts commit feb933b4df.
2023-12-13 17:49:55 -05:00
Jon Chambers
a024949311 Revert "Retire "migrate signed pre-keys" configuration"
This reverts commit 44145073f1.
2023-12-13 17:49:55 -05:00
Jon Chambers
609c901867 Refactor key-fetching to be reactive 2023-12-13 12:46:48 -05:00
Jon Chambers
4ce060a963 Count wildcard "get keys" requests by platform 2023-12-13 12:46:48 -05:00
Jon Chambers
c4ca0fee40 Synchronize access to responseItems when assembling a "get keys" response 2023-12-13 12:46:48 -05:00
Chris Eager
8d4acf0330 Remove ForkJoinPool.managedBlock in favor of async updates 2023-12-13 10:18:04 -05:00
Jon Chambers
28a981f29f Assume that all devices have signed pre-keys 2023-12-13 10:17:51 -05:00
Jon Chambers
c29113d17a Reject requests with missing device capabilities 2023-12-12 11:37:44 -05:00
Katherine
951f978447 Use start of subscription period as fallback timestamp 2023-12-12 11:34:44 -05:00
Jon Chambers
07899f35bd Return DeviceController#linkDevice to synchronous processing 2023-12-12 11:34:23 -05:00
Katherine
3cbbf37468 Use payment success timestamp to calculate recurring donation badge expiration 2023-12-12 10:01:20 -05:00
Jon Chambers
6c7a3df5ae Retire non-atomic device-linking pathways 2023-12-12 09:53:51 -05:00
Jon Chambers
2054ab2771 Revert "Count requests with missing device capabilities"
This reverts commit 6cdf8ebd2c.
2023-12-12 09:53:51 -05:00
Jon Chambers
44145073f1 Retire "migrate signed pre-keys" configuration 2023-12-12 09:53:34 -05:00
Jon Chambers
feb933b4df Treat the stand-alone signed pre-keys table as the source of truth for signed pre-keys 2023-12-11 11:39:54 -05:00
Jon Chambers
c7cc3002d5 Retire the "migrate signed pre-keys" command 2023-12-11 11:07:00 -05:00
xiaolou86
049b901d63 Fix typos 2023-12-09 10:04:12 -05:00
Jon Chambers
3cf1b92dfc Do not set one-time pre-keys if the lists of new keys are empty 2023-12-08 14:03:08 -05:00
Jon Chambers
5b0fcbe854 Always expose sequential fluxes to account crawlers 2023-12-08 13:43:42 -05:00
Jon Chambers
cca747a1f6 Treat transaction conflicts during transactional account updates as contested optimistic locks 2023-12-08 12:13:23 -05:00
Jon Chambers
417d99a17e Check story rate limits in parallel 2023-12-08 12:13:08 -05:00
Jon Chambers
e9708b9259 Use random UUIDs throughout MessageControllerTest 2023-12-08 12:13:08 -05:00
Jon Chambers
e5d3be16b0 Fetch destination accounts in parallel when sending multi-recipient messages 2023-12-08 12:13:08 -05:00
Jordan Rose
2ab3c97ee8 Replace MultiRecipientMessage parsing with libsignal's implementation
Co-authored-by: Jonathan Klabunde Tomer <jkt@signal.org>
2023-12-08 11:52:47 -05:00
Jon Chambers
f20d3043d6 Process key migrations sequentially to better control concurrency 2023-12-07 21:42:49 -05:00
Jonathan Klabunde Tomer
4efda89358 multisend cleanup 2023-12-07 12:23:02 -08:00
Jon Chambers
1fb88271e5 Invalidate cache entries for accounts after successfully adding devices 2023-12-07 13:27:26 -05:00
Jon Chambers
a843780f68 Add a (failing!) test for device-linking 2023-12-07 13:27:26 -05:00
Jon Chambers
5ad83da4e0 Remove the now-unused admin logger 2023-12-07 12:58:43 -05:00
Jon Chambers
949cc9e214 Update to the latest version of the spam filter 2023-12-07 12:58:23 -05:00
Jon Chambers
50d92265ea Add devices to accounts transactionally 2023-12-07 11:19:40 -05:00
Chris Eager
e084a9f2b6 Remove PUT/DELETE methods from RemoteConfigController 2023-12-07 10:54:19 -05:00
Jon Chambers
664f9f36e1 Use padded base64 encoding for outbound device names 2023-12-07 10:53:56 -05:00
Jon Chambers
4c9efdb936 Revert "Revert "Represent device names as byte arrays""
This reverts commit 45848e7bfe.
2023-12-07 10:53:56 -05:00
Jon Chambers
45848e7bfe Revert "Represent device names as byte arrays"
This reverts commit 5ae2e5281a.
2023-12-06 17:05:04 -05:00
Ravi Khadiwala
4fa10e5783 Fix NPE in request metrics when a finished request is missing a response 2023-12-06 15:11:41 -05:00
Ravi Khadiwala
fc0bc85f4d Add some extra request validation to /v1/archives/keys 2023-12-06 15:11:27 -05:00
Jon Chambers
5ae2e5281a Represent device names as byte arrays 2023-12-06 15:10:45 -05:00
Jon Chambers
34a943832a Align push notification types and delivery priorities 2023-12-06 12:40:16 -05:00
Jon Chambers
db17693ba7 Revert "Temporarily make registration challenge notifications "noisy""
This reverts commit 9069c5abb6.
2023-12-06 12:40:16 -05:00
Jon Chambers
6cdf8ebd2c Count requests with missing device capabilities 2023-12-06 10:40:50 -05:00
Ravi Khadiwala
072b470f46 Be more specific about encodings in /v1/archive docs 2023-12-06 10:40:20 -05:00
Ravi Khadiwala
78b2df2ecc Use long instead of int for epoch seconds 2023-12-06 10:40:06 -05:00
Jon Chambers
51a825f25c Update to the latest version of the spam filter 2023-12-06 10:39:04 -05:00
Jon Chambers
00e72a30c9 Assume all accounts have primary devices 2023-12-06 10:28:19 -05:00
Ravi Khadiwala
69990c23a5 Avoid test dep in integration-test 2023-12-05 16:35:51 -06:00
Jon Chambers
df421e0182 Update signed pre-keys in transactions 2023-12-05 14:20:16 -05:00
Jon Chambers
ede9297139 Disallow identity key changes 2023-12-05 14:14:24 -05:00
Jon Chambers
85383fe581 Remove an unused "store keys" method 2023-12-05 14:14:24 -05:00
Jon Chambers
4cca7aa4bd Normalize identity types throughout KeysController 2023-12-05 14:14:24 -05:00
Jon Chambers
e2037dea6c Rename PreKeyState to SetKeysRequest 2023-12-05 14:14:24 -05:00
Jon Chambers
f10f772e94 Convert PreKeyState to a record 2023-12-05 14:14:24 -05:00
Jon Chambers
9ecfe15ac4 Use multiline strings for PreKeyState documentation 2023-12-05 14:14:24 -05:00
Jon Chambers
5f0726af8a Perform cleanup operations before overwriting an existing account record 2023-12-05 12:18:09 -05:00
Ravi Khadiwala
331bbdd4e6 replace deprecated apache RandomUtils 2023-12-05 10:48:18 -06:00
Jon Chambers
37e3bcfc3e Move "remove device" logic into AccountsManager 2023-12-05 11:44:58 -05:00
Jon Chambers
4f42c10d60 Disallow sync messages to PNIs 2023-12-05 11:44:27 -05:00
Jonathan Klabunde Tomer
20392a567b Revert "multisend cleanup"
This reverts commit c03249b411.
2023-12-01 14:39:31 -08:00
Jonathan Klabunde Tomer
c03249b411 multisend cleanup 2023-11-30 15:50:36 -08:00
gram-signal
22e6584402 Add auth controller for SVR3 to /v3/backup. 2023-11-30 15:50:21 -08:00
Chris Eager
c18aca9215 Update to the latest version of the spam filter 2023-11-29 16:08:31 -06:00
Jon Chambers
aa23a5422a Don't modify registration IDs from the "set account attributes" endpoint 2023-11-29 16:56:47 -05:00
Jon Chambers
01fde4f9ca Require PNI-associated registration IDs for new devices 2023-11-29 16:56:47 -05:00
Jon Chambers
3980dec123 Revert "Add metrics to check presence of PNI-associated registration IDs"
This reverts commit 1da3f96d10.
2023-11-29 16:56:47 -05:00
Jon Chambers
c97f837f45 Revert "Instrument registration ID changes via the "set account attributes" endpoint"
This reverts commit 8fbc1dac74.
2023-11-29 16:56:47 -05:00
Chris Eager
9c54d2407b Update tests 2023-11-29 15:48:24 -06:00
Chris Eager
a027c4ce1f Conditionally use request.remoteAddr instead of X-Forwarded-For 2023-11-29 15:48:24 -06:00
Chris Eager
b1fd025ea6 Use EpochSecondSerializer for UserRemoteConfigList.serverEpochTime 2023-11-29 15:47:10 -06:00
Jonathan Klabunde Tomer
a05a230085 use Account-specific json serializer when reserving username hash 2023-11-29 13:40:06 -08:00
Jon Chambers
8fbc1dac74 Instrument registration ID changes via the "set account attributes" endpoint 2023-11-29 10:26:41 -05:00
Katherine
f46842c6c9 Validate registration IDs 2023-11-28 15:43:35 -08:00
Ravi Khadiwala
8b95bb0c03 Only run integration tests if integration bucket is configured 2023-11-28 15:10:49 -06:00
ravi-signal
202dd8e92d Add copy endpoint to ArchiveController
Co-authored-by: Jonathan Klabunde Tomer <125505367+jkt-signal@users.noreply.github.com>
Co-authored-by: Chris Eager <79161849+eager-signal@users.noreply.github.com>
2023-11-28 11:45:41 -06:00
Jon Chambers
1da3f96d10 Add metrics to check presence of PNI-associated registration IDs 2023-11-28 12:26:15 -05:00
Chris Eager
5f6fe4d670 Add NoneHealthResponder to replace default responder 2023-11-27 14:38:47 -06:00
Chris Eager
a74438d1ee Add test for concurrent in-flight outbound messages on WebSocket queue processing
This also elevates the implicit default concurrency (via reactor’s `Queues.SMALL_BUFFER_SIZE`) to be explicit.
2023-11-27 14:37:46 -06:00
Jon Chambers
c8033f875d Create accounts transactionally 2023-11-27 09:57:43 -05:00
Jon Chambers
07c04006df Avoid blocking calls in async account updates 2023-11-27 09:57:43 -05:00
Jon Chambers
521900c048 Always require atomic account creation 2023-11-27 09:52:57 -05:00
Jon Chambers
9069c5abb6 Temporarily make registration challenge notifications "noisy" 2023-11-22 10:25:59 -05:00
Jon Chambers
ff7a5f471b Always use "conserve power" priority for challenge notifications 2023-11-21 17:58:32 -05:00
Ravi Khadiwala
42a47406cc Fix platform tag for push challenge metric 2023-11-21 17:57:32 -05:00
Katherine
de10b6de7b Add metric to count invalid registration ID when setting account attributes 2023-11-20 14:25:27 -08:00
Chris Eager
d6ade0e1ac Send HTTP 408 on idle timeout 2023-11-20 16:24:54 -06:00
Ravi Khadiwala
e04b5e5c9f run integration-test job daily at 19:30 UTC 2023-11-20 15:31:45 -06:00
Ravi Khadiwala
15a6c46d47 use a configured number in integration test 2023-11-20 15:02:53 -06:00
Jonathan Klabunde Tomer
cb1fc734c2 report exceptions in fanned-out sends of multi-recipient messages 2023-11-20 10:46:26 -08:00
Katherine Yen
db7f18aae7 Add missing boolean parameter to getSubscriptionStatus method call 2023-11-16 19:28:10 -05:00
Katherine
7fbc327591 For Braintree, return active subscription status if latest transaction also succeeded 2023-11-16 19:15:46 -05:00
Chris Eager
84b56ae1b2 Upgrade to dropwizard 3.0 2023-11-16 17:57:48 -06:00
Katherine
041aa8639a Enforce story ratelimit 2023-11-16 12:36:43 -05:00
Katherine
216ac72ad0 Use Braintree subscription status alongside transaction status to determine 204 and 402 responses for /v1/subscription/{subscriberId}/receipt_credentials 2023-11-16 12:36:19 -05:00
Jon Chambers
c85ddaeb9c Make DynamoDB client connection pool sizes configurable 2023-11-16 12:29:43 -05:00
Jonathan Klabunde Tomer
e09dec330a Update to the latest version of the spam filter 2023-11-15 17:34:57 -08:00
Jonathan Klabunde Tomer
8f7bae54fe When persisting messages fails due to a full queue in DynamoDB, automatically unlink one device to free up room.
Co-authored-by: Chris Eager <79161849+eager-signal@users.noreply.github.com>
2023-11-15 17:15:17 -08:00
Jon Chambers
ce60f13320 Don't track IPv4/IPv6 metrics at the application level
We can measure this at the infrastructure level without observing IPs directly.
2023-11-15 17:18:54 -05:00
Jon Chambers
1ac0140666 Remove a couple of now-unused variables 2023-11-15 17:12:08 -05:00
Jon Chambers
6cc8b147a9 Remove unused X-Forwarded-For parameters 2023-11-15 17:09:47 -05:00
Jon Chambers
e078161e2f Consistently use HttpHeaders.X_FORWARDED_FOR 2023-11-15 17:09:36 -05:00
Jonathan Klabunde Tomer
7764185c57 return explicit Response rather than Void from async controllers with no expected body content 2023-11-14 21:57:25 -08:00
Katherine
d4ef2adf0a Remove low priority attempt login notification workaround for old iOS clients 2023-11-13 12:06:55 -05:00
Jonathan Klabunde Tomer
a83378a44e add an option to replace username ciphertext without rotating the link handle 2023-11-13 09:01:54 -08:00
ravi-signal
a4a4204762 Allow re-registered accounts to reclaim their usernames 2023-11-13 10:41:23 -06:00
Jon Chambers
acd1140ef6 Make registration challenge APNs payloads "silent" 2023-11-13 08:39:58 -08:00
Jon Chambers
fbf71c93ff Make rate limit challenge APNs payloads "silent" 2023-11-13 08:39:58 -08:00
Katherine
38bc0c466a Change sepaMaximumEuros field to number in JSON response 2023-11-10 10:16:03 -08:00
Katherine
71e4351743 Add sepaMaximumEuros field to subscription configuration 2023-11-10 09:13:51 -08:00
Katherine
387e4b94b4 Expand charge object on Stripe subscription to surface charge failure information 2023-11-10 09:12:59 -08:00
Katherine
201c76b861 Add charge failure details to /v1/subscription/{subscriberId}/receipt_credentials 402 response 2023-11-08 10:54:14 -08:00
Chris Eager
1c3aa87ca6 Update to the latest version of the spam filter 2023-11-06 10:11:41 -06:00
Sergey Skrobotov
db63ff6b88 gRPC validations 2023-11-03 11:30:48 -07:00
Katherine
115431a486 Un-hardcode payment activation flag 2023-11-03 11:27:34 -07:00
Jonathan Klabunde Tomer
d47ff9b7c7 don't make empty transactions 2023-11-02 16:20:19 -07:00
Chris Eager
b0818148cf Update to the latest version of the spam filter 2023-11-02 11:48:50 -05:00
Chris Eager
2bc4412d66 Encapsulate device ID in ProvisioningAddress 2023-11-02 11:48:10 -05:00
Chris Eager
6a428b4da9 Convert Device.id from long to byte 2023-11-02 11:48:10 -05:00
Jonathan Klabunde Tomer
7299067829 Don't attempt to update PNI PQ prekeys for disabled devices 2023-11-01 16:55:55 -07:00
Chris Eager
5659cb2820 Update to the latest version of the spam filter 2023-11-01 15:53:26 -05:00
Chris Eager
570aa4b9e2 Remove several unused classes 2023-11-01 15:46:10 -05:00
Chris Eager
c4079a3b11 Update to the latest version of the spam filter 2023-11-01 10:07:42 -05:00
Ravi Khadiwala
6b38b538f1 Add ArchiveController
Adds endpoints for creating and managing backup objects with ZK
anonymous credentials.
2023-10-30 14:02:19 -05:00
Chris Eager
ba139dddd8 Use all devices when checking limit 2023-10-30 12:40:06 -05:00
Chris Eager
38b581a231 Update to the latest version of the spam filter 2023-10-27 10:22:50 -05:00
Chris Eager
3c2675b41a Update libphonenumber to 8.13.23 2023-10-27 09:26:40 -05:00
Chris Eager
0f5c62ade5 Set max threads = min threads on command executor services 2023-10-27 09:26:32 -05:00
Jon Chambers
54bc3bce96 Add an authentication-required gRPC service for working with accounts 2023-10-25 14:47:20 -04:00
Jon Chambers
3d92e5b8a9 Explicitly stop and start managed dependencies 2023-10-24 16:50:02 -04:00
Chris Eager
325d145ac3 Update to the latest version of the spam filter 2023-10-24 14:33:31 -05:00
Chris Eager
b0654a416a Update maven plugins 2023-10-24 14:32:55 -05:00
Chris Eager
19930ec2e4 Update dependencies
- AWS: 2.20.130 → 2.21.5
- Braintree: 3.25.0 → 3.27.0
- commons-csv: 2.13.0 → 2.14.0
- dropwizard: 2.1.7 → 2.1.9
- Google libraries BOM: 26.22.0 → 26.25.0
- grpc: 1.56.1 → 1.58.0
- kotlin: 1.9.0 → 1.9.10
- protobuf: 3.23.2 → 3.24.3
- stripe: 23.1.1 → 23.10.0
- junit-pioneer: 2.0.1 → 2.1.0
- firebase-admin: 9.1.1 → 9.2.0
- swagger-jaxrs2: 2.2.8 → 2.2.17
- java-uuid-generator: 4.2.0 → 4.3.0
- log4j: 2.17.0 → 2.21.0
- reactor-bom: 2022.0.10 → 2022.0.12
2023-10-24 14:32:55 -05:00
Jon Chambers
e4de6bf4a7 Only update devices that aren't already disabled 2023-10-24 15:29:03 -04:00
Jon Chambers
21125c2f5a Update to the latest version of the spam filter 2023-10-20 16:38:04 -04:00
Katherine Yen
6f166425fe Fix bank mandate test 2023-10-20 16:19:31 -04:00
Chris Eager
cf2353bcf9 Remove InstrumentedExecutorService wrapping 2023-10-20 15:14:35 -05:00
Jon Chambers
744eb58071 Discard old chunk-based account crawler machinery 2023-10-20 16:09:17 -04:00
Jon Chambers
9d47a6f41f Introduce a reactive push notification feedback processor 2023-10-20 16:09:17 -04:00
Jonathan Klabunde Tomer
4f4c23b12f Update to the latest version of the spam filter 2023-10-20 09:39:46 -07:00
Jonathan Klabunde Tomer
fb02815c27 Update to the latest version of the spam filter 2023-10-20 09:12:37 -07:00
Jonathan Klabunde Tomer
fd19299ae0 Accept a captcha score threshold for challenges from the spam filter 2023-10-20 09:09:22 -07:00
Jon Chambers
9c053e20da Drop Util#isEmpty/Util#nonEmpty in favor of StringUtils 2023-10-20 12:04:15 -04:00
Jon Chambers
19d7b5c65d Drop Util#wait 2023-10-20 12:04:15 -04:00
Jon Chambers
7b9d8829da Remove entirely unused Util methods 2023-10-20 12:04:15 -04:00
Jon Chambers
3505ac498c Update to the latest version of the spam filter 2023-10-20 10:52:43 -04:00
Jon Chambers
f0ab52eb5d Rename "master device" to "primary device" 2023-10-20 10:52:13 -04:00
Jon Chambers
e8cebad27e Avoid modifying original Account instances when constructing JSON for updates 2023-10-20 10:51:50 -04:00
Jon Chambers
6441d5838d Clear username links in the same transaction when clearing username hashes 2023-10-20 10:51:50 -04:00
Jon Chambers
ac0c8b1e9a Introduce a canonical constant for UAK length 2023-10-20 10:50:44 -04:00
Katherine
8ec062fbef Define an endpoint to set the default payment method for iDEAL subscriptions 2023-10-19 10:29:40 -07:00
Katherine
5990a100db Add charge failure details to /v1/subscription/boost/receipt_credential 402 response 2023-10-19 10:21:26 -07:00
Jon Chambers
bc35278684 Drop the old AccountCleaner 2023-10-19 10:34:24 -04:00
Jon Chambers
c3c7329ebb Add a single-shot command for removing expired accounts 2023-10-19 10:34:24 -04:00
Jon Chambers
6fd1c84126 Make command namespace available to subclasses 2023-10-19 10:34:24 -04:00
Jon Chambers
0100f0fcc9 Migrate a username links test from AccountsTest to AccountsManagerUsernameIntegrationTest 2023-10-18 10:20:48 -04:00
Jon Chambers
0cdc32cf65 Really REALLY fix instrumentation for re-registration of recently-deleted accounts 2023-10-18 10:15:03 -04:00
Jon Chambers
601e9eebbd Implement an anonymous account service for looking up accounts 2023-10-18 10:14:52 -04:00
Jon Chambers
eaa868cf06 Add a remote address interceptor to base gRPC tests 2023-10-18 10:14:52 -04:00
Jon Chambers
f55504c665 Add utility methods for rate-limiting by remote address 2023-10-18 10:14:52 -04:00
Katherine Yen
b2ff016cc1 Add back story ratelimiter with counter but do not enforce 2023-10-17 12:22:17 -04:00
Jon Chambers
33b4f17945 Make username-related operations asynchronous 2023-10-17 12:21:52 -04:00
Jon Chambers
e310a3560b Remove unused configuration for the legacy Secure Backup Service 2023-10-17 12:21:14 -04:00
Jon Chambers
162b27323e Fix instrumentation for re-registration of recently-deleted accounts 2023-10-17 12:20:58 -04:00
Jon Chambers
ae976ef8d6 Retire legacy Secure Value Recovery plumbing 2023-10-13 15:32:41 -04:00
Katherine
c6b4e2b71d Support iDEAL 2023-10-12 09:54:05 -07:00
Jon Chambers
33c8bbd0ce Trim stale capabilities from the profiles gRPC service 2023-10-12 12:52:32 -04:00
Jon Chambers
f2a3b8dba4 Treat APNs team/key IDs as secrets so they can change atomically with the key itself 2023-10-12 12:52:13 -04:00
Katherine
207ae6129b Add paymentMethod and paymentProcessing fields to GET /v1/subscription/{subscriberId} endpoint 2023-10-10 09:56:50 -07:00
Katherine
e1aa734c40 Define endpoint to get localized bank mandate text 2023-10-05 09:53:33 -07:00
Jonathan Klabunde Tomer
9b1b03bbfa Update to the latest version of the spam filter 2023-10-05 09:46:27 -07:00
Jon Chambers
bb7e0528c4 Make account deletion an asynchronous operation 2023-10-04 10:44:50 -04:00
Jonathan Klabunde Tomer
010eadcd10 UnlinkDeviceCommand improvements 2023-10-03 15:14:02 -07:00
Katherine
c43e0b54f2 Exclude SEPA_DEBIT as a supported payment method for certain iOS client versions 2023-10-03 11:34:52 -07:00
Chris Eager
6522b74e20 Remove obsolete metrics 2023-10-03 11:42:25 -05:00
Chris Eager
8c7975d89a Clear presence only if the connection’s displacement listener is still present 2023-10-03 11:42:25 -05:00
Chris Eager
407070c9fc Unsubscribe from keyspace notifications only if queue still maps to the listener 2023-10-03 11:42:25 -05:00
Katherine
7821a3cd61 Accommodate PayPal with SEPA changes 2023-09-28 10:28:17 -07:00
Katherine
a00c2fcfdb Support SEPA 2023-09-28 08:26:01 -07:00
Jonathan Klabunde Tomer
9cd21d1326 count ItemCollectionSizeLimitExceededExceptions persisting messages 2023-09-27 10:58:28 -07:00
Jonathan Klabunde Tomer
aaba95f9b8 return null for empty username hash in AccountIdentityResponse 2023-09-27 10:58:04 -07:00
Chris Eager
8d1135a2a3 Refine RegistrationController logic
Local device transfer on iOS uses the `409` status code to prompt the
transfer UI. This needs to happen before sending a `423` and locking
an existing account, since the device transfer
includes the local device database verbatim.
2023-09-25 15:54:31 -05:00
Jon Chambers
f9fabbedce Convert SubscriptionController request/response entities to records 2023-09-25 12:32:49 -07:00
Chris Eager
16012e6ffe Remove obsolete ManagedPeriodicWork 2023-09-25 12:15:17 -07:00
Jon Chambers
d10a132b0c Remove unused methods in SubscriptionController 2023-09-25 12:14:56 -07:00
Sergey Skrobotov
0b3af7d824 gRPC API for external services credentials service 2023-09-25 12:14:49 -07:00
Sergey Skrobotov
d0fdae3df7 Enable header-based auth for WebSocket connections 2023-09-25 12:14:40 -07:00
Ravi Khadiwala
a263611746 editorconfig: keep_simple_classes_in_one_line 2023-09-25 10:10:44 -05:00
Chris Eager
0e989419c6 Add metric for late removal of message availability and displacement listeners 2023-09-19 12:04:24 -05:00
ravi-signal
0fa8276d2d retry hCaptcha errors
Co-authored-by: Jon Chambers <63609320+jon-signal@users.noreply.github.com>
2023-09-14 16:07:35 -05:00
Ravi Khadiwala
b594986241 Set an idle timeout on registration gRPC client 2023-09-14 16:06:49 -05:00
Sergey Skrobotov
9f3ffa3707 gRPC API for payments service 2023-09-14 11:12:00 -07:00
Jonathan Klabunde Tomer
8e598c19dc don't attempt to update KEM prekeys if we have no PQ-enabled devices 2023-09-14 11:11:22 -07:00
Katherine
2601d6e906 Convert some fields on CreateProfileRequest and VersionedProfileResponse to byte arrays 2023-09-13 14:00:03 -07:00
Jon Chambers
de41088051 Update to WireMock 2.35.1 2023-09-13 16:56:15 -04:00
Jon Chambers
f2752b2a02 Update to the latest version of the spam filter 2023-09-13 16:02:46 -04:00
Jon Chambers
f0544fab89 Update recently-deleted accounts table transactionally as part of account mutations 2023-09-13 16:02:19 -04:00
Jon Chambers
1b9bf01ab1 Absorb DeletedAccounts into Accounts 2023-09-13 16:02:19 -04:00
Ravi Khadiwala
9945367fa1 Update to the latest version of the spam filter 2023-09-11 15:19:10 -05:00
Katherine
cbc3887226 Define identity key check endpoint in keys anonymous service 2023-09-11 11:57:00 -07:00
Ravi Khadiwala
c11b74e9c0 Update to the latest version of the spam filter 2023-09-11 13:37:07 -05:00
Jon Chambers
2b764c2abd Don't allow callers to unlink their primary device 2023-09-11 14:29:48 -04:00
Jon Chambers
845fc338d7 Add a (failing) test for removing primary devices from accounts 2023-09-11 14:29:48 -04:00
Sergey Skrobotov
977243ebfd DRY gRPC tests, refactor error mapping 2023-09-08 17:12:08 -07:00
Chris Eager
29ca544c95 Revert "Set suppressCancel=true in Mono.fromFuture"
This reverts commit 8348263fab.
2023-09-07 17:03:33 -05:00
Ravi Khadiwala
94b41d3a2c Fixup default rate limits
A previous refactor left the default rate limits off by a factor of 60.
2023-09-07 16:07:42 -05:00
Chris Eager
92bb783cbb Use static exception instance when a connection is closed 2023-09-07 16:06:16 -05:00
Chris Eager
8348263fab Set suppressCancel=true in Mono.fromFuture 2023-09-07 16:06:03 -05:00
Ravi Khadiwala
48f633de11 Fix type for comparison in integration test 2023-09-07 14:41:29 -05:00
Ravi Khadiwala
b3b9a629f3 Update to the latest version of the spam filter 2023-09-07 11:18:48 -05:00
Ravi Khadiwala
5934b7344a Remove unused captcha configuration 2023-09-07 11:16:32 -05:00
Chris Eager
a9a2e40fed Move onErrorResume to individual sendMessage Mono 2023-09-07 11:15:57 -05:00
Chris Eager
656326355a Invert String.equals() to prevent NullPointerException 2023-09-07 11:14:36 -05:00
Chris Eager
b89e2e5355 Propagate certain subscription processor errors to client responses 2023-09-06 15:57:14 -05:00
Chris Eager
2d187abf13 Handle WebSocket sendMessage errors with onErrorResume 2023-09-06 15:53:01 -05:00
Chris Eager
b701412295 Update maven-wrapper.properties 2023-09-06 15:48:27 -05:00
Jonathan Klabunde Tomer
b4dad81220 Update to the latest version of the spam filter 2023-09-05 13:55:07 -07:00
Jonathan Klabunde Tomer
6bccdad998 Update to the latest version of the spam filter 2023-09-05 10:23:39 -07:00
Chris Eager
ecd6b0174a Add timeouts to crawl chunk join()s 2023-08-31 15:03:19 -05:00
Chris Eager
a1e534a515 Add default request timeout to FaultTolerantHttpClient 2023-08-31 15:03:19 -05:00
Sergey Skrobotov
ebbe19ba63 Add missing copyright headers and reorder some imports 2023-08-30 16:07:53 -07:00
Katherine Yen
6a37b73463 Profile gRPC: Define getExpiringProfileKeyCredential endpoint 2023-08-30 14:56:43 -07:00
Katherine Yen
dd18fcaea2 Profile gRPC: Define getVersionedProfile endpoint 2023-08-30 14:47:11 -07:00
Katherine Yen
5afc058f90 Profile gRPC: Define getUnversionedProfile endpoint 2023-08-30 14:24:43 -07:00
Jon Chambers
5e221fa9a3 Tests for validation of Kyber keys on PNI change/key distribution events
Co-authored-by: Jonathan Klabunde Tomer <jkt@signal.org>
2023-08-30 14:07:33 -07:00
Jon Chambers
0e0cb4d422 Drop the non-normalized account crawler 2023-08-30 13:55:41 -04:00
Jonathan Klabunde Tomer
09f6d60ae9 Update to the latest version of the spam filter 2023-08-29 15:52:42 -07:00
Jonathan Klabunde Tomer
9577d552c6 pass challenge type to rate limit reset listeners 2023-08-29 15:19:49 -07:00
Chris Eager
093f17dce2 Update to stripe-java 23.1.1 2023-08-29 15:18:16 -07:00
Jon Chambers
6089f49b9c Add a gRPC interceptor for getting client addresses 2023-08-29 15:18:06 -07:00
Sergey Skrobotov
cfb910e87e Adding copyright headers to proto files 2023-08-28 14:39:33 -07:00
Ravi Khadiwala
376cffc61d Update to the latest version of the spam filter 2023-08-25 16:49:05 -05:00
Chris Eager
d338ba5152 Convert some KeysController methods return CompletableFutures 2023-08-24 11:59:28 -05:00
Chris Eager
f181397664 Add test for round-trip AccountsManager JSON serialization 2023-08-24 11:18:01 -05:00
Chris Eager
708f23a2ee Remove deprecated identity key and signed pre-key methods 2023-08-24 11:18:01 -05:00
Chris Eager
2d1a979eba Update libphonenumber to 8.13.19 2023-08-24 11:07:18 -05:00
Chris Eager
ee0be92967 Update to the latest version of the spam filter 2023-08-24 11:06:30 -05:00
Chris Eager
7536b75508 Remove unused test fixtures 2023-08-24 11:06:11 -05:00
Jonathan Klabunde Tomer
7237ae6c54 check that pq last-resort prekeys, if submitted, match device list 2023-08-24 09:04:29 -07:00
Sergey Skrobotov
ca05753a3e adding 400 response documentation to the API call 2023-08-23 13:20:07 -07:00
Chris Eager
9ca8503eac Downgrade to stripe-java 22.30.0 2023-08-22 16:31:46 -05:00
Jon Chambers
754f71ce00 Add a gRPC service for working with devices 2023-08-22 16:31:02 -05:00
Jon Chambers
619b05e56c Add utility a method for requiring authentication with the account's primary device 2023-08-22 16:31:02 -05:00
Jon Chambers
8b13826949 Convert DeviceInfo and DeviceInfoList to a record 2023-08-22 16:31:02 -05:00
Jon Chambers
a96ee57c7e Defer asynchronous actions when deriving Mono instances from futures 2023-08-22 16:28:02 -05:00
Jon Chambers
ff1ef90a6d Defer actions taken after rate limit checks 2023-08-22 16:28:02 -05:00
Chris Eager
22905fa8ee Downgrade logstash-logback-encoder to 7.3 2023-08-21 12:44:02 -05:00
Chris Eager
9e218ddd1c Update to the latest version of the spam filter 2023-08-21 11:42:11 -05:00
Chris Eager
6f0462622b Update maven and various plugins 2023-08-21 11:34:08 -05:00
Chris Eager
2f17161163 Update various dependencies 2023-08-21 11:34:08 -05:00
Ravi Khadiwala
17d48b95ac keep lettuce metrics; strip remote tags 2023-08-18 16:28:19 -05:00
Chris Eager
eeea97e2fe Return a single OAuth2 credentials JSON 2023-08-18 16:16:31 -05:00
Chris Eager
360e101660 Update to the latest version of the spam filter 2023-08-18 16:13:30 -05:00
Jon Chambers
3501a944a3 Update to the latest version of the spam filter 2023-08-18 11:49:11 -04:00
Jon Chambers
76305190a2 Temporarily restore explicit service/version/environment/host tags 2023-08-17 18:30:59 -04:00
Jon Chambers
ab83990170 Send latency metrics as distributions 2023-08-17 17:10:16 -04:00
Jon Chambers
8103a22026 Submit Micrometer metrics via dogstatsd instead of the Datadog API 2023-08-17 17:01:36 -04:00
Jonathan Klabunde Tomer
1f8e4713ef limit concurrency of async DynamoDB ops 2023-08-17 13:56:09 -07:00
Katherine Yen
ff9fe2c1be Remove record equality test 2023-08-17 13:55:27 -07:00
Jon Chambers
7f37c8ee5e Retire now-unused HTTP transport configuration for Datadog metric reporter 2023-08-17 16:53:53 -04:00
Jon Chambers
ed0a723fef Include underlying exceptions when logging failures to write exit files 2023-08-17 12:32:45 -04:00
Jon Chambers
5c31ef43c9 Send an HTTP/440 response instead of an HTTP/502 if an upstream provider rejects a "send verification code" request 2023-08-17 12:15:00 -04:00
Katherine Yen
43fd8518c0 Add missing java.util.Base64 import to ProfileController 2023-08-16 14:02:53 -07:00
Katherine Yen
19a08f01e8 Write certain profile data as bytes instead of strings to dynamo and represent those fields as byte arrays on VersionedProfile 2023-08-16 13:45:16 -07:00
Jonathan Klabunde Tomer
33498cf147 Update to the latest version of the spam filter 2023-08-16 10:19:00 -07:00
Jon Chambers
beeb85cf8d Update to the latest version of the spam filter 2023-08-15 14:21:00 -04:00
Jon Chambers
ccd860207b Make MessagesManager#clear asynchronous 2023-08-15 14:08:16 -04:00
Jon Chambers
2c835b5c51 Make message deletion from DynamoDB asynchronous 2023-08-15 14:08:16 -04:00
Jon Chambers
5caa951c61 Make MessagesCache#clear methods asynchronous 2023-08-15 14:08:16 -04:00
Jon Chambers
4d8c4d6693 Also delete APNs VOIP tokens when clearing APNs tokens 2023-08-15 14:08:00 -04:00
Jon Chambers
a9d0574ea8 Remove most @Timed annotations 2023-08-15 14:06:31 -04:00
Jonathan Klabunde Tomer
3954494eae Update to the latest version of the spam filter 2023-08-11 15:11:58 -07:00
Ravi Khadiwala
ed6a2c55eb adjust lettuce metric denial for post-transform name 2023-08-11 09:43:41 -05:00
Ravi Khadiwala
b6ee074149 fix captcha shortening url path resolution 2023-08-10 16:01:56 -05:00
Ravi Khadiwala
f6b3500e92 remove most high cardinality lettuce metrics 2023-08-10 16:01:16 -05:00
Katherine Yen
a71dc48b9b Prepare to read profile data stored as byte arrays 2023-08-10 14:00:35 -07:00
Katherine Yen
bc5eed48c3 Add authentication interceptor to profile gRPC service 2023-08-10 13:59:46 -07:00
Jon Chambers
2ecf3cb303 Revert "Don't immediately require PNI-associated keys for "atomic" device linking"
This reverts commit 4ec97cf006.
2023-08-10 16:59:35 -04:00
Jon Chambers
bed33d042a Revert "Require PNI-associated keys if the target account has a PNI identity key"
This reverts commit 1dde612855.
2023-08-10 16:59:35 -04:00
Jonathan Klabunde Tomer
d7975626be Update to the latest version of the spam filter 2023-08-10 09:58:26 -07:00
Ravi Khadiwala
3ac7aba6b2 Add a captcha short-code expander 2023-08-09 12:41:31 -05:00
Jon Chambers
1dde612855 Require PNI-associated keys if the target account has a PNI identity key 2023-08-09 12:10:56 -04:00
Jon Chambers
4ec97cf006 Don't immediately require PNI-associated keys for "atomic" device linking 2023-08-09 12:10:56 -04:00
Jon Chambers
d51c6fd2f8 Convert Device.Capabilities to a record 2023-08-08 15:38:37 -04:00
Jon Chambers
d868e3075c Retire fully-adopted device capabilities 2023-08-08 15:38:37 -04:00
Jon Chambers
ae61ee5486 Retire AnalyzeDeviceCapabilitiesCommand 2023-08-08 15:38:37 -04:00
Katherine Yen
58fd9ddb27 Count profile data that cannot be parsed as base64 2023-08-08 10:54:25 -07:00
Katherine Yen
a953cb33b7 Define ProfileController protobufs and setProfile endpoint 2023-08-08 10:53:11 -07:00
Jon Chambers
95b90e7c5a Add a preliminary gRPC service for dealing with calling credentials 2023-08-08 12:46:55 -04:00
Jon Chambers
6a3ecb2881 Convert TurnToken to a record 2023-08-08 12:46:55 -04:00
Jon Chambers
6cf4241283 Add a reactive method for checking rate limits by UUID 2023-08-08 12:46:55 -04:00
Jon Chambers
42141e51a1 Use ACIs instead of E164s for TURN URI overrides 2023-08-08 12:46:55 -04:00
Jon Chambers
b01945ff50 Clarify parameterized tests by modifying prototype request objects; remove spurious warning suppressions 2023-08-08 10:33:29 -04:00
Jon Chambers
a131f2116f Retire verification code storage machinery 2023-08-04 17:26:55 -04:00
Jon Chambers
625637b888 Stop checking for stored verification codes when linking devices 2023-08-04 17:26:55 -04:00
Jon Chambers
c873f62025 Produce verification tokens instead of stored verification codes for linking devices 2023-08-04 16:04:47 -04:00
Jon Chambers
43d91e5bd6 Convert VerificationCode to a record 2023-08-04 16:04:47 -04:00
Jon Chambers
5c4c729703 Disallow reuse of device verification tokens 2023-08-04 13:40:37 -05:00
Jon Chambers
308da3343d Accept signed tokens in addition to randomly-generated codes for authorizing device linking 2023-08-04 13:40:37 -05:00
Chris Eager
48c7572dd5 Add CommandStopListener 2023-08-04 13:29:35 -05:00
Ravi Khadiwala
dc5f35460b Update to the latest version of the spam filter 2023-08-04 11:38:33 -05:00
Jon Chambers
69ea9b0296 Add a request counter tagged by client version 2023-08-04 12:16:48 -04:00
Jon Chambers
969c6884c0 Add a command for analyzing device capabilities 2023-08-04 12:14:08 -04:00
Jon Chambers
fcf311aab3 Retire the PendingAccounts table 2023-08-04 12:13:57 -04:00
ravi-signal
888879dfb2 Estimate message byte limit exceeded error count 2023-08-04 11:10:58 -05:00
Chris Eager
e003197f77 Update to protobuf-java 3.23.3 2023-08-03 17:09:31 -05:00
Chris Eager
f57910cd97 Update to dropwizard 2.1.7, jackson 2.13.5 2023-08-03 16:18:27 -05:00
Chris Eager
d85e25dba0 Update to the latest version of the spam filter 2023-08-02 16:17:22 -05:00
Chris Eager
89a4034fc6 Remove s3-upload from deploy phase 2023-08-02 16:16:27 -05:00
Chris Eager
f53743d287 Add configuration for Datadog UDP transport 2023-08-02 13:54:15 -05:00
Jon Chambers
2d132128e1 Switched to a composed request object model for anonymous keys gRPC operations 2023-07-28 14:20:24 -05:00
Chris Eager
6e5ffbe7b5 Restore aci field to BatchIdentityCheckRequest 2023-07-28 14:16:48 -05:00
Jonathan Klabunde Tomer
a81c9681a0 Update to the latest version of the spam filter 2023-07-26 14:57:18 -07:00
Ravi Khadiwala
baf98accd0 acquire lock before checking message listeners in MessagesCache 2023-07-26 16:45:53 -04:00
Ravi Khadiwala
901c950ee6 Add metrics to keyspace-notifier executor 2023-07-26 16:45:53 -04:00
Ravi Khadiwala
50ac7f9dc2 adjust messageDeletionAsyncExecutor core pool size 2023-07-26 16:45:41 -04:00
Jon Chambers
c2ea4a5290 Update to the latest version of the spam filter 2023-07-26 16:45:13 -04:00
Jon Chambers
b691b8d37d Log successful client version refreshes 2023-07-26 16:41:54 -04:00
Jon Chambers
4ead8527c8 Use ClientReleasesManager when deciding whether to add client version tags 2023-07-26 16:41:54 -04:00
Jon Chambers
6f4801fd6f Add a manager class for checking "liveness" of client versions 2023-07-26 16:41:54 -04:00
Jon Chambers
10689843b0 Add a repository for client release information 2023-07-26 16:41:54 -04:00
Chris Eager
60cc0c482e Add @Produces to PUT /v1/accounts/apn 2023-07-26 16:35:23 -04:00
Jon Chambers
e1a5105c28 Revert "Restore max concurrency when migrating pre-keys"
This reverts commit ed8a1ed579.
2023-07-26 12:56:35 -04:00
Jon Chambers
ed8a1ed579 Restore max concurrency when migrating pre-keys 2023-07-26 12:34:32 -04:00
Jon Chambers
c3fd2e2284 Retry key storage attempts when migrating signed pre-keys 2023-07-26 12:34:32 -04:00
Chris Eager
872ef5d0a0 Add environment variable to toggle tcp appender 2023-07-24 13:13:13 -05:00
Chris Eager
b44599cd59 Remove unused jedis library 2023-07-24 10:54:34 -04:00
Jordan Rose
7a5dcc700e Add support for AuthCredentialAsPni with pniAsServiceId=true
Update to libsignal 0.30.0, and add a new query param to
/v1/certificate/auth/group, "pniAsServiceId=true", that uses the new
encoding of PNIs in zkgroup rather than encoding PNIs the same way as
ACIs, as we have been doing.

Also includes all the adjustments for the libsignal 0.30.0 update.
2023-07-24 10:53:59 -04:00
ravi-signal
705fb93e45 Add v4 attachment controller
Add AttachmentControllerV4 which can be configured to generate upload
forms for a TUS based CDN
2023-07-21 12:09:45 -05:00
Jon Chambers
9df923d916 Update keys gRPC endpoint to use service identifiers 2023-07-21 13:03:01 -04:00
Chris Eager
dc1cb9093a Remove unused code 2023-07-21 11:08:32 -05:00
Jon Chambers
e32043ae79 Remove outdated documentation 2023-07-21 10:24:17 -04:00
Jon Chambers
881c921d56 Update to the latest version of the spam filter 2023-07-21 09:44:53 -04:00
Jon Chambers
abb32bd919 Introduce "service identifiers" 2023-07-21 09:34:10 -04:00
Katherine Yen
4a6c7152cf Update to the latest version of the spam filter 2023-07-20 14:37:12 -07:00
Sergey Skrobotov
cf92007f66 Moving Account serialization logic to storage-specific classes 2023-07-20 14:28:07 -07:00
Jon Chambers
f5c57e5741 Make ContestedOptimisticLockException extend NoStackTraceRuntimeException 2023-07-20 11:15:08 -04:00
Jon Chambers
5627209fdd Add a gRPC service for working with pre-keys 2023-07-20 11:10:26 -04:00
Jonathan Klabunde Tomer
0188d314ce minor username api refinements 2023-07-19 15:12:47 -07:00
Jonathan Klabunde Tomer
67343f6bdc accept encrypted username with confirm-username-hash requests 2023-07-19 10:54:11 -07:00
Katherine Yen
ade2e9c6cf Define asynchronous ProfilesManager operations 2023-07-19 10:43:58 -07:00
Sergey Skrobotov
352e1b2249 test classes moved to same packages with components they test 2023-07-17 13:34:58 -07:00
Jon Chambers
b8d8d349f4 Control inbound message byte limits with a dynamic configuration flag 2023-07-14 16:25:33 -04:00
Jon Chambers
e87468fbe0 Add a rate limit for inbound message bytes for a given account 2023-07-14 16:25:33 -04:00
Jon Chambers
e38a713ccc Support sub-millisecond permit regeneration durations in rate limiters 2023-07-14 16:25:33 -04:00
Jon Chambers
82ed783a2d Introduce async account updaters 2023-07-14 16:25:19 -04:00
Jon Chambers
d17c7aaba6 Add support for clearing accounts from Redis asynchronously 2023-07-14 16:25:19 -04:00
Katherine Yen
8c93368b20 Update to the latest version of the spam filter 2023-07-13 12:43:07 -07:00
Jon Chambers
41f61c66a3 Add public methods for fetching accounts asynchronously 2023-07-13 13:53:29 -04:00
Jon Chambers
1b7a20619e Add tools for testing asynchronous Redis operations 2023-07-13 13:53:29 -04:00
Jon Chambers
7d19e58953 Add parallel pathways for getting accounts asyncronously to Accounts 2023-07-13 13:53:29 -04:00
Jon Chambers
1605676509 Store signed EC pre-keys in a dedicated table when setting signed pre-keys individually 2023-07-12 14:58:10 -04:00
Jon Chambers
a0d6146ff5 Make key deletion operations asynchronous 2023-07-12 14:58:10 -04:00
Jon Chambers
f709b00be3 Make KeysManager storage/retrieval operations asynchronous 2023-07-12 14:58:10 -04:00
Jonathan Klabunde Tomer
5847300290 Revert "Allow use of the token returned with spam challenges as auth for the challenge verification request" 2023-07-12 11:45:02 -07:00
Jonathan Klabunde Tomer
9aaac0eefd don't require all devices to support PNI for PNIHW 2023-07-12 10:14:16 -07:00
Jon Chambers
c5ae9913fe Update to the latest version of the spam filter 2023-07-11 13:48:07 -04:00
Jon Chambers
fc2ad20c63 Update to the latest version of the spam filter 2023-07-11 13:36:12 -04:00
Jon Chambers
6db97f5541 Standardize client tag version handling; add client version tags to delivery latency metrics 2023-07-11 13:35:29 -04:00
Jon Chambers
adf6c751ee Use an explicit-allow model for tagging client versions in metrics 2023-07-11 13:35:29 -04:00
Jon Chambers
c315b34395 Update formatting in UserAgentTagUtil 2023-07-11 13:35:29 -04:00
Jon Chambers
f592201e4c Limit attachment controller tags to UA platform (instead of platform and version) 2023-07-11 13:35:29 -04:00
Jon Chambers
8bf5ee45ed Filter out command tags from Lettuce metrics and prepend a "chat." prefix to Lettuce metric names 2023-07-11 13:35:03 -04:00
Jon Chambers
25f759dd07 Drop ActiveUserTally 2023-07-11 13:34:36 -04:00
Jonathan Klabunde Tomer
e5f4c17148 update openapi docs for several endpoints, notably those with PQXDH changes
Co-authored-by: Katherine Yen <katherine@signal.org>
2023-07-06 15:45:33 -07:00
Jonathan Klabunde Tomer
098b177bd3 Allow use of the token returned with spam challenges as auth for the challenge verification request 2023-07-06 15:25:19 -07:00
Jon Chambers
ef1a8fc50f Use PascalCase RPC names for the registration service 2023-07-06 17:12:37 -04:00
Jon Chambers
76f2e93a2c Reduce concurrency limit for pre-key migration task 2023-07-06 16:45:03 -04:00
Jon Chambers
25ea1df299 Limit concurrency when writing signed EC pre-keys 2023-07-06 15:51:12 -04:00
Chris Eager
5ced86af1d Set consistentRead=true for registration recovery password lookup
This avoids a race condition (in integration test situations) where a lookup could return no results
2023-07-06 15:47:16 -04:00
Jon Chambers
62e02a49df Log errors from single-shot account crawlers rather than printing them to stderr 2023-07-06 15:46:28 -04:00
Jon Chambers
540550d72a Handle exceptions thrown when checking pre-key signatures 2023-07-06 15:46:11 -04:00
Jon Chambers
8cb83fb6e4 Switch to temporary registration endpoints to facilitate a change from snake_case to PascalCase 2023-07-06 15:46:00 -04:00
Jon Chambers
56db925f0e Update to the latest version of the spam filter 2023-07-06 10:33:58 -04:00
Jon Chambers
2c0fc8fe3e Remove legacy methods from RegistrationServiceClient 2023-07-06 10:32:58 -04:00
Jon Chambers
08c7baafac Remove legacy registration endpoints from AccountController 2023-07-06 10:32:58 -04:00
Jon Chambers
8edb450d73 Parallelize single-shot account crawlers 2023-07-06 10:15:16 -04:00
ravi-signal
fedeef4da5 Add an optional parameter to require atomic account creation
By default, if a registration request has no optional fields for atomic
account creation set, the request will proceed non-atomically. If a
client sets the `atomic` field, now such a request would be rejected.
2023-07-05 11:24:11 -05:00
Jon Chambers
b593d49399 Control signed pre-key deletion via a dynamic configuration flag to facilitate migration 2023-07-05 12:17:17 -04:00
Chris Eager
4a91fc3c3d Set daemon=true for pubsub topology change event thread 2023-07-05 11:15:12 -05:00
Chris Eager
bb9605d7c3 Use RedisClient#setDefaultTimeout for a non-clustered client 2023-07-05 11:09:28 -05:00
Jon Chambers
1049326a70 Turn on Lettuce latency metrics 2023-06-30 18:20:43 -04:00
Chris Eager
457ecf145f Add test for Redis timeouts 2023-06-30 12:55:37 -05:00
Chris Eager
463dd9d7d8 Update to Lettuce 6.2.4 2023-06-30 12:55:37 -05:00
Chris Eager
bdcd055aaf Configure Redis timeouts using TimeoutOptions and RediURI 2023-06-30 12:55:37 -05:00
Jon Chambers
30ae2037e8 Correct order of constructor arguments for KeysManager 2023-06-30 12:05:16 -04:00
Jon Chambers
ce4fdbfb3c Untangle metric names for RepeatedUseSignedPreKeyStore subclasses 2023-06-30 10:33:24 -04:00
Jon Chambers
2d154eb0cf Add a command to copy signed pre-keys from Account records to their own table 2023-06-30 10:33:24 -04:00
Jon Chambers
a3e82dfae8 Add a temporary method for storing signed EC pre-keys if and only if another key has not already been stored 2023-06-30 10:33:24 -04:00
Jon Chambers
97a7469432 Measure signed EC pre-key agreement 2023-06-30 10:33:24 -04:00
Jon Chambers
1a1defb055 Store signed EC pre-keys in a dedicated table 2023-06-30 10:33:24 -04:00
Jon Chambers
93c78b6e40 Introduce RepeatedUseECSignedPreKeyStore 2023-06-30 10:33:24 -04:00
Chris Eager
b852d6681d FaultTolerantHttpClient: used managed ScheduledExecutorService for retries 2023-06-30 10:24:18 -04:00
Chris Eager
8e48ac4ede Add messagesCache and clientPresenceManager to managed command dependencies 2023-06-30 10:24:18 -04:00
Ehren Kret
859f646c55 Correct timestamp resolution to intended integer value 2023-06-29 16:05:59 -05:00
Chris Eager
fb39b2edaf Improve two @Disabled flaky tests 2023-06-29 14:56:41 -05:00
Chris Eager
d7bf815bd5 Update to the latest version of the spam filter 2023-06-28 14:30:15 -05:00
Chris Eager
c93af9e31e Remove MessagePersister from WhisperServerService environment
Persistence is now exclusively done by a separate command.
2023-06-28 14:17:49 -05:00
Chris Eager
b81a0e99d4 Always have 0 ApnPushNotificationScheduler worker threads in front-end service 2023-06-28 14:17:23 -05:00
Chris Eager
f8fefe2e5e Remove AccountCrawler (and doPeriodicWork) from WhisperServerService 2023-06-28 14:16:07 -05:00
Jon Chambers
f26bc70b59 Add a basic, prototype authentication interceptor for gRPC services 2023-06-27 10:21:11 -04:00
Jon Chambers
b5fd131aba Add an abstract base class for single-shot account crawls 2023-06-27 10:18:35 -04:00
Jon Chambers
06997e19e0 Add a method for iterating across all accounts 2023-06-27 10:18:35 -04:00
Jon Chambers
97710540c0 Use Timer.Sample throughout Experiment 2023-06-27 10:18:20 -04:00
Jon Chambers
c78c109577 Drop a disused endpoint for fetching the caller's own signed pre-key 2023-06-27 10:16:39 -04:00
Jonathan Klabunde Tomer
8d995e456e initial grpc service code in chat 2023-06-26 17:10:13 -07:00
Ehren Kret
cc3cab9c88 Add server time to remote config fetch
Enable clients to very roughly adjust some actions for clock skew by
providing current server time in the remote config fetch.
2023-06-21 17:11:35 -05:00
Jon Chambers
0122b410be Include push notification urgency in push latency metrics 2023-06-21 15:10:26 -04:00
Jon Chambers
2ddd2b9476 Convert PushRecord to a record and make PushType non-optional 2023-06-21 15:10:26 -04:00
Jon Chambers
a768498250 Record general message delivery latency 2023-06-21 15:10:14 -04:00
Sergey Skrobotov
a45aadae16 Cleaning up references to the legacy format from the rate limiters lua script 2023-06-21 15:09:46 -04:00
Sergey Skrobotov
25802432c2 adding a property to skip uploading to s3 during deploy task 2023-06-21 15:09:18 -04:00
Chris Eager
98578b18aa Update to dynamodb-lock-client 1.2.0 2023-06-21 15:08:40 -04:00
Chris Eager
6d81f69785 Start and stop all lifecycle-managed objects in CrawlAccountsCommand 2023-06-17 10:17:46 -05:00
Chris Eager
7dce183170 Add worker thread pool to PushFeedbackProcessor 2023-06-16 11:36:28 -05:00
Chris Eager
f1962a03ef Parameterize worker thread count in CrawlAccountsCommand 2023-06-16 11:36:28 -05:00
Jon Chambers
cb26bfd807 Update to the latest version of the spam filter 2023-06-15 13:12:54 -04:00
Chris Eager
befd336372 Remove static Remote Config auth tokens 2023-06-15 12:11:20 -05:00
Chris Eager
8501e61eb1 Set maxThreads = minThreads on command thread pools 2023-06-15 12:11:10 -05:00
Jon Chambers
ae489e5a52 Log account ages when identity keys change 2023-06-15 13:10:35 -04:00
Chris Eager
13afdbda97 Report system resource metrics from background tasks 2023-06-14 16:48:23 -05:00
Jon Chambers
9cfd88a23f Move turn secret to static configuration 2023-06-14 10:47:17 -04:00
Jon Chambers
13456bad3a Update date math with JSR310 types 2023-06-14 10:47:17 -04:00
Jon Chambers
45be85c5ef Update formatting and resolve warnings/suggestions 2023-06-14 10:47:17 -04:00
Jonathan Klabunde Tomer
861dc0d021 reject message sends that have the same device more than once 2023-06-13 09:49:50 -07:00
Chris Eager
128d709c99 Additional counters and timers for WebSocket connections 2023-06-13 11:46:15 -05:00
Jon Chambers
e8f01be8ef Inject version bytes if missing from existing EC pre-keys 2023-06-09 11:41:51 -04:00
Jon Chambers
7f1ee015d1 Treat blank strings as null pre-keys 2023-06-09 10:39:16 -04:00
Jon Chambers
17aa5d8e74 Use strongly-typed pre-keys 2023-06-09 10:08:49 -04:00
Jon Chambers
b27334b0ff Treat blank strings as null identity keys 2023-06-09 10:08:18 -04:00
Jon Chambers
7fc6b1e802 Count invalid pre-keys 2023-06-09 09:25:31 -04:00
Jon Chambers
25b7c8f802 Update to libsignal-server 0.26.0 2023-06-09 09:25:31 -04:00
Jon Chambers
8ec6a24a2d Fix a metric name/tag set 2023-06-08 12:34:27 -04:00
Jon Chambers
234707169e Represent identity keys as IdentityKey instances 2023-06-08 11:36:58 -04:00
Jon Chambers
1c8443210a Check for missing version bytes in invalid identity keys 2023-06-08 09:56:21 -04:00
g1a55er
aaf43a592f Replace reserved "notification" key with "newMessageAlert" 2023-06-08 09:53:31 -04:00
Jon Chambers
2b08742c0a Create separate key stores for different kinds of pre-keys 2023-06-06 17:08:26 -04:00
Jon Chambers
cac04146de Identify specific cases with invalid identity keys 2023-06-06 17:08:01 -04:00
ravi-signal
2b266c7beb Validate registration ids for new accounts 2023-06-06 11:08:54 -04:00
Jonathan Klabunde Tomer
099932ae68 ApnPushNotifcationScheduler: always run worker thread at least once 2023-06-06 11:04:44 -04:00
Jon Chambers
8579babde6 Count instances where an account's identity key could not be interpreted as an IdentityKey 2023-06-06 11:01:25 -04:00
Jon Chambers
9c93d379a8 Fix a sneaky merge conflict 2023-06-05 12:38:35 -04:00
Jon Chambers
085c7a67c8 Refactor account locks/deleted account manager 2023-06-05 12:30:44 -04:00
Sergey Skrobotov
e6917d8427 minor cleanup, docs, and integration tests for username API 2023-06-02 10:35:07 -07:00
Sergey Skrobotov
47cc7fd615 username links API 2023-06-02 10:26:14 -07:00
Jonathan Klabunde Tomer
ecd207f0a1 Check structural validity of prekeys at upload time 2023-05-31 14:29:39 -07:00
Chris Eager
0ab66f2f14 Add aws-java-sdk-sts to dependencies 2023-05-31 14:57:48 -05:00
Chris Eager
d1e38737ce Support ID token at PUT /v1/config and DELETE /v1/config 2023-05-30 10:28:28 -05:00
Chris Eager
f17de58a71 Change ScheduledApnPushNotificationSenderServiceCommand to extend ServerCommand 2023-05-30 10:14:33 -05:00
Chris Eager
dd552e8e8f Change MessagePersisterServiceCommand to extend ServerCommand 2023-05-30 10:14:33 -05:00
Chris Eager
18480e9d18 Move metrics registry environment.manage() to utility 2023-05-30 10:14:33 -05:00
Chris Eager
7ffccd9c3a Initialize metrics in ScheduledApnPushNotificationSenderServiceCommand 2023-05-26 16:41:17 -05:00
Chris Eager
0edd99e9cf Initialize metrics in MessagePersisterServiceCommand 2023-05-26 16:41:17 -05:00
Chris Eager
defdc14d5e Initialize metrics in CrawlAccountsCommand 2023-05-26 16:41:17 -05:00
Chris Eager
5dcf8edd38 Factor metrics registry intialization to a utility 2023-05-26 16:41:17 -05:00
Jon Chambers
a320766bb6 Update to the latest version of the spam filter 2023-05-26 14:22:52 -04:00
Jon Chambers
91805caa9a Finalize rate limit unit inversion 2023-05-26 14:17:30 -04:00
Jon Chambers
48d39dccbd Fix rate limit division errors 2023-05-26 13:13:02 -04:00
Jon Chambers
fc9e1f59a5 Update to the latest version of the spam filter 2023-05-26 12:46:36 -04:00
Chris Eager
e7bc8bd6b9 Consistently use AWS credentials providers from WhisperServerService 2023-05-26 12:45:38 -04:00
Jon Chambers
23337d7992 Update to the latest version of the spam filter 2023-05-26 11:43:16 -04:00
Jon Chambers
f513dc0398 Invert rate limit units in default configurations 2023-05-26 11:37:06 -04:00
Jon Chambers
184969336e Allow RateLimiterConfig to accept either a leak rate per minute or a permit regeneration duration 2023-05-26 11:37:06 -04:00
Chris Eager
1534f1aa6a Add web identity token AWS SDK credentials provider 2023-05-26 11:07:41 -04:00
Chris Eager
cd8f74e60b Add support for environment-dependent secondary OAuth2 credentials JSON 2023-05-26 11:07:30 -04:00
Jon Chambers
d832eaa759 Represent identity keys as byte arrays 2023-05-26 10:12:22 -04:00
Jon Chambers
796863341d Revert "Count identity keys that are present, but can't be parsed as base64"
This reverts commit 024dd02628a7d989424273501528b52fe18c3ee9.
2023-05-26 10:12:22 -04:00
Jon Chambers
217b68a1e0 Represent pre-key public keys and signatures as byte arrays in DAOs 2023-05-26 09:58:38 -04:00
Jon Chambers
4a8ad3103c Actually write pre-keys as byte arrays 2023-05-26 08:23:54 -04:00
Jon Chambers
a5f853c67a Change inactive account age threshold from 365 to 180 days 2023-05-26 08:23:19 -04:00
Jon Chambers
70b54e227e Count the prevalence of keys stored as strings or as bytes 2023-05-25 10:04:38 -05:00
Jonathan Klabunde Tomer
1ab6bff54e add @Produces annotations to a few methods in DeviceController 2023-05-25 09:57:06 -05:00
Chris Eager
c2317e8493 Start the dynamic configuration manager in dependent commands 2023-05-25 09:52:01 -05:00
Jon Chambers
b034a088b1 Add support for "atomic" device linking/activation 2023-05-19 16:13:37 -04:00
Jon Chambers
ae7cb8036e Factor DeviceActivationRequest out into its own record 2023-05-19 16:13:37 -04:00
Jon Chambers
1a5327aece Update to the latest version of the spam filter 2023-05-19 15:59:09 -04:00
Jon Chambers
8ce2b04fe4 Discard test device codes 2023-05-19 15:57:14 -04:00
Chris Eager
a3c37aed47 Remove obsolete field from SecureValueRecovery2Configuration 2023-05-19 15:57:01 -04:00
Jon Chambers
fa8f19fd43 Group atomic account creation operations 2023-05-19 15:56:45 -04:00
Jon Chambers
c9a9409b9a Count identity keys that are present, but can't be parsed as base64 2023-05-19 15:56:27 -04:00
Jon Chambers
d3e0ba6d44 Prepare to read pre-keys stored as byte arrays 2023-05-19 15:56:13 -04:00
Jon Chambers
300ac16cf1 Handle "transport not allowed" responses from the registration service 2023-05-19 15:55:53 -04:00
Chris Eager
3e53884979 Add MessagePersisterServiceCommand 2023-05-18 15:37:54 -05:00
Chris Eager
859fbe9ab1 Update to the latest version of the spam filter 2023-05-18 11:44:38 -05:00
Chris Eager
6043c1a4e8 Add ScheduledApnPushNotificationSenderServiceCommand 2023-05-18 11:44:01 -05:00
Chris Eager
0d9fd043a4 Add container image build using Jib 2023-05-18 11:43:29 -05:00
Chris Eager
f06eaf13d1 Send 1009 for too-large message frames 2023-05-18 11:42:22 -05:00
Jon Chambers
66a619a378 Allow for atomic account creation and activation 2023-05-18 09:51:13 -04:00
Jon Chambers
fb1b1e1c04 Update libsignal-server to 0.24.0 2023-05-18 09:51:13 -04:00
Katherine Yen
9450f88c8c Add annotation to catch empty request body 2023-05-17 14:28:41 -07:00
Sergey Skrobotov
0706171264 Update to the latest version of the spam filter 2023-05-17 11:43:17 -07:00
Sergey Skrobotov
287e2fa89a Moving secret values out of the main configuration file 2023-05-17 11:25:59 -07:00
Chris Eager
8d1c26d07d Add CrawlAccountsCommand 2023-05-17 12:22:49 -05:00
Jonathan Klabunde Tomer
caae27c44c PQXDH endpoints for chat server 2023-05-16 14:34:33 -07:00
Katherine Yen
34d77e73ff Fix integer division in call link ratelimit leak rate 2023-05-16 14:34:06 -07:00
Chris Eager
0889741f34 Update GitHub Actions versions 2023-05-12 12:53:47 -05:00
Ravi Khadiwala
8c42199baf Add svr2 credentials to RegistrationLockFailure responses
Add an svr2 credential to 423 responses for:
  - PUT v2/accounts/number
  - POST v1/registration

Also add some openapi annotations to those endpoints
2023-05-12 11:02:32 -05:00
Katherine Yen
7395b5760a Remove unused call link config 2023-05-12 11:01:42 -05:00
Jon Chambers
c8f97ed065 Update to the latest version of the spam filter 2023-05-10 15:29:10 -04:00
Jon Chambers
d2baa8b8fb Stop sending API keys to the registration service 2023-05-10 15:28:12 -04:00
Jon Chambers
1beee5fd04 Update to the latest version of the spam filter 2023-05-10 15:01:37 -04:00
Chris Eager
281b91a59a Remove obsolete ContactDiscoveryWriter 2023-05-10 14:01:09 -05:00
Jon Chambers
2be2b4ff23 Authenticate with the registration service using OIDC identity tokens in addition to shared API keys 2023-05-10 14:59:07 -04:00
Jon Chambers
a83fd1d3fe Include request method as a request counter dimension 2023-05-09 15:17:46 -05:00
Jon Chambers
cb72e4f426 Simplify request counter 2023-05-09 15:17:46 -05:00
Chris Eager
3214852a41 Fix 401 on /v1/keepalive 2023-05-09 15:08:03 -05:00
Jon Chambers
1057bd7e1f Resolve warnings/suggestions throughout ProfileControllerTest 2023-05-09 10:32:32 -04:00
Jonathan Klabunde Tomer
33903553ab reinstate per-{path,status,platform,is-websocket} request counters 2023-05-09 09:49:20 -04:00
Katherine Yen
c309afc04b Displace client presence when existing account reregisters 2023-05-05 11:31:18 -07:00
Erik Osheim
f6c4ba898b Update to the latest version of the spam filter 2023-05-05 11:22:29 -04:00
Katherine Yen
7ba86b40aa Create call link credential endpoint 2023-05-04 14:33:45 -07:00
Katherine Yen
b2b0aee4b7 Call link auth credential 2023-05-04 14:17:01 -07:00
Jon Chambers
919cc7e5eb Update libsignal to 0.23 2023-05-04 14:10:51 -07:00
Jonathan Klabunde Tomer
e38911b2c5 Always check prekey signatures when new prekeys are uploaded 2023-05-04 11:31:45 -07:00
Chris Eager
bc68b67cdf account crawler: remove obsolete accelerated mode 2023-05-04 11:27:16 -05:00
Chris Eager
42a9f1b3e4 account crawler: remove set*Dynamo methods 2023-05-04 11:27:16 -05:00
ravi-signal
08333d5989 Implement /v2/backup/auth/check 2023-05-04 11:23:33 -05:00
Ravi Khadiwala
0e0c0c5dfe return 400 instead of 503 for bad verification session-id 2023-05-04 09:22:51 -07:00
Ravi Khadiwala
59ebe65643 Add counter to /v2/attachments 2023-05-04 09:22:18 -07:00
Chris Eager
4fd2422e4d Catch and close() after UninitializedMessageException in websocket messages 2023-05-03 13:36:29 -05:00
Chris Eager
6181d439f6 Update to the latest version of the spam filter 2023-05-03 13:35:35 -05:00
Chris Eager
57b6c10dd1 Remove obsolete dynamic configuration 2023-05-03 13:20:44 -05:00
Jon Chambers
3ee5ac4514 Fix a late-breaking merge conflict 2023-05-02 16:12:26 -04:00
Jonathan Klabunde Tomer
be176f98ad metric for take-prekey yielding an empty result 2023-05-02 13:03:49 -07:00
Jon Chambers
12b58a31a1 Retire integration with legacy contact discovery system 2023-05-02 15:57:03 -04:00
Jon Chambers
8d468d17e3 Add a temporary counter for profile key credential types 2023-05-02 15:56:19 -04:00
Erik Osheim
30df4c3d29 Update to the latest version of the spam filter 2023-05-02 10:37:22 -05:00
Brenden Stahle
5122a1c466 Change the copyright date from 2022 to 2023. 2023-05-02 10:31:04 -05:00
Chris Eager
e135d50d82 Add counter for ContactDiscoverWriter updates 2023-05-01 13:42:14 -05:00
Chris Eager
487b5edc75 Handle potentially null payment method when canceling subscription 2023-05-01 13:42:05 -05:00
Jonathan Klabunde Tomer
47ad5779ad new /v2/accounts endpoint to distribute PNI key material without changing phone number 2023-04-21 12:20:57 -07:00
Katherine Yen
4fb89360ce Allow registration via recovery password for reglock enabled accounts 2023-04-20 09:21:04 -07:00
Jon Chambers
6dfdbeb7bb Check for no-op APNs token changes 2023-04-19 17:01:01 -04:00
Jon Chambers
d0ccbd5526 Simplify a check for no-op FCM token changes 2023-04-19 17:01:01 -04:00
Jon Chambers
031ee57371 Convert "set push token" request objects to records 2023-04-19 17:01:01 -04:00
Jon Chambers
2043678739 Remove the removeSignalingKey API endpoint 2023-04-19 17:00:47 -04:00
Jon Chambers
dd27e3b0c8 Convert attachment descriptors to records 2023-04-19 17:00:34 -04:00
Jon Chambers
1083d8bde0 Remove the legacy group credential endpoint 2023-04-19 17:00:14 -04:00
Jon Chambers
d1eb247d8c Clarify the purpose of an addListener method 2023-04-18 12:04:54 -04:00
Jon Chambers
fd5e9ea016 Drop the old (and now unused!) redis-dispatch module 2023-04-18 12:04:54 -04:00
Jon Chambers
11829d1f9f Refactor provisioning plumbing to use Lettuce 2023-04-18 12:04:54 -04:00
Ehren Kret
ae70d1113c use same protoc version as library dependency 2023-04-17 14:41:55 -05:00
Katherine Yen
c485d317fb Mock apnPushNotificationScheduler 2023-04-17 10:55:15 -07:00
Katherine Yen
350682b83a Lock account and send notification when someone passes phone verification but fails reglock 2023-04-17 10:30:36 -07:00
ravi-signal
0fe6485038 Add a configuration to make rate limiters fail open 2023-04-14 13:08:14 -05:00
Sergey Skrobotov
a553093046 integration tests initial setup 2023-04-13 11:12:34 -07:00
Erik Osheim
af0d5adcdc Update to the latest version of the spam filter 2023-04-11 16:40:03 -04:00
Katherine Yen
61af1ba029 Clean up prohibited username references 2023-04-10 15:21:02 -07:00
ravi-signal
8847cb92ac Don't block when scheduling background apns pushes 2023-04-10 13:51:36 -05:00
Erik Osheim
5242514874 Update to the latest version of the spam filter 2023-04-07 17:13:48 -04:00
Chris Eager
33a6577b6e Decrease message delivery executor thread count to 20 2023-04-07 10:56:23 -05:00
Chris Eager
23d5006f70 Add prefix to executor metric names 2023-04-05 09:51:53 -05:00
Chris Eager
2697872bdd Use Apache StringUtils#join 2023-04-05 09:51:30 -05:00
Ravi Khadiwala
7b331edcde Separate username and signature truncation fields 2023-04-05 09:51:00 -05:00
Katherine Yen
e4da59c236 Generic credential auth endpoint for call links 2023-04-04 10:28:35 -07:00
Jonathan Klabunde Tomer
48ebafa4e0 DynamoDBExtension refactor and helpers for our schema (#1327)
There's a lot of boilerplate involved in setting up a DynamoDBExtension, and some tests were creating several extensions
rather than one with several tables, which is probably slower than it has to be.

This change adds a new DynamoDbExtensionSchema class in which we can define the Dynamo schema for tests, and refactors
DynamoDbExtension to make it easy to instantiate a single extension with all the tables one wants (and no more, both to
minimize test startup time and to ensure we explicitly test our dependencies and lack thereof).

Tests requiring a DynamoDbExtension with a table schema that's not part of the normal Signal schema can instantiate a
DynamoDbExtension.RawSchema instead.

Test timings are unaffected, at least on my machine. Before:
```[INFO] service ............................................ SUCCESS [01:18 min]```

After:
```[INFO] service ............................................ SUCCESS [01:18 min]```

Co-authored-by: Jonathan Klabunde Tomer <jkt@viola.signal.org>
2023-04-03 13:08:43 -07:00
Erik Osheim
f5726f63bd Update to the latest version of the spam filter 2023-04-03 14:34:13 -04:00
Jonathan Klabunde Tomer
391b070cff KeysController: return correct number of unsigned prekeys
When GET /v2/keys was orignally added in b263f47, prekeys were stored in
Postgres, with a user's unsigned and signed keys together in the same table.
Therefore GET /v2/keys subtracted one from the count returned by storage.

In d4d9403, we changed to a different storage schema, with unsigned prekeys in
one DynamoDB table and unsigned prekeys in the accounts Dynamo table.
Unfortunately, GET /v2/keys was not changed to stop subtracting one from the
count of prekeys in the keys table at the same time. This commit fixes that.
2023-04-03 14:32:45 -04:00
gram-signal
781cd0ca3f Truncate SVR2 IDs to 16 bytes rather than 10. 2023-03-30 17:19:18 -06:00
Erik Osheim
84355963f9 Update to the latest version of the spam filter 2023-03-29 16:51:48 -04:00
Chris Eager
3ccfeb490b Add retry after exceptions during a cluster topology change event callback 2023-03-29 11:41:19 -05:00
Chris Eager
0cc84131de Add enabled to SVR2 configuration 2023-03-29 11:40:21 -05:00
Chris Eager
4fa08fb189 Add secure value recovery 2 to AccountsManager#delete() 2023-03-29 11:40:21 -05:00
Chris Eager
2a551d1d41 Add SecureValueRecovery2Client 2023-03-29 11:40:21 -05:00
Chris Eager
391aa9c518 Wrap runtime exceptions during WebSocket auth into AuthenticationException 2023-03-29 10:08:55 -05:00
Erik Osheim
39d9fd0317 Update to the latest version of the spam filter 2023-03-28 11:20:18 -04:00
Chris Eager
18b1fcd724 Update to the latest version of the spam filter 2023-03-22 13:08:58 -05:00
Chris Eager
f5c62a3d85 Migrate from bounded elastic to dedicated executor for message delivery 2023-03-22 12:57:44 -05:00
Chris Eager
6075d5137b Add /v2/accounts/data_report 2023-03-22 12:57:21 -05:00
ravi-signal
890293e429 change v1/challenge response for invalid captcha 2023-03-21 17:38:30 -05:00
Ravi Khadiwala
05b43a878b Register unlink device command 2023-03-21 17:35:57 -05:00
Chris Eager
fe9c3982a1 Remove prepended username from /v2/backup/auth response 2023-03-21 17:35:42 -05:00
Ravi Khadiwala
82baa892f7 Update to the latest version of spam filter 2023-03-21 17:34:58 -05:00
Ravi Khadiwala
ee53260d72 Add filter-provided captcha score thresholds 2023-03-21 17:34:58 -05:00
Ravi Khadiwala
a8eb27940d Add per-action captcha site-key configuration
- reject captcha requests without valid actions
- require specific site keys for each action
2023-03-21 17:34:58 -05:00
Erik Osheim
fd8918eaff Update to the latest version of the spam filter 2023-03-21 15:47:55 -04:00
Katherine Yen
a3a7d7108b Change reglock expiration check to be > 0 instead of >= 0 2023-03-21 12:46:35 -07:00
Jon Chambers
cd27fe0409 Update to the latest version of the spam filter 2023-03-20 15:28:01 -04:00
Jon Chambers
35606a9afd Send "account already exists" flag when creating registration sessions 2023-03-20 15:18:55 -04:00
Jon Chambers
2052e62c01 Use a purpose-specific method when checking verification codes via the legacy registration API 2023-03-20 15:18:38 -04:00
Erik Osheim
8ccab5c1e0 Update to the latest version of the spam filter 2023-03-17 16:41:48 -04:00
Chris Eager
292f69256e Refactor WebSocket message sending error and completion to subscriber from “doOn…” 2023-03-17 12:42:57 -05:00
ravi-signal
fbdcb942e8 Add unlink user command 2023-03-16 11:17:36 -05:00
Sergey Skrobotov
c14ef7e6cf migrate token bucket redis record format from json to hash: phase 2 2023-03-16 09:15:22 -07:00
Jon Chambers
a04fe133b6 Fix a typo in a method name 2023-03-15 16:01:14 -07:00
Sergey Skrobotov
483e444174 migrate token bucket redis record format from json to hash: phase 1 2023-03-15 16:01:06 -07:00
Sergey Skrobotov
ebf8aa7b15 fixing embedded redis based tests 2023-03-15 13:56:40 -07:00
Katherine Yen
7c52be2ac1 Bump old registration default ratelimiter to match Bravo 2023-03-15 09:44:02 -07:00
Sergey Skrobotov
203a49975c artifact is now available in maven central 2023-03-14 12:02:16 -07:00
Sergey Skrobotov
7d45838a1e reordering maven repositories 2023-03-13 22:22:25 -07:00
Katherine Yen
2683f1c6e7 Encode username hash to base64 string without padding 2023-03-13 15:35:27 -07:00
Sergey Skrobotov
d13413aff2 Update to the latest version of the spam filter 2023-03-13 15:04:51 -07:00
Sergey Skrobotov
4c85e7ba66 Moving RateLimiter logic to Redis Lua and adding async API 2023-03-13 14:50:26 -07:00
Katherine Yen
46fef4082c Add metrics for registration lock flow 2023-03-09 09:07:21 -08:00
Ravi Khadiwala
c06313dd2e Drop tagging for legacy user agents 2023-03-09 10:43:45 -06:00
Ravi Khadiwala
59bc2c5535 Add by-action captcha score config
Enable setting different captcha score thresholds for different captcha
actions via configuration
2023-03-09 10:43:16 -06:00
Chris Eager
437bc1358b Use server timestamp for queue score 2023-03-06 11:31:11 -06:00
Katherine Yen
99e651e902 Update to the latest version of the spam filter 2023-03-03 14:10:56 -08:00
Chris Eager
757ce42a35 Update s3-upload-maven-plugin to 2.0.1 2023-03-03 13:17:28 -06:00
Chris Eager
179f3df847 Allow DisabledPermittedAuthenticatedAccount at /v1/accounts/me 2023-03-03 13:17:17 -06:00
Chris Eager
8a889516b0 Improve LoggingUnhandledExceptionMapper combination with CompletionExceptionMapper 2023-03-03 13:17:07 -06:00
Jon Chambers
7de5c0a27d Keep counts of open websockets by client platform 2023-03-03 13:16:24 -06:00
Chris Eager
71d234e1e4 Update default rate limiter config 2023-03-02 10:27:07 -06:00
Chris Eager
b5fb33e21e Remove unused metrics 2023-03-02 10:14:58 -06:00
Sergey Skrobotov
2be22c2a8e Updating documentation github action to handle no changes case 2023-02-28 14:48:09 -08:00
Chris Eager
db198237f3 Expand try-finally scope of deleted accounts reconciliation lock 2023-02-28 12:42:18 -06:00
Chris Eager
d0ccae129a Remove obsolete metric 2023-02-27 16:33:34 -06:00
Chris Eager
ecbef9c6ee Add micrometer metrics to RateLimiter 2023-02-27 16:33:27 -06:00
Chris Eager
ef2cc6620e Add @Produces annotation for validation error response 2023-02-27 16:33:18 -06:00
ravi-signal
b8f363b187 Add documentation to challenge controller 2023-02-24 17:41:15 -06:00
Sergey Skrobotov
c3f4956ead OpenAPI support 2023-02-24 13:03:30 -08:00
Chris Eager
047f4a1c00 Update metric name 2023-02-24 13:07:07 -06:00
Sergey Skrobotov
41c0fe9ffa Adding a uniform configuration for all json/yaml mapper use cases: part 2 2023-02-24 09:28:55 -08:00
Sergey Skrobotov
6edb0d49e9 Adding a uniform configuration for all json/yaml mapper use cases: bugfix 2023-02-23 20:01:32 -08:00
Sergey Skrobotov
a5e3b81a50 Update to the latest version of the spam filter 2023-02-23 17:12:12 -08:00
Sergey Skrobotov
b9b4e3fdd8 Adding a uniform configuration for all json/yaml mapper use cases: part 1 2023-02-23 16:38:48 -08:00
Jon Chambers
6ee9c6ad46 Remove deprecated registration service response fields 2023-02-23 12:41:56 -08:00
Sergey Skrobotov
6d6556eee5 Update to the latest version of the spam filter 2023-02-23 11:04:14 -08:00
Sergey Skrobotov
7529c35013 Rate limiters code refactored 2023-02-23 10:49:06 -08:00
erik-signal
378b32d44d Add missing token field to OutgoingMessageEntity 2023-02-23 11:18:07 -05:00
Chris Eager
e1fcd3e3f6 Remove Lettuce command latency recorder 2023-02-23 10:17:31 -06:00
Chris Eager
d7ad8dd448 Add micrometer timer to FaultTolerantPubSubConnection 2023-02-23 10:17:24 -06:00
Chris Eager
859f2302a9 Remove unused metrics 2023-02-23 10:17:24 -06:00
Chris Eager
a6d11789e9 Add ClosedChannelException to expected errors 2023-02-23 10:17:16 -06:00
Chris Eager
43f83076fa Update to reactor 3.5.3 2023-02-23 10:16:57 -06:00
erik-signal
71c0fc8d4a Improve metrics around spam report tokens. 2023-02-22 15:43:44 -05:00
Chris Eager
d2f723de12 Update to the latest version of the spam filter 2023-02-22 14:33:29 -06:00
Chris Eager
1f4f926ce6 Add platform tag to subscription receipt metrics 2023-02-22 14:31:30 -06:00
Chris Eager
35286f838e Add /v1/verification 2023-02-22 14:27:05 -06:00
Jon Chambers
e1ea3795bb Reuse registration sessions if possible when requesting pre-auth codes 2023-02-22 12:45:26 -05:00
erik-signal
95237a22a9 Relax validation to allow null reporting tokens. 2023-02-22 11:06:51 -05:00
Katherine Yen
11c93c5f53 Keep username hash during reregistration 2023-02-21 09:07:30 -08:00
Jon Chambers
b59b8621c5 Add reporter platform as a reported message dimension 2023-02-17 16:44:13 -05:00
Chris Eager
44c61d9a58 Allow updates if the profile already has a payment address 2023-02-17 16:44:01 -05:00
Ehren Kret
63a17bc14b add support for running tests from aarch64 2023-02-16 09:57:34 -06:00
Jon Chambers
f4f93bb24d Update to the latest version of the spam filter 2023-02-14 12:36:34 -05:00
Jon Chambers
7561622bc8 Log cases where we fall back to a no-op spam-reporting token provider 2023-02-14 12:35:56 -05:00
Jon Chambers
b041566aba Simplify construction of spam reporting token providers 2023-02-14 12:35:56 -05:00
Jon Chambers
cb72158abc Add the presence of spam reporting tokens as a dimension 2023-02-14 12:35:21 -05:00
Jon Chambers
5c432d094f Fix a typo in a metric name 2023-02-14 12:34:48 -05:00
Chris Eager
24ac48b3b1 Update counter name 2023-02-10 14:54:02 -06:00
Katherine Yen
c03060fe3c Phone number discoverability update endpoint 2023-02-10 11:52:51 -08:00
Chris Eager
3ebd5141ae Update to the latest version of the spam filter 2023-02-10 12:15:10 -06:00
Chris Eager
c16006dc4b Add PUT /v2/account/number 2023-02-10 12:09:03 -06:00
Sergey Skrobotov
8fc465b3e8 removing redundant logic in new registration flow 2023-02-09 09:06:48 -08:00
Chris Eager
ce689bdff3 Use DisabledPermittedAuthenticatedAccount at DELETE /v1/accounts/me 2023-02-09 09:05:29 -08:00
Chris Eager
e23386ddc7 Remove unused JUnit extension from test 2023-02-09 09:05:11 -08:00
Jon Chambers
0f17d63774 Add tests for ProvisioningController 2023-02-09 09:04:52 -08:00
Katherine Yen
4fc3949367 Add zkproof validation in username flow 2023-02-09 09:02:53 -08:00
Katherine Yen
e19c04377b Update to the latest version of the spam filter 2023-02-09 09:00:38 -08:00
Sergey Skrobotov
7c3f429c56 Update E164 constraint message 2023-02-08 13:22:00 -08:00
Sergey Skrobotov
7558489ad0 Registration Recovery Password support in /v1/registration 2023-02-08 13:20:23 -08:00
Katherine Yen
4a3880b5ae usernameHashes on reserve request can't be null 2023-02-07 08:44:04 -08:00
Chris Eager
ca7a4abd30 Update to the latest version of the spam filter 2023-02-06 16:40:09 -06:00
Chris Eager
a4a45de161 Add /v1/registration 2023-02-06 16:11:59 -06:00
Chris Eager
358a286523 Use java.util Hex and Base64 codecs 2023-02-06 12:16:59 -06:00
Sergey Skrobotov
3bbab0027b Update to the latest version of the spam filter 2023-02-03 16:39:34 -08:00
Sergey Skrobotov
8afe917a6c Registration recovery passwords store and manager 2023-02-03 16:33:03 -08:00
Erik Osheim
f5fec5e6bb Update to the latest version of the spam filter 2023-02-03 16:24:35 -05:00
Erik Osheim
0b81743683 Update to the latest version of the spam filter 2023-02-02 18:06:43 -05:00
Erik Osheim
9f715c3224 Update to the latest version of the spam filter 2023-02-02 18:05:02 -05:00
Katherine Yen
24f515ccb4 Revert "Revert "Stored hashed username"" 2023-02-02 11:20:44 -08:00
Erik Osheim
fd531242c9 Update to the latest version of the spam filter 2023-02-02 12:20:45 -05:00
Erik Osheim
3855bd257d Update to the latest version of the spam filter 2023-02-01 17:41:58 -05:00
Katherine Yen
c98b54ff15 Revert "Stored hashed username" 2023-02-01 14:31:44 -08:00
Katherine Yen
d93d50d038 Stored hashed username 2023-02-01 12:08:25 -08:00
Jon Chambers
448365c7a0 Preserve legacy registration API error handling 2023-01-31 15:45:23 -05:00
Sergey Skrobotov
515a863195 Update .gitmodules 2023-01-30 15:45:41 -08:00
Sergey Skrobotov
8d0e23bde1 AuthenticationCredentials name changed to SaltedTokenHash 2023-01-30 15:45:24 -08:00
Sergey Skrobotov
dc8f62a4ad /v1/backup/auth/check endpoint added 2023-01-30 15:39:42 -08:00
Jon Chambers
896e65545e Update to the latest version of the spam filter 2023-01-30 16:30:14 -05:00
Jon Chambers
cd4a4b1dcf Retire VoiceVerificationController 2023-01-30 16:28:14 -05:00
Jon Chambers
38a0737afb Retire ReportSpamTokenHandler interface in favor of ReportedMessageListener 2023-01-30 16:27:54 -05:00
Jon Chambers
4a2768b81d Add spam report token support to ReportedMessageListener 2023-01-30 16:27:54 -05:00
Jon Chambers
00e08b8402 Simplify parsing/validation of spam report tokens 2023-01-30 16:27:54 -05:00
Erik Osheim
48e8584e13 Update to current version of the spam-filter. 2023-01-27 11:41:27 -05:00
erik-signal
a89e30fe75 Clarify naming around spam filtering. 2023-01-27 11:40:33 -05:00
gram-signal
a01fcdad28 Add in controller for SVR2 auth. 2023-01-27 09:15:52 -07:00
Chris Eager
2a99529921 Remove old badge strings 2023-01-26 09:23:11 -06:00
Sergey Skrobotov
c934405a3e fixing config field names 2023-01-25 17:28:03 -08:00
Sergey Skrobotov
b8d922fcb7 Update to latest version of the spam module 2023-01-25 15:41:54 -08:00
Sergey Skrobotov
eb499833c6 refactoring of ExternalServiceCredentialGenerator 2023-01-25 15:20:28 -08:00
Chris Eager
dd98f7f043 Support changing just the currency of an existing subscription 2023-01-25 15:14:17 -06:00
Chris Eager
e8978ef91c Add tests for SubscriptionController#setSubscriptionLevel 2023-01-25 15:14:17 -06:00
Chris Eager
669ff1cadf DynamoDB Local Release Repository: snapshots.enabled = false 2023-01-25 15:12:02 -06:00
Jon Chambers
4ce85fdb19 Treat "check code" exceptions as false for legacy API compatibility 2023-01-25 14:39:29 -05:00
Jon Chambers
035ddc4834 Fix a mistake where we're looking for verification codes in place that hasn't been deployed yet (but will be soon!) 2023-01-25 11:43:06 -05:00
Chris Eager
c2f40b8503 Remove duplicate code 2023-01-25 11:09:23 -05:00
Jon Chambers
cf738a1c14 Look for registration service errors in response bodies in addition to status responses 2023-01-25 10:49:36 -05:00
erik-signal
52d40c2321 Add metrics for spam report tokens received. 2023-01-24 10:25:40 -05:00
Erik Osheim
cbf12d6b46 Update to latest version of the spam module 2023-01-19 11:20:08 -05:00
erik-signal
ab26a65b6a Introduce spam report tokens 2023-01-19 11:13:43 -05:00
erik-signal
ee5aaf5383 Ignore files created by emacs / lsp. 2023-01-18 15:44:29 -05:00
Jon Chambers
1c1714b2c2 Clarify a counter name 2023-01-17 17:13:06 -05:00
Jon Chambers
accb017ec5 Use a longer expiration window for quantile calculation 2023-01-17 17:13:06 -05:00
Chris Eager
304782d583 Use processor from SubscriptionProcessorManager for issued receipts 2023-01-17 16:12:03 -06:00
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
1146 changed files with 86001 additions and 31059 deletions

View File

@@ -158,7 +158,7 @@ ij_java_keep_indents_on_empty_lines = false
ij_java_keep_line_breaks = true
ij_java_keep_multiple_expressions_in_one_line = false
ij_java_keep_simple_blocks_in_one_line = false
ij_java_keep_simple_classes_in_one_line = false
ij_java_keep_simple_classes_in_one_line = true
ij_java_keep_simple_lambdas_in_one_line = false
ij_java_keep_simple_methods_in_one_line = false
ij_java_label_indent_absolute = false

33
.github/workflows/documentation.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: Update Documentation
on:
push:
branches:
- main
jobs:
build:
permissions:
contents: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
with:
distribution: 'temurin'
java-version: '21'
cache: 'maven'
- name: Compile and Build OpenAPI file
run: ./mvnw compile
- name: Update Documentation
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
cp -r api-doc/target/openapi/signal-server-openapi.yaml /tmp/
git config user.email "github@signal.org"
git config user.name "Documentation Updater"
git fetch origin gh-pages
git checkout gh-pages
cp /tmp/signal-server-openapi.yaml .
git diff --quiet || git commit -a -m "Updating documentation"
git push origin gh-pages -q

34
.github/workflows/integration-tests.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: Integration Tests
on:
schedule:
- cron: '30 19 * * MON-FRI'
workflow_dispatch:
jobs:
build:
if: ${{ vars.INTEGRATION_TESTS_BUCKET != '' }}
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
with:
distribution: 'temurin'
java-version: '21'
cache: 'maven'
- uses: aws-actions/configure-aws-credentials@5579c002bb4778aa43395ef1df492868a9a1c83f # v4.0.2
name: Configure AWS credentials from Test account
with:
role-to-assume: ${{ vars.AWS_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
- name: Fetch integration utils library
run: |
mkdir -p integration-tests/.libs
mkdir -p integration-tests/src/main/resources
wget -O integration-tests/.libs/software.amazon.awssdk-sso.jar https://repo1.maven.org/maven2/software/amazon/awssdk/sso/2.19.8/sso-2.19.8.jar
aws s3 cp "s3://${{ vars.INTEGRATION_TESTS_BUCKET }}/config-latest.yml" integration-tests/src/main/resources/config.yml
- name: Run and verify integration tests
run: ./mvnw clean compile test-compile failsafe:integration-test failsafe:verify

View File

@@ -1,6 +1,9 @@
name: Service CI
on: [push]
on:
push:
branches-ignore:
- gh-pages
jobs:
build:
@@ -8,16 +11,19 @@ jobs:
container: ubuntu:22.04
steps:
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
- name: Set up JDK 17
uses: actions/setup-java@de1bb2b0c5634f0fc4438d7aa9944e68f9bf86cc # v3.6.0
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Set up JDK 21
uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
with:
distribution: 'temurin'
java-version: 17
java-version: 21
cache: 'maven'
env:
# work around an issue with actions/runner setting an incorrect HOME in containers, which breaks maven caching
# https://github.com/actions/setup-java/issues/356
HOME: /root
- name: Install APT packages
# ca-certificates: required for AWS CRT client
run: apt update && apt install -y ca-certificates
- name: Build with Maven
run: ./mvnw -e -B verify

4
.gitignore vendored
View File

@@ -26,3 +26,7 @@ deployer.log
!/service/src/main/resources/org/signal/badges/Badges_en.properties
/service/src/main/resources/org/signal/subscriptions/Subscriptions_*.properties
!/service/src/main/resources/org/signal/subscriptions/Subscriptions_en.properties
.project
.classpath
.settings
.DS_Store

8
.gitmodules vendored
View File

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

View File

@@ -4,6 +4,6 @@
<extension>
<groupId>fr.brouillard.oss</groupId>
<artifactId>jgitver-maven-plugin</artifactId>
<version>1.7.1</version>
<version>1.9.0</version>
</extension>
</extensions>

Binary file not shown.

View File

@@ -14,5 +14,7 @@
# 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
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.8/apache-maven-3.9.8-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar
distributionSha256Sum=8351955a9acf2f83c136c4eee0f6db894ab6265fdbe0a94b32a380307dbaa3e1
wrapperSha256Sum=3d8f20ce6103913be8b52aef6d994e0c54705fb527324ceb9b835b338739c7a8

View File

@@ -296,7 +296,7 @@ commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
procedures, authorization keysManager, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object

View File

@@ -21,6 +21,6 @@ The form and manner of this distribution makes it eligible for export under the
License
---------------------
Copyright 2013-2022 Signal Messenger, LLC
Copyright 2013-2024 Signal Messenger, LLC
Licensed under the AGPLv3: https://www.gnu.org/licenses/agpl-3.0.html
Licensed under the GNU AGPLv3: https://www.gnu.org/licenses/agpl-3.0.html

30
TESTING.md Normal file
View File

@@ -0,0 +1,30 @@
# Testing
## Automated tests
The full suite of automated tests can be run using Maven from the project root:
```sh
./mvnw verify
```
## Test server
The service can be run in a feature-limited test mode by running the Maven `integration-test`
goal with the `test-server` profile activated:
```sh
./mvnw integration-test -Ptest-server [-DskipTests=true]
```
This runs [`LocalWhisperServerService`][lwss] with [test configuration][test.yml] and [secrets][test secrets]. External
registration clients are stubbed so that:
- a captcha requirement can be satisfied with `test.test.registration.test`
- any string will be accepted for a phone verification code
[lwss]: service/src/test/java/org/whispersystems/textsecuregcm/LocalWhisperServerService.java
[test.yml]: service/src/test/resources/config/test.yml
[test secrets]: service/src/test/resources/config/test-secrets-bundle.yml

53
api-doc/pom.xml Normal file
View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>TextSecureServer</artifactId>
<groupId>org.whispersystems.textsecure</groupId>
<version>JGITVER</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>api-doc</artifactId>
<dependencies>
<dependency>
<groupId>org.whispersystems.textsecure</groupId>
<artifactId>service</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-maven-plugin</artifactId>
<version>${swagger.version}</version>
<configuration>
<outputFileName>signal-server-openapi</outputFileName>
<outputPath>${project.build.directory}/openapi</outputPath>
<outputFormat>YAML</outputFormat>
<configurationFilePath>${project.basedir}/src/main/resources/openapi/openapi-configuration.yaml
</configurationFilePath>
</configuration>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>resolve</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<configuration>
<!-- we don't want jib to execute on this module -->
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.openapi;
import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.type.SimpleType;
import io.dropwizard.auth.Auth;
import io.swagger.v3.jaxrs2.ResolvedParameter;
import io.swagger.v3.jaxrs2.ext.AbstractOpenAPIExtension;
import io.swagger.v3.jaxrs2.ext.OpenAPIExtension;
import io.swagger.v3.oas.models.Components;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.Set;
import javax.ws.rs.Consumes;
import org.whispersystems.textsecuregcm.auth.AuthenticatedDevice;
/**
* One of the extension mechanisms of Swagger Core library (OpenAPI processor) is via custom implementations
* of the {@link AbstractOpenAPIExtension} class.
* <p/>
* The purpose of this extension is to customize certain aspects of the OpenAPI model generation on a lower level.
* This extension works in coordination with {@link OpenApiReader} that has access to the model on a higher level.
* <p/>
* The extension is enabled by being listed in {@code META-INF/services/io.swagger.v3.jaxrs2.ext.OpenAPIExtension} file.
* @see ServiceLoader
* @see OpenApiReader
* @see <a href="https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Extensions">Swagger 2.X Extensions</a>
*/
public class OpenApiExtension extends AbstractOpenAPIExtension {
public static final ResolvedParameter AUTHENTICATED_ACCOUNT = new ResolvedParameter();
public static final ResolvedParameter OPTIONAL_AUTHENTICATED_ACCOUNT = new ResolvedParameter();
/**
* When parsing endpoint methods, Swagger will treat the first parameter not annotated as header/path/query param
* as a request body (and will ignore other not annotated parameters). In our case, this behavior conflicts with
* the {@code @Auth}-annotated parameters. Here we're checking if parameters are known to be anything other than
* a body and return an appropriate {@link ResolvedParameter} representation.
*/
@Override
public ResolvedParameter extractParameters(
final List<Annotation> annotations,
final Type type,
final Set<Type> typesToSkip,
final Components components,
final Consumes classConsumes,
final Consumes methodConsumes,
final boolean includeRequestBody,
final JsonView jsonViewAnnotation,
final Iterator<OpenAPIExtension> chain) {
if (annotations.stream().anyMatch(a -> a.annotationType().equals(Auth.class))) {
// this is the case of authenticated endpoint,
if (type instanceof SimpleType simpleType
&& simpleType.getRawClass().equals(AuthenticatedDevice.class)) {
return AUTHENTICATED_ACCOUNT;
}
if (type instanceof SimpleType simpleType
&& isOptionalOfType(simpleType, AuthenticatedDevice.class)) {
return OPTIONAL_AUTHENTICATED_ACCOUNT;
}
}
return super.extractParameters(
annotations,
type,
typesToSkip,
components,
classConsumes,
methodConsumes,
includeRequestBody,
jsonViewAnnotation,
chain);
}
private static boolean isOptionalOfType(final SimpleType simpleType, final Class<?> expectedType) {
if (!simpleType.getRawClass().equals(Optional.class)) {
return false;
}
final List<JavaType> typeParameters = simpleType.getBindings().getTypeParameters();
if (typeParameters.isEmpty()) {
return false;
}
return typeParameters.get(0) instanceof SimpleType optionalParameterType
&& optionalParameterType.getRawClass().equals(expectedType);
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.openapi;
import static com.google.common.base.MoreObjects.firstNonNull;
import static org.signal.openapi.OpenApiExtension.AUTHENTICATED_ACCOUNT;
import static org.signal.openapi.OpenApiExtension.OPTIONAL_AUTHENTICATED_ACCOUNT;
import com.fasterxml.jackson.annotation.JsonView;
import com.google.common.collect.ImmutableList;
import io.swagger.v3.jaxrs2.Reader;
import io.swagger.v3.jaxrs2.ResolvedParameter;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;
import javax.ws.rs.Consumes;
/**
* One of the extension mechanisms of Swagger Core library (OpenAPI processor) is via custom implementations
* of the {@link Reader} class.
* <p/>
* The purpose of this extension is to customize certain aspects of the OpenAPI model generation on a higher level.
* This extension works in coordination with {@link OpenApiExtension} that has access to the model on a lower level.
* <p/>
* The extension is enabled by being listed in {@code resources/openapi/openapi-configuration.yaml} file.
* @see OpenApiExtension
* @see <a href="https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Extensions">Swagger 2.X Extensions</a>
*/
public class OpenApiReader extends Reader {
private static final String AUTHENTICATED_ACCOUNT_AUTH_SCHEMA = "authenticatedAccount";
/**
* Overriding this method allows converting a resolved parameter into other operation entities,
* in this case, into security requirements.
*/
@Override
protected ResolvedParameter getParameters(
final Type type,
final List<Annotation> annotations,
final Operation operation,
final Consumes classConsumes,
final Consumes methodConsumes,
final JsonView jsonViewAnnotation) {
final ResolvedParameter resolved = super.getParameters(
type, annotations, operation, classConsumes, methodConsumes, jsonViewAnnotation);
if (resolved == AUTHENTICATED_ACCOUNT) {
operation.setSecurity(ImmutableList.<SecurityRequirement>builder()
.addAll(firstNonNull(operation.getSecurity(), Collections.emptyList()))
.add(new SecurityRequirement().addList(AUTHENTICATED_ACCOUNT_AUTH_SCHEMA))
.build());
}
if (resolved == OPTIONAL_AUTHENTICATED_ACCOUNT) {
operation.setSecurity(ImmutableList.<SecurityRequirement>builder()
.addAll(firstNonNull(operation.getSecurity(), Collections.emptyList()))
.add(new SecurityRequirement().addList(AUTHENTICATED_ACCOUNT_AUTH_SCHEMA))
.add(new SecurityRequirement())
.build());
}
return resolved;
}
}

View File

@@ -0,0 +1 @@
org.signal.openapi.OpenApiExtension

View File

@@ -0,0 +1,25 @@
resourcePackages:
- org.whispersystems.textsecuregcm
prettyPrint: true
cacheTTL: 0
readerClass: org.signal.openapi.OpenApiReader
openAPI:
info:
title: Signal Server API
license:
name: AGPL-3.0-only
url: https://www.gnu.org/licenses/agpl-3.0.txt
servers:
- url: https://chat.signal.org
description: Production service
- url: https://chat.staging.signal.org
description: Staging service
components:
securitySchemes:
authenticatedAccount:
type: http
scheme: basic
description: |
Account authentication is based on Basic authentication schema,
where `username` has a format of `<user_id>[.<device_id>]`. If `device_id` is not specified,
user's `main` device is assumed.

View File

@@ -1,86 +0,0 @@
<?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

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

@@ -1,41 +0,0 @@
/*
* 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()))
}
}

2
integration-tests/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.libs
src/main/resources/config.yml

62
integration-tests/pom.xml Normal file
View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>TextSecureServer</artifactId>
<groupId>org.whispersystems.textsecure</groupId>
<version>JGITVER</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>integration-tests</artifactId>
<dependencies>
<dependency>
<groupId>org.whispersystems.textsecure</groupId>
<artifactId>service</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>dynamodb</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<excludes>
<exclude>**</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<additionalClasspathElements>
<additionalClasspathElement>${project.basedir}/.libs/software.amazon.awssdk-sso.jar</additionalClasspathElement>
</additionalClasspathElements>
<includes>
<include>**/*.java</include>
</includes>
</configuration>
</plugin>
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<configuration>
<!-- we don't want jib to execute on this module -->
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,102 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.integration;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.util.Base64;
import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.ecc.Curve;
import org.signal.libsignal.protocol.ecc.ECPublicKey;
public final class Codecs {
private Codecs() {
// utility class
}
@FunctionalInterface
public interface CheckedFunction<T, R> {
R apply(T t) throws Exception;
}
public static class Base64BasedSerializer<T> extends JsonSerializer<T> {
private final CheckedFunction<T, byte[]> mapper;
public Base64BasedSerializer(final CheckedFunction<T, byte[]> mapper) {
this.mapper = mapper;
}
@Override
public void serialize(final T value, final JsonGenerator gen, final SerializerProvider serializers) throws IOException {
try {
gen.writeString(Base64.getEncoder().withoutPadding().encodeToString(mapper.apply(value)));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public static class Base64BasedDeserializer<T> extends JsonDeserializer<T> {
private final CheckedFunction<byte[], T> mapper;
public Base64BasedDeserializer(final CheckedFunction<byte[], T> mapper) {
this.mapper = mapper;
}
@Override
public T deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException {
try {
return mapper.apply(Base64.getDecoder().decode(p.getValueAsString()));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public static class ByteArraySerializer extends Base64BasedSerializer<byte[]> {
public ByteArraySerializer() {
super(bytes -> bytes);
}
}
public static class ByteArrayDeserializer extends Base64BasedDeserializer<byte[]> {
public ByteArrayDeserializer() {
super(bytes -> bytes);
}
}
public static class ECPublicKeySerializer extends Base64BasedSerializer<ECPublicKey> {
public ECPublicKeySerializer() {
super(ECPublicKey::serialize);
}
}
public static class ECPublicKeyDeserializer extends Base64BasedDeserializer<ECPublicKey> {
public ECPublicKeyDeserializer() {
super(bytes -> Curve.decodePoint(bytes, 0));
}
}
public static class IdentityKeySerializer extends Base64BasedSerializer<IdentityKey> {
public IdentityKeySerializer() {
super(IdentityKey::serialize);
}
}
public static class IdentityKeyDeserializer extends Base64BasedDeserializer<IdentityKey> {
public IdentityKeyDeserializer() {
super(bytes -> new IdentityKey(bytes, 0));
}
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.integration;
import java.time.Clock;
import java.time.Duration;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import org.signal.integration.config.Config;
import org.whispersystems.textsecuregcm.registration.VerificationSession;
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswords;
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager;
import org.whispersystems.textsecuregcm.storage.VerificationSessionManager;
import org.whispersystems.textsecuregcm.storage.VerificationSessions;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
public class IntegrationTools {
private final RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager;
private final VerificationSessionManager verificationSessionManager;
public static IntegrationTools create(final Config config) {
final AwsCredentialsProvider credentialsProvider = DefaultCredentialsProvider.builder().build();
final DynamoDbAsyncClient dynamoDbAsyncClient = config.dynamoDbClient().buildAsyncClient(credentialsProvider);
final DynamoDbClient dynamoDbClient = config.dynamoDbClient().buildSyncClient(credentialsProvider);
final RegistrationRecoveryPasswords registrationRecoveryPasswords = new RegistrationRecoveryPasswords(
config.dynamoDbTables().registrationRecovery(), Duration.ofDays(1), dynamoDbClient, dynamoDbAsyncClient);
final VerificationSessions verificationSessions = new VerificationSessions(
dynamoDbAsyncClient, config.dynamoDbTables().verificationSessions(), Clock.systemUTC());
return new IntegrationTools(
new RegistrationRecoveryPasswordsManager(registrationRecoveryPasswords),
new VerificationSessionManager(verificationSessions)
);
}
private IntegrationTools(
final RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager,
final VerificationSessionManager verificationSessionManager) {
this.registrationRecoveryPasswordsManager = registrationRecoveryPasswordsManager;
this.verificationSessionManager = verificationSessionManager;
}
public CompletableFuture<Void> populateRecoveryPassword(final String e164, final byte[] password) {
return registrationRecoveryPasswordsManager.storeForCurrentNumber(e164, password);
}
public CompletableFuture<Optional<String>> peekVerificationSessionPushChallenge(final String sessionId) {
return verificationSessionManager.findForId(sessionId)
.thenApply(maybeSession -> maybeSession.map(VerificationSession::pushChallenge));
}
}

View File

@@ -0,0 +1,343 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.integration;
import static java.util.Objects.requireNonNull;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.io.Resources;
import com.google.common.net.HttpHeaders;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.net.URI;
import java.net.URL;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Executors;
import io.dropwizard.configuration.ConfigurationValidationException;
import io.dropwizard.jersey.validation.Validators;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.tuple.Pair;
import org.signal.integration.config.Config;
import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.ecc.Curve;
import org.signal.libsignal.protocol.ecc.ECKeyPair;
import org.signal.libsignal.protocol.ecc.ECPublicKey;
import org.signal.libsignal.protocol.kem.KEMKeyPair;
import org.signal.libsignal.protocol.kem.KEMKeyType;
import org.signal.libsignal.protocol.kem.KEMPublicKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
import org.whispersystems.textsecuregcm.entities.AccountIdentityResponse;
import org.whispersystems.textsecuregcm.entities.ECSignedPreKey;
import org.whispersystems.textsecuregcm.entities.KEMSignedPreKey;
import org.whispersystems.textsecuregcm.entities.RegistrationRequest;
import org.whispersystems.textsecuregcm.http.FaultTolerantHttpClient;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.util.HeaderUtils;
import org.whispersystems.textsecuregcm.util.HttpUtils;
import org.whispersystems.textsecuregcm.util.SystemMapper;
import javax.validation.ConstraintViolation;
public final class Operations {
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final Config CONFIG = loadConfigFromClasspath("config.yml");
private static final IntegrationTools INTEGRATION_TOOLS = IntegrationTools.create(CONFIG);
private static final String USER_AGENT = "integration-test";
private static final FaultTolerantHttpClient CLIENT = buildClient();
private Operations() {
// utility class
}
public static TestUser newRegisteredUser(final String number) {
final byte[] registrationPassword = randomBytes(32);
final String accountPassword = Base64.getEncoder().encodeToString(randomBytes(32));
final TestUser user = TestUser.create(number, accountPassword, registrationPassword);
final AccountAttributes accountAttributes = user.accountAttributes();
INTEGRATION_TOOLS.populateRecoveryPassword(number, registrationPassword).join();
final ECKeyPair aciIdentityKeyPair = Curve.generateKeyPair();
final ECKeyPair pniIdentityKeyPair = Curve.generateKeyPair();
// register account
final RegistrationRequest registrationRequest = new RegistrationRequest(null,
registrationPassword,
accountAttributes,
true,
new IdentityKey(aciIdentityKeyPair.getPublicKey()),
new IdentityKey(pniIdentityKeyPair.getPublicKey()),
generateSignedECPreKey(1, aciIdentityKeyPair),
generateSignedECPreKey(2, pniIdentityKeyPair),
generateSignedKEMPreKey(3, aciIdentityKeyPair),
generateSignedKEMPreKey(4, pniIdentityKeyPair),
Optional.empty(),
Optional.empty());
final AccountIdentityResponse registrationResponse = apiPost("/v1/registration", registrationRequest)
.authorized(number, accountPassword)
.executeExpectSuccess(AccountIdentityResponse.class);
user.setAciUuid(registrationResponse.uuid());
user.setPniUuid(registrationResponse.pni());
return user;
}
public record PrescribedVerificationNumber(String number, String verificationCode) {}
public static PrescribedVerificationNumber prescribedVerificationNumber() {
return new PrescribedVerificationNumber(
CONFIG.prescribedRegistrationNumber(),
CONFIG.prescribedRegistrationCode());
}
public static void deleteUser(final TestUser user) {
apiDelete("/v1/accounts/me").authorized(user).executeExpectSuccess();
}
public static String peekVerificationSessionPushChallenge(final String sessionId) {
return INTEGRATION_TOOLS.peekVerificationSessionPushChallenge(sessionId).join()
.orElseThrow(() -> new RuntimeException("push challenge not found for the verification session"));
}
public static <T> T sendEmptyRequestAuthenticated(
final String endpoint,
final String method,
final String username,
final String password,
final Class<T> outputType) {
try {
final HttpRequest request = HttpRequest.newBuilder()
.uri(serverUri(endpoint, Collections.emptyList()))
.method(method, HttpRequest.BodyPublishers.noBody())
.header(HttpHeaders.AUTHORIZATION, HeaderUtils.basicAuthHeader(username, password))
.header(HttpHeaders.CONTENT_TYPE, "application/json")
.build();
return CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8))
.whenComplete((response, error) -> {
if (error != null) {
logger.error("request error", error);
error.printStackTrace();
} else {
logger.info("response: {}", response.statusCode());
System.out.println("response: " + response.statusCode() + ", " + response.body());
}
})
.thenApply(response -> {
try {
return outputType.equals(Void.class)
? null
: SystemMapper.jsonMapper().readValue(response.body(), outputType);
} catch (final IOException e) {
throw new RuntimeException(e);
}
})
.get();
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
private static byte[] randomBytes(int numBytes) {
final byte[] bytes = new byte[numBytes];
new SecureRandom().nextBytes(bytes);
return bytes;
}
public static RequestBuilder apiGet(final String endpoint) {
return new RequestBuilder(HttpRequest.newBuilder().GET(), endpoint);
}
public static RequestBuilder apiDelete(final String endpoint) {
return new RequestBuilder(HttpRequest.newBuilder().DELETE(), endpoint);
}
public static <R> RequestBuilder apiPost(final String endpoint, final R input) {
return RequestBuilder.withJsonBody(endpoint, "POST", input);
}
public static <R> RequestBuilder apiPut(final String endpoint, final R input) {
return RequestBuilder.withJsonBody(endpoint, "PUT", input);
}
public static <R> RequestBuilder apiPatch(final String endpoint, final R input) {
return RequestBuilder.withJsonBody(endpoint, "PATCH", input);
}
private static URI serverUri(final String endpoint, final List<String> queryParams) {
final String query = queryParams.isEmpty()
? StringUtils.EMPTY
: "?" + String.join("&", queryParams);
return URI.create("https://" + CONFIG.domain() + endpoint + query);
}
public static class RequestBuilder {
private final HttpRequest.Builder builder;
private final String endpoint;
private final List<String> queryParams = new ArrayList<>();
private RequestBuilder(final HttpRequest.Builder builder, final String endpoint) {
this.builder = builder;
this.endpoint = endpoint;
}
private static <R> RequestBuilder withJsonBody(final String endpoint, final String method, final R input) {
try {
final byte[] body = SystemMapper.jsonMapper().writeValueAsBytes(input);
return new RequestBuilder(HttpRequest.newBuilder()
.header(HttpHeaders.CONTENT_TYPE, "application/json")
.method(method, HttpRequest.BodyPublishers.ofByteArray(body)), endpoint);
} catch (final JsonProcessingException e) {
throw new RuntimeException(e);
}
}
public RequestBuilder authorized(final TestUser user) {
return authorized(user, Device.PRIMARY_ID);
}
public RequestBuilder authorized(final TestUser user, final byte deviceId) {
final String username = "%s.%d".formatted(user.aciUuid().toString(), deviceId);
return authorized(username, user.accountPassword());
}
public RequestBuilder authorized(final String username, final String password) {
builder.header(HttpHeaders.AUTHORIZATION, HeaderUtils.basicAuthHeader(username, password));
return this;
}
public RequestBuilder queryParam(final String key, final String value) {
queryParams.add("%s=%s".formatted(key, value));
return this;
}
public RequestBuilder header(final String name, final String value) {
builder.header(name, value);
return this;
}
public Pair<Integer, Void> execute() {
return execute(Void.class);
}
public Pair<Integer, Void> executeExpectSuccess() {
final Pair<Integer, Void> execute = execute();
Validate.isTrue(
HttpUtils.isSuccessfulResponse(execute.getLeft()),
"Unexpected response code: %d",
execute.getLeft());
return execute;
}
public <T> T executeExpectSuccess(final Class<T> expectedType) {
final Pair<Integer, T> execute = execute(expectedType);
Validate.isTrue(
HttpUtils.isSuccessfulResponse(execute.getLeft()),
"Unexpected response code: %d : %s",
execute.getLeft(), execute.getRight());
return requireNonNull(execute.getRight());
}
public void executeExpectStatusCode(final int expectedStatusCode) {
final Pair<Integer, Void> execute = execute(Void.class);
Validate.isTrue(
execute.getLeft() == expectedStatusCode,
"Unexpected response code: %d",
execute.getLeft()
);
}
public <T> Pair<Integer, T> execute(final Class<T> expectedType) {
builder.uri(serverUri(endpoint, queryParams))
.header(HttpHeaders.USER_AGENT, USER_AGENT);
return CLIENT.sendAsync(builder.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8))
.whenComplete((response, error) -> {
if (error != null) {
logger.error("request error", error);
error.printStackTrace();
}
})
.thenApply(response -> {
try {
final T result = expectedType.equals(Void.class)
? null
: SystemMapper.jsonMapper().readValue(response.body(), expectedType);
return Pair.of(response.statusCode(), result);
} catch (final IOException e) {
throw new RuntimeException(e);
}
})
.join();
}
}
private static FaultTolerantHttpClient buildClient() {
try {
return FaultTolerantHttpClient.newBuilder()
.withName("integration-test")
.withExecutor(Executors.newFixedThreadPool(16))
.withRetryExecutor(Executors.newSingleThreadScheduledExecutor())
.withCircuitBreaker(new CircuitBreakerConfiguration())
.withTrustedServerCertificates(CONFIG.rootCert())
.build();
} catch (final CertificateException e) {
throw new RuntimeException(e);
}
}
private static Config loadConfigFromClasspath(final String filename) {
try {
final URL configFileUrl = Resources.getResource(filename);
final Config config = SystemMapper.yamlMapper().readValue(Resources.toByteArray(configFileUrl), Config.class);
final Set<ConstraintViolation<Config>> constraintViolations = Validators.newValidator().validate(config);
if (!constraintViolations.isEmpty()) {
throw new ConfigurationValidationException(filename, constraintViolations);
}
return config;
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
private static ECSignedPreKey generateSignedECPreKey(long id, final ECKeyPair identityKeyPair) {
final ECPublicKey pubKey = Curve.generateKeyPair().getPublicKey();
final byte[] sig = identityKeyPair.getPrivateKey().calculateSignature(pubKey.serialize());
return new ECSignedPreKey(id, pubKey, sig);
}
private static KEMSignedPreKey generateSignedKEMPreKey(long id, final ECKeyPair identityKeyPair) {
final KEMPublicKey pubKey = KEMKeyPair.generate(KEMKeyType.KYBER_1024).getPublicKey();
final byte[] sig = identityKeyPair.getPrivateKey().calculateSignature(pubKey.serialize());
return new KEMSignedPreKey(id, pubKey, sig);
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.integration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.tuple.Pair;
import org.signal.libsignal.protocol.IdentityKeyPair;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.ecc.Curve;
import org.signal.libsignal.protocol.ecc.ECKeyPair;
import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
public class TestDevice {
private final byte deviceId;
private final Map<Integer, Pair<IdentityKeyPair, SignedPreKeyRecord>> signedPreKeys = new ConcurrentHashMap<>();
public static TestDevice create(
final byte deviceId,
final IdentityKeyPair aciIdentityKeyPair,
final IdentityKeyPair pniIdentityKeyPair) {
final TestDevice device = new TestDevice(deviceId);
device.addSignedPreKey(aciIdentityKeyPair);
device.addSignedPreKey(pniIdentityKeyPair);
return device;
}
public TestDevice(final byte deviceId) {
this.deviceId = deviceId;
}
public byte deviceId() {
return deviceId;
}
public SignedPreKeyRecord latestSignedPreKey(final IdentityKeyPair identity) {
final int id = signedPreKeys.entrySet()
.stream()
.filter(p -> p.getValue().getLeft().equals(identity))
.mapToInt(Map.Entry::getKey)
.max()
.orElseThrow();
return signedPreKeys.get(id).getRight();
}
public SignedPreKeyRecord addSignedPreKey(final IdentityKeyPair identity) {
try {
final int nextId = signedPreKeys.keySet().stream().mapToInt(k -> k + 1).max().orElse(0);
final ECKeyPair keyPair = Curve.generateKeyPair();
final byte[] signature = Curve.calculateSignature(identity.getPrivateKey(), keyPair.getPublicKey().serialize());
final SignedPreKeyRecord signedPreKeyRecord = new SignedPreKeyRecord(nextId, System.currentTimeMillis(), keyPair, signature);
signedPreKeys.put(nextId, Pair.of(identity, signedPreKeyRecord));
return signedPreKeyRecord;
} catch (InvalidKeyException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,192 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.integration;
import static java.util.Objects.requireNonNull;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.security.SecureRandom;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.IdentityKeyPair;
import org.signal.libsignal.protocol.ecc.ECPublicKey;
import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
import org.signal.libsignal.protocol.util.KeyHelper;
import org.whispersystems.textsecuregcm.auth.UnidentifiedAccessUtil;
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
import org.whispersystems.textsecuregcm.storage.Device;
public class TestUser {
private final int registrationId;
private final int pniRegistrationId;
private final IdentityKeyPair aciIdentityKey;
private final Map<Byte, TestDevice> devices = new ConcurrentHashMap<>();
private final byte[] unidentifiedAccessKey;
private String phoneNumber;
private IdentityKeyPair pniIdentityKey;
private String accountPassword;
private byte[] registrationPassword;
private UUID aciUuid;
private UUID pniUuid;
public static TestUser create(final String phoneNumber, final String accountPassword, final byte[] registrationPassword) {
// ACI identity key pair
final IdentityKeyPair aciIdentityKey = IdentityKeyPair.generate();
// PNI identity key pair
final IdentityKeyPair pniIdentityKey = IdentityKeyPair.generate();
// registration id
final int registrationId = KeyHelper.generateRegistrationId(false);
final int pniRegistrationId = KeyHelper.generateRegistrationId(false);
// uak
final byte[] unidentifiedAccessKey = new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH];
new SecureRandom().nextBytes(unidentifiedAccessKey);
return new TestUser(
registrationId,
pniRegistrationId,
aciIdentityKey,
phoneNumber,
pniIdentityKey,
unidentifiedAccessKey,
accountPassword,
registrationPassword);
}
public TestUser(
final int registrationId,
final int pniRegistrationId,
final IdentityKeyPair aciIdentityKey,
final String phoneNumber,
final IdentityKeyPair pniIdentityKey,
final byte[] unidentifiedAccessKey,
final String accountPassword,
final byte[] registrationPassword) {
this.registrationId = registrationId;
this.pniRegistrationId = pniRegistrationId;
this.aciIdentityKey = aciIdentityKey;
this.phoneNumber = phoneNumber;
this.pniIdentityKey = pniIdentityKey;
this.unidentifiedAccessKey = unidentifiedAccessKey;
this.accountPassword = accountPassword;
this.registrationPassword = registrationPassword;
devices.put(Device.PRIMARY_ID, TestDevice.create(Device.PRIMARY_ID, aciIdentityKey, pniIdentityKey));
}
public int registrationId() {
return registrationId;
}
public IdentityKeyPair aciIdentityKey() {
return aciIdentityKey;
}
public String phoneNumber() {
return phoneNumber;
}
public IdentityKeyPair pniIdentityKey() {
return pniIdentityKey;
}
public String accountPassword() {
return accountPassword;
}
public byte[] registrationPassword() {
return registrationPassword;
}
public UUID aciUuid() {
return aciUuid;
}
public UUID pniUuid() {
return pniUuid;
}
public AccountAttributes accountAttributes() {
return new AccountAttributes(true, registrationId, pniRegistrationId, "".getBytes(StandardCharsets.UTF_8), "", true, new Device.DeviceCapabilities(false, false, false, false))
.withUnidentifiedAccessKey(unidentifiedAccessKey)
.withRecoveryPassword(registrationPassword);
}
public void setAciUuid(final UUID aciUuid) {
this.aciUuid = aciUuid;
}
public void setPniUuid(final UUID pniUuid) {
this.pniUuid = pniUuid;
}
public void setPhoneNumber(final String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public void setPniIdentityKey(final IdentityKeyPair pniIdentityKey) {
this.pniIdentityKey = pniIdentityKey;
}
public void setAccountPassword(final String accountPassword) {
this.accountPassword = accountPassword;
}
public void setRegistrationPassword(final byte[] registrationPassword) {
this.registrationPassword = registrationPassword;
}
public PreKeySetPublicView preKeys(final byte deviceId, final boolean pni) {
final IdentityKeyPair identity = pni
? pniIdentityKey
: aciIdentityKey;
final TestDevice device = requireNonNull(devices.get(deviceId));
final SignedPreKeyRecord signedPreKeyRecord = device.latestSignedPreKey(identity);
return new PreKeySetPublicView(
Collections.emptyList(),
identity.getPublicKey(),
new SignedPreKeyPublicView(
signedPreKeyRecord.getId(),
signedPreKeyRecord.getKeyPair().getPublicKey(),
signedPreKeyRecord.getSignature()
)
);
}
public record SignedPreKeyPublicView(
int keyId,
@JsonSerialize(using = Codecs.ECPublicKeySerializer.class)
@JsonDeserialize(using = Codecs.ECPublicKeyDeserializer.class)
ECPublicKey publicKey,
@JsonSerialize(using = Codecs.ByteArraySerializer.class)
@JsonDeserialize(using = Codecs.ByteArrayDeserializer.class)
byte[] signature) {
}
public record PreKeySetPublicView(
List<String> preKeys,
@JsonSerialize(using = Codecs.IdentityKeySerializer.class)
@JsonDeserialize(using = Codecs.IdentityKeyDeserializer.class)
IdentityKey identityKey,
SignedPreKeyPublicView signedPreKey) {
}
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.integration.config;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.DynamoDbClientFactory;
public record Config(@NotBlank String domain,
@NotBlank String rootCert,
@NotNull @Valid DynamoDbClientFactory dynamoDbClient,
@NotNull @Valid DynamoDbTables dynamoDbTables,
@NotBlank String prescribedRegistrationNumber,
@NotBlank String prescribedRegistrationCode) {
}

View File

@@ -0,0 +1,12 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.integration.config;
import javax.validation.constraints.NotBlank;
public record DynamoDbTables(@NotBlank String registrationRecovery,
@NotBlank String verificationSessions) {
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.integration;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.http.HttpStatus;
import org.junit.jupiter.api.Test;
import org.signal.libsignal.usernames.BaseUsernameException;
import org.signal.libsignal.usernames.Username;
import org.whispersystems.textsecuregcm.entities.AccountIdentifierResponse;
import org.whispersystems.textsecuregcm.entities.AccountIdentityResponse;
import org.whispersystems.textsecuregcm.entities.ConfirmUsernameHashRequest;
import org.whispersystems.textsecuregcm.entities.ReserveUsernameHashRequest;
import org.whispersystems.textsecuregcm.entities.ReserveUsernameHashResponse;
import org.whispersystems.textsecuregcm.entities.UsernameHashResponse;
import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier;
public class AccountTest {
@Test
public void testCreateAccount() throws Exception {
final TestUser user = Operations.newRegisteredUser("+19995550101");
try {
final Pair<Integer, AccountIdentityResponse> execute = Operations.apiGet("/v1/accounts/whoami")
.authorized(user)
.execute(AccountIdentityResponse.class);
assertEquals(HttpStatus.SC_OK, execute.getLeft());
} finally {
Operations.deleteUser(user);
}
}
@Test
public void testCreateAccountAtomic() throws Exception {
final TestUser user = Operations.newRegisteredUser("+19995550201");
try {
final Pair<Integer, AccountIdentityResponse> execute = Operations.apiGet("/v1/accounts/whoami")
.authorized(user)
.execute(AccountIdentityResponse.class);
assertEquals(HttpStatus.SC_OK, execute.getLeft());
} finally {
Operations.deleteUser(user);
}
}
@Test
public void testUsernameOperations() throws Exception {
final TestUser user = Operations.newRegisteredUser("+19995550102");
try {
verifyFullUsernameLifecycle(user);
// no do it again to check changing usernames
verifyFullUsernameLifecycle(user);
} finally {
Operations.deleteUser(user);
}
}
private static void verifyFullUsernameLifecycle(final TestUser user) throws BaseUsernameException {
final String preferred = "test";
final List<Username> candidates = Username.candidatesFrom(preferred, preferred.length(), preferred.length() + 1);
// reserve a username
final ReserveUsernameHashRequest reserveUsernameHashRequest = new ReserveUsernameHashRequest(
candidates.stream().map(Username::getHash).toList());
// try unauthorized
Operations
.apiPut("/v1/accounts/username_hash/reserve", reserveUsernameHashRequest)
.executeExpectStatusCode(HttpStatus.SC_UNAUTHORIZED);
final ReserveUsernameHashResponse reserveUsernameHashResponse = Operations
.apiPut("/v1/accounts/username_hash/reserve", reserveUsernameHashRequest)
.authorized(user)
.executeExpectSuccess(ReserveUsernameHashResponse.class);
// find which one is the reserved username
final byte[] reservedHash = reserveUsernameHashResponse.usernameHash();
final Username reservedUsername = candidates.stream()
.filter(u -> Arrays.equals(u.getHash(), reservedHash))
.findAny()
.orElseThrow();
// confirm a username
final ConfirmUsernameHashRequest confirmUsernameHashRequest = new ConfirmUsernameHashRequest(
reservedUsername.getHash(),
reservedUsername.generateProof(),
"cluck cluck i'm a parrot".getBytes()
);
// try unauthorized
Operations
.apiPut("/v1/accounts/username_hash/confirm", confirmUsernameHashRequest)
.executeExpectStatusCode(HttpStatus.SC_UNAUTHORIZED);
Operations
.apiPut("/v1/accounts/username_hash/confirm", confirmUsernameHashRequest)
.authorized(user)
.executeExpectSuccess(UsernameHashResponse.class);
// lookup username
final AccountIdentifierResponse accountIdentifierResponse = Operations
.apiGet("/v1/accounts/username_hash/" + Base64.getUrlEncoder().encodeToString(reservedHash))
.executeExpectSuccess(AccountIdentifierResponse.class);
assertEquals(new AciServiceIdentifier(user.aciUuid()), accountIdentifierResponse.uuid());
// try authorized
Operations
.apiGet("/v1/accounts/username_hash/" + Base64.getUrlEncoder().encodeToString(reservedHash))
.authorized(user)
.executeExpectStatusCode(HttpStatus.SC_BAD_REQUEST);
// delete username
Operations
.apiDelete("/v1/accounts/username_hash")
.authorized(user)
.executeExpectSuccess();
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.integration;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.List;
import org.apache.commons.lang3.tuple.Pair;
import org.junit.jupiter.api.Test;
import org.whispersystems.textsecuregcm.entities.IncomingMessage;
import org.whispersystems.textsecuregcm.entities.IncomingMessageList;
import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntityList;
import org.whispersystems.textsecuregcm.entities.SendMessageResponse;
import org.whispersystems.textsecuregcm.storage.Device;
public class MessagingTest {
@Test
public void testSendMessageUnsealed() {
final TestUser userA = Operations.newRegisteredUser("+19995550102");
final TestUser userB = Operations.newRegisteredUser("+19995550103");
try {
final byte[] expectedContent = "Hello, World!".getBytes(StandardCharsets.UTF_8);
final String contentBase64 = Base64.getEncoder().encodeToString(expectedContent);
final IncomingMessage message = new IncomingMessage(1, Device.PRIMARY_ID, userB.registrationId(), contentBase64);
final IncomingMessageList messages = new IncomingMessageList(List.of(message), false, true, System.currentTimeMillis());
final Pair<Integer, SendMessageResponse> sendMessage = Operations
.apiPut("/v1/messages/%s".formatted(userB.aciUuid().toString()), messages)
.authorized(userA)
.execute(SendMessageResponse.class);
final Pair<Integer, OutgoingMessageEntityList> receiveMessages = Operations.apiGet("/v1/messages")
.authorized(userB)
.execute(OutgoingMessageEntityList.class);
final byte[] actualContent = receiveMessages.getRight().messages().get(0).content();
assertArrayEquals(expectedContent, actualContent);
} finally {
Operations.deleteUser(userA);
Operations.deleteUser(userB);
}
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.integration;
import io.micrometer.common.util.StringUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.whispersystems.textsecuregcm.entities.CreateVerificationSessionRequest;
import org.whispersystems.textsecuregcm.entities.SubmitVerificationCodeRequest;
import org.whispersystems.textsecuregcm.entities.UpdateVerificationSessionRequest;
import org.whispersystems.textsecuregcm.entities.VerificationCodeRequest;
import org.whispersystems.textsecuregcm.entities.VerificationSessionResponse;
public class RegistrationTest {
@Test
public void testRegistration() throws Exception {
final UpdateVerificationSessionRequest originalRequest = new UpdateVerificationSessionRequest(
"test", UpdateVerificationSessionRequest.PushTokenType.FCM, null, null, null, null);
final Operations.PrescribedVerificationNumber params = Operations.prescribedVerificationNumber();
final CreateVerificationSessionRequest input = new CreateVerificationSessionRequest(params.number(),
originalRequest);
final VerificationSessionResponse verificationSessionResponse = Operations
.apiPost("/v1/verification/session", input)
.executeExpectSuccess(VerificationSessionResponse.class);
final String sessionId = verificationSessionResponse.id();
Assertions.assertTrue(StringUtils.isNotBlank(sessionId));
final String pushChallenge = Operations.peekVerificationSessionPushChallenge(sessionId);
// supply push challenge
final UpdateVerificationSessionRequest updatedRequest = new UpdateVerificationSessionRequest(
"test", UpdateVerificationSessionRequest.PushTokenType.FCM, pushChallenge, null, null, null);
final VerificationSessionResponse pushChallengeSupplied = Operations
.apiPatch("/v1/verification/session/%s".formatted(sessionId), updatedRequest)
.executeExpectSuccess(VerificationSessionResponse.class);
Assertions.assertTrue(pushChallengeSupplied.allowedToRequestCode());
// request code
final VerificationCodeRequest verificationCodeRequest = new VerificationCodeRequest(
VerificationCodeRequest.Transport.SMS, "android-ng");
final VerificationSessionResponse codeRequested = Operations
.apiPost("/v1/verification/session/%s/code".formatted(sessionId), verificationCodeRequest)
.executeExpectSuccess(VerificationSessionResponse.class);
// verify code
final SubmitVerificationCodeRequest submitVerificationCodeRequest = new SubmitVerificationCodeRequest(
params.verificationCode());
final VerificationSessionResponse codeVerified = Operations
.apiPut("/v1/verification/session/%s/code".formatted(sessionId), submitVerificationCodeRequest)
.executeExpectSuccess(VerificationSessionResponse.class);
}
}

218
mvnw vendored
View File

@@ -19,7 +19,7 @@
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven Start Up Batch script
# Apache Maven Wrapper startup batch script, version 3.2.0
#
# Required ENV vars:
# ------------------
@@ -27,7 +27,6 @@
#
# 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
@@ -54,7 +53,7 @@ fi
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
case "$(uname)" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
@@ -62,9 +61,9 @@ case "`uname`" in
# 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`"
JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
else
export JAVA_HOME="/Library/Java/Home"
JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
fi
fi
;;
@@ -72,68 +71,38 @@ esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
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"`
JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$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)`"
[ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
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
readLink=$(which readlink)
if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
javaHome="$(dirname "\"$javaExecutable\"")"
javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
javaExecutable="$(readlink -f "\"$javaExecutable\"")"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
javaHome="$(dirname "\"$javaExecutable\"")"
javaHome=$(expr "$javaHome" : '\(.*\)/bin')
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
@@ -149,7 +118,7 @@ if [ -z "$JAVACMD" ] ; then
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`\\unset -f command; \\command -v java`"
JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
fi
fi
@@ -163,12 +132,9 @@ 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"
@@ -184,96 +150,99 @@ find_maven_basedir() {
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
wdir=$(cd "$wdir/.." || exit 1; pwd)
fi
# end of workaround
done
echo "${basedir}"
printf '%s' "$(cd "$basedir" || exit 1; pwd)"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
# Remove \r in case we run on Windows within Git Bash
# and check out the repository with auto CRLF management
# enabled. Otherwise, we may read lines that are delimited with
# \r\n and produce $'-Xarg\r' rather than -Xarg due to word
# splitting rules.
tr -s '\r\n' ' ' < "$1"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
log() {
if [ "$MVNW_VERBOSE" = true ]; then
printf '%s\n' "$1"
fi
}
BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
log "$MAVEN_PROJECTBASEDIR"
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
if [ -r "$wrapperJarPath" ]; then
log "Found $wrapperJarPath"
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
log "Couldn't find $wrapperJarPath, downloading it ..."
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.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"
wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
while IFS="=" read -r key value; do
# Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
safeValue=$(echo "$value" | tr -d '\r')
case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
esac
done < "$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"
done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
log "Downloading from: $wrapperUrl"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
log "Found wget ... using wget"
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
log "Found curl ... using curl"
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
log "Falling back to using Java to download"
javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
javaSource=$(cygpath --path --windows "$javaSource")
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")
if [ -e "$javaSource" ]; then
if [ ! -e "$javaClass" ]; then
log " - Compiling MavenWrapperDownloader.java ..."
("$JAVA_HOME/bin/javac" "$javaSource")
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")
if [ -e "$javaClass" ]; then
log " - Running MavenWrapperDownloader.java ..."
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
fi
fi
fi
@@ -282,35 +251,58 @@ fi
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
# If specified, validate the SHA-256 sum of the Maven wrapper jar file
wrapperSha256Sum=""
while IFS="=" read -r key value; do
case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
esac
done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
if [ -n "$wrapperSha256Sum" ]; then
wrapperSha256Result=false
if command -v sha256sum > /dev/null; then
if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
wrapperSha256Result=true
fi
elif command -v shasum > /dev/null; then
if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
wrapperSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
exit 1
fi
if [ $wrapperSha256Result = false ]; then
echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
exit 1
fi
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$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 $@"
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
# shellcheck disable=SC2086 # safe args
exec "$JAVACMD" \
$MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" \
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

31
mvnw.cmd vendored
View File

@@ -18,13 +18,12 @@
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven Start Up Batch script
@REM Apache Maven Wrapper startup batch script, version 3.2.0
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM 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
@@ -120,10 +119,10 @@ 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"
set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@@ -134,11 +133,11 @@ if exist %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"
SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
echo Downloading from: %WRAPPER_URL%
)
powershell -Command "&{"^
@@ -146,7 +145,7 @@ if exist %WRAPPER_JAR% (
"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%')"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
@@ -154,6 +153,24 @@ if exist %WRAPPER_JAR% (
)
@REM End of extension
@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
SET WRAPPER_SHA_256_SUM=""
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
)
IF NOT %WRAPPER_SHA_256_SUM%=="" (
powershell -Command "&{"^
"$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
"If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
" Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
" Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
" Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
" exit 1;"^
"}"^
"}"
if ERRORLEVEL 1 goto error
)
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*

244
pom.xml
View File

@@ -14,11 +14,6 @@
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>dynamodb-local-oregon</id>
<name>DynamoDB Local Release Repository</name>
<url>https://s3-us-west-2.amazonaws.com/dynamodb-local/release</url>
</repository>
</repositories>
<pluginRepositories>
@@ -35,43 +30,51 @@
</pluginRepositories>
<modules>
<module>event-logger</module>
<module>redis-dispatch</module>
<module>websocket-resources</module>
<module>api-doc</module>
<module>integration-tests</module>
<module>service</module>
<module>websocket-resources</module>
</modules>
<properties>
<aws.sdk.version>1.12.287</aws.sdk.version>
<aws.sdk2.version>2.17.258</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-io.version>2.9.0</commons-io.version>
<dropwizard.version>2.0.32</dropwizard.version>
<aws.sdk2.version>2.23.8</aws.sdk2.version>
<braintree.version>3.34.0</braintree.version>
<commons-csv.version>1.11.0</commons-csv.version>
<commons-io.version>2.16.1</commons-io.version>
<dropwizard.version>3.0.7</dropwizard.version>
<dropwizard-metrics-datadog.version>1.1.13</dropwizard-metrics-datadog.version>
<grpc.version>1.49.2</grpc.version>
<gson.version>2.9.0</gson.version>
<guava.version>30.1.1-jre</guava.version>
<jackson.version>2.13.4</jackson.version>
<dynamodblocal.version>1.23.0</dynamodblocal.version>
<google-cloud-libraries.version>26.33.0</google-cloud-libraries.version>
<grpc.version>1.61.1</grpc.version> <!-- should be kept in sync with the value from Google libraries-bom -->
<gson.version>2.11.0</gson.version>
<!-- several libraries (AWS, Google Cloud) use Apache http components transitively, and we need to align them -->
<httpcore.version>4.4.16</httpcore.version>
<httpclient.version>4.5.14</httpclient.version>
<jackson.version>2.17.2</jackson.version>
<jaxb.version>2.3.1</jaxb.version>
<jedis.version>2.9.0</jedis.version>
<kotlin.version>1.7.21</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.0.1</logstash.logback.version>
<micrometer.version>1.10.0</micrometer.version>
<mockito.version>4.7.0</mockito.version>
<netty.version>4.1.82.Final</netty.version>
<opentest4j.version>1.2.0</opentest4j.version>
<protobuf.version>3.21.7</protobuf.version>
<pushy.version>0.15.1</pushy.version>
<resilience4j.version>1.7.0</resilience4j.version>
<junit-pioneer.version>2.2.0</junit-pioneer.version>
<jsr305.version>3.0.2</jsr305.version>
<kotlin.version>1.9.24</kotlin.version>
<kotlinx-serialization.version>1.5.1</kotlinx-serialization.version>
<lettuce.version>6.3.2.RELEASE</lettuce.version>
<libphonenumber.version>8.13.40</libphonenumber.version>
<logstash.logback.version>7.3</logstash.logback.version>
<log4j-bom.version>2.23.1</log4j-bom.version>
<luajava.version>3.4.0</luajava.version>
<micrometer.version>1.13.2</micrometer.version>
<netty.version>4.1.111.Final</netty.version>
<protobuf.version>3.25.2</protobuf.version> <!-- should be kept in sync with the value from Google libraries-bom -->
<pushy.version>0.15.4</pushy.version>
<reactive.grpc.version>1.2.4</reactive.grpc.version>
<reactor-bom.version>2023.0.8</reactor-bom.version> <!-- 3.6.x, see https://github.com/reactor/reactor#bom-versioning-scheme -->
<resilience4j.version>2.2.0</resilience4j.version>
<semver4j.version>3.1.0</semver4j.version>
<slf4j.version>1.7.30</slf4j.version>
<stripe.version>21.2.0</stripe.version>
<vavr.version>0.10.4</vavr.version>
<slf4j.version>2.0.13</slf4j.version>
<stripe.version>23.10.0</stripe.version>
<swagger.version>2.2.22</swagger.version>
<!-- 21.0.4_7-jre-jammy (note: always use the multi-arch manifest *LIST* here) -->
<docker.image.sha256>870aae69d4521fdaf26e952f8026f75b37cb721e6302d4d4d7100f6b09823057</docker.image.sha256>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
@@ -96,13 +99,6 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-bom</artifactId>
<version>${grpc.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Needed for gRPC with Java 9+ -->
<dependency>
<groupId>org.apache.tomcat</groupId>
@@ -117,13 +113,6 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-bom</artifactId>
<version>${aws.sdk.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
@@ -134,10 +123,15 @@
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>libraries-bom</artifactId>
<version>26.1.0</version>
<version>${google-cloud-libraries.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.salesforce.servicelibs</groupId>
<artifactId>reactor-grpc-stub</artifactId>
<version>${reactive.grpc.version}</version>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-bom</artifactId>
@@ -155,7 +149,7 @@
<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 -->
<version>${reactor-bom.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -176,11 +170,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>
@@ -196,11 +185,6 @@
<artifactId>semver4j</artifactId>
<version>${semver4j.version}</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>${commons-codec.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
@@ -211,11 +195,6 @@
<artifactId>lettuce-core</artifactId>
<version>${lettuce.version}</version>
</dependency>
<dependency>
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
<version>${vavr.version}</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
@@ -242,24 +221,6 @@
<version>${jaxb.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.opentest4j</groupId>
<artifactId>opentest4j</artifactId>
<version>${opentest4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
@@ -271,11 +232,6 @@
<version>${slf4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
@@ -284,7 +240,7 @@
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.2</version>
<version>9.5</version>
<scope>test</scope>
</dependency>
<dependency>
@@ -297,6 +253,11 @@
<artifactId>braintree-java</artifactId>
<version>${braintree.version}</version>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>${jsr305.version}</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
@@ -305,21 +266,42 @@
<dependency>
<groupId>org.signal</groupId>
<artifactId>embedded-redis</artifactId>
<version>0.8.3</version>
<version>0.9.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.signal</groupId>
<artifactId>libsignal-server</artifactId>
<version>0.21.1</version>
<version>0.54.2</version>
</dependency>
<dependency>
<groupId>org.signal.forks</groupId>
<artifactId>noise-java</artifactId>
<version>0.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-bom</artifactId>
<version>2.17.1</version>
<version>${log4j-bom.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>${httpcore.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>DynamoDBLocal</artifactId>
<version>${dynamodblocal.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
@@ -331,25 +313,19 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8</artifactId>
<version>2.34.0</version>
<groupId>software.amazon.awssdk</groupId>
<artifactId>aws-crt-client</artifactId>
</dependency>
<dependency>
<groupId>org.wiremock</groupId>
<!-- use standalone until Dropwizard 4 + jakarta.* -->
<artifactId>wiremock-standalone</artifactId>
<version>3.3.1</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
<exclusion>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
@@ -362,27 +338,33 @@
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit-pioneer</groupId>
<artifactId>junit-pioneer</artifactId>
<version>${junit-pioneer.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>include-abusive-message-filter</id>
<id>include-spam-filter</id>
<activation>
<file>
<exists>abusive-message-filter/pom.xml</exists>
<exists>spam-filter/pom.xml</exists>
</file>
</activation>
<modules>
<module>abusive-message-filter</module>
<module>spam-filter</module>
</modules>
</profile>
<profile>
<id>exclude-abusive-message-filter</id>
<id>exclude-spam-filter</id>
<activation>
<file>
<missing>abusive-message-filter/pom.xml</missing>
<missing>spam-filter/pom.xml</missing>
</file>
</activation>
</profile>
@@ -396,6 +378,15 @@
<version>1.7.0</version>
</extension>
</extensions>
<pluginManagement>
<plugins>
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.4.3</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
@@ -404,9 +395,19 @@
<version>0.6.1</version>
<configuration>
<checkStaleness>false</checkStaleness>
<protocArtifact>com.google.protobuf:protoc:3.21.1:exe:${os.detected.classifier}</protocArtifact>
<protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
<protocPlugins>
<protocPlugin>
<id>reactor-grpc</id>
<groupId>com.salesforce.servicelibs</groupId>
<artifactId>reactor-grpc</artifactId>
<version>${reactive.grpc.version}</version>
<mainClass>com.salesforce.reactorgrpc.ReactorGrpcGenerator</mainClass>
</protocPlugin>
</protocPlugins>
</configuration>
<executions>
<execution>
@@ -414,6 +415,7 @@
<goal>compile</goal>
<goal>compile-custom</goal>
<goal>test-compile</goal>
<goal>test-compile-custom</goal>
</goals>
</execution>
</executions>
@@ -422,16 +424,16 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<version>3.13.0</version>
<configuration>
<release>17</release>
<release>21</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<version>3.4.2</version>
<configuration>
<archive>
<manifest>
@@ -444,7 +446,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.1.2</version>
<version>3.7.1</version>
<executions>
<execution>
<id>copy</id>
@@ -464,7 +466,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<version>3.3.0</version>
<configuration>
<systemProperties>
<property>
@@ -478,7 +480,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.0.0-M3</version>
<version>3.5.0</version>
<executions>
<execution>
<goals>
@@ -499,7 +501,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>3.0.0-M1</version>
<version>3.1.2</version>
<configuration>
<skip>true</skip>
</configuration>
@@ -508,7 +510,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>3.0.0-M1</version>
<version>3.1.2</version>
<configuration>
<skip>true</skip>
</configuration>

View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>TextSecureServer</artifactId>
<groupId>org.whispersystems.textsecure</groupId>
<version>JGITVER</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>redis-dispatch</artifactId>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,11 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch;
public interface DispatchChannel {
void onDispatchMessage(String channel, byte[] message);
void onDispatchSubscribed(String channel);
void onDispatchUnsubscribed(String channel);
}

View File

@@ -1,157 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.dispatch.io.RedisPubSubConnectionFactory;
import org.whispersystems.dispatch.redis.PubSubConnection;
import org.whispersystems.dispatch.redis.PubSubReply;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public class DispatchManager extends Thread {
private final Logger logger = LoggerFactory.getLogger(DispatchManager.class);
private final Executor executor = Executors.newCachedThreadPool();
private final Map<String, DispatchChannel> subscriptions = new ConcurrentHashMap<>();
private final Optional<DispatchChannel> deadLetterChannel;
private final RedisPubSubConnectionFactory redisPubSubConnectionFactory;
private PubSubConnection pubSubConnection;
private volatile boolean running;
public DispatchManager(RedisPubSubConnectionFactory redisPubSubConnectionFactory,
Optional<DispatchChannel> deadLetterChannel)
{
this.redisPubSubConnectionFactory = redisPubSubConnectionFactory;
this.deadLetterChannel = deadLetterChannel;
}
@Override
public void start() {
this.pubSubConnection = redisPubSubConnectionFactory.connect();
this.running = true;
super.start();
}
public void shutdown() {
this.running = false;
this.pubSubConnection.close();
}
public synchronized void subscribe(String name, DispatchChannel dispatchChannel) {
Optional<DispatchChannel> previous = Optional.ofNullable(subscriptions.get(name));
subscriptions.put(name, dispatchChannel);
try {
pubSubConnection.subscribe(name);
} catch (IOException e) {
logger.warn("Subscription error", e);
}
previous.ifPresent(channel -> dispatchUnsubscription(name, channel));
}
public synchronized void unsubscribe(String name, DispatchChannel channel) {
Optional<DispatchChannel> subscription = Optional.ofNullable(subscriptions.get(name));
if (subscription.isPresent() && subscription.get() == channel) {
subscriptions.remove(name);
try {
pubSubConnection.unsubscribe(name);
} catch (IOException e) {
logger.warn("Unsubscribe error", e);
}
dispatchUnsubscription(name, subscription.get());
}
}
public boolean hasSubscription(String name) {
return subscriptions.containsKey(name);
}
@Override
public void run() {
while (running) {
try {
PubSubReply reply = pubSubConnection.read();
switch (reply.getType()) {
case UNSUBSCRIBE: break;
case SUBSCRIBE: dispatchSubscribe(reply); break;
case MESSAGE: dispatchMessage(reply); break;
default: throw new AssertionError("Unknown pubsub reply type! " + reply.getType());
}
} catch (IOException e) {
logger.warn("***** PubSub Connection Error *****", e);
if (running) {
this.pubSubConnection.close();
this.pubSubConnection = redisPubSubConnectionFactory.connect();
resubscribeAll();
}
}
}
logger.warn("DispatchManager Shutting Down...");
}
private void dispatchSubscribe(final PubSubReply reply) {
Optional<DispatchChannel> subscription = Optional.ofNullable(subscriptions.get(reply.getChannel()));
if (subscription.isPresent()) {
dispatchSubscription(reply.getChannel(), subscription.get());
} else {
logger.info("Received subscribe event for non-existing channel: " + reply.getChannel());
}
}
private void dispatchMessage(PubSubReply reply) {
Optional<DispatchChannel> subscription = Optional.ofNullable(subscriptions.get(reply.getChannel()));
if (subscription.isPresent()) {
dispatchMessage(reply.getChannel(), subscription.get(), reply.getContent().get());
} else if (deadLetterChannel.isPresent()) {
dispatchMessage(reply.getChannel(), deadLetterChannel.get(), reply.getContent().get());
} else {
logger.warn("Received message for non-existing channel, with no dead letter handler: " + reply.getChannel());
}
}
private void resubscribeAll() {
new Thread(() -> {
synchronized (DispatchManager.this) {
try {
for (String name : subscriptions.keySet()) {
pubSubConnection.subscribe(name);
}
} catch (IOException e) {
logger.warn("***** RESUBSCRIPTION ERROR *****", e);
}
}
}).start();
}
private void dispatchMessage(final String name, final DispatchChannel channel, final byte[] message) {
executor.execute(() -> channel.onDispatchMessage(name, message));
}
private void dispatchSubscription(final String name, final DispatchChannel channel) {
executor.execute(() -> channel.onDispatchSubscribed(name));
}
private void dispatchUnsubscription(final String name, final DispatchChannel channel) {
executor.execute(() -> channel.onDispatchUnsubscribed(name));
}
}

View File

@@ -1,68 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch.io;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class RedisInputStream {
private static final byte CR = 0x0D;
private static final byte LF = 0x0A;
private final InputStream inputStream;
public RedisInputStream(InputStream inputStream) {
this.inputStream = inputStream;
}
public String readLine() throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
boolean foundCr = false;
while (true) {
int character = inputStream.read();
if (character == -1) {
throw new IOException("Stream closed!");
}
baos.write(character);
if (foundCr && character == LF) break;
else if (character == CR) foundCr = true;
else if (foundCr) foundCr = false;
}
byte[] data = baos.toByteArray();
return new String(data, 0, data.length-2);
}
public byte[] readFully(int size) throws IOException {
byte[] result = new byte[size];
int offset = 0;
int remaining = result.length;
while (remaining > 0) {
int read = inputStream.read(result, offset, remaining);
if (read < 0) {
throw new IOException("Stream closed!");
}
offset += read;
remaining -= read;
}
return result;
}
public void close() throws IOException {
inputStream.close();
}
}

View File

@@ -1,13 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch.io;
import org.whispersystems.dispatch.redis.PubSubConnection;
public interface RedisPubSubConnectionFactory {
PubSubConnection connect();
}

View File

@@ -1,123 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch.redis;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.dispatch.io.RedisInputStream;
import org.whispersystems.dispatch.redis.protocol.ArrayReplyHeader;
import org.whispersystems.dispatch.redis.protocol.IntReply;
import org.whispersystems.dispatch.redis.protocol.StringReplyHeader;
import org.whispersystems.dispatch.util.Util;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
public class PubSubConnection {
private final Logger logger = LoggerFactory.getLogger(PubSubConnection.class);
private static final byte[] UNSUBSCRIBE_TYPE = {'u', 'n', 's', 'u', 'b', 's', 'c', 'r', 'i', 'b', 'e' };
private static final byte[] SUBSCRIBE_TYPE = {'s', 'u', 'b', 's', 'c', 'r', 'i', 'b', 'e' };
private static final byte[] MESSAGE_TYPE = {'m', 'e', 's', 's', 'a', 'g', 'e' };
private static final byte[] SUBSCRIBE_COMMAND = {'S', 'U', 'B', 'S', 'C', 'R', 'I', 'B', 'E', ' ' };
private static final byte[] UNSUBSCRIBE_COMMAND = {'U', 'N', 'S', 'U', 'B', 'S', 'C', 'R', 'I', 'B', 'E', ' '};
private static final byte[] CRLF = {'\r', '\n' };
private final OutputStream outputStream;
private final RedisInputStream inputStream;
private final Socket socket;
private final AtomicBoolean closed;
public PubSubConnection(Socket socket) throws IOException {
this.socket = socket;
this.outputStream = socket.getOutputStream();
this.inputStream = new RedisInputStream(new BufferedInputStream(socket.getInputStream()));
this.closed = new AtomicBoolean(false);
}
public void subscribe(String channelName) throws IOException {
if (closed.get()) throw new IOException("Connection closed!");
byte[] command = Util.combine(SUBSCRIBE_COMMAND, channelName.getBytes(), CRLF);
outputStream.write(command);
}
public void unsubscribe(String channelName) throws IOException {
if (closed.get()) throw new IOException("Connection closed!");
byte[] command = Util.combine(UNSUBSCRIBE_COMMAND, channelName.getBytes(), CRLF);
outputStream.write(command);
}
public PubSubReply read() throws IOException {
if (closed.get()) throw new IOException("Connection closed!");
ArrayReplyHeader replyHeader = new ArrayReplyHeader(inputStream.readLine());
if (replyHeader.getElementCount() != 3) {
throw new IOException("Received array reply header with strange count: " + replyHeader.getElementCount());
}
StringReplyHeader replyTypeHeader = new StringReplyHeader(inputStream.readLine());
byte[] replyType = inputStream.readFully(replyTypeHeader.getStringLength());
inputStream.readLine();
if (Arrays.equals(SUBSCRIBE_TYPE, replyType)) return readSubscribeReply();
else if (Arrays.equals(UNSUBSCRIBE_TYPE, replyType)) return readUnsubscribeReply();
else if (Arrays.equals(MESSAGE_TYPE, replyType)) return readMessageReply();
else throw new IOException("Unknown reply type: " + new String(replyType));
}
public void close() {
try {
this.closed.set(true);
this.inputStream.close();
this.outputStream.close();
this.socket.close();
} catch (IOException e) {
logger.warn("Exception while closing", e);
}
}
private PubSubReply readMessageReply() throws IOException {
StringReplyHeader channelNameHeader = new StringReplyHeader(inputStream.readLine());
byte[] channelName = inputStream.readFully(channelNameHeader.getStringLength());
inputStream.readLine();
StringReplyHeader messageHeader = new StringReplyHeader(inputStream.readLine());
byte[] message = inputStream.readFully(messageHeader.getStringLength());
inputStream.readLine();
return new PubSubReply(PubSubReply.Type.MESSAGE, new String(channelName), Optional.of(message));
}
private PubSubReply readUnsubscribeReply() throws IOException {
String channelName = readSubscriptionReply();
return new PubSubReply(PubSubReply.Type.UNSUBSCRIBE, channelName, Optional.empty());
}
private PubSubReply readSubscribeReply() throws IOException {
String channelName = readSubscriptionReply();
return new PubSubReply(PubSubReply.Type.SUBSCRIBE, channelName, Optional.empty());
}
private String readSubscriptionReply() throws IOException {
StringReplyHeader channelNameHeader = new StringReplyHeader(inputStream.readLine());
byte[] channelName = inputStream.readFully(channelNameHeader.getStringLength());
inputStream.readLine();
new IntReply(inputStream.readLine());
return new String(channelName);
}
}

View File

@@ -1,40 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch.redis;
import java.util.Optional;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public class PubSubReply {
public enum Type {
MESSAGE,
SUBSCRIBE,
UNSUBSCRIBE
}
private final Type type;
private final String channel;
private final Optional<byte[]> content;
public PubSubReply(Type type, String channel, Optional<byte[]> content) {
this.type = type;
this.channel = channel;
this.content = content;
}
public Type getType() {
return type;
}
public String getChannel() {
return channel;
}
public Optional<byte[]> getContent() {
return content;
}
}

View File

@@ -1,28 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch.redis.protocol;
import java.io.IOException;
public class ArrayReplyHeader {
private final int elementCount;
public ArrayReplyHeader(String header) throws IOException {
if (header == null || header.length() < 2 || header.charAt(0) != '*') {
throw new IOException("Invalid array reply header: " + header);
}
try {
this.elementCount = Integer.parseInt(header.substring(1));
} catch (NumberFormatException e) {
throw new IOException(e);
}
}
public int getElementCount() {
return elementCount;
}
}

View File

@@ -1,28 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch.redis.protocol;
import java.io.IOException;
public class IntReply {
private final int value;
public IntReply(String reply) throws IOException {
if (reply == null || reply.length() < 2 || reply.charAt(0) != ':') {
throw new IOException("Invalid int reply: " + reply);
}
try {
this.value = Integer.parseInt(reply.substring(1));
} catch (NumberFormatException e) {
throw new IOException(e);
}
}
public int getValue() {
return value;
}
}

View File

@@ -1,28 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch.redis.protocol;
import java.io.IOException;
public class StringReplyHeader {
private final int stringLength;
public StringReplyHeader(String header) throws IOException {
if (header == null || header.length() < 2 || header.charAt(0) != '$') {
throw new IOException("Invalid string reply header: " + header);
}
try {
this.stringLength = Integer.parseInt(header.substring(1));
} catch (NumberFormatException e) {
throw new IOException(e);
}
}
public int getStringLength() {
return stringLength;
}
}

View File

@@ -1,40 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class Util {
public static byte[] combine(byte[]... elements) {
try {
int sum = 0;
for (byte[] element : elements) {
sum += element.length;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream(sum);
for (byte[] element : elements) {
baos.write(element);
}
return baos.toByteArray();
} catch (IOException e) {
throw new AssertionError(e);
}
}
public static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new AssertionError(e);
}
}
}

View File

@@ -1,123 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch;
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;
private RedisPubSubConnectionFactory socketFactory;
private DispatchManager dispatchManager;
private PubSubReplyInputStream 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((Answer<PubSubReply>) invocationOnMock -> pubSubReplyInputStream.read());
dispatchManager = new DispatchManager(socketFactory, Optional.empty());
dispatchManager.start();
}
@AfterEach
void tearDown() {
dispatchManager.shutdown();
}
@Test
public void testConnect() {
verify(socketFactory).connect();
}
@Test
public void testSubscribe() {
DispatchChannel dispatchChannel = mock(DispatchChannel.class);
dispatchManager.subscribe("foo", dispatchChannel);
pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.SUBSCRIBE, "foo", Optional.empty()));
verify(dispatchChannel, timeout(1000)).onDispatchSubscribed(eq("foo"));
}
@Test
public void testSubscribeUnsubscribe() {
DispatchChannel dispatchChannel = mock(DispatchChannel.class);
dispatchManager.subscribe("foo", dispatchChannel);
dispatchManager.unsubscribe("foo", dispatchChannel);
pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.SUBSCRIBE, "foo", Optional.empty()));
pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.UNSUBSCRIBE, "foo", Optional.empty()));
verify(dispatchChannel, timeout(1000)).onDispatchUnsubscribed(eq("foo"));
}
@Test
public void testMessages() {
DispatchChannel fooChannel = mock(DispatchChannel.class);
DispatchChannel barChannel = mock(DispatchChannel.class);
dispatchManager.subscribe("foo", fooChannel);
dispatchManager.subscribe("bar", barChannel);
pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.SUBSCRIBE, "foo", Optional.empty()));
pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.SUBSCRIBE, "bar", Optional.empty()));
verify(fooChannel, timeout(1000)).onDispatchSubscribed(eq("foo"));
verify(barChannel, timeout(1000)).onDispatchSubscribed(eq("bar"));
pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.MESSAGE, "foo", Optional.of("hello".getBytes())));
pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.MESSAGE, "bar", Optional.of("there".getBytes())));
ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class);
verify(fooChannel, timeout(1000)).onDispatchMessage(eq("foo"), captor.capture());
assertArrayEquals("hello".getBytes(), captor.getValue());
verify(barChannel, timeout(1000)).onDispatchMessage(eq("bar"), captor.capture());
assertArrayEquals("there".getBytes(), captor.getValue());
}
private static class PubSubReplyInputStream {
private final List<PubSubReply> pubSubReplyList = new LinkedList<>();
public synchronized PubSubReply read() {
try {
while (pubSubReplyList.isEmpty()) wait();
return pubSubReplyList.remove(0);
} catch (InterruptedException e) {
throw new AssertionError(e);
}
}
public synchronized void write(PubSubReply pubSubReply) {
pubSubReplyList.add(pubSubReply);
notifyAll();
}
}
}

View File

@@ -1,264 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch.redis;
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;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.security.SecureRandom;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
class PubSubConnectionTest {
private static final String REPLY = "*3\r\n" +
"$9\r\n" +
"subscribe\r\n" +
"$5\r\n" +
"abcde\r\n" +
":1\r\n" +
"*3\r\n" +
"$9\r\n" +
"subscribe\r\n" +
"$5\r\n" +
"fghij\r\n" +
":2\r\n" +
"*3\r\n" +
"$9\r\n" +
"subscribe\r\n" +
"$5\r\n" +
"klmno\r\n" +
":2\r\n" +
"*3\r\n" +
"$7\r\n" +
"message\r\n" +
"$5\r\n" +
"abcde\r\n" +
"$10\r\n" +
"1234567890\r\n" +
"*3\r\n" +
"$7\r\n" +
"message\r\n" +
"$5\r\n" +
"klmno\r\n" +
"$10\r\n" +
"0987654321\r\n";
@Test
void testSubscribe() throws IOException {
OutputStream outputStream = mock(OutputStream.class);
Socket socket = mock(Socket.class );
when(socket.getOutputStream()).thenReturn(outputStream);
PubSubConnection connection = new PubSubConnection(socket);
connection.subscribe("foobar");
ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class);
verify(outputStream).write(captor.capture());
assertArrayEquals(captor.getValue(), "SUBSCRIBE foobar\r\n".getBytes());
}
@Test
void testUnsubscribe() throws IOException {
OutputStream outputStream = mock(OutputStream.class);
Socket socket = mock(Socket.class );
when(socket.getOutputStream()).thenReturn(outputStream);
PubSubConnection connection = new PubSubConnection(socket);
connection.unsubscribe("bazbar");
ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class);
verify(outputStream).write(captor.capture());
assertArrayEquals(captor.getValue(), "UNSUBSCRIBE bazbar\r\n".getBytes());
}
@Test
void testTricklyResponse() throws Exception {
InputStream inputStream = mockInputStreamFor(new TrickleInputStream(REPLY.getBytes()));
OutputStream outputStream = mock(OutputStream.class);
Socket socket = mock(Socket.class );
when(socket.getOutputStream()).thenReturn(outputStream);
when(socket.getInputStream()).thenReturn(inputStream);
PubSubConnection pubSubConnection = new PubSubConnection(socket);
readResponses(pubSubConnection);
}
@Test
void testFullResponse() throws Exception {
InputStream inputStream = mockInputStreamFor(new FullInputStream(REPLY.getBytes()));
OutputStream outputStream = mock(OutputStream.class);
Socket socket = mock(Socket.class );
when(socket.getOutputStream()).thenReturn(outputStream);
when(socket.getInputStream()).thenReturn(inputStream);
PubSubConnection pubSubConnection = new PubSubConnection(socket);
readResponses(pubSubConnection);
}
@Test
void testRandomLengthResponse() throws Exception {
InputStream inputStream = mockInputStreamFor(new RandomInputStream(REPLY.getBytes()));
OutputStream outputStream = mock(OutputStream.class);
Socket socket = mock(Socket.class );
when(socket.getOutputStream()).thenReturn(outputStream);
when(socket.getInputStream()).thenReturn(inputStream);
PubSubConnection pubSubConnection = new PubSubConnection(socket);
readResponses(pubSubConnection);
}
private InputStream mockInputStreamFor(final MockInputStream stub) throws IOException {
InputStream result = mock(InputStream.class);
when(result.read()).thenAnswer(new Answer<Integer>() {
@Override
public Integer answer(InvocationOnMock invocationOnMock) throws Throwable {
return stub.read();
}
});
when(result.read(any(byte[].class))).thenAnswer(new Answer<Integer>() {
@Override
public Integer answer(InvocationOnMock invocationOnMock) throws Throwable {
byte[] buffer = (byte[])invocationOnMock.getArguments()[0];
return stub.read(buffer, 0, buffer.length);
}
});
when(result.read(any(byte[].class), anyInt(), anyInt())).thenAnswer(new Answer<Integer>() {
@Override
public Integer answer(InvocationOnMock invocationOnMock) throws Throwable {
byte[] buffer = (byte[]) invocationOnMock.getArguments()[0];
int offset = (int) invocationOnMock.getArguments()[1];
int length = (int) invocationOnMock.getArguments()[2];
return stub.read(buffer, offset, length);
}
});
return result;
}
private void readResponses(PubSubConnection pubSubConnection) throws Exception {
PubSubReply reply = pubSubConnection.read();
assertEquals(reply.getType(), PubSubReply.Type.SUBSCRIBE);
assertEquals(reply.getChannel(), "abcde");
assertFalse(reply.getContent().isPresent());
reply = pubSubConnection.read();
assertEquals(reply.getType(), PubSubReply.Type.SUBSCRIBE);
assertEquals(reply.getChannel(), "fghij");
assertFalse(reply.getContent().isPresent());
reply = pubSubConnection.read();
assertEquals(reply.getType(), PubSubReply.Type.SUBSCRIBE);
assertEquals(reply.getChannel(), "klmno");
assertFalse(reply.getContent().isPresent());
reply = pubSubConnection.read();
assertEquals(reply.getType(), PubSubReply.Type.MESSAGE);
assertEquals(reply.getChannel(), "abcde");
assertArrayEquals(reply.getContent().get(), "1234567890".getBytes());
reply = pubSubConnection.read();
assertEquals(reply.getType(), PubSubReply.Type.MESSAGE);
assertEquals(reply.getChannel(), "klmno");
assertArrayEquals(reply.getContent().get(), "0987654321".getBytes());
}
private interface MockInputStream {
public int read();
public int read(byte[] input, int offset, int length);
}
private static class TrickleInputStream implements MockInputStream {
private final byte[] data;
private int index = 0;
private TrickleInputStream(byte[] data) {
this.data = data;
}
public int read() {
return data[index++];
}
public int read(byte[] input, int offset, int length) {
input[offset] = data[index++];
return 1;
}
}
private static class FullInputStream implements MockInputStream {
private final byte[] data;
private int index = 0;
private FullInputStream(byte[] data) {
this.data = data;
}
public int read() {
return data[index++];
}
public int read(byte[] input, int offset, int length) {
int amount = Math.min(data.length - index, length);
System.arraycopy(data, index, input, offset, amount);
index += length;
return amount;
}
}
private static class RandomInputStream implements MockInputStream {
private final byte[] data;
private int index = 0;
private RandomInputStream(byte[] data) {
this.data = data;
}
public int read() {
return data[index++];
}
public int read(byte[] input, int offset, int length) {
int maxCopy = Math.min(data.length - index, length);
int randomCopy = new SecureRandom().nextInt(maxCopy) + 1;
int copyAmount = Math.min(maxCopy, randomCopy);
System.arraycopy(data, index, input, offset, copyAmount);
index += copyAmount;
return copyAmount;
}
}
}

View File

@@ -1,54 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch.redis.protocol;
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;
class ArrayReplyHeaderTest {
@Test
void testNull() {
assertThrows(IOException.class, () -> new ArrayReplyHeader(null));
}
@Test
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

@@ -1,39 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch.redis.protocol;
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;
class IntReplyHeaderTest {
@Test
void testNull() {
assertThrows(IOException.class, () -> new IntReply(null));
}
@Test
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

@@ -1,35 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch.redis.protocol;
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;
class StringReplyHeaderTest {
@Test
void testNull() {
assertThrows(IOException.class, () -> new StringReplyHeader(null));
}
@Test
void testBadNumber() {
assertThrows(IOException.class, () -> new StringReplyHeader("$100A"));
}
@Test
void testBadPrefix() {
assertThrows(IOException.class, () -> new StringReplyHeader("*"));
}
@Test
void testValid() throws IOException {
assertEquals(1000, new StringReplyHeader("$1000").getStringLength());
}
}

View File

@@ -0,0 +1,102 @@
datadog.apiKey: unset
stripe.apiKey: unset
stripe.idempotencyKeyGenerator: abcdefg12345678= # base64 for creating request idempotency hash
braintree.privateKey: unset
directoryV2.client.userAuthenticationTokenSharedSecret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with CDS to generate auth tokens for Signal users
directoryV2.client.userIdTokenSharedSecret: bbcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with CDS to generate auth identity tokens for Signal users
svr2.userAuthenticationTokenSharedSecret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with SVR2 to generate auth tokens for Signal users
svr2.userIdTokenSharedSecret: bbcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with SVR2 to generate auth identity tokens for Signal users
svr3.userAuthenticationTokenSharedSecret: cbcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with SVR3 to generate auth tokens for Signal users
svr3.userIdTokenSharedSecret: dbcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with SVR3 to generate auth identity tokens for Signal users
tus.userAuthenticationTokenSharedSecret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG=
awsAttachments.accessKey: test
awsAttachments.accessSecret: test
gcpAttachments.rsaSigningKey: |
-----BEGIN PRIVATE KEY-----
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
AAAAAAAA
-----END PRIVATE KEY-----
apn.teamId: team-id
apn.keyId: key-id
apn.signingKey: |
-----BEGIN PRIVATE KEY-----
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
AAAAAAAA
-----END PRIVATE KEY-----
fcm.credentials: |
{ "json": true }
cdn.accessKey: test # AWS Access Key ID
cdn.accessSecret: test # AWS Access Secret
cdn3StorageManager.clientSecret: test
unidentifiedDelivery.certificate: ABCD1234
unidentifiedDelivery.privateKey: ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789AAAAAAA
hCaptcha.apiKey: unset
storageService.userAuthenticationTokenSharedSecret: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
zkConfig-libsignal-0.42.serverSecret: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdef
genericZkConfig.serverSecret: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzAA==
callingZkConfig.serverSecret: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzAA==
backupsZkConfig.serverSecret: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzAA==
paymentsService.userAuthenticationTokenSharedSecret: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= # base64-encoded 32-byte secret shared with MobileCoin services used to generate auth tokens for Signal users
paymentsService.fixerApiKey: unset
paymentsService.coinMarketCapApiKey: unset
artService.userAuthenticationTokenSharedSecret: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= # base64-encoded 32-byte secret not shared with any external service, but used in ArtController
artService.userAuthenticationTokenUserIdSecret: AAAAAAAAAAA= # base64-encoded secret to obscure user phone numbers from Sticker Creator
currentReportingKey.secret: AAAAAAAAAAA=
currentReportingKey.salt: AAAAAAAAAAA=
turn.secret: AAAAAAAAAAA=
turn.cloudflare.apiToken: ABCDEFGHIJKLM
linkDevice.secret: AAAAAAAAAAA=
tlsKeyStore.password: unset
noiseTunnel.tlsKeyStorePassword: ABCDEFGHIJKLMNOPQRSTUVWXYZ
noiseTunnel.noiseStaticPrivateKey: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
noiseTunnel.recognizedProxySecret: ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789AAAAAAA

View File

@@ -3,39 +3,79 @@
# `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
logging:
level: INFO
appenders:
- type: console
threshold: ALL
timeZone: UTC
target: stdout
- type: logstashtcpsocket
destination: example.com:10516
apiKey: secret://datadog.apiKey
environment: staging
metrics:
reporters:
- type: signal-datadog
frequency: 10 seconds
tags:
- "env:staging"
- "service:chat"
udpTransport:
statsdHost: localhost
port: 8125
excludesAttributes:
- m1_rate
- m5_rate
- m15_rate
- mean_rate
- stddev
useRegexFilters: true
excludes:
- ^.+\.total$
- ^.+\.request\.filtering$
- ^.+\.response\.filtering$
- ^executor\..+$
- ^lettuce\..+$
reportOnStop: true
tlsKeyStore:
password: secret://tlsKeyStore.password
stripe:
apiKey: unset
idempotencyKeyGenerator: abcdefg12345678= # base64 for creating request idempotency hash
apiKey: secret://stripe.apiKey
idempotencyKeyGenerator: secret://stripe.idempotencyKeyGenerator
boostDescription: >
Example
supportedCurrencies:
- xts
# - ...
# - Nth supported currency
supportedCurrenciesByPaymentMethod:
CARD:
- usd
- eur
SEPA_DEBIT:
- eur
braintree:
merchantId: unset
publicKey: unset
privateKey: unset
privateKey: secret://braintree.privateKey
environment: unset
graphqlUrl: unset
merchantAccounts:
# ISO 4217 currency code and its corresponding sub-merchant account
'xts': unset
supportedCurrencies:
- xts
# - ...
# - Nth supported currency
supportedCurrenciesByPaymentMethod:
PAYPAL:
- usd
pubSubPublisher:
project: example-project
topic: example-topic
credentialConfiguration: |
{
"credential": "configuration"
}
dynamoDbClientConfiguration:
dynamoDbClient:
region: us-west-2 # AWS Region
dynamoDbTables:
@@ -44,42 +84,59 @@ dynamoDbTables:
phoneNumberTableName: Example_Accounts_PhoneNumbers
phoneNumberIdentifierTableName: Example_Accounts_PhoneNumberIdentifiers
usernamesTableName: Example_Accounts_Usernames
scanPageSize: 100
backups:
tableName: Example_Backups
clientReleases:
tableName: Example_ClientReleases
deletedAccounts:
tableName: Example_DeletedAccounts
needsReconciliationIndexName: NeedsReconciliation
deletedAccountsLock:
tableName: Example_DeletedAccountsLock
issuedReceipts:
tableName: Example_IssuedReceipts
expiration: P30D # Duration of time until rows expire
generator: abcdefg12345678= # random base64-encoded binary sequence
keys:
ecKeys:
tableName: Example_Keys
ecSignedPreKeys:
tableName: Example_EC_Signed_Pre_Keys
pqKeys:
tableName: Example_PQ_Keys
pqLastResortKeys:
tableName: Example_PQ_Last_Resort_Keys
messages:
tableName: Example_Messages
expiration: P30D # Duration of time until rows expire
pendingAccounts:
tableName: Example_PendingAccounts
pendingDevices:
tableName: Example_PendingDevices
onetimeDonations:
tableName: Example_OnetimeDonations
expiration: P90D
phoneNumberIdentifiers:
tableName: Example_PhoneNumberIdentifiers
profiles:
tableName: Example_Profiles
pushChallenge:
tableName: Example_PushChallenge
pushNotificationExperimentSamples:
tableName: Example_PushNotificationExperimentSamples
redeemedReceipts:
tableName: Example_RedeemedReceipts
expiration: P30D # Duration of time until rows expire
registrationRecovery:
tableName: Example_RegistrationRecovery
expiration: P300D # Duration of time until rows expire
remoteConfig:
tableName: Example_RemoteConfig
reportMessage:
tableName: Example_ReportMessage
reservedUsernames:
tableName: Example_ReservedUsernames
scheduledJobs:
tableName: Example_ScheduledJobs
expiration: P7D
subscriptions:
tableName: Example_Subscriptions
clientPublicKeys:
tableName: Example_ClientPublicKeys
verificationSessions:
tableName: Example_VerificationSessions
cacheCluster: # Redis server configuration for cache cluster
configurationUri: redis://redis.example.com:6379/
@@ -87,10 +144,9 @@ cacheCluster: # Redis server configuration for cache cluster
clientPresenceCluster: # Redis server configuration for client presence cluster
configurationUri: redis://redis.example.com:6379/
pubsub: # Redis server configuration for pubsub cluster
url: redis://redis.example.com:6379/
replicaUrls:
- redis://redis.example.com:6379/
provisioning:
pubsub: # Redis server configuration for pubsub cluster
uri: redis://redis.example.com:6379/
pushSchedulerCluster: # Redis server configuration for push scheduler cluster
configurationUri: redis://redis.example.com:6379/
@@ -98,47 +154,67 @@ pushSchedulerCluster: # Redis server configuration for push scheduler cluster
rateLimitersCluster: # Redis server configuration for rate limiters cluster
configurationUri: redis://redis.example.com:6379/
directory:
client: # Configuration for interfacing with Contact Discovery Service cluster
userAuthenticationTokenSharedSecret: 00000f # hex-encoded secret shared with CDS used to generate auth tokens for Signal users
userAuthenticationTokenUserIdSecret: 00000f # hex-encoded secret shared among Signal-Servers to obscure user phone numbers from CDS
sqs:
accessKey: test # AWS SQS accessKey
accessSecret: test # AWS SQS accessSecret
queueUrls: # AWS SQS queue urls
- https://sqs.example.com/directory.fifo
server: # One or more CDS servers
- replicationName: example # CDS replication name
replicationUrl: cds.example.com # CDS replication endpoint base url
replicationPassword: example # CDS replication endpoint password
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
userAuthenticationTokenSharedSecret: secret://directoryV2.client.userAuthenticationTokenSharedSecret
userIdTokenSharedSecret: secret://directoryV2.client.userIdTokenSharedSecret
svr2:
uri: svr2.example.com
userAuthenticationTokenSharedSecret: secret://svr2.userAuthenticationTokenSharedSecret
userIdTokenSharedSecret: secret://svr2.userIdTokenSharedSecret
svrCaCertificates:
- |
-----BEGIN CERTIFICATE-----
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
AAAAAAAAAAAAAAAAAAAA
-----END CERTIFICATE-----
svr3:
uri: svr3.example.com
userAuthenticationTokenSharedSecret: secret://svr3.userAuthenticationTokenSharedSecret
userIdTokenSharedSecret: secret://svr3.userIdTokenSharedSecret
svrCaCertificates:
- |
-----BEGIN CERTIFICATE-----
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
AAAAAAAAAAAAAAAAAAAA
-----END CERTIFICATE-----
messageCache: # Redis server configuration for message store cache
persistDelayMinutes: 1
@@ -149,9 +225,10 @@ metricsCluster:
configurationUri: redis://redis.example.com:6379/
awsAttachments: # AWS S3 configuration
accessKey: test
accessSecret: test
bucket: aws-attachments
credentials:
accessKeyId: secret://awsAttachments.accessKey
secretAccessKey: secret://awsAttachments.accessSecret
region: us-west-2
gcpAttachments: # GCP Storage configuration
@@ -159,84 +236,55 @@ gcpAttachments: # GCP Storage configuration
email: user@example.cocm
maxSizeInBytes: 1024
pathPrefix:
rsaSigningKey: |
-----BEGIN PRIVATE KEY-----
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
AAAAAAAA
-----END PRIVATE KEY-----
rsaSigningKey: secret://gcpAttachments.rsaSigningKey
accountDatabaseCrawler:
chunkSize: 10 # accounts per run
chunkIntervalMs: 60000 # time per run
tus:
uploadUri: https://example.org/upload
userAuthenticationTokenSharedSecret: secret://tus.userAuthenticationTokenSharedSecret
apn: # Apple Push Notifications configuration
sandbox: true
bundleId: com.example.textsecuregcm
keyId: unset
teamId: unset
signingKey: |
-----BEGIN PRIVATE KEY-----
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
AAAAAAAA
-----END PRIVATE KEY-----
keyId: secret://apn.keyId
teamId: secret://apn.teamId
signingKey: secret://apn.signingKey
fcm: # FCM configuration
credentials: |
{ "json": true }
credentials: secret://fcm.credentials
cdn:
accessKey: test # AWS Access Key ID
accessSecret: test # AWS Access Secret
bucket: cdn # S3 Bucket name
credentials:
accessKeyId: secret://cdn.accessKey
secretAccessKey: secret://cdn.accessSecret
region: us-west-2 # AWS region
datadog:
apiKey: unset
cdn3StorageManager:
baseUri: https://storage-manager.example.com
clientId: example
clientSecret: secret://cdn3StorageManager.clientSecret
sourceSchemes:
2: gcs
3: r2
dogstatsd:
environment: dev
host: 127.0.0.1
unidentifiedDelivery:
certificate: ABCD1234
privateKey: ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789AAAAAAA
certificate: secret://unidentifiedDelivery.certificate
privateKey: secret://unidentifiedDelivery.privateKey
expiresDays: 7
voiceVerification:
url: https://cdn-ca.signal.org/verification/
locales:
- en
hCaptcha:
apiKey: secret://hCaptcha.apiKey
recaptcha:
projectPath: projects/example
credentialConfigurationJson: "{ }" # service account configuration for backend authentication
shortCode:
baseUrl: https://example.com/shortcodes/
storageService:
uri: storage.example.com
userAuthenticationTokenSharedSecret: 00000f
userAuthenticationTokenSharedSecret: secret://storageService.userAuthenticationTokenSharedSecret
storageCaCertificates:
- |
-----BEGIN CERTIFICATE-----
@@ -261,36 +309,15 @@ storageService:
AAAAAAAAAAAAAAAAAAAA
-----END CERTIFICATE-----
backupService:
uri: backup.example.com
userAuthenticationTokenSharedSecret: 00000f
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
serverSecret: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzAA==
serverPublic: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzAB==
serverSecret: secret://zkConfig-libsignal-0.42.serverSecret
callingZkConfig:
serverSecret: secret://callingZkConfig.serverSecret
backupsZkConfig:
serverSecret: secret://backupsZkConfig.serverSecret
appConfig:
application: example
@@ -298,23 +325,23 @@ appConfig:
configuration: example
remoteConfig:
authorizedTokens:
- # 1st authorized token
- # 2nd authorized token
- # ...
- # Nth authorized token
globalConfig: # keys and values that are given to clients on GET /v1/config
EXAMPLE_KEY: VALUE
paymentsService:
userAuthenticationTokenSharedSecret: 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
userAuthenticationTokenSharedSecret: secret://paymentsService.userAuthenticationTokenSharedSecret
paymentCurrencies:
# list of symbols for supported currencies
- MOB
externalClients:
fixerApiKey: secret://paymentsService.fixerApiKey
coinMarketCapApiKey: secret://paymentsService.coinMarketCapApiKey
coinMarketCapCurrencyIds:
MOB: 7878
artService:
userAuthenticationTokenSharedSecret: secret://artService.userAuthenticationTokenSharedSecret
userAuthenticationTokenUserIdSecret: secret://artService.userAuthenticationTokenUserIdSecret
badges:
badges:
@@ -337,7 +364,10 @@ badges:
'1': TEST
subscription: # configuration for Stripe subscriptions
badgeExpiration: P30D
badgeGracePeriod: P15D
backupExpiration: P30D
backupFreeTierMediaDuration: P30D
levels:
500:
badge: EXAMPLE
@@ -345,9 +375,12 @@ 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
oneTimeDonations:
sepaMaximumEuros: '10000'
boost:
level: 1
expiration: P90D
@@ -371,7 +404,12 @@ oneTimeDonations:
registrationService:
host: registration.example.com
apiKey: EXAMPLE
port: 443
credentialConfigurationJson: |
{
"example": "example"
}
identityTokenAudience: https://registration.example.com
registrationCaCertificate: | # Registration service TLS certificate trust root
-----BEGIN CERTIFICATE-----
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
@@ -394,3 +432,86 @@ registrationService:
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
AAAAAAAAAAAAAAAAAAAA
-----END CERTIFICATE-----
keyTransparencyService:
host: kt.example.com
port: 443
tlsCertificate: |
-----BEGIN CERTIFICATE-----
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
AAAAAAAAAAAAAAAAAAAA
-----END CERTIFICATE-----
turn:
secret: secret://turn.secret
cloudflare:
apiToken: secret://turn.cloudflare.apiToken
endpoint: https://rtc.live.cloudflare.com/v1/turn/keys/LMNOP/credentials/generate
urls:
- turn:turn.example.com:80
urlsWithIps:
- turn:%s
- turn:%s:80?transport=tcp
- turns:%s:443?transport=tcp
ttl: 86400
hostname: turn.cloudflare.example.com
linkDevice:
secret: secret://linkDevice.secret
maxmindCityDatabase:
s3Region: a-region
s3Bucket: a-bucket
objectKey: an-object.tar.gz
maxSize: 32777216
callingTurnDnsRecords:
s3Region: a-region
s3Bucket: a-bucket
objectKey: an-object.tar.gz
maxSize: 32777216
callingTurnPerformanceTable:
s3Region: a-region
s3Bucket: a-bucket
objectKey: an-object.tar.gz
maxSize: 32777216
callingTurnManualTable:
s3Region: a-region
s3Bucket: a-bucket
objectKey: an-object.tar.gz
maxSize: 32777216
noiseTunnel:
port: 8443
tlsKeyStoreFile: /path/to/file.p12
tlsKeyStoreEntryAlias: example.com
tlsKeyStorePassword: secret://noiseTunnel.tlsKeyStorePassword
noiseStaticPrivateKey: secret://noiseTunnel.noiseStaticPrivateKey
recognizedProxySecret: secret://noiseTunnel.recognizedProxySecret
externalRequestFilter:
grpcMethods:
- com.example.grpc.ExampleService/exampleMethod
paths:
- /example
permittedInternalRanges:
- 127.0.0.0/8

View File

@@ -10,7 +10,18 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>service</artifactId>
<properties>
<firebase-admin.version>9.2.0</firebase-admin.version>
<java-uuid-generator.version>5.1.0</java-uuid-generator.version>
<sqlite4java.version>1.0.392</sqlite4java.version>
</properties>
<dependencies>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-jaxrs2</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
@@ -24,16 +35,6 @@
<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>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.whispersystems.textsecure</groupId>
<artifactId>websocket-resources</artifactId>
@@ -44,6 +45,11 @@
<artifactId>libsignal-server</artifactId>
</dependency>
<dependency>
<groupId>org.signal.forks</groupId>
<artifactId>noise-java</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-core</artifactId>
@@ -58,7 +64,7 @@
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-db</artifactId>
<artifactId>dropwizard-http2</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
@@ -152,67 +158,85 @@
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-testing</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>party.iroiro.luajava</groupId>
<artifactId>luajava</artifactId>
<version>${luajava.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>party.iroiro.luajava</groupId>
<artifactId>lua51</artifactId>
<version>${luajava.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>party.iroiro.luajava</groupId>
<artifactId>lua51-platform</artifactId>
<version>${luajava.version}</version>
<classifier>natives-desktop</classifier>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-api</artifactId>
<artifactId>websocket-jetty-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlets</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-jetty-client</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.26.0</version>
</dependency>
<dependency>
<groupId>com.google.firebase</groupId>
<artifactId>firebase-admin</artifactId>
<version>9.0.0</version>
<groupId>com.maxmind.geoip2</groupId>
<artifactId>geoip2</artifactId>
<version>4.2.0</version>
</dependency>
<!-- firebase-admin has conflicting versions of these artifacts in its dependency tree; for firebase-admin
9.0.0, we'll need to depend directly on com.google.api-client:google-api-client:1.35.1 and
com.google.oauth-client:google-oauth-client:1.34.1 -->
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-pubsub</artifactId>
<exclusions>
<!-- Conflicts with our direct Guava dependency -->
<exclusion>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.oauth-client</groupId>
<artifactId>google-oauth-client</artifactId>
<groupId>com.google.guava</groupId>
<artifactId>failureaccess</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client</artifactId>
<version>1.35.1</version>
</dependency>
<dependency>
<groupId>com.google.oauth-client</groupId>
<artifactId>google-oauth-client</artifactId>
<version>1.34.1</version>
<groupId>com.google.firebase</groupId>
<artifactId>firebase-admin</artifactId>
<version>${firebase-admin.version}</version>
<exclusions>
<exclusion>
<!-- fix dependency convergence from older 1.0.1 -->
<groupId>com.google.guava</groupId>
<artifactId>failureaccess</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
@@ -235,8 +259,14 @@
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<scope>runtime</scope>
<artifactId>grpc-netty</artifactId>
<exclusions>
<!-- fix dependency convergence from older 0.26.0 -->
<exclusion>
<groupId>io.perfmark</groupId>
<artifactId>perfmark-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
@@ -259,7 +289,7 @@
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-datadog</artifactId>
<artifactId>micrometer-registry-statsd</artifactId>
</dependency>
<dependency>
<groupId>org.coursera</groupId>
@@ -290,6 +320,19 @@
<artifactId>jackson-jaxrs-json-provider</artifactId>
</dependency>
<dependency>
<groupId>com.salesforce.servicelibs</groupId>
<artifactId>reactor-grpc-stub</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>apache-client</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>netty-nio-client</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sts</artifactId>
@@ -298,10 +341,6 @@
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sqs</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>dynamodb</artifactId>
@@ -314,25 +353,10 @@
<groupId>software.amazon.awssdk</groupId>
<artifactId>appconfigdata</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-core</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>dynamodb-lock-client</artifactId>
<version>1.1.0</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
@@ -369,11 +393,24 @@
<artifactId>libphonenumber</artifactId>
</dependency>
<!-- Provides tools for mapping phone numbers to time zones, which is helpful for scheduling push notifications
during waking hours -->
<dependency>
<groupId>com.googlecode.libphonenumber</groupId>
<artifactId>geocoder</artifactId>
<version>2.234</version>
</dependency>
<dependency>
<groupId>net.sourceforge.argparse4j</groupId>
<artifactId>argparse4j</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec-haproxy</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.test-framework</groupId>
<artifactId>jersey-test-framework-core</artifactId>
@@ -389,22 +426,12 @@
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</exclusion>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.almworks.sqlite4java</groupId>
<artifactId>sqlite4java</artifactId>
<version>1.0.392</version>
<version>${sqlite4java.version}</version>
<scope>test</scope>
</dependency>
@@ -413,8 +440,8 @@
<artifactId>reactor-core</artifactId>
</dependency>
<dependency>
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core-micrometer</artifactId>
</dependency>
<dependency>
@@ -437,20 +464,26 @@
<dependency>
<groupId>com.fasterxml.uuid</groupId>
<artifactId>java-uuid-generator</artifactId>
<version>4.0.1</version>
<version>${java-uuid-generator.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>DynamoDBLocal</artifactId>
<version>1.19.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.github.ganadist.sqlite4java</groupId>
<artifactId>libsqlite4java-osx-aarch64</artifactId>
<version>${sqlite4java.version}</version>
<type>dylib</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-recaptchaenterprise</artifactId>
<groupId>com.google.auth</groupId>
<artifactId>google-auth-library-oauth2-http</artifactId>
</dependency>
<dependency>
@@ -466,20 +499,27 @@
<dependency>
<groupId>com.apollographql.apollo3</groupId>
<artifactId>apollo-api-jvm</artifactId>
<version>3.7.1</version>
<version>3.8.2</version>
<exclusions>
<exclusion>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<profiles>
<profile>
<id>exclude-abusive-message-filter</id>
<id>exclude-spam-filter</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<version>3.6.0</version>
<configuration>
<createDependencyReducedPom>true</createDependencyReducedPom>
<filters>
@@ -514,7 +554,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<version>3.7.1</version>
<configuration>
<descriptors>
<descriptor>assembly.xml</descriptor>
@@ -534,7 +574,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>properties-maven-plugin</artifactId>
<version>1.0.0</version>
<version>1.2.1</version>
<executions>
<execution>
<id>read-deploy-configuration</id>
@@ -550,24 +590,99 @@
</plugin>
<plugin>
<groupId>org.signal</groupId>
<artifactId>s3-upload-maven-plugin</artifactId>
<version>1.6-SNAPSHOT</version>
<configuration>
<source>${project.build.directory}/${project.build.finalName}-bin.tar.gz</source>
<bucketName>${deploy.bucketName}</bucketName>
<region>${deploy.bucketRegion}</region>
<destination>${project.build.finalName}-bin.tar.gz</destination>
</configuration>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<executions>
<execution>
<id>deploy-to-s3</id>
<phase>deploy</phase>
<goals>
<goal>s3-upload</goal>
<goal>build</goal>
</goals>
</execution>
</executions>
<configuration>
<from>
<image>eclipse-temurin@sha256:${docker.image.sha256}</image>
<platforms>
<platform>
<architecture>amd64</architecture>
<os>linux</os>
</platform>
<platform>
<architecture>arm64</architecture>
<os>linux</os>
</platform>
</platforms>
</from>
<to>
<image>${docker.repo}:${project.version}</image>
</to>
<container>
<mainClass>org.whispersystems.textsecuregcm.WhisperServerService</mainClass>
<jvmFlags>
<jvmFlag>-server</jvmFlag>
<jvmFlag>-Djava.awt.headless=true</jvmFlag>
<jvmFlag>-Djdk.nio.maxCachedBufferSize=262144</jvmFlag>
<jvmFlag>-Dlog4j2.formatMsgNoLookups=true</jvmFlag>
<jvmFlag>-XX:MaxRAMPercentage=75</jvmFlag>
<jvmFlag>-XX:+HeapDumpOnOutOfMemoryError</jvmFlag>
<jvmFlag>-XX:HeapDumpPath=/tmp/heapdump.bin</jvmFlag>
</jvmFlags>
<ports>
<port>8080</port>
</ports>
<creationTime>USE_CURRENT_TIMESTAMP</creationTime>
</container>
<extraDirectories>
<paths>
<path>
<from>${project.basedir}/config</from>
<includes>*.yml</includes>
<into>/usr/share/signal/</into>
</path>
</paths>
</extraDirectories>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>include-spam-filter</id>
<build>
<plugins>
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<configuration>
<!-- we don't want jib to execute on this module -->
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>test-server</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>start-test-server</id>
<phase>integration-test</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>org.whispersystems.textsecuregcm.LocalWhisperServerService</mainClass>
<classpathScope>test</classpathScope>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
@@ -580,7 +695,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>templating-maven-plugin</artifactId>
<version>1.0.0</version>
<version>3.0.0</version>
<executions>
<execution>
<id>filter-src</id>
@@ -591,6 +706,16 @@
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<!-- work around PATCH not being a supported method on HttpUrlConnection -->
<argLine>--add-opens=java.base/java.net=ALL-UNNAMED</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
@@ -606,7 +731,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.0.0</version>
<version>3.3.0</version>
<executions>
<execution>
<id>check-all-service-config</id>
@@ -614,15 +739,15 @@
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>org.whispersystems.textsecuregcm.CheckServiceConfigurations</mainClass>
<classpathScope>test</classpathScope>
<arguments>
<argument>${project.basedir}/config</argument>
</arguments>
</configuration>
</execution>
</executions>
<configuration>
<mainClass>org.whispersystems.textsecuregcm.CheckServiceConfigurations</mainClass>
<classpathScope>test</classpathScope>
<arguments>
<argument>${project.basedir}/config</argument>
</arguments>
</configuration>
</plugin>
<plugin>

View File

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

View File

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

View File

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

View File

@@ -1,53 +1,68 @@
/*
* 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.core.Configuration;
import java.time.Duration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.AbusiveMessageFilterConfiguration;
import org.whispersystems.textsecuregcm.configuration.AccountDatabaseCrawlerConfiguration;
import org.whispersystems.textsecuregcm.configuration.AdminEventLoggingConfiguration;
import org.whispersystems.textsecuregcm.attachments.TusConfiguration;
import org.whispersystems.textsecuregcm.configuration.ApnConfiguration;
import org.whispersystems.textsecuregcm.configuration.AppConfigConfiguration;
import org.whispersystems.textsecuregcm.configuration.ArtServiceConfiguration;
import org.whispersystems.textsecuregcm.configuration.AwsAttachmentsConfiguration;
import org.whispersystems.textsecuregcm.configuration.AwsCredentialsProviderFactory;
import org.whispersystems.textsecuregcm.configuration.BadgesConfiguration;
import org.whispersystems.textsecuregcm.configuration.BraintreeConfiguration;
import org.whispersystems.textsecuregcm.configuration.Cdn3StorageManagerConfiguration;
import org.whispersystems.textsecuregcm.configuration.CdnConfiguration;
import org.whispersystems.textsecuregcm.configuration.ClientCdnConfiguration;
import org.whispersystems.textsecuregcm.configuration.ClientReleaseConfiguration;
import org.whispersystems.textsecuregcm.configuration.DatadogConfiguration;
import org.whispersystems.textsecuregcm.configuration.DirectoryConfiguration;
import org.whispersystems.textsecuregcm.configuration.DefaultAwsCredentialsFactory;
import org.whispersystems.textsecuregcm.configuration.DirectoryV2Configuration;
import org.whispersystems.textsecuregcm.configuration.DynamoDbClientConfiguration;
import org.whispersystems.textsecuregcm.configuration.DogstatsdConfiguration;
import org.whispersystems.textsecuregcm.configuration.DynamicConfigurationManagerFactory;
import org.whispersystems.textsecuregcm.configuration.DynamoDbClientFactory;
import org.whispersystems.textsecuregcm.configuration.DynamoDbTables;
import org.whispersystems.textsecuregcm.configuration.ExternalRequestFilterConfiguration;
import org.whispersystems.textsecuregcm.configuration.FaultTolerantRedisClusterFactory;
import org.whispersystems.textsecuregcm.configuration.FcmConfiguration;
import org.whispersystems.textsecuregcm.configuration.GcpAttachmentsConfiguration;
import org.whispersystems.textsecuregcm.configuration.GenericZkConfig;
import org.whispersystems.textsecuregcm.configuration.HCaptchaClientFactory;
import org.whispersystems.textsecuregcm.configuration.KeyTransparencyServiceConfiguration;
import org.whispersystems.textsecuregcm.configuration.LinkDeviceSecretConfiguration;
import org.whispersystems.textsecuregcm.configuration.MaxDeviceConfiguration;
import org.whispersystems.textsecuregcm.configuration.MessageByteLimitCardinalityEstimatorConfiguration;
import org.whispersystems.textsecuregcm.configuration.MessageCacheConfiguration;
import org.whispersystems.textsecuregcm.configuration.NoiseWebSocketTunnelConfiguration;
import org.whispersystems.textsecuregcm.configuration.OneTimeDonationConfiguration;
import org.whispersystems.textsecuregcm.configuration.PaymentsServiceConfiguration;
import org.whispersystems.textsecuregcm.configuration.RateLimitsConfiguration;
import org.whispersystems.textsecuregcm.configuration.RecaptchaConfiguration;
import org.whispersystems.textsecuregcm.configuration.RedisClusterConfiguration;
import org.whispersystems.textsecuregcm.configuration.RedisConfiguration;
import org.whispersystems.textsecuregcm.configuration.RegistrationServiceConfiguration;
import org.whispersystems.textsecuregcm.configuration.ProvisioningConfiguration;
import org.whispersystems.textsecuregcm.configuration.RegistrationServiceClientFactory;
import org.whispersystems.textsecuregcm.configuration.RemoteConfigConfiguration;
import org.whispersystems.textsecuregcm.configuration.ReportMessageConfiguration;
import org.whispersystems.textsecuregcm.configuration.SecureBackupServiceConfiguration;
import org.whispersystems.textsecuregcm.configuration.S3ObjectMonitorFactory;
import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration;
import org.whispersystems.textsecuregcm.configuration.SecureValueRecovery2Configuration;
import org.whispersystems.textsecuregcm.configuration.SecureValueRecovery3Configuration;
import org.whispersystems.textsecuregcm.configuration.ShortCodeExpanderConfiguration;
import org.whispersystems.textsecuregcm.configuration.SpamFilterConfiguration;
import org.whispersystems.textsecuregcm.configuration.StripeConfiguration;
import org.whispersystems.textsecuregcm.configuration.SubscriptionConfiguration;
import org.whispersystems.textsecuregcm.configuration.TestDeviceConfiguration;
import org.whispersystems.textsecuregcm.configuration.TlsKeyStoreConfiguration;
import org.whispersystems.textsecuregcm.configuration.TurnConfiguration;
import org.whispersystems.textsecuregcm.configuration.UnidentifiedDeliveryConfiguration;
import org.whispersystems.textsecuregcm.configuration.UsernameConfiguration;
import org.whispersystems.textsecuregcm.configuration.VoiceVerificationConfiguration;
import org.whispersystems.textsecuregcm.configuration.VirtualThreadConfiguration;
import org.whispersystems.textsecuregcm.configuration.ZkConfig;
import org.whispersystems.textsecuregcm.limits.RateLimiterConfig;
import org.whispersystems.websocket.configuration.WebSocketConfiguration;
/** @noinspection MismatchedQueryAndUpdateOfCollection, WeakerAccess */
@@ -56,7 +71,12 @@ public class WhisperServerConfiguration extends Configuration {
@NotNull
@Valid
@JsonProperty
private AdminEventLoggingConfiguration adminEventLoggingConfiguration;
private TlsKeyStoreConfiguration tlsKeyStore;
@NotNull
@Valid
@JsonProperty
AwsCredentialsProviderFactory awsCredentialsProvider = new DefaultAwsCredentialsFactory();
@NotNull
@Valid
@@ -71,7 +91,7 @@ public class WhisperServerConfiguration extends Configuration {
@NotNull
@Valid
@JsonProperty
private DynamoDbClientConfiguration dynamoDbClientConfiguration;
private DynamoDbClientFactory dynamoDbClient;
@NotNull
@Valid
@@ -96,27 +116,27 @@ public class WhisperServerConfiguration extends Configuration {
@NotNull
@Valid
@JsonProperty
private DatadogConfiguration datadog;
private Cdn3StorageManagerConfiguration cdn3StorageManager;
@NotNull
@Valid
@JsonProperty
private RedisClusterConfiguration cacheCluster;
private DatadogConfiguration dogstatsd = new DogstatsdConfiguration();
@NotNull
@Valid
@JsonProperty
private RedisConfiguration pubsub;
private FaultTolerantRedisClusterFactory cacheCluster;
@NotNull
@Valid
@JsonProperty
private RedisClusterConfiguration metricsCluster;
private FaultTolerantRedisClusterFactory metricsCluster;
@NotNull
@Valid
@JsonProperty
private DirectoryConfiguration directory;
private ProvisioningConfiguration provisioning;
@NotNull
@Valid
@@ -126,17 +146,21 @@ public class WhisperServerConfiguration extends Configuration {
@NotNull
@Valid
@JsonProperty
private AccountDatabaseCrawlerConfiguration accountDatabaseCrawler;
private SecureValueRecovery2Configuration svr2;
@NotNull
@Valid
@JsonProperty
private SecureValueRecovery3Configuration svr3;
@NotNull
@Valid
@JsonProperty
private RedisClusterConfiguration pushSchedulerCluster;
private FaultTolerantRedisClusterFactory pushSchedulerCluster;
@NotNull
@Valid
@JsonProperty
private RedisClusterConfiguration rateLimitersCluster;
private FaultTolerantRedisClusterFactory rateLimitersCluster;
@NotNull
@Valid
@@ -146,12 +170,7 @@ public class WhisperServerConfiguration extends Configuration {
@NotNull
@Valid
@JsonProperty
private RedisClusterConfiguration clientPresenceCluster;
@Valid
@NotNull
@JsonProperty
private List<TestDeviceConfiguration> testDevices = new LinkedList<>();
private FaultTolerantRedisClusterFactory clientPresenceCluster;
@Valid
@NotNull
@@ -161,7 +180,7 @@ public class WhisperServerConfiguration extends Configuration {
@Valid
@NotNull
@JsonProperty
private RateLimitsConfiguration limits = new RateLimitsConfiguration();
private Map<String, RateLimiterConfig> limits = new HashMap<>();
@Valid
@NotNull
@@ -186,12 +205,12 @@ public class WhisperServerConfiguration extends Configuration {
@Valid
@NotNull
@JsonProperty
private VoiceVerificationConfiguration voiceVerification;
private HCaptchaClientFactory hCaptcha;
@Valid
@NotNull
@JsonProperty
private RecaptchaConfiguration recaptcha;
private ShortCodeExpanderConfiguration shortCode;
@Valid
@NotNull
@@ -201,18 +220,28 @@ public class WhisperServerConfiguration extends Configuration {
@Valid
@NotNull
@JsonProperty
private SecureBackupServiceConfiguration backupService;
private PaymentsServiceConfiguration paymentsService;
@Valid
@NotNull
@JsonProperty
private PaymentsServiceConfiguration paymentsService;
private ArtServiceConfiguration artService;
@Valid
@NotNull
@JsonProperty
private ZkConfig zkConfig;
@Valid
@NotNull
@JsonProperty
private GenericZkConfig callingZkConfig;
@Valid
@NotNull
@JsonProperty
private GenericZkConfig backupsZkConfig;
@Valid
@NotNull
@JsonProperty
@@ -221,7 +250,7 @@ public class WhisperServerConfiguration extends Configuration {
@Valid
@NotNull
@JsonProperty
private AppConfigConfiguration appConfig;
private DynamicConfigurationManagerFactory appConfig;
@Valid
@NotNull
@@ -244,21 +273,86 @@ public class WhisperServerConfiguration extends Configuration {
private ReportMessageConfiguration reportMessage = new ReportMessageConfiguration();
@Valid
@NotNull
@JsonProperty
private UsernameConfiguration username = new UsernameConfiguration();
@Valid
@JsonProperty
private AbusiveMessageFilterConfiguration abusiveMessageFilter;
private SpamFilterConfiguration spamFilter;
@Valid
@NotNull
@JsonProperty
private RegistrationServiceConfiguration registrationService;
private RegistrationServiceClientFactory registrationService;
public AdminEventLoggingConfiguration getAdminEventLoggingConfiguration() {
return adminEventLoggingConfiguration;
@Valid
@NotNull
@JsonProperty
private TurnConfiguration turn;
@Valid
@NotNull
@JsonProperty
private TusConfiguration tus;
@Valid
@NotNull
@JsonProperty
private ClientReleaseConfiguration clientRelease = new ClientReleaseConfiguration(Duration.ofHours(4));
@Valid
@NotNull
@JsonProperty
private MessageByteLimitCardinalityEstimatorConfiguration messageByteLimitCardinalityEstimator = new MessageByteLimitCardinalityEstimatorConfiguration(Duration.ofDays(1));
@Valid
@NotNull
@JsonProperty
private LinkDeviceSecretConfiguration linkDevice;
@Valid
@NotNull
@JsonProperty
private VirtualThreadConfiguration virtualThread = new VirtualThreadConfiguration(Duration.ofMillis(1));
@Valid
@NotNull
@JsonProperty
private S3ObjectMonitorFactory maxmindCityDatabase;
@Valid
@NotNull
@JsonProperty
private S3ObjectMonitorFactory callingTurnDnsRecords;
@Valid
@NotNull
@JsonProperty
private S3ObjectMonitorFactory callingTurnPerformanceTable;
@Valid
@NotNull
@JsonProperty
private S3ObjectMonitorFactory callingTurnManualTable;
@Valid
@NotNull
@JsonProperty
private NoiseWebSocketTunnelConfiguration noiseTunnel;
@Valid
@NotNull
@JsonProperty
private ExternalRequestFilterConfiguration externalRequestFilter;
@Valid
@NotNull
@JsonProperty
private KeyTransparencyServiceConfiguration keyTransparencyService;
public TlsKeyStoreConfiguration getTlsKeyStoreConfiguration() {
return tlsKeyStore;
}
public AwsCredentialsProviderFactory getAwsCredentialsConfiguration() {
return awsCredentialsProvider;
}
public StripeConfiguration getStripe() {
@@ -269,20 +363,20 @@ public class WhisperServerConfiguration extends Configuration {
return braintree;
}
public DynamoDbClientConfiguration getDynamoDbClientConfiguration() {
return dynamoDbClientConfiguration;
public DynamoDbClientFactory getDynamoDbClientConfiguration() {
return dynamoDbClient;
}
public DynamoDbTables getDynamoDbTables() {
return dynamoDbTables;
}
public RecaptchaConfiguration getRecaptchaConfiguration() {
return recaptcha;
public HCaptchaClientFactory getHCaptchaConfiguration() {
return hCaptcha;
}
public VoiceVerificationConfiguration getVoiceVerificationConfiguration() {
return voiceVerification;
public ShortCodeExpanderConfiguration getShortCodeRetrieverConfiguration() {
return shortCode;
}
public WebSocketConfiguration getWebSocketConfiguration() {
@@ -297,20 +391,24 @@ public class WhisperServerConfiguration extends Configuration {
return gcpAttachments;
}
public RedisClusterConfiguration getCacheClusterConfiguration() {
public FaultTolerantRedisClusterFactory getCacheClusterConfiguration() {
return cacheCluster;
}
public RedisConfiguration getPubsubCacheConfiguration() {
return pubsub;
public ProvisioningConfiguration getProvisioningConfiguration() {
return provisioning;
}
public RedisClusterConfiguration getMetricsClusterConfiguration() {
public FaultTolerantRedisClusterFactory getMetricsClusterConfiguration() {
return metricsCluster;
}
public DirectoryConfiguration getDirectoryConfiguration() {
return directory;
public SecureValueRecovery2Configuration getSvr2Configuration() {
return svr2;
}
public SecureValueRecovery3Configuration getSvr3Configuration() {
return svr3;
}
public DirectoryV2Configuration getDirectoryV2Configuration() {
@@ -321,27 +419,23 @@ public class WhisperServerConfiguration extends Configuration {
return storageService;
}
public AccountDatabaseCrawlerConfiguration getAccountDatabaseCrawlerConfiguration() {
return accountDatabaseCrawler;
}
public MessageCacheConfiguration getMessageCacheConfiguration() {
return messageCache;
}
public RedisClusterConfiguration getClientPresenceClusterConfiguration() {
public FaultTolerantRedisClusterFactory getClientPresenceClusterConfiguration() {
return clientPresenceCluster;
}
public RedisClusterConfiguration getPushSchedulerCluster() {
public FaultTolerantRedisClusterFactory getPushSchedulerCluster() {
return pushSchedulerCluster;
}
public RedisClusterConfiguration getRateLimitersCluster() {
public FaultTolerantRedisClusterFactory getRateLimitersCluster() {
return rateLimitersCluster;
}
public RateLimitsConfiguration getLimitsConfiguration() {
public Map<String, RateLimiterConfig> getLimitsConfiguration() {
return limits;
}
@@ -357,25 +451,18 @@ public class WhisperServerConfiguration extends Configuration {
return cdn;
}
public Cdn3StorageManagerConfiguration getCdn3StorageManagerConfiguration() {
return cdn3StorageManager;
}
public DatadogConfiguration getDatadogConfiguration() {
return datadog;
return dogstatsd;
}
public UnidentifiedDeliveryConfiguration getDeliveryCertificate() {
return unidentifiedDelivery;
}
public Map<String, Integer> getTestDevices() {
Map<String, Integer> results = new HashMap<>();
for (TestDeviceConfiguration testDeviceConfiguration : testDevices) {
results.put(testDeviceConfiguration.getNumber(),
testDeviceConfiguration.getCode());
}
return results;
}
public Map<String, Integer> getMaxDevices() {
Map<String, Integer> results = new HashMap<>();
@@ -387,23 +474,31 @@ public class WhisperServerConfiguration extends Configuration {
return results;
}
public SecureBackupServiceConfiguration getSecureBackupServiceConfiguration() {
return backupService;
}
public PaymentsServiceConfiguration getPaymentsServiceConfiguration() {
return paymentsService;
}
public ArtServiceConfiguration getArtServiceConfiguration() {
return artService;
}
public ZkConfig getZkConfig() {
return zkConfig;
}
public GenericZkConfig getCallingZkConfig() {
return callingZkConfig;
}
public GenericZkConfig getBackupsZkConfig() {
return backupsZkConfig;
}
public RemoteConfigConfiguration getRemoteConfigConfiguration() {
return remoteConfig;
}
public AppConfigConfiguration getAppConfig() {
public DynamicConfigurationManagerFactory getAppConfig() {
return appConfig;
}
@@ -423,15 +518,63 @@ public class WhisperServerConfiguration extends Configuration {
return reportMessage;
}
public AbusiveMessageFilterConfiguration getAbusiveMessageFilterConfiguration() {
return abusiveMessageFilter;
public SpamFilterConfiguration getSpamFilterConfiguration() {
return spamFilter;
}
public UsernameConfiguration getUsername() {
return username;
}
public RegistrationServiceConfiguration getRegistrationServiceConfiguration() {
public RegistrationServiceClientFactory getRegistrationServiceConfiguration() {
return registrationService;
}
public TurnConfiguration getTurnConfiguration() {
return turn;
}
public TusConfiguration getTus() {
return tus;
}
public ClientReleaseConfiguration getClientReleaseConfiguration() {
return clientRelease;
}
public MessageByteLimitCardinalityEstimatorConfiguration getMessageByteLimitCardinalityEstimator() {
return messageByteLimitCardinalityEstimator;
}
public LinkDeviceSecretConfiguration getLinkDeviceSecretConfiguration() {
return linkDevice;
}
public VirtualThreadConfiguration getVirtualThreadConfiguration() {
return virtualThread;
}
public S3ObjectMonitorFactory getMaxmindCityDatabase() {
return maxmindCityDatabase;
}
public S3ObjectMonitorFactory getCallingTurnDnsRecords() {
return callingTurnDnsRecords;
}
public S3ObjectMonitorFactory getCallingTurnPerformanceTable() {
return callingTurnPerformanceTable;
}
public S3ObjectMonitorFactory getCallingTurnManualTable() {
return callingTurnManualTable;
}
public NoiseWebSocketTunnelConfiguration getNoiseWebSocketTunnelConfiguration() {
return noiseTunnel;
}
public ExternalRequestFilterConfiguration getExternalRequestFilterConfiguration() {
return externalRequestFilter;
}
public KeyTransparencyServiceConfiguration getKeyTransparencyServiceConfiguration() {
return keyTransparencyService;
}
}

View File

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

View File

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

View File

@@ -1,24 +0,0 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.abuse;
import org.whispersystems.textsecuregcm.storage.Account;
import java.io.IOException;
public interface RateLimitChallengeListener {
void handleRateLimitChallengeAnswered(Account account);
/**
* Configures this rate limit challenge listener. This method will be called before the service begins processing any
* challenges.
*
* @param environmentName the name of the environment in which this listener is running (e.g. "staging" or "production")
* @throws IOException if the listener could not read its configuration source for any reason
*/
void configure(String environmentName) throws IOException;
}

View File

@@ -1,12 +0,0 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.abuse;
public enum RateLimitChallengeType {
PUSH_CHALLENGE,
RECAPTCHA
}

View File

@@ -0,0 +1,15 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.attachments;
import java.util.Map;
public interface AttachmentGenerator {
record Descriptor(Map<String, String> headers, String signedUploadLocation) {}
Descriptor generateAttachment(final String key);
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.attachments;
import org.whispersystems.textsecuregcm.gcp.CanonicalRequest;
import org.whispersystems.textsecuregcm.gcp.CanonicalRequestGenerator;
import org.whispersystems.textsecuregcm.gcp.CanonicalRequestSigner;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.spec.InvalidKeySpecException;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Map;
public class GcsAttachmentGenerator implements AttachmentGenerator {
@Nonnull
private final CanonicalRequestGenerator canonicalRequestGenerator;
@Nonnull
private final CanonicalRequestSigner canonicalRequestSigner;
public GcsAttachmentGenerator(@Nonnull String domain, @Nonnull String email,
int maxSizeInBytes, @Nonnull String pathPrefix, @Nonnull String rsaSigningKey)
throws IOException, InvalidKeyException, InvalidKeySpecException {
this.canonicalRequestGenerator = new CanonicalRequestGenerator(domain, email, maxSizeInBytes, pathPrefix);
this.canonicalRequestSigner = new CanonicalRequestSigner(rsaSigningKey);
}
@Override
public Descriptor generateAttachment(final String key) {
final ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);
final CanonicalRequest canonicalRequest = canonicalRequestGenerator.createFor(key, now);
return new Descriptor(getHeaderMap(canonicalRequest), getSignedUploadLocation(canonicalRequest));
}
private String getSignedUploadLocation(@Nonnull CanonicalRequest canonicalRequest) {
return "https://" + canonicalRequest.getDomain() + canonicalRequest.getResourcePath()
+ '?' + canonicalRequest.getCanonicalQuery()
+ "&X-Goog-Signature=" + canonicalRequestSigner.sign(canonicalRequest);
}
private static Map<String, String> getHeaderMap(@Nonnull CanonicalRequest canonicalRequest) {
return Map.of(
"host", canonicalRequest.getDomain(),
"x-goog-content-length-range", "1," + canonicalRequest.getMaxSizeInBytes(),
"x-goog-resumable", "start");
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.attachments;
import org.apache.http.HttpHeaders;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.util.HeaderUtils;
import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.util.Base64;
import java.util.Map;
public class TusAttachmentGenerator implements AttachmentGenerator {
private static final String ATTACHMENTS = "attachments";
final ExternalServiceCredentialsGenerator credentialsGenerator;
final String tusUri;
public TusAttachmentGenerator(final TusConfiguration cfg) {
this.tusUri = cfg.uploadUri();
this.credentialsGenerator = credentialsGenerator(Clock.systemUTC(), cfg);
}
private static ExternalServiceCredentialsGenerator credentialsGenerator(final Clock clock, final TusConfiguration cfg) {
return ExternalServiceCredentialsGenerator
.builder(cfg.userAuthenticationTokenSharedSecret())
.prependUsername(false)
.withClock(clock)
.build();
}
@Override
public Descriptor generateAttachment(final String key) {
final ExternalServiceCredentials credentials = credentialsGenerator.generateFor(ATTACHMENTS + "/" + key);
final String b64Key = Base64.getEncoder().encodeToString(key.getBytes(StandardCharsets.UTF_8));
final Map<String, String> headers = Map.of(
HttpHeaders.AUTHORIZATION, HeaderUtils.basicAuthHeader(credentials),
"Upload-Metadata", String.format("filename %s", b64Key)
);
return new Descriptor(headers, tusUri + "/" + ATTACHMENTS);
}
}

View File

@@ -0,0 +1,15 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.attachments;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
import org.whispersystems.textsecuregcm.util.ExactlySize;
import javax.validation.constraints.NotEmpty;
public record TusConfiguration(
@ExactlySize(32) SecretBytes userAuthenticationTokenSharedSecret,
@NotEmpty String uploadUri
){}

View File

@@ -1,25 +1,159 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import static com.codahale.metrics.MetricRegistry.name;
import com.google.common.annotations.VisibleForTesting;
import io.dropwizard.auth.Authenticator;
import io.dropwizard.auth.basic.BasicCredentials;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tags;
import java.time.Clock;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Optional;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.util.Pair;
import org.whispersystems.textsecuregcm.util.Util;
public class AccountAuthenticator extends BaseAccountAuthenticator implements
Authenticator<BasicCredentials, AuthenticatedAccount> {
public class AccountAuthenticator implements Authenticator<BasicCredentials, AuthenticatedDevice> {
private static final String LEGACY_NAME_PREFIX = "org.whispersystems.textsecuregcm.auth.BaseAccountAuthenticator";
private static final String AUTHENTICATION_COUNTER_NAME = name(LEGACY_NAME_PREFIX, "authentication");
private static final String AUTHENTICATION_SUCCEEDED_TAG_NAME = "succeeded";
private static final String AUTHENTICATION_FAILURE_REASON_TAG_NAME = "reason";
private static final String DAYS_SINCE_LAST_SEEN_DISTRIBUTION_NAME = name(LEGACY_NAME_PREFIX, "daysSinceLastSeen");
private static final String IS_PRIMARY_DEVICE_TAG = "isPrimary";
private static final Counter OLD_TOKEN_VERSION_COUNTER =
Metrics.counter(name(AccountAuthenticator.class, "oldTokenVersionCounter"));
@VisibleForTesting
static final char DEVICE_ID_SEPARATOR = '.';
private final AccountsManager accountsManager;
private final Clock clock;
public AccountAuthenticator(AccountsManager accountsManager) {
super(accountsManager);
this(accountsManager, Clock.systemUTC());
}
@VisibleForTesting
public AccountAuthenticator(AccountsManager accountsManager, Clock clock) {
this.accountsManager = accountsManager;
this.clock = clock;
}
static Pair<String, Byte> getIdentifierAndDeviceId(final String basicUsername) {
final String identifier;
final byte deviceId;
final int deviceIdSeparatorIndex = basicUsername.indexOf(DEVICE_ID_SEPARATOR);
if (deviceIdSeparatorIndex == -1) {
identifier = basicUsername;
deviceId = Device.PRIMARY_ID;
} else {
identifier = basicUsername.substring(0, deviceIdSeparatorIndex);
deviceId = Byte.parseByte(basicUsername.substring(deviceIdSeparatorIndex + 1));
}
return new Pair<>(identifier, deviceId);
}
@Override
public Optional<AuthenticatedAccount> authenticate(BasicCredentials basicCredentials) {
return super.authenticate(basicCredentials, true);
public Optional<AuthenticatedDevice> authenticate(BasicCredentials basicCredentials) {
boolean succeeded = false;
String failureReason = null;
try {
final UUID accountUuid;
final byte deviceId;
{
final Pair<String, Byte> identifierAndDeviceId = getIdentifierAndDeviceId(basicCredentials.getUsername());
accountUuid = UUID.fromString(identifierAndDeviceId.first());
deviceId = identifierAndDeviceId.second();
}
Optional<Account> account = accountsManager.getByAccountIdentifier(accountUuid);
if (account.isEmpty()) {
failureReason = "noSuchAccount";
return Optional.empty();
}
Optional<Device> device = account.get().getDevice(deviceId);
if (device.isEmpty()) {
failureReason = "noSuchDevice";
return Optional.empty();
}
SaltedTokenHash deviceSaltedTokenHash = device.get().getAuthTokenHash();
if (deviceSaltedTokenHash.verify(basicCredentials.getPassword())) {
succeeded = true;
Account authenticatedAccount = updateLastSeen(account.get(), device.get());
if (deviceSaltedTokenHash.getVersion() != SaltedTokenHash.CURRENT_VERSION) {
OLD_TOKEN_VERSION_COUNTER.increment();
authenticatedAccount = accountsManager.updateDeviceAuthentication(
authenticatedAccount,
device.get(),
SaltedTokenHash.generateFor(basicCredentials.getPassword())); // new credentials have current version
}
return Optional.of(new AuthenticatedDevice(authenticatedAccount, device.get()));
} else {
failureReason = "incorrectPassword";
return Optional.empty();
}
} catch (IllegalArgumentException | InvalidAuthorizationHeaderException iae) {
failureReason = "invalidHeader";
return Optional.empty();
} finally {
Tags tags = Tags.of(
AUTHENTICATION_SUCCEEDED_TAG_NAME, String.valueOf(succeeded));
if (StringUtils.isNotBlank(failureReason)) {
tags = tags.and(AUTHENTICATION_FAILURE_REASON_TAG_NAME, failureReason);
}
Metrics.counter(AUTHENTICATION_COUNTER_NAME, tags).increment();
}
}
@VisibleForTesting
public Account updateLastSeen(Account account, Device device) {
// compute a non-negative integer between 0 and 86400.
long n = Util.ensureNonNegativeLong(account.getUuid().getLeastSignificantBits());
final long lastSeenOffsetSeconds = n % ChronoUnit.DAYS.getDuration().toSeconds();
// produce a truncated timestamp which is either today at UTC midnight
// or yesterday at UTC midnight, based on per-user randomized offset used.
final long todayInMillisWithOffset = Util.todayInMillisGivenOffsetFromNow(clock,
Duration.ofSeconds(lastSeenOffsetSeconds).negated());
// only update the device's last seen time when it falls behind the truncated timestamp.
// this ensures a few things:
// (1) each account will only update last-seen at most once per day
// (2) these updates will occur throughout the day rather than all occurring at UTC midnight.
if (device.getLastSeen() < todayInMillisWithOffset) {
Metrics.summary(DAYS_SINCE_LAST_SEEN_DISTRIBUTION_NAME, IS_PRIMARY_DEVICE_TAG, String.valueOf(device.isPrimary()))
.record(Duration.ofMillis(todayInMillisWithOffset - device.getLastSeen()).toDays());
return accountsManager.updateDeviceLastSeen(account, device, Util.todayInMillis(clock));
}
return account;
}
}

View File

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

View File

@@ -0,0 +1,10 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import org.signal.libsignal.zkgroup.backups.BackupLevel;
public record AuthenticatedBackupUser(byte[] backupId, BackupLevel backupLevel, String backupDir, String mediaDir) {}

View File

@@ -6,28 +6,28 @@
package org.whispersystems.textsecuregcm.auth;
import java.security.Principal;
import java.util.function.Supplier;
import javax.security.auth.Subject;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.util.Pair;
public class AuthenticatedAccount implements Principal, AccountAndAuthenticatedDeviceHolder {
public class AuthenticatedDevice implements Principal, AccountAndAuthenticatedDeviceHolder {
private final Supplier<Pair<Account, Device>> accountAndDevice;
private final Account account;
private final Device device;
public AuthenticatedAccount(final Supplier<Pair<Account, Device>> accountAndDevice) {
this.accountAndDevice = accountAndDevice;
public AuthenticatedDevice(final Account account, final Device device) {
this.account = account;
this.device = device;
}
@Override
public Account getAccount() {
return accountAndDevice.get().first();
return account;
}
@Override
public Device getAuthenticatedDevice() {
return accountAndDevice.get().second();
return device;
}
// Principal implementation

View File

@@ -1,85 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.codec.binary.Hex;
import org.signal.libsignal.protocol.kdf.HKDF;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
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 = getV2HashedValue(salt, authenticationToken);
}
@VisibleForTesting
public AuthenticationCredentials v1ForTesting(String authenticationToken) {
String salt = String.valueOf(Math.abs(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() {
return hashedAuthenticationToken;
}
public String getSalt() {
return salt;
}
public boolean verify(String 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 getV1HashedValue(String salt, String token) {
try {
return new String(Hex.encodeHex(MessageDigest.getInstance("SHA1").digest((salt + token).getBytes(StandardCharsets.UTF_8))));
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
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

@@ -1,161 +0,0 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import static com.codahale.metrics.MetricRegistry.name;
import com.google.common.annotations.VisibleForTesting;
import io.dropwizard.auth.basic.BasicCredentials;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tags;
import java.time.Clock;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Optional;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.storage.RefreshingAccountAndDeviceSupplier;
import org.whispersystems.textsecuregcm.util.Pair;
import org.whispersystems.textsecuregcm.util.Util;
public class BaseAccountAuthenticator {
private static final String AUTHENTICATION_COUNTER_NAME = name(BaseAccountAuthenticator.class, "authentication");
private static final String AUTHENTICATION_SUCCEEDED_TAG_NAME = "succeeded";
private static final String AUTHENTICATION_FAILURE_REASON_TAG_NAME = "reason";
private static final String AUTHENTICATION_ENABLED_REQUIRED_TAG_NAME = "enabledRequired";
private static final String 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";
private final AccountsManager accountsManager;
private final Clock clock;
public BaseAccountAuthenticator(AccountsManager accountsManager) {
this(accountsManager, Clock.systemUTC());
}
@VisibleForTesting
public BaseAccountAuthenticator(AccountsManager accountsManager, Clock clock) {
this.accountsManager = accountsManager;
this.clock = clock;
}
static Pair<String, Long> getIdentifierAndDeviceId(final String basicUsername) {
final String identifier;
final long deviceId;
final int deviceIdSeparatorIndex = basicUsername.indexOf('.');
if (deviceIdSeparatorIndex == -1) {
identifier = basicUsername;
deviceId = Device.MASTER_ID;
} else {
identifier = basicUsername.substring(0, deviceIdSeparatorIndex);
deviceId = Long.parseLong(basicUsername.substring(deviceIdSeparatorIndex + 1));
}
return new Pair<>(identifier, deviceId);
}
public Optional<AuthenticatedAccount> authenticate(BasicCredentials basicCredentials, boolean enabledRequired) {
boolean succeeded = false;
String failureReason = null;
boolean hasStoryCapability = false;
try {
final UUID accountUuid;
final long deviceId;
{
final Pair<String, Long> identifierAndDeviceId = getIdentifierAndDeviceId(basicCredentials.getUsername());
accountUuid = UUID.fromString(identifierAndDeviceId.first());
deviceId = identifierAndDeviceId.second();
}
Optional<Account> account = accountsManager.getByAccountIdentifier(accountUuid);
if (account.isEmpty()) {
failureReason = "noSuchAccount";
return Optional.empty();
}
hasStoryCapability = account.map(Account::isStoriesSupported).orElse(false);
Optional<Device> device = account.get().getDevice(deviceId);
if (device.isEmpty()) {
failureReason = "noSuchDevice";
return Optional.empty();
}
if (enabledRequired) {
if (!device.get().isEnabled()) {
failureReason = "deviceDisabled";
return Optional.empty();
}
if (!account.get().isEnabled()) {
failureReason = "accountDisabled";
return Optional.empty();
}
}
AuthenticationCredentials deviceAuthenticationCredentials = device.get().getAuthenticationCredentials();
if (deviceAuthenticationCredentials.verify(basicCredentials.getPassword())) {
succeeded = true;
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)));
}
return Optional.empty();
} catch (IllegalArgumentException | InvalidAuthorizationHeaderException iae) {
failureReason = "invalidHeader";
return Optional.empty();
} finally {
Tags tags = Tags.of(
AUTHENTICATION_SUCCEEDED_TAG_NAME, String.valueOf(succeeded),
AUTHENTICATION_ENABLED_REQUIRED_TAG_NAME, String.valueOf(enabledRequired));
if (StringUtils.isNotBlank(failureReason)) {
tags = tags.and(AUTHENTICATION_FAILURE_REASON_TAG_NAME, failureReason);
}
Metrics.counter(AUTHENTICATION_COUNTER_NAME, tags).increment();
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();
final long todayInMillisWithOffset = Util.todayInMillisGivenOffsetFromNow(clock, Duration.ofSeconds(lastSeenOffsetSeconds).negated());
if (device.getLastSeen() < todayInMillisWithOffset) {
Metrics.summary(DAYS_SINCE_LAST_SEEN_DISTRIBUTION_NAME, IS_PRIMARY_DEVICE_TAG, String.valueOf(device.isMaster()))
.record(Duration.ofMillis(todayInMillisWithOffset - device.getLastSeen()).toDays());
return accountsManager.updateDeviceLastSeen(account, device, Util.todayInMillis(clock));
}
return account;
}
}

View File

@@ -11,10 +11,10 @@ import org.whispersystems.textsecuregcm.util.Pair;
public class BasicAuthorizationHeader {
private final String username;
private final long deviceId;
private final byte deviceId;
private final String password;
private BasicAuthorizationHeader(final String username, final long deviceId, final String password) {
private BasicAuthorizationHeader(final String username, final byte deviceId, final String password) {
this.username = username;
this.deviceId = deviceId;
this.password = password;
@@ -59,10 +59,10 @@ public class BasicAuthorizationHeader {
final String usernameComponent = credentials.substring(0, credentialSeparatorIndex);
final String username;
final long deviceId;
final byte deviceId;
{
final Pair<String, Long> identifierAndDeviceId =
BaseAccountAuthenticator.getIdentifierAndDeviceId(usernameComponent);
final Pair<String, Byte> identifierAndDeviceId =
AccountAuthenticator.getIdentifierAndDeviceId(usernameComponent);
username = identifierAndDeviceId.first();
deviceId = identifierAndDeviceId.second();

View File

@@ -8,12 +8,12 @@ package org.whispersystems.textsecuregcm.auth;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
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.identity.IdentityType;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Device;
@@ -33,11 +33,11 @@ public class CertificateGenerator {
public byte[] createFor(Account account, Device device, boolean includeE164) throws InvalidKeyException {
SenderCertificate.Certificate.Builder builder = SenderCertificate.Certificate.newBuilder()
.setSenderDevice(Math.toIntExact(device.getId()))
.setExpires(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(expiresDays))
.setIdentityKey(ByteString.copyFrom(Base64.getDecoder().decode(account.getIdentityKey())))
.setSigner(serverCertificate)
.setSenderUuid(account.getUuid().toString());
.setSenderDevice(Math.toIntExact(device.getId()))
.setExpires(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(expiresDays))
.setIdentityKey(ByteString.copyFrom(account.getIdentityKey(IdentityType.ACI).serialize()))
.setSigner(serverCertificate)
.setSenderUuid(account.getUuid().toString());
if (includeE164) {
builder.setSender(account.getNumber());

View File

@@ -16,5 +16,5 @@ import java.lang.annotation.Target;
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ChangesDeviceEnabledState {
public @interface ChangesLinkedDevices {
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Indicates that an endpoint changes the phone number and PNI keys associated with an account, and that
* any websockets associated with the account may need to be refreshed after a call to that endpoint.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ChangesPhoneNumber {
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.netty.resolver.dns.DnsNameResolver;
import java.io.IOException;
import java.net.Inet6Address;
import java.net.URI;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.List;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import javax.ws.rs.core.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
import org.whispersystems.textsecuregcm.configuration.RetryConfiguration;
import org.whispersystems.textsecuregcm.http.FaultTolerantHttpClient;
import org.whispersystems.textsecuregcm.util.ExceptionUtils;
import org.whispersystems.textsecuregcm.util.SystemMapper;
public class CloudflareTurnCredentialsManager {
private static final Logger logger = LoggerFactory.getLogger(CloudflareTurnCredentialsManager.class);
private final List<String> cloudflareTurnUrls;
private final List<String> cloudflareTurnUrlsWithIps;
private final String cloudflareTurnHostname;
private final HttpRequest request;
private final FaultTolerantHttpClient cloudflareTurnClient;
private final DnsNameResolver dnsNameResolver;
record CredentialRequest(long ttl) {}
record CloudflareTurnResponse(IceServer iceServers) {
record IceServer(
String username,
String credential,
List<String> urls) {
}
}
public CloudflareTurnCredentialsManager(final String cloudflareTurnApiToken,
final String cloudflareTurnEndpoint, final long cloudflareTurnTtl, final List<String> cloudflareTurnUrls,
final List<String> cloudflareTurnUrlsWithIps, final String cloudflareTurnHostname,
final CircuitBreakerConfiguration circuitBreaker, final ExecutorService executor, final RetryConfiguration retry,
final ScheduledExecutorService retryExecutor, final DnsNameResolver dnsNameResolver) {
this.cloudflareTurnClient = FaultTolerantHttpClient.newBuilder()
.withName("cloudflare-turn")
.withCircuitBreaker(circuitBreaker)
.withExecutor(executor)
.withRetry(retry)
.withRetryExecutor(retryExecutor)
.build();
this.cloudflareTurnUrls = cloudflareTurnUrls;
this.cloudflareTurnUrlsWithIps = cloudflareTurnUrlsWithIps;
this.cloudflareTurnHostname = cloudflareTurnHostname;
this.dnsNameResolver = dnsNameResolver;
try {
final String body = SystemMapper.jsonMapper().writeValueAsString(new CredentialRequest(cloudflareTurnTtl));
this.request = HttpRequest.newBuilder()
.uri(URI.create(cloudflareTurnEndpoint))
.header("Content-Type", "application/json")
.header("Authorization", String.format("Bearer %s", cloudflareTurnApiToken))
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
} catch (JsonProcessingException e) {
throw new IllegalArgumentException(e);
}
}
public TurnToken retrieveFromCloudflare() throws IOException {
final List<String> cloudflareTurnComposedUrls;
try {
cloudflareTurnComposedUrls = dnsNameResolver.resolveAll(cloudflareTurnHostname).get().stream()
.map(i -> switch (i) {
case Inet6Address i6 -> "[" + i6.getHostAddress() + "]";
default -> i.getHostAddress();
})
.flatMap(i -> cloudflareTurnUrlsWithIps.stream().map(u -> u.formatted(i)))
.toList();
} catch (Exception e) {
throw new IOException(e);
}
final HttpResponse<String> response;
try {
response = cloudflareTurnClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join();
} catch (CompletionException e) {
logger.warn("failed to make http request to Cloudflare Turn: {}", e.getMessage());
throw new IOException(ExceptionUtils.unwrap(e));
}
if (response.statusCode() != Response.Status.CREATED.getStatusCode()) {
logger.warn("failure request credentials from Cloudflare Turn (code={}): {}", response.statusCode(), response);
throw new IOException("Cloudflare Turn http failure : " + response.statusCode());
}
final CloudflareTurnResponse cloudflareTurnResponse = SystemMapper.jsonMapper()
.readValue(response.body(), CloudflareTurnResponse.class);
return new TurnToken(cloudflareTurnResponse.iceServers().username(),
cloudflareTurnResponse.iceServers().credential(),
cloudflareTurnUrls, cloudflareTurnComposedUrls, cloudflareTurnHostname);
}
}

View File

@@ -16,7 +16,7 @@ public class CombinedUnidentifiedSenderAccessKeys {
public CombinedUnidentifiedSenderAccessKeys(String header) {
try {
this.combinedUnidentifiedSenderAccessKeys = Base64.getDecoder().decode(header);
if (this.combinedUnidentifiedSenderAccessKeys == null || this.combinedUnidentifiedSenderAccessKeys.length != 16) {
if (this.combinedUnidentifiedSenderAccessKeys == null || this.combinedUnidentifiedSenderAccessKeys.length != UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH) {
throw new WebApplicationException("Invalid combined unidentified sender access keys", Status.UNAUTHORIZED);
}
} catch (IllegalArgumentException e) {

View File

@@ -7,15 +7,39 @@ package org.whispersystems.textsecuregcm.auth;
import org.glassfish.jersey.server.ContainerRequest;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Device;
import javax.ws.rs.core.SecurityContext;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
class ContainerRequestUtil {
static Optional<Account> getAuthenticatedAccount(final ContainerRequest request) {
/**
* A read-only subset of the authenticated Account object, to enforce that filter-based consumers do not perform
* account modifying operations.
*/
record AccountInfo(UUID accountId, String e164, Set<Byte> deviceIds) {
static AccountInfo fromAccount(final Account account) {
return new AccountInfo(
account.getUuid(),
account.getNumber(),
account.getDevices().stream().map(Device::getId).collect(Collectors.toSet()));
}
}
static Optional<AccountInfo> getAuthenticatedAccount(final ContainerRequest request) {
return Optional.ofNullable(request.getSecurityContext())
.map(SecurityContext::getUserPrincipal)
.map(principal -> principal instanceof AccountAndAuthenticatedDeviceHolder
? ((AccountAndAuthenticatedDeviceHolder) principal).getAccount() : null);
.map(principal -> {
if (principal instanceof AccountAndAuthenticatedDeviceHolder aaadh) {
return aaadh.getAccount();
}
return null;
})
.map(AccountInfo::fromAccount);
}
}

View File

@@ -1,26 +0,0 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import io.dropwizard.auth.Authenticator;
import io.dropwizard.auth.basic.BasicCredentials;
import java.util.Optional;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
public class DisabledPermittedAccountAuthenticator extends BaseAccountAuthenticator implements
Authenticator<BasicCredentials, DisabledPermittedAuthenticatedAccount> {
public DisabledPermittedAccountAuthenticator(AccountsManager accountsManager) {
super(accountsManager);
}
@Override
public Optional<DisabledPermittedAuthenticatedAccount> authenticate(BasicCredentials credentials) {
Optional<AuthenticatedAccount> account = super.authenticate(credentials, false);
return account.map(DisabledPermittedAuthenticatedAccount::new);
}
}

View File

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

View File

@@ -1,86 +0,0 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import com.google.common.annotations.VisibleForTesting;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Clock;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Hex;
import org.whispersystems.textsecuregcm.util.Util;
public class ExternalServiceCredentialGenerator {
private final byte[] key;
private final byte[] userIdKey;
private final boolean usernameDerivation;
private final boolean prependUsername;
private final Clock clock;
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey) {
this(key, userIdKey, true, true);
}
public ExternalServiceCredentialGenerator(byte[] key, boolean prependUsername) {
this(key, new byte[0], false, prependUsername);
}
@VisibleForTesting
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey, boolean usernameDerivation) {
this(key, userIdKey, usernameDerivation, true);
}
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey, boolean usernameDerivation,
boolean prependUsername) {
this(key, userIdKey, usernameDerivation, prependUsername, Clock.systemUTC());
}
@VisibleForTesting
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey, boolean usernameDerivation,
boolean prependUsername, Clock clock) {
this.key = key;
this.userIdKey = userIdKey;
this.usernameDerivation = usernameDerivation;
this.prependUsername = prependUsername;
this.clock = clock;
}
public ExternalServiceCredentials generateFor(String identity) {
Mac mac = getMacInstance();
String username = getUserId(identity, mac, usernameDerivation);
long currentTimeSeconds = clock.millis() / 1000;
String prefix = username + ":" + currentTimeSeconds;
String output = Hex.encodeHexString(Util.truncate(getHmac(key, prefix.getBytes(), mac), 10));
String token = (prependUsername ? prefix : currentTimeSeconds) + ":" + output;
return new ExternalServiceCredentials(username, token);
}
private String getUserId(String number, Mac mac, boolean usernameDerivation) {
if (usernameDerivation) return Hex.encodeHexString(Util.truncate(getHmac(userIdKey, number.getBytes(), mac), 10));
else return number;
}
private Mac getMacInstance() {
try {
return Mac.getInstance("HmacSHA256");
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
private byte[] getHmac(byte[] key, byte[] input, Mac mac) {
try {
mac.init(new SecretKeySpec(key, "HmacSHA256"));
return mac.doFinal(input);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
}

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

@@ -0,0 +1,293 @@
/*
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import static java.util.Objects.requireNonNull;
import static org.whispersystems.textsecuregcm.util.HmacUtils.hmac256ToHexString;
import static org.whispersystems.textsecuregcm.util.HmacUtils.hmac256TruncatedToHexString;
import static org.whispersystems.textsecuregcm.util.HmacUtils.hmacHexStringsEqual;
import com.google.common.annotations.VisibleForTesting;
import java.time.Clock;
import java.time.Instant;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Function;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
public class ExternalServiceCredentialsGenerator {
private static final String DELIMITER = ":";
private static final int TRUNCATED_SIGNATURE_LENGTH = 10;
private final byte[] key;
private final byte[] userDerivationKey;
private final boolean prependUsername;
private final boolean truncateSignature;
private final String usernameTimestampPrefix;
private final Function<Instant, Instant> usernameTimestampTruncator;
private final Clock clock;
private final int derivedUsernameTruncateLength;
public static ExternalServiceCredentialsGenerator.Builder builder(final SecretBytes key) {
return builder(key.value());
}
@VisibleForTesting
public static ExternalServiceCredentialsGenerator.Builder builder(final byte[] key) {
return new Builder(key);
}
private ExternalServiceCredentialsGenerator(
final byte[] key,
final byte[] userDerivationKey,
final boolean prependUsername,
final boolean truncateSignature,
final int derivedUsernameTruncateLength,
final String usernameTimestampPrefix,
final Function<Instant, Instant> usernameTimestampTruncator,
final Clock clock) {
this.key = requireNonNull(key);
this.userDerivationKey = requireNonNull(userDerivationKey);
this.prependUsername = prependUsername;
this.truncateSignature = truncateSignature;
this.usernameTimestampPrefix = usernameTimestampPrefix;
this.usernameTimestampTruncator = usernameTimestampTruncator;
this.clock = requireNonNull(clock);
this.derivedUsernameTruncateLength = derivedUsernameTruncateLength;
if (hasUsernameTimestampPrefix() ^ hasUsernameTimestampTruncator()) {
throw new RuntimeException("Configured to have only one of (usernameTimestampPrefix, usernameTimestampTruncator)");
}
}
/**
* A convenience method for the case of identity in the form of {@link UUID}.
* @param uuid identity to generate credentials for
* @return an instance of {@link ExternalServiceCredentials}
*/
public ExternalServiceCredentials generateForUuid(final UUID uuid) {
return generateFor(uuid.toString());
}
/**
* Generates `ExternalServiceCredentials` for the given identity following this generator's configuration.
* @param identity identity string to generate credentials for
* @return an instance of {@link ExternalServiceCredentials}
*/
public ExternalServiceCredentials generateFor(final String identity) {
if (usernameIsTimestamp()) {
throw new RuntimeException("Configured to use timestamp as username");
}
return generate(identity);
}
/**
* Generates `ExternalServiceCredentials` using a prefix concatenated with a truncated timestamp as the username, following this generator's configuration.
* @return an instance of {@link ExternalServiceCredentials}
*/
public ExternalServiceCredentials generateWithTimestampAsUsername() {
if (!usernameIsTimestamp()) {
throw new RuntimeException("Not configured to use timestamp as username");
}
final String truncatedTimestampSeconds = String.valueOf(usernameTimestampTruncator.apply(clock.instant()).getEpochSecond());
return generate(usernameTimestampPrefix + DELIMITER + truncatedTimestampSeconds);
}
private ExternalServiceCredentials generate(final String identity) {
final String username = shouldDeriveUsername()
? hmac256TruncatedToHexString(userDerivationKey, identity, derivedUsernameTruncateLength)
: identity;
final long currentTimeSeconds = currentTimeSeconds();
final String dataToSign = usernameIsTimestamp() ? username : username + DELIMITER + currentTimeSeconds;
final String signature = truncateSignature
? hmac256TruncatedToHexString(key, dataToSign, TRUNCATED_SIGNATURE_LENGTH)
: hmac256ToHexString(key, dataToSign);
final String token = (prependUsername ? dataToSign : currentTimeSeconds) + DELIMITER + signature;
return new ExternalServiceCredentials(username, token);
}
/**
* In certain cases, identity (as it was passed to `generate` method)
* is a part of the signature (`password`, in terms of `ExternalServiceCredentials`) string itself.
* For such cases, this method returns the value of the identity string.
* @param password `password` part of `ExternalServiceCredentials`
* @return non-empty optional with an identity string value, or empty if value can't be extracted.
*/
public Optional<String> identityFromSignature(final String password) {
// for some generators, identity in the clear is just not a part of the password
if (!prependUsername || shouldDeriveUsername() || StringUtils.isBlank(password)) {
return Optional.empty();
}
// checking for the case of unexpected format
if (StringUtils.countMatches(password, DELIMITER) == 2) {
if (usernameIsTimestamp()) {
final int indexOfSecondDelimiter = password.indexOf(DELIMITER, password.indexOf(DELIMITER) + 1);
return Optional.of(password.substring(0, indexOfSecondDelimiter));
} else {
return Optional.of(password.substring(0, password.indexOf(DELIMITER)));
}
}
return Optional.empty();
}
/**
* Given an instance of {@link ExternalServiceCredentials} object, checks that the password
* matches the username taking into account this generator's configuration.
* @param credentials an instance of {@link ExternalServiceCredentials}
* @return An optional with a timestamp (seconds) of when the credentials were generated,
* or an empty optional if the password doesn't match the username for any reason (including malformed data)
*/
public Optional<Long> validateAndGetTimestamp(final ExternalServiceCredentials credentials) {
final String[] parts = requireNonNull(credentials).password().split(DELIMITER);
final String timestampSeconds;
final String actualSignature;
// making sure password format matches our expectations based on the generator configuration
if (parts.length == 3 && prependUsername) {
final String username = usernameIsTimestamp() ? parts[0] + DELIMITER + parts[1] : parts[0];
// username has to match the one from `credentials`
if (!credentials.username().equals(username)) {
return Optional.empty();
}
timestampSeconds = parts[1];
actualSignature = parts[2];
} else if (parts.length == 2 && !prependUsername) {
timestampSeconds = parts[0];
actualSignature = parts[1];
} else {
// unexpected password format
return Optional.empty();
}
final String signedData = usernameIsTimestamp() ? credentials.username() : credentials.username() + DELIMITER + timestampSeconds;
final String expectedSignature = truncateSignature
? hmac256TruncatedToHexString(key, signedData, TRUNCATED_SIGNATURE_LENGTH)
: hmac256ToHexString(key, signedData);
// if the signature is valid it's safe to parse the `timestampSeconds` string into Long
return hmacHexStringsEqual(expectedSignature, actualSignature)
? Optional.of(Long.valueOf(timestampSeconds))
: Optional.empty();
}
/**
* Given an instance of {@link ExternalServiceCredentials} object and the max allowed age for those credentials,
* checks if credentials are valid and not expired.
* @param credentials an instance of {@link ExternalServiceCredentials}
* @param maxAgeSeconds age in seconds
* @return An optional with a timestamp (seconds) of when the credentials were generated,
* or an empty optional if the password doesn't match the username for any reason (including malformed data)
*/
public Optional<Long> validateAndGetTimestamp(final ExternalServiceCredentials credentials, final long maxAgeSeconds) {
return validateAndGetTimestamp(credentials)
.filter(ts -> currentTimeSeconds() - ts <= maxAgeSeconds);
}
private boolean shouldDeriveUsername() {
return userDerivationKey.length > 0;
}
private boolean hasUsernameTimestampPrefix() {
return usernameTimestampPrefix != null;
}
private boolean hasUsernameTimestampTruncator() {
return usernameTimestampTruncator != null;
}
private boolean usernameIsTimestamp() {
return hasUsernameTimestampPrefix() && hasUsernameTimestampTruncator();
}
private long currentTimeSeconds() {
return clock.instant().getEpochSecond();
}
public static class Builder {
private final byte[] key;
private byte[] userDerivationKey = new byte[0];
private boolean prependUsername = true;
private boolean truncateSignature = true;
private int derivedUsernameTruncateLength = 10;
private String usernameTimestampPrefix = null;
private Function<Instant, Instant> usernameTimestampTruncator = null;
private Clock clock = Clock.systemUTC();
private Builder(final byte[] key) {
this.key = requireNonNull(key);
}
public Builder withUserDerivationKey(final SecretBytes userDerivationKey) {
return withUserDerivationKey(userDerivationKey.value());
}
public Builder withUserDerivationKey(final byte[] userDerivationKey) {
Validate.isTrue(requireNonNull(userDerivationKey).length > 0, "userDerivationKey must not be empty");
this.userDerivationKey = userDerivationKey;
return this;
}
public Builder withClock(final Clock clock) {
this.clock = requireNonNull(clock);
return this;
}
public Builder withDerivedUsernameTruncateLength(int truncateLength) {
Validate.inclusiveBetween(10, 32, truncateLength);
this.derivedUsernameTruncateLength = truncateLength;
return this;
}
public Builder prependUsername(final boolean prependUsername) {
this.prependUsername = prependUsername;
return this;
}
public Builder truncateSignature(final boolean truncateSignature) {
this.truncateSignature = truncateSignature;
return this;
}
public Builder withUsernameTimestampTruncatorAndPrefix(final Function<Instant, Instant> truncator, final String prefix) {
this.usernameTimestampTruncator = truncator;
this.usernameTimestampPrefix = prefix;
return this;
}
public ExternalServiceCredentialsGenerator build() {
return new ExternalServiceCredentialsGenerator(
key, userDerivationKey, prependUsername, truncateSignature, derivedUsernameTruncateLength, usernameTimestampPrefix, usernameTimestampTruncator, clock);
}
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class ExternalServiceCredentialsSelector {
private ExternalServiceCredentialsSelector() {}
public record CredentialInfo(String token, boolean valid, ExternalServiceCredentials credentials, long timestamp) {
/**
* @return a copy of this record with valid=false
*/
private CredentialInfo invalidate() {
return new CredentialInfo(token, false, credentials, timestamp);
}
}
/**
* Validate a list of username:password credentials.
* A credential is valid if it passes validation by the provided credentialsGenerator AND it is the most recent
* credential in the provided list for a username.
*
* @param tokens A list of credentials, potentially with different usernames
* @param credentialsGenerator To validate these credentials
* @param maxAgeSeconds The maximum allowable age of the credential
* @return A {@link CredentialInfo} for each provided token
*/
public static List<CredentialInfo> check(
final List<String> tokens,
final ExternalServiceCredentialsGenerator credentialsGenerator,
final long maxAgeSeconds) {
// the credential for the username with the latest timestamp (so far)
final Map<String, CredentialInfo> bestForUsername = new HashMap<>();
final List<CredentialInfo> results = new ArrayList<>();
for (String token : tokens) {
// each token is supposed to be in a "${username}:${password}" form,
// (note that password part may also contain ':' characters)
final String[] parts = token.split(":", 2);
if (parts.length != 2) {
results.add(new CredentialInfo(token, false, null, 0L));
continue;
}
final ExternalServiceCredentials credentials = new ExternalServiceCredentials(parts[0], parts[1]);
final Optional<Long> maybeTimestamp = credentialsGenerator.validateAndGetTimestamp(credentials, maxAgeSeconds);
if (maybeTimestamp.isEmpty()) {
results.add(new CredentialInfo(token, false, null, 0L));
continue;
}
// now that we validated signature and token age, we will also find the latest of the tokens
// for each username
final long timestamp = maybeTimestamp.get();
final CredentialInfo best = bestForUsername.get(credentials.username());
if (best == null) {
bestForUsername.put(credentials.username(), new CredentialInfo(token, true, credentials, timestamp));
continue;
}
if (best.timestamp() < timestamp) {
// we found a better credential for the username
bestForUsername.put(credentials.username(), new CredentialInfo(token, true, credentials, timestamp));
// mark the previous best as an invalid credential, since we have a better credential now
results.add(best.invalidate());
} else {
// the credential we already had was more recent, this one can be marked invalid
results.add(new CredentialInfo(token, false, null, 0L));
}
}
// all invalid tokens should be in results, just add the valid ones
results.addAll(bestForUsername.values());
return results;
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import java.util.Base64;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response.Status;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.groupsend.GroupSendFullToken;
public record GroupSendTokenHeader(GroupSendFullToken token) {
public static GroupSendTokenHeader valueOf(String header) {
try {
return new GroupSendTokenHeader(new GroupSendFullToken(Base64.getDecoder().decode(header)));
} catch (InvalidInputException | IllegalArgumentException e) {
// Base64 throws IllegalArgumentException; GroupSendFullToken ctor throws InvalidInputException
throw new WebApplicationException(e, Status.UNAUTHORIZED);
}
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.monitoring.RequestEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.util.Pair;
/**
* This {@link WebsocketRefreshRequirementProvider} observes intra-request changes in devices linked to an
* {@link Account} and triggers a WebSocket refresh if that set changes. If a change in linked devices is observed, then
* any active WebSocket connections for the account must be closed in order for clients to get a refreshed
* {@link io.dropwizard.auth.Auth} object with a current device list.
*
* @see AuthenticatedDevice
*/
public class LinkedDeviceRefreshRequirementProvider implements WebsocketRefreshRequirementProvider {
private final AccountsManager accountsManager;
private static final Logger logger = LoggerFactory.getLogger(LinkedDeviceRefreshRequirementProvider.class);
private static final String ACCOUNT_UUID = LinkedDeviceRefreshRequirementProvider.class.getName() + ".accountUuid";
private static final String LINKED_DEVICE_IDS = LinkedDeviceRefreshRequirementProvider.class.getName() + ".deviceIds";
public LinkedDeviceRefreshRequirementProvider(final AccountsManager accountsManager) {
this.accountsManager = accountsManager;
}
@Override
public void handleRequestFiltered(final RequestEvent requestEvent) {
if (requestEvent.getUriInfo().getMatchedResourceMethod().getInvocable().getHandlingMethod().getAnnotation(
ChangesLinkedDevices.class) != null) {
// The authenticated principal, if any, will be available after filters have run. Now that the account is known,
// capture a snapshot of the account's linked devices before carrying out the requests business logic.
ContainerRequestUtil.getAuthenticatedAccount(requestEvent.getContainerRequest())
.ifPresent(account -> setAccount(requestEvent.getContainerRequest(), account));
}
}
public static void setAccount(final ContainerRequest containerRequest, final Account account) {
setAccount(containerRequest, ContainerRequestUtil.AccountInfo.fromAccount(account));
}
private static void setAccount(final ContainerRequest containerRequest, final ContainerRequestUtil.AccountInfo info) {
containerRequest.setProperty(ACCOUNT_UUID, info.accountId());
containerRequest.setProperty(LINKED_DEVICE_IDS, info.deviceIds());
}
@Override
public List<Pair<UUID, Byte>> handleRequestFinished(final RequestEvent requestEvent) {
// Now that the request is finished, check whether the set of linked devices has changed. If the value did change or
// if a devices was added or removed, all devices must disconnect and reauthenticate.
if (requestEvent.getContainerRequest().getProperty(LINKED_DEVICE_IDS) != null) {
@SuppressWarnings("unchecked") final Set<Byte> initialLinkedDeviceIds =
(Set<Byte>) requestEvent.getContainerRequest().getProperty(LINKED_DEVICE_IDS);
return accountsManager.getByAccountIdentifier((UUID) requestEvent.getContainerRequest().getProperty(ACCOUNT_UUID))
.map(ContainerRequestUtil.AccountInfo::fromAccount)
.map(accountInfo -> {
final Set<Byte> deviceIdsToDisplace;
final Set<Byte> currentLinkedDeviceIds = accountInfo.deviceIds();
if (!initialLinkedDeviceIds.equals(currentLinkedDeviceIds)) {
deviceIdsToDisplace = new HashSet<>(initialLinkedDeviceIds);
deviceIdsToDisplace.addAll(currentLinkedDeviceIds);
} else {
deviceIdsToDisplace = Collections.emptySet();
}
return deviceIdsToDisplace.stream()
.map(deviceId -> new Pair<>(accountInfo.accountId(), deviceId))
.collect(Collectors.toList());
}).orElseGet(() -> {
logger.error("Request had account, but it is no longer present");
return Collections.emptyList();
});
} else {
return Collections.emptyList();
}
}
}

View File

@@ -5,35 +5,37 @@
package org.whispersystems.textsecuregcm.auth;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Device;
import java.security.MessageDigest;
import java.util.Optional;
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;
import java.util.Optional;
import org.whispersystems.textsecuregcm.identity.IdentityType;
import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Device;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public class OptionalAccess {
public static final String UNIDENTIFIED = "Unidentified-Access-Key";
public static String ALL_DEVICES_SELECTOR = "*";
public static void verify(Optional<Account> requestAccount,
Optional<Anonymous> accessKey,
Optional<Account> targetAccount,
ServiceIdentifier targetIdentifier,
String deviceSelector) {
public static void verify(Optional<Account> requestAccount,
Optional<Anonymous> accessKey,
Optional<Account> targetAccount,
String deviceSelector)
{
try {
verify(requestAccount, accessKey, targetAccount);
verify(requestAccount, accessKey, targetAccount, targetIdentifier);
if (!deviceSelector.equals("*")) {
long deviceId = Long.parseLong(deviceSelector);
if (!ALL_DEVICES_SELECTOR.equals(deviceSelector)) {
byte deviceId = Byte.parseByte(deviceSelector);
Optional<Device> targetDevice = targetAccount.get().getDevice(deviceId);
if (targetDevice.isPresent() && targetDevice.get().isEnabled()) {
if (targetDevice.isPresent()) {
return;
}
@@ -48,29 +50,50 @@ public class OptionalAccess {
}
}
public static void verify(Optional<Account> requestAccount,
Optional<Anonymous> accessKey,
Optional<Account> targetAccount)
{
if (requestAccount.isPresent() && targetAccount.isPresent() && targetAccount.get().isEnabled()) {
public static void verify(Optional<Account> requestAccount,
Optional<Anonymous> accessKey,
Optional<Account> targetAccount,
ServiceIdentifier targetIdentifier) {
if (requestAccount.isPresent()) {
// Authenticated requests are never unauthorized; if the target exists, return OK, otherwise throw not-found.
if (targetAccount.isPresent()) {
return;
} else {
throw new NotFoundException();
}
}
// Anything past this point can only be authenticated by an access key. Even when the target
// has unrestricted unidentified access, callers need to supply a fake access key. Likewise, if
// the target account does not exist, we *also* report unauthorized here (*not* not-found,
// since that would provide a free exists check).
if (accessKey.isEmpty() || targetAccount.isEmpty()) {
throw new NotAuthorizedException(Response.Status.UNAUTHORIZED);
}
// Unrestricted unidentified access does what it says on the tin: we don't check if the key the
// caller provided is right or not.
if (targetAccount.get().isUnrestrictedUnidentifiedAccess()) {
return;
}
//noinspection ConstantConditions
if (requestAccount.isPresent() && (targetAccount.isEmpty() || (targetAccount.isPresent() && !targetAccount.get().isEnabled()))) {
throw new NotFoundException();
if (!targetAccount.get().isIdentifiedBy(targetIdentifier)) {
throw new IllegalArgumentException("Target account is not identified by the given identifier");
}
if (accessKey.isPresent() && targetAccount.isPresent() && targetAccount.get().isEnabled() && targetAccount.get().isUnrestrictedUnidentifiedAccess()) {
return;
// Unidentified access is only for ACI identities
if (IdentityType.PNI.equals(targetIdentifier.identityType())) {
throw new NotAuthorizedException(Response.Status.UNAUTHORIZED);
}
if (accessKey.isPresent() &&
targetAccount.isPresent() &&
targetAccount.get().getUnidentifiedAccessKey().isPresent() &&
targetAccount.get().isEnabled() &&
MessageDigest.isEqual(accessKey.get().getAccessKey(), targetAccount.get().getUnidentifiedAccessKey().get()))
{
// At this point, any successful authentication requires a real access key on the target account
if (targetAccount.get().getUnidentifiedAccessKey().isEmpty()) {
throw new NotAuthorizedException(Response.Status.UNAUTHORIZED);
}
// Otherwise, access is gated by the caller having the unidentified-access key matching the target account.
if (MessageDigest.isEqual(accessKey.get().getAccessKey(), targetAccount.get().getUnidentifiedAccessKey().get())) {
return;
}

View File

@@ -7,40 +7,50 @@ package org.whispersystems.textsecuregcm.auth;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.glassfish.jersey.server.monitoring.RequestEvent;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.util.Pair;
public class PhoneNumberChangeRefreshRequirementProvider implements WebsocketRefreshRequirementProvider {
private static final String ACCOUNT_UUID =
PhoneNumberChangeRefreshRequirementProvider.class.getName() + ".accountUuid";
private static final String INITIAL_NUMBER_KEY =
PhoneNumberChangeRefreshRequirementProvider.class.getName() + ".initialNumber";
private final AccountsManager accountsManager;
public PhoneNumberChangeRefreshRequirementProvider(final AccountsManager accountsManager) {
this.accountsManager = accountsManager;
}
@Override
public void handleRequestFiltered(final RequestEvent requestEvent) {
if (requestEvent.getUriInfo().getMatchedResourceMethod().getInvocable().getHandlingMethod()
.getAnnotation(ChangesPhoneNumber.class) == null) {
return;
}
ContainerRequestUtil.getAuthenticatedAccount(requestEvent.getContainerRequest())
.ifPresent(account -> requestEvent.getContainerRequest().setProperty(INITIAL_NUMBER_KEY, account.getNumber()));
.ifPresent(account -> {
requestEvent.getContainerRequest().setProperty(INITIAL_NUMBER_KEY, account.e164());
requestEvent.getContainerRequest().setProperty(ACCOUNT_UUID, account.accountId());
});
}
@Override
public List<Pair<UUID, Long>> handleRequestFinished(final RequestEvent requestEvent) {
public List<Pair<UUID, Byte>> handleRequestFinished(final RequestEvent requestEvent) {
final String initialNumber = (String) requestEvent.getContainerRequest().getProperty(INITIAL_NUMBER_KEY);
if (initialNumber != null) {
final Optional<Account> maybeAuthenticatedAccount =
ContainerRequestUtil.getAuthenticatedAccount(requestEvent.getContainerRequest());
return maybeAuthenticatedAccount
.filter(account -> !initialNumber.equals(account.getNumber()))
.map(account -> account.getDevices().stream()
.map(device -> new Pair<>(account.getUuid(), device.getId()))
.collect(Collectors.toList()))
.orElse(Collections.emptyList());
} else {
if (initialNumber == null) {
return Collections.emptyList();
}
return accountsManager.getByAccountIdentifier((UUID) requestEvent.getContainerRequest().getProperty(ACCOUNT_UUID))
.filter(account -> !initialNumber.equals(account.getNumber()))
.map(account -> account.getDevices().stream()
.map(device -> new Pair<>(account.getUuid(), device.getId()))
.collect(Collectors.toList()))
.orElse(Collections.emptyList());
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import java.security.MessageDigest;
import java.time.Duration;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.ForbiddenException;
import javax.ws.rs.NotAuthorizedException;
import javax.ws.rs.ServerErrorException;
import javax.ws.rs.core.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.entities.PhoneVerificationRequest;
import org.whispersystems.textsecuregcm.entities.RegistrationServiceSession;
import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient;
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager;
public class PhoneVerificationTokenManager {
private static final Logger logger = LoggerFactory.getLogger(PhoneVerificationTokenManager.class);
private static final Duration REGISTRATION_RPC_TIMEOUT = Duration.ofSeconds(15);
private static final long VERIFICATION_TIMEOUT_SECONDS = REGISTRATION_RPC_TIMEOUT.plusSeconds(1).getSeconds();
private final RegistrationServiceClient registrationServiceClient;
private final RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager;
public PhoneVerificationTokenManager(final RegistrationServiceClient registrationServiceClient,
final RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager) {
this.registrationServiceClient = registrationServiceClient;
this.registrationRecoveryPasswordsManager = registrationRecoveryPasswordsManager;
}
/**
* Checks if a {@link PhoneVerificationRequest} has a token that verifies the caller has confirmed access to the e164
* number
*
* @param number the e164 presented for verification
* @param request the request with exactly one verification token (RegistrationService sessionId or registration
* recovery password)
* @return if verification was successful, returns the verification type
* @throws BadRequestException if the number does not match the sessionIds number, or the remote service rejects
* the session ID as invalid
* @throws NotAuthorizedException if the session is not verified
* @throws ForbiddenException if the recovery password is not valid
* @throws InterruptedException if verification did not complete before a timeout
*/
public PhoneVerificationRequest.VerificationType verify(final String number, final PhoneVerificationRequest request)
throws InterruptedException {
final PhoneVerificationRequest.VerificationType verificationType = request.verificationType();
switch (verificationType) {
case SESSION -> verifyBySessionId(number, request.decodeSessionId());
case RECOVERY_PASSWORD -> verifyByRecoveryPassword(number, request.recoveryPassword());
}
return verificationType;
}
private void verifyBySessionId(final String number, final byte[] sessionId) throws InterruptedException {
try {
final RegistrationServiceSession session = registrationServiceClient
.getSession(sessionId, REGISTRATION_RPC_TIMEOUT)
.get(VERIFICATION_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.orElseThrow(() -> new NotAuthorizedException("session not verified"));
if (!MessageDigest.isEqual(number.getBytes(), session.number().getBytes())) {
throw new BadRequestException("number does not match session");
}
if (!session.verified()) {
throw new NotAuthorizedException("session not verified");
}
} catch (final ExecutionException e) {
if (e.getCause() instanceof StatusRuntimeException grpcRuntimeException) {
if (grpcRuntimeException.getStatus().getCode() == Status.Code.INVALID_ARGUMENT) {
throw new BadRequestException();
}
}
logger.error("Registration service failure", e);
throw new ServerErrorException(Response.Status.SERVICE_UNAVAILABLE);
} catch (final CancellationException | TimeoutException e) {
logger.error("Registration service failure", e);
throw new ServerErrorException(Response.Status.SERVICE_UNAVAILABLE);
}
}
private void verifyByRecoveryPassword(final String number, final byte[] recoveryPassword)
throws InterruptedException {
try {
final boolean verified = registrationRecoveryPasswordsManager.verify(number, recoveryPassword)
.get(VERIFICATION_TIMEOUT_SECONDS, TimeUnit.SECONDS);
if (!verified) {
throw new ForbiddenException("recoveryPassword couldn't be verified");
}
} catch (final ExecutionException | TimeoutException e) {
throw new ServerErrorException(Response.Status.SERVICE_UNAVAILABLE);
}
}
}

View File

@@ -0,0 +1,197 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
import com.google.common.annotations.VisibleForTesting;
import io.micrometer.core.instrument.DistributionSummary;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import javax.annotation.Nullable;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import org.apache.commons.lang3.StringUtils;
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
import org.whispersystems.textsecuregcm.entities.PhoneVerificationRequest;
import org.whispersystems.textsecuregcm.entities.RegistrationLockFailure;
import org.whispersystems.textsecuregcm.entities.Svr3Credentials;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.NotPushRegisteredException;
import org.whispersystems.textsecuregcm.push.PushNotificationManager;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager;
public class RegistrationLockVerificationManager {
public enum Flow {
REGISTRATION,
CHANGE_NUMBER
}
@VisibleForTesting
public static final int FAILURE_HTTP_STATUS = 423;
private static final String EXPIRED_REGISTRATION_LOCK_COUNTER_NAME =
name(RegistrationLockVerificationManager.class, "expiredRegistrationLock");
private static final String REQUIRED_REGISTRATION_LOCK_COUNTER_NAME =
name(RegistrationLockVerificationManager.class, "requiredRegistrationLock");
private static final String CHALLENGED_DEVICE_NOT_PUSH_REGISTERED_COUNTER_NAME =
name(RegistrationLockVerificationManager.class, "challengedDeviceNotPushRegistered");
private static final String ALREADY_LOCKED_TAG_NAME = "alreadyLocked";
private static final String REGISTRATION_LOCK_VERIFICATION_FLOW_TAG_NAME = "flow";
private static final String REGISTRATION_LOCK_MATCHES_TAG_NAME = "registrationLockMatches";
private static final String PHONE_VERIFICATION_TYPE_TAG_NAME = "phoneVerificationType";
private final AccountsManager accounts;
private final ClientPresenceManager clientPresenceManager;
private final ExternalServiceCredentialsGenerator svr2CredentialGenerator;
private final ExternalServiceCredentialsGenerator svr3CredentialGenerator;
private final RateLimiters rateLimiters;
private final RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager;
private final PushNotificationManager pushNotificationManager;
public RegistrationLockVerificationManager(
final AccountsManager accounts, final ClientPresenceManager clientPresenceManager,
final ExternalServiceCredentialsGenerator svr2CredentialGenerator,
final ExternalServiceCredentialsGenerator svr3CredentialGenerator,
final RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager,
final PushNotificationManager pushNotificationManager,
final RateLimiters rateLimiters) {
this.accounts = accounts;
this.clientPresenceManager = clientPresenceManager;
this.svr2CredentialGenerator = svr2CredentialGenerator;
this.svr3CredentialGenerator = svr3CredentialGenerator;
this.registrationRecoveryPasswordsManager = registrationRecoveryPasswordsManager;
this.pushNotificationManager = pushNotificationManager;
this.rateLimiters = rateLimiters;
}
/**
* Verifies the given registration lock credentials against the accounts current registration lock, if any
*
* @param account
* @param clientRegistrationLock
* @throws RateLimitExceededException
* @throws WebApplicationException
*/
public void verifyRegistrationLock(final Account account, @Nullable final String clientRegistrationLock,
final String userAgent,
final Flow flow,
final PhoneVerificationRequest.VerificationType phoneVerificationType
) throws RateLimitExceededException, WebApplicationException {
final Tags expiredTags = Tags.of(UserAgentTagUtil.getPlatformTag(userAgent),
Tag.of(REGISTRATION_LOCK_VERIFICATION_FLOW_TAG_NAME, flow.name()),
Tag.of(PHONE_VERIFICATION_TYPE_TAG_NAME, phoneVerificationType.name())
);
final StoredRegistrationLock existingRegistrationLock = account.getRegistrationLock();
switch (existingRegistrationLock.getStatus()) {
case EXPIRED:
Metrics.counter(EXPIRED_REGISTRATION_LOCK_COUNTER_NAME, expiredTags).increment();
return;
case ABSENT:
return;
case REQUIRED:
break;
default:
throw new RuntimeException("Unexpected status: " + existingRegistrationLock.getStatus());
}
if (StringUtils.isNotEmpty(clientRegistrationLock)) {
rateLimiters.getPinLimiter().validate(account.getNumber());
}
final String phoneNumber = account.getNumber();
final boolean registrationLockMatches = existingRegistrationLock.verify(clientRegistrationLock);
final boolean alreadyLocked = account.hasLockedCredentials();
final Tags additionalTags = expiredTags.and(
REGISTRATION_LOCK_MATCHES_TAG_NAME, Boolean.toString(registrationLockMatches),
ALREADY_LOCKED_TAG_NAME, Boolean.toString(alreadyLocked)
);
Metrics.counter(REQUIRED_REGISTRATION_LOCK_COUNTER_NAME, additionalTags).increment();
final DistributionSummary registrationLockIdleDays = DistributionSummary
.builder(name(RegistrationLockVerificationManager.class, "registrationLockIdleDays"))
.tags(additionalTags)
.publishPercentiles(0.75, 0.95, 0.99, 0.999)
.distributionStatisticExpiry(Duration.ofHours(2))
.register(Metrics.globalRegistry);
final Instant accountLastSeen = Instant.ofEpochMilli(account.getLastSeen());
final Duration timeSinceLastSeen = Duration.between(accountLastSeen, Instant.now());
registrationLockIdleDays.record(timeSinceLastSeen.toDays());
if (!registrationLockMatches) {
// At this point, the client verified ownership of the phone number but doesnt have the reglock PIN.
// Freezing the existing account credentials will definitively start the reglock timeout.
// Until the timeout, the current reglock can still be supplied,
// along with phone number verification, to restore access.
final Account updatedAccount;
if (!alreadyLocked) {
updatedAccount = accounts.update(account, Account::lockAuthTokenHash);
} else {
updatedAccount = account;
}
// The client often sends an empty registration lock token on the first request
// and sends an actual token if the server returns a 423 indicating that one is required.
// This logic accounts for that behavior by not deleting the registration recovery password
// if the user verified correctly via registration recovery password and sent an empty token.
// This allows users to re-register via registration recovery password
// instead of always being forced to fall back to SMS verification.
if (!phoneVerificationType.equals(PhoneVerificationRequest.VerificationType.RECOVERY_PASSWORD) || clientRegistrationLock != null) {
registrationRecoveryPasswordsManager.removeForNumber(updatedAccount.getNumber());
}
final List<Byte> deviceIds = updatedAccount.getDevices().stream().map(Device::getId).toList();
clientPresenceManager.disconnectAllPresences(updatedAccount.getUuid(), deviceIds);
try {
// Send a push notification that prompts the client to attempt login and fail due to locked credentials
pushNotificationManager.sendAttemptLoginNotification(updatedAccount, "failedRegistrationLock");
} catch (final NotPushRegisteredException e) {
Metrics.counter(CHALLENGED_DEVICE_NOT_PUSH_REGISTERED_COUNTER_NAME).increment();
}
throw new WebApplicationException(Response.status(FAILURE_HTTP_STATUS)
.entity(new RegistrationLockFailure(
existingRegistrationLock.getTimeRemaining().toMillis(),
svr2FailureCredentials(existingRegistrationLock, updatedAccount),
svr3FailureCredentials(existingRegistrationLock, updatedAccount)))
.build());
}
rateLimiters.getPinLimiter().clear(phoneNumber);
}
private @Nullable ExternalServiceCredentials svr2FailureCredentials(final StoredRegistrationLock existingRegistrationLock, final Account account) {
if (!existingRegistrationLock.needsFailureCredentials()) {
return null;
}
return svr2CredentialGenerator.generateForUuid(account.getUuid());
}
private @Nullable Svr3Credentials svr3FailureCredentials(final StoredRegistrationLock existingRegistrationLock, final Account account) {
if (!existingRegistrationLock.needsFailureCredentials()) {
return null;
}
final ExternalServiceCredentials creds = svr3CredentialGenerator.generateForUuid(account.getUuid());
return new Svr3Credentials(creds.username(), creds.password(), account.getSvr3ShareSet());
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.HexFormat;
import org.signal.libsignal.protocol.kdf.HKDF;
public record SaltedTokenHash(String hash, String salt) {
public enum Version {
V1,
V2,
}
public static final Version CURRENT_VERSION = Version.V2;
private static final String V2_PREFIX = "2.";
private static final byte[] AUTH_TOKEN_HKDF_INFO = "authtoken".getBytes(StandardCharsets.UTF_8);
private static final int SALT_SIZE = 16;
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
public static SaltedTokenHash generateFor(final String token) {
final String salt = generateSalt();
final String hash = calculateV2Hash(salt, token);
return new SaltedTokenHash(hash, salt);
}
public Version getVersion() {
return hash.startsWith(V2_PREFIX) ? Version.V2 : Version.V1;
}
public boolean verify(final String token) {
final String theirValue = switch (getVersion()) {
case V1 -> calculateV1Hash(salt, token);
case V2 -> calculateV2Hash(salt, token);
};
return MessageDigest.isEqual(
theirValue.getBytes(StandardCharsets.UTF_8),
hash.getBytes(StandardCharsets.UTF_8));
}
private static String generateSalt() {
final byte[] salt = new byte[SALT_SIZE];
SECURE_RANDOM.nextBytes(salt);
return HexFormat.of().formatHex(salt);
}
private static String calculateV1Hash(final String salt, final String token) {
try {
return HexFormat.of()
.formatHex(MessageDigest.getInstance("SHA1").digest((salt + token).getBytes(StandardCharsets.UTF_8)));
} catch (final NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
private static String calculateV2Hash(final String salt, final String token) {
final byte[] secret = HKDF.deriveSecrets(
token.getBytes(StandardCharsets.UTF_8), // key
salt.getBytes(StandardCharsets.UTF_8), // salt
AUTH_TOKEN_HKDF_INFO,
32);
return V2_PREFIX + HexFormat.of().formatHex(secret);
}
}

View File

@@ -1,31 +1,40 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import com.google.common.annotations.VisibleForTesting;
import org.whispersystems.textsecuregcm.util.Util;
import javax.annotation.Nullable;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public class StoredRegistrationLock {
public enum Status {
REQUIRED,
EXPIRED,
ABSENT
}
@VisibleForTesting
static final Duration REGISTRATION_LOCK_EXPIRATION_DAYS = Duration.ofDays(7);
private final Optional<String> registrationLock;
private final Optional<String> registrationLockSalt;
private final long lastSeen;
private final Instant lastSeen;
/**
* @return milliseconds since the last time the account was seen.
*/
private long timeSinceLastSeen() {
return System.currentTimeMillis() - lastSeen;
return System.currentTimeMillis() - lastSeen.toEpochMilli();
}
/**
@@ -35,36 +44,40 @@ public class StoredRegistrationLock {
return registrationLock.isPresent() && registrationLockSalt.isPresent();
}
public StoredRegistrationLock(Optional<String> registrationLock, Optional<String> registrationLockSalt, long lastSeen) {
public boolean isPresent() {
return hasLockAndSalt();
}
public StoredRegistrationLock(Optional<String> registrationLock, Optional<String> registrationLockSalt, Instant lastSeen) {
this.registrationLock = registrationLock;
this.registrationLockSalt = registrationLockSalt;
this.lastSeen = lastSeen;
}
public boolean requiresClientRegistrationLock() {
boolean hasTimeRemaining = getTimeRemaining() >= 0;
return hasLockAndSalt() && hasTimeRemaining;
public Status getStatus() {
if (!isPresent()) {
return Status.ABSENT;
}
if (getTimeRemaining().toMillis() > 0) {
return Status.REQUIRED;
}
return Status.EXPIRED;
}
public boolean needsFailureCredentials() {
return hasLockAndSalt();
}
public long getTimeRemaining() {
return TimeUnit.DAYS.toMillis(7) - timeSinceLastSeen();
public Duration getTimeRemaining() {
return REGISTRATION_LOCK_EXPIRATION_DAYS.minus(timeSinceLastSeen(), ChronoUnit.MILLIS);
}
public boolean verify(@Nullable String clientRegistrationLock) {
if (hasLockAndSalt() && Util.nonEmpty(clientRegistrationLock)) {
AuthenticationCredentials credentials = new AuthenticationCredentials(registrationLock.get(), registrationLockSalt.get());
if (hasLockAndSalt() && StringUtils.isNotEmpty(clientRegistrationLock)) {
SaltedTokenHash credentials = new SaltedTokenHash(registrationLock.get(), registrationLockSalt.get());
return credentials.verify(clientRegistrationLock);
} else {
return false;
}
}
@VisibleForTesting
public StoredRegistrationLock forTime(long timestamp) {
return new StoredRegistrationLock(registrationLock, registrationLockSalt, timestamp);
}
}

View File

@@ -1,31 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import java.security.MessageDigest;
import java.time.Duration;
import javax.annotation.Nullable;
import org.whispersystems.textsecuregcm.util.Util;
public record StoredVerificationCode(String code,
long timestamp,
String pushCode,
@Nullable String twilioVerificationSid,
@Nullable byte[] sessionId) {
public static final Duration EXPIRATION = Duration.ofMinutes(10);
public boolean isValid(String theirCodeString) {
if (Util.isEmpty(code) || Util.isEmpty(theirCodeString)) {
return false;
}
byte[] ourCode = code.getBytes();
byte[] theirCode = theirCodeString.getBytes();
return MessageDigest.isEqual(ourCode, theirCode);
}
}

View File

@@ -5,30 +5,9 @@
package org.whispersystems.textsecuregcm.auth;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;
import javax.annotation.Nullable;
import java.util.List;
public class TurnToken {
@JsonProperty
private String username;
@JsonProperty
private String password;
@JsonProperty
private List<String> urls;
public TurnToken(String username, String password, List<String> urls) {
this.username = username;
this.password = password;
this.urls = urls;
}
@VisibleForTesting
List<String> getUrls() {
return urls;
}
public record TurnToken(String username, String password, List<String> urls, @Nullable List<String> urlsWithIps,
@Nullable String hostname) {
}

View File

@@ -5,56 +5,81 @@
package org.whispersystems.textsecuregcm.auth;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.time.Duration;
import java.time.Instant;
import java.util.Base64;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.whispersystems.textsecuregcm.calls.routing.TurnServerOptions;
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;
import java.security.InvalidKeyException;
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 DynamicConfigurationManager<DynamicConfiguration> dynamicConfiguration;
private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager;
public TurnTokenGenerator(final DynamicConfigurationManager<DynamicConfiguration> config) {
this.dynamicConfiguration = config;
private final byte[] turnSecret;
private static final String ALGORITHM = "HmacSHA1";
private static final String WithUrlsProtocol = "00";
private static final String WithIpsProtocol = "01";
public TurnTokenGenerator(final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager,
final byte[] turnSecret) {
this.dynamicConfigurationManager = dynamicConfigurationManager;
this.turnSecret = turnSecret;
}
public TurnToken generate(final String e164) {
@Deprecated
public TurnToken generate(final UUID aci) {
return generateToken(null, null, urls(aci));
}
public TurnToken generateWithTurnServerOptions(TurnServerOptions options) {
return generateToken(options.hostname(), options.urlsWithIps(), options.urlsWithHostname());
}
private TurnToken generateToken(String hostname, List<String> urlsWithIps, List<String> urlsWithHostname) {
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());
String userTime = validUntilSeconds + ":" + user;
final Mac mac = Mac.getInstance(ALGORITHM);
final long validUntilSeconds = Instant.now().plus(Duration.ofDays(1)).getEpochSecond();
final long user = Util.ensureNonNegativeInt(new SecureRandom().nextInt());
final String userTime = validUntilSeconds + ":" + user;
final String protocol = urlsWithIps != null && !urlsWithIps.isEmpty()
? WithIpsProtocol
: WithUrlsProtocol;
final String protocolUserTime = userTime + "#" + protocol;
mac.init(new SecretKeySpec(key, "HmacSHA1"));
String password = Base64.getEncoder().encodeToString(mac.doFinal(userTime.getBytes()));
mac.init(new SecretKeySpec(turnSecret, ALGORITHM));
final String password = Base64.getEncoder().encodeToString(mac.doFinal(protocolUserTime.getBytes()));
return new TurnToken(userTime, password, urls);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
return new TurnToken(protocolUserTime, password, urlsWithHostname, urlsWithIps, hostname);
} catch (final NoSuchAlgorithmException | InvalidKeyException e) {
throw new AssertionError(e);
}
}
private List<String> urls(final String e164) {
final DynamicTurnConfiguration turnConfig = dynamicConfiguration.getConfiguration().getTurnConfiguration();
private List<String> urls(final UUID aci) {
final DynamicTurnConfiguration turnConfig = dynamicConfigurationManager.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))
.filter(config -> config.getEnrolledAcis().contains(aci))
.findFirst();
if (enrolled.isPresent()) {
return enrolled.get().getUris();
}
@@ -63,6 +88,6 @@ public class TurnTokenGenerator {
return WeightedRandomSelect.select(turnConfig
.getUriConfigs()
.stream()
.map(c -> new Pair<List<String>, Long>(c.getUris(), c.getWeight())).toList());
.map(c -> new Pair<>(c.getUris(), c.getWeight())).toList());
}
}

View File

@@ -9,23 +9,21 @@ import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Optional;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public class UnidentifiedAccessChecksum {
public static String generateFor(Optional<byte[]> unidentifiedAccessKey) {
public static byte[] generateFor(byte[] unidentifiedAccessKey) {
try {
if (!unidentifiedAccessKey.isPresent()|| unidentifiedAccessKey.get().length != 16) return null;
if (unidentifiedAccessKey.length != UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH) {
throw new IllegalArgumentException("Invalid UAK length: " + unidentifiedAccessKey.length);
}
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(unidentifiedAccessKey.get(), "HmacSHA256"));
mac.init(new SecretKeySpec(unidentifiedAccessKey, "HmacSHA256"));
return Base64.getEncoder().encodeToString(mac.doFinal(new byte[32]));
return mac.doFinal(new byte[32]);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new AssertionError(e);
}
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import org.whispersystems.textsecuregcm.storage.Account;
import java.security.MessageDigest;
public class UnidentifiedAccessUtil {
public static final int UNIDENTIFIED_ACCESS_KEY_LENGTH = 16;
private UnidentifiedAccessUtil() {
}
/**
* Checks whether an action (e.g. sending a message or retrieving pre-keys) may be taken on the target account by an
* actor presenting the given unidentified access key.
*
* @param targetAccount the account on which an actor wishes to take an action
* @param unidentifiedAccessKey the unidentified access key presented by the actor
*
* @return {@code true} if an actor presenting the given unidentified access key has permission to take an action on
* the target account or {@code false} otherwise
*/
public static boolean checkUnidentifiedAccess(final Account targetAccount, final byte[] unidentifiedAccessKey) {
return targetAccount.isUnrestrictedUnidentifiedAccess()
|| targetAccount.getUnidentifiedAccessKey()
.map(targetUnidentifiedAccessKey -> MessageDigest.isEqual(targetUnidentifiedAccessKey, unidentifiedAccessKey))
.orElse(false);
}
}

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