From df69d9f19571317b451c61aabce1516a617d89c6 Mon Sep 17 00:00:00 2001 From: Ravi Khadiwala Date: Wed, 14 Feb 2024 16:54:42 -0600 Subject: [PATCH] Annotate authenticated endpoints with `@ReadOnly` or `@Mutable` --- .../controllers/AccountController.java | 42 ++++++++++--------- .../controllers/AccountControllerV2.java | 11 +++-- .../controllers/ArchiveController.java | 25 ++++++----- .../controllers/ArtController.java | 3 +- .../controllers/AttachmentControllerV2.java | 3 +- .../controllers/AttachmentControllerV3.java | 3 +- .../controllers/AttachmentControllerV4.java | 3 +- .../controllers/CallLinkController.java | 3 +- .../controllers/CallRoutingController.java | 3 +- .../controllers/CertificateController.java | 5 ++- .../controllers/ChallengeController.java | 5 ++- .../controllers/DeviceController.java | 12 +++--- .../controllers/DirectoryV2Controller.java | 3 +- .../controllers/DonationController.java | 3 +- .../controllers/KeepAliveController.java | 3 +- .../controllers/KeysController.java | 12 ++++-- .../controllers/MessageController.java | 9 ++-- .../controllers/PaymentsController.java | 5 ++- .../controllers/ProfileController.java | 10 +++-- .../controllers/ProvisioningController.java | 3 +- .../controllers/RemoteConfigController.java | 3 +- .../controllers/SecureStorageController.java | 3 +- .../SecureValueRecovery2Controller.java | 3 +- .../SecureValueRecovery3Controller.java | 3 +- .../controllers/StickerController.java | 3 +- .../controllers/SubscriptionController.java | 21 +++++----- 26 files changed, 119 insertions(+), 83 deletions(-) diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java index 588d393e4..4df2024ed 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java @@ -65,6 +65,8 @@ import org.whispersystems.textsecuregcm.util.ExceptionUtils; import org.whispersystems.textsecuregcm.util.HeaderUtils; import org.whispersystems.textsecuregcm.util.UsernameHashZkProofVerifier; import org.whispersystems.textsecuregcm.util.Util; +import org.whispersystems.websocket.auth.Mutable; +import org.whispersystems.websocket.auth.ReadOnly; @SuppressWarnings("OptionalUsedAsFieldOrParameterType") @Path("/v1/accounts") @@ -97,7 +99,7 @@ public class AccountController { @GET @Path("/turn/") @Produces(MediaType.APPLICATION_JSON) - public TurnToken getTurnToken(@Auth AuthenticatedAccount auth) throws RateLimitExceededException { + public TurnToken getTurnToken(@ReadOnly @Auth AuthenticatedAccount auth) throws RateLimitExceededException { rateLimiters.getTurnLimiter().validate(auth.getAccount().getUuid()); return turnTokenGenerator.generate(auth.getAccount().getUuid()); } @@ -107,7 +109,7 @@ public class AccountController { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @ChangesDeviceEnabledState - public void setGcmRegistrationId(@Auth AuthenticatedAccount auth, + public void setGcmRegistrationId(@Mutable @Auth AuthenticatedAccount auth, @NotNull @Valid GcmRegistrationId registrationId) { final Account account = auth.getAccount(); @@ -128,7 +130,7 @@ public class AccountController { @DELETE @Path("/gcm/") @ChangesDeviceEnabledState - public void deleteGcmRegistrationId(@Auth AuthenticatedAccount auth) { + public void deleteGcmRegistrationId(@Mutable @Auth AuthenticatedAccount auth) { Account account = auth.getAccount(); Device device = auth.getAuthenticatedDevice(); @@ -144,7 +146,7 @@ public class AccountController { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @ChangesDeviceEnabledState - public void setApnRegistrationId(@Auth AuthenticatedAccount auth, + public void setApnRegistrationId(@Mutable @Auth AuthenticatedAccount auth, @NotNull @Valid ApnRegistrationId registrationId) { final Account account = auth.getAccount(); @@ -167,7 +169,7 @@ public class AccountController { @DELETE @Path("/apn/") @ChangesDeviceEnabledState - public void deleteApnRegistrationId(@Auth AuthenticatedAccount auth) { + public void deleteApnRegistrationId(@Mutable @Auth AuthenticatedAccount auth) { Account account = auth.getAccount(); Device device = auth.getAuthenticatedDevice(); @@ -186,7 +188,7 @@ public class AccountController { @PUT @Produces(MediaType.APPLICATION_JSON) @Path("/registration_lock") - public void setRegistrationLock(@Auth AuthenticatedAccount auth, @NotNull @Valid RegistrationLock accountLock) { + public void setRegistrationLock(@Mutable @Auth AuthenticatedAccount auth, @NotNull @Valid RegistrationLock accountLock) { SaltedTokenHash credentials = SaltedTokenHash.generateFor(accountLock.getRegistrationLock()); accounts.update(auth.getAccount(), @@ -195,13 +197,13 @@ public class AccountController { @DELETE @Path("/registration_lock") - public void removeRegistrationLock(@Auth AuthenticatedAccount auth) { + public void removeRegistrationLock(@Mutable @Auth AuthenticatedAccount auth) { accounts.update(auth.getAccount(), a -> a.setRegistrationLock(null, null)); } @PUT @Path("/name/") - public void setName(@Auth AuthenticatedAccount auth, @NotNull @Valid DeviceName deviceName) { + public void setName(@Mutable @Auth AuthenticatedAccount auth, @NotNull @Valid DeviceName deviceName) { Account account = auth.getAccount(); Device device = auth.getAuthenticatedDevice(); accounts.updateDevice(account, device.getId(), d -> d.setName(deviceName.getDeviceName())); @@ -213,7 +215,7 @@ public class AccountController { @Produces(MediaType.APPLICATION_JSON) @ChangesDeviceEnabledState public void setAccountAttributes( - @Auth AuthenticatedAccount auth, + @Mutable @Auth AuthenticatedAccount auth, @HeaderParam(HeaderUtils.X_SIGNAL_AGENT) String userAgent, @NotNull @Valid AccountAttributes attributes) { final Account account = auth.getAccount(); @@ -243,14 +245,14 @@ public class AccountController { @Path("/me") @Deprecated() // use whoami @Produces(MediaType.APPLICATION_JSON) - public AccountIdentityResponse getMe(@Auth AuthenticatedAccount auth) { + public AccountIdentityResponse getMe(@ReadOnly @Auth AuthenticatedAccount auth) { return buildAccountIdentityResponse(auth); } @GET @Path("/whoami") @Produces(MediaType.APPLICATION_JSON) - public AccountIdentityResponse whoAmI(@Auth AuthenticatedAccount auth) { + public AccountIdentityResponse whoAmI(@ReadOnly @Auth AuthenticatedAccount auth) { return buildAccountIdentityResponse(auth); } @@ -273,7 +275,7 @@ public class AccountController { ) @ApiResponse(responseCode = "204", description = "Username successfully deleted.", useReturnTypeSchema = true) @ApiResponse(responseCode = "401", description = "Account authentication check failed.") - public CompletableFuture deleteUsernameHash(@Auth final AuthenticatedAccount auth) { + public CompletableFuture deleteUsernameHash(@Mutable @Auth final AuthenticatedAccount auth) { return accounts.clearUsernameHash(auth.getAccount()) .thenApply(Util.ASYNC_EMPTY_RESPONSE); } @@ -295,7 +297,7 @@ public class AccountController { @ApiResponse(responseCode = "422", description = "Invalid request format.") @ApiResponse(responseCode = "429", description = "Ratelimited.") public CompletableFuture reserveUsernameHash( - @Auth final AuthenticatedAccount auth, + @Mutable @Auth final AuthenticatedAccount auth, @NotNull @Valid final ReserveUsernameHashRequest usernameRequest) throws RateLimitExceededException { rateLimiters.getUsernameReserveLimiter().validate(auth.getAccount().getUuid()); @@ -335,7 +337,7 @@ public class AccountController { @ApiResponse(responseCode = "422", description = "Invalid request format.") @ApiResponse(responseCode = "429", description = "Ratelimited.") public CompletableFuture confirmUsernameHash( - @Auth final AuthenticatedAccount auth, + @Mutable @Auth final AuthenticatedAccount auth, @NotNull @Valid final ConfirmUsernameHashRequest confirmRequest) { try { @@ -380,7 +382,7 @@ public class AccountController { @ApiResponse(responseCode = "400", description = "Request must not be authenticated.") @ApiResponse(responseCode = "404", description = "Account not found for the given username.") public CompletableFuture lookupUsernameHash( - @Auth final Optional maybeAuthenticatedAccount, + @ReadOnly @Auth final Optional maybeAuthenticatedAccount, @PathParam("usernameHash") final String usernameHash) { requireNotAuthenticated(maybeAuthenticatedAccount); @@ -419,7 +421,7 @@ public class AccountController { @ApiResponse(responseCode = "422", description = "Invalid request format.") @ApiResponse(responseCode = "429", description = "Ratelimited.") public UsernameLinkHandle updateUsernameLink( - @Auth final AuthenticatedAccount auth, + @Mutable @Auth final AuthenticatedAccount auth, @NotNull @Valid final EncryptedUsername encryptedUsername) throws RateLimitExceededException { // check ratelimiter for username link operations rateLimiters.forDescriptor(RateLimiters.For.USERNAME_LINK_OPERATION).validate(auth.getAccount().getUuid()); @@ -453,7 +455,7 @@ public class AccountController { @ApiResponse(responseCode = "204", description = "Username Link successfully deleted.", useReturnTypeSchema = true) @ApiResponse(responseCode = "401", description = "Account authentication check failed.") @ApiResponse(responseCode = "429", description = "Ratelimited.") - public void deleteUsernameLink(@Auth final AuthenticatedAccount auth) throws RateLimitExceededException { + public void deleteUsernameLink(@Mutable @Auth final AuthenticatedAccount auth) throws RateLimitExceededException { // check ratelimiter for username link operations rateLimiters.forDescriptor(RateLimiters.For.USERNAME_LINK_OPERATION).validate(auth.getAccount().getUuid()); clearUsernameLink(auth.getAccount()); @@ -476,7 +478,7 @@ public class AccountController { @ApiResponse(responseCode = "422", description = "Invalid request format.") @ApiResponse(responseCode = "429", description = "Ratelimited.") public CompletableFuture lookupUsernameLink( - @Auth final Optional maybeAuthenticatedAccount, + @ReadOnly @Auth final Optional maybeAuthenticatedAccount, @PathParam("uuid") final UUID usernameLinkHandle) { requireNotAuthenticated(maybeAuthenticatedAccount); @@ -502,7 +504,7 @@ public class AccountController { @Path("/account/{identifier}") @RateLimitedByIp(RateLimiters.For.CHECK_ACCOUNT_EXISTENCE) public Response accountExists( - @Auth final Optional authenticatedAccount, + @ReadOnly @Auth final Optional authenticatedAccount, @Parameter(description = "An ACI or PNI account identifier to check") @PathParam("identifier") final ServiceIdentifier accountIdentifier) { @@ -517,7 +519,7 @@ public class AccountController { @DELETE @Path("/me") - public CompletableFuture deleteAccount(@Auth AuthenticatedAccount auth) { + public CompletableFuture deleteAccount(@Mutable @Auth AuthenticatedAccount auth) { return accounts.delete(auth.getAccount(), AccountsManager.DeletionReason.USER_REQUEST).thenApply(Util.ASYNC_EMPTY_RESPONSE); } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountControllerV2.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountControllerV2.java index 7025557f0..4a33df350 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountControllerV2.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountControllerV2.java @@ -58,6 +58,8 @@ import org.whispersystems.textsecuregcm.util.ua.ClientPlatform; import org.whispersystems.textsecuregcm.util.ua.UnrecognizedUserAgentException; import org.whispersystems.textsecuregcm.util.ua.UserAgent; import org.whispersystems.textsecuregcm.util.ua.UserAgentUtil; +import org.whispersystems.websocket.auth.Mutable; +import org.whispersystems.websocket.auth.ReadOnly; @Path("/v2/accounts") @io.swagger.v3.oas.annotations.tags.Tag(name = "Account") @@ -103,7 +105,7 @@ public class AccountControllerV2 { @ApiResponse(responseCode = "429", description = "Too many attempts", headers = @Header( name = "Retry-After", description = "If present, an positive integer indicating the number of seconds before a subsequent attempt could succeed")) - public AccountIdentityResponse changeNumber(@Auth final AuthenticatedAccount authenticatedAccount, + public AccountIdentityResponse changeNumber(@Mutable @Auth final AuthenticatedAccount authenticatedAccount, @NotNull @Valid final ChangeNumberRequest request, @HeaderParam(HttpHeaders.USER_AGENT) final String userAgentString) throws RateLimitExceededException, InterruptedException { @@ -191,7 +193,8 @@ public class AccountControllerV2 { content = @Content(schema = @Schema(implementation = MismatchedDevices.class))) @ApiResponse(responseCode = "410", description = "The registration IDs provided for some devices do not match those stored on the server.", content = @Content(schema = @Schema(implementation = StaleDevices.class))) - public AccountIdentityResponse distributePhoneNumberIdentityKeys(@Auth final AuthenticatedAccount authenticatedAccount, + public AccountIdentityResponse distributePhoneNumberIdentityKeys( + @Mutable @Auth final AuthenticatedAccount authenticatedAccount, @NotNull @Valid final PhoneNumberIdentityKeyDistributionRequest request) { if (!authenticatedAccount.getAuthenticatedDevice().isPrimary()) { @@ -234,7 +237,7 @@ public class AccountControllerV2 { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public void setPhoneNumberDiscoverability( - @Auth AuthenticatedAccount auth, + @Mutable @Auth AuthenticatedAccount auth, @NotNull @Valid PhoneNumberDiscoverabilityRequest phoneNumberDiscoverability ) { accountsManager.update(auth.getAccount(), a -> a.setDiscoverableByPhoneNumber( @@ -248,7 +251,7 @@ public class AccountControllerV2 { @ApiResponse(responseCode = "200", description = "Response with data report. A plain text representation is a field in the response.", useReturnTypeSchema = true) - public AccountDataReportResponse getAccountDataReport(@Auth final AuthenticatedAccount auth) { + public AccountDataReportResponse getAccountDataReport(@ReadOnly @Auth final AuthenticatedAccount auth) { final Account account = auth.getAccount(); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ArchiveController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ArchiveController.java index bfb1ef052..81fb94af6 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ArchiveController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ArchiveController.java @@ -62,6 +62,8 @@ import org.whispersystems.textsecuregcm.util.ECPublicKeyAdapter; import org.whispersystems.textsecuregcm.util.ExactlySize; import org.whispersystems.textsecuregcm.util.ExceptionUtils; import org.whispersystems.textsecuregcm.util.Util; +import org.whispersystems.websocket.auth.Mutable; +import org.whispersystems.websocket.auth.ReadOnly; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -108,7 +110,7 @@ public class ArchiveController { @ApiResponse(responseCode = "400", description = "The provided backup auth credential request was invalid") @ApiResponse(responseCode = "429", description = "Rate limited. Too many attempts to change the backup-id have been made") public CompletionStage setBackupId( - @Auth final AuthenticatedAccount account, + @Mutable @Auth final AuthenticatedAccount account, @Valid @NotNull final SetBackupIdRequest setBackupIdRequest) throws RateLimitExceededException { return this.backupAuthManager .commitBackupId(account.getAccount(), setBackupIdRequest.backupAuthCredentialRequest) @@ -142,7 +144,7 @@ public class ArchiveController { @ApiResponse(responseCode = "404", description = "Could not find an existing blinded backup id") @ApiResponse(responseCode = "429", description = "Rate limited.") public CompletionStage getBackupZKCredentials( - @Auth AuthenticatedAccount auth, + @ReadOnly @Auth AuthenticatedAccount auth, @NotNull @QueryParam("redemptionStartSeconds") Long startSeconds, @NotNull @QueryParam("redemptionEndSeconds") Long endSeconds) { @@ -212,7 +214,7 @@ public class ArchiveController { @ApiResponse(responseCode = "429", description = "Rate limited.") @ApiResponseZkAuth public CompletionStage readAuth( - @Auth final Optional account, + @ReadOnly @Auth final Optional account, @Parameter(description = BackupAuthCredentialPresentationHeader.DESCRIPTION, schema = @Schema(implementation = String.class)) @NotNull @@ -256,7 +258,7 @@ public class ArchiveController { @ApiResponse(responseCode = "429", description = "Rate limited.") @ApiResponseZkAuth public CompletionStage backupInfo( - @Auth final Optional account, + @ReadOnly @Auth final Optional account, @Parameter(description = BackupAuthCredentialPresentationHeader.DESCRIPTION, schema = @Schema(implementation = String.class)) @NotNull @@ -300,7 +302,7 @@ public class ArchiveController { @ApiResponse(responseCode = "204", description = "The public key was set") @ApiResponse(responseCode = "429", description = "Rate limited.") public CompletionStage setPublicKey( - @Auth final Optional account, + @ReadOnly @Auth final Optional account, @Parameter(description = BackupAuthCredentialPresentationHeader.DESCRIPTION, schema = @Schema(implementation = String.class)) @NotNull @@ -337,7 +339,7 @@ public class ArchiveController { @ApiResponse(responseCode = "429", description = "Rate limited.") @ApiResponseZkAuth public CompletionStage backup( - @Auth final Optional account, + @ReadOnly @Auth final Optional account, @Parameter(description = BackupAuthCredentialPresentationHeader.DESCRIPTION, schema = @Schema(implementation = String.class)) @NotNull @@ -425,7 +427,8 @@ public class ArchiveController { @ApiResponse(responseCode = "410", description = "The source object was not found.") @ApiResponse(responseCode = "429", description = "Rate limited.") @ApiResponseZkAuth - public CompletionStage copyMedia(@Auth final Optional account, + public CompletionStage copyMedia( + @ReadOnly @Auth final Optional account, @Parameter(description = BackupAuthCredentialPresentationHeader.DESCRIPTION, schema = @Schema(implementation = String.class)) @NotNull @@ -533,7 +536,7 @@ public class ArchiveController { @ApiResponse(responseCode = "429", description = "Rate limited.") @ApiResponseZkAuth public CompletionStage copyMedia( - @Auth final Optional account, + @ReadOnly @Auth final Optional account, @Parameter(description = BackupAuthCredentialPresentationHeader.DESCRIPTION, schema = @Schema(implementation = String.class)) @NotNull @@ -599,7 +602,7 @@ public class ArchiveController { @ApiResponse(responseCode = "429", description = "Rate limited.") @ApiResponseZkAuth public CompletionStage refresh( - @Auth final Optional account, + @ReadOnly @Auth final Optional account, @Parameter(description = BackupAuthCredentialPresentationHeader.DESCRIPTION, schema = @Schema(implementation = String.class)) @NotNull @@ -656,7 +659,7 @@ public class ArchiveController { @ApiResponse(responseCode = "429", description = "Rate limited.") @ApiResponseZkAuth public CompletionStage listMedia( - @Auth final Optional account, + @ReadOnly @Auth final Optional account, @Parameter(description = BackupAuthCredentialPresentationHeader.DESCRIPTION, schema = @Schema(implementation = String.class)) @NotNull @@ -709,7 +712,7 @@ public class ArchiveController { @ApiResponse(responseCode = "429", description = "Rate limited.") @ApiResponseZkAuth public CompletionStage deleteMedia( - @Auth final Optional account, + @ReadOnly @Auth final Optional account, @Parameter(description = BackupAuthCredentialPresentationHeader.DESCRIPTION, schema = @Schema(implementation = String.class)) @NotNull diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ArtController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ArtController.java index 7178fa76d..714acbee1 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ArtController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ArtController.java @@ -17,6 +17,7 @@ import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; import org.whispersystems.textsecuregcm.configuration.ArtServiceConfiguration; import org.whispersystems.textsecuregcm.limits.RateLimiters; +import org.whispersystems.websocket.auth.ReadOnly; @Path("/v1/art") @Tag(name = "Art") @@ -42,7 +43,7 @@ public class ArtController { @GET @Path("/auth") @Produces(MediaType.APPLICATION_JSON) - public ExternalServiceCredentials getAuth(final @Auth AuthenticatedAccount auth) + public ExternalServiceCredentials getAuth(final @ReadOnly @Auth AuthenticatedAccount auth) throws RateLimitExceededException { final UUID uuid = auth.getAccount().getUuid(); rateLimiters.forDescriptor(RateLimiters.For.EXTERNAL_SERVICE_CREDENTIALS).validate(uuid); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV2.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV2.java index e15befe4b..c568c445d 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV2.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV2.java @@ -29,6 +29,7 @@ import org.whispersystems.textsecuregcm.s3.PolicySigner; import org.whispersystems.textsecuregcm.s3.PostPolicyGenerator; import org.whispersystems.textsecuregcm.util.Conversions; import org.whispersystems.textsecuregcm.util.Pair; +import org.whispersystems.websocket.auth.ReadOnly; @Path("/v2/attachments") @Tag(name = "Attachments") @@ -51,7 +52,7 @@ public class AttachmentControllerV2 { @Produces(MediaType.APPLICATION_JSON) @Path("/form/upload") public AttachmentDescriptorV2 getAttachmentUploadForm( - @Auth AuthenticatedAccount auth, + @ReadOnly @Auth AuthenticatedAccount auth, @HeaderParam(HttpHeaders.USER_AGENT) String userAgent) throws RateLimitExceededException { rateLimiter.validate(auth.getAccount().getUuid()); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV3.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV3.java index 2d2c03c5c..1e70ab4a8 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV3.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV3.java @@ -23,6 +23,7 @@ import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.entities.AttachmentDescriptorV3; import org.whispersystems.textsecuregcm.limits.RateLimiter; import org.whispersystems.textsecuregcm.limits.RateLimiters; +import org.whispersystems.websocket.auth.ReadOnly; @Path("/v3/attachments") @Tag(name = "Attachments") @@ -47,7 +48,7 @@ public class AttachmentControllerV3 { @GET @Produces(MediaType.APPLICATION_JSON) @Path("/form/upload") - public AttachmentDescriptorV3 getAttachmentUploadForm(@Auth AuthenticatedAccount auth) + public AttachmentDescriptorV3 getAttachmentUploadForm(@ReadOnly @Auth AuthenticatedAccount auth) throws RateLimitExceededException { rateLimiter.validate(auth.getAccount().getUuid()); final String key = generateAttachmentKey(); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV4.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV4.java index 4ec3f2ca9..e0fd48c4c 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV4.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV4.java @@ -26,6 +26,7 @@ import org.whispersystems.textsecuregcm.entities.AttachmentDescriptorV3; import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager; import org.whispersystems.textsecuregcm.limits.RateLimiter; import org.whispersystems.textsecuregcm.limits.RateLimiters; +import org.whispersystems.websocket.auth.ReadOnly; /** @@ -76,7 +77,7 @@ public class AttachmentControllerV4 { @ApiResponse(responseCode = "429", description = "Too many attempts", headers = @Header( name = "Retry-After", description = "If present, an positive integer indicating the number of seconds before a subsequent attempt could succeed")) - public AttachmentDescriptorV3 getAttachmentUploadForm(@Auth AuthenticatedAccount auth) + public AttachmentDescriptorV3 getAttachmentUploadForm(@ReadOnly @Auth AuthenticatedAccount auth) throws RateLimitExceededException { rateLimiter.validate(auth.getAccount().getUuid()); final String key = generateAttachmentKey(); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/CallLinkController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/CallLinkController.java index 0c55bcd05..d41613aaf 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/CallLinkController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/CallLinkController.java @@ -20,6 +20,7 @@ import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.entities.CreateCallLinkCredential; import org.whispersystems.textsecuregcm.entities.GetCreateCallLinkCredentialsRequest; import org.whispersystems.textsecuregcm.limits.RateLimiters; +import org.whispersystems.websocket.auth.ReadOnly; @Path("/v1/call-link") @io.swagger.v3.oas.annotations.tags.Tag(name = "CallLink") @@ -51,7 +52,7 @@ public class CallLinkController { @ApiResponse(responseCode = "422", description = "Invalid request format.") @ApiResponse(responseCode = "429", description = "Ratelimited.") public CreateCallLinkCredential getCreateAuth( - final @Auth AuthenticatedAccount auth, + final @ReadOnly @Auth AuthenticatedAccount auth, final @NotNull @Valid GetCreateCallLinkCredentialsRequest request ) throws RateLimitExceededException { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/CallRoutingController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/CallRoutingController.java index b365fb410..6135acdab 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/CallRoutingController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/CallRoutingController.java @@ -25,6 +25,7 @@ import org.whispersystems.textsecuregcm.calls.routing.TurnServerOptions; import org.whispersystems.textsecuregcm.calls.routing.TurnCallRouter; import org.whispersystems.textsecuregcm.filters.RemoteAddressFilter; import org.whispersystems.textsecuregcm.limits.RateLimiters; +import org.whispersystems.websocket.auth.ReadOnly; import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name; @@ -64,7 +65,7 @@ public class CallRoutingController { @ApiResponse(responseCode = "422", description = "Invalid request format.") @ApiResponse(responseCode = "429", description = "Ratelimited.") public TurnToken getCallingRelays( - final @Auth AuthenticatedAccount auth, + final @ReadOnly @Auth AuthenticatedAccount auth, @Context ContainerRequestContext requestContext ) throws RateLimitExceededException { UUID aci = auth.getAccount().getUuid(); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/CertificateController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/CertificateController.java index 5be299183..8a8795271 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/CertificateController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/CertificateController.java @@ -39,6 +39,7 @@ import org.whispersystems.textsecuregcm.auth.CertificateGenerator; import org.whispersystems.textsecuregcm.entities.DeliveryCertificate; import org.whispersystems.textsecuregcm.entities.GroupCredentials; import org.whispersystems.textsecuregcm.identity.IdentityType; +import org.whispersystems.websocket.auth.ReadOnly; @SuppressWarnings("OptionalUsedAsFieldOrParameterType") @Path("/v1/certificate") @@ -69,7 +70,7 @@ public class CertificateController { @GET @Produces(MediaType.APPLICATION_JSON) @Path("/delivery") - public DeliveryCertificate getDeliveryCertificate(@Auth AuthenticatedAccount auth, + public DeliveryCertificate getDeliveryCertificate(@ReadOnly @Auth AuthenticatedAccount auth, @QueryParam("includeE164") @DefaultValue("true") boolean includeE164) throws InvalidKeyException { @@ -88,7 +89,7 @@ public class CertificateController { @Produces(MediaType.APPLICATION_JSON) @Path("/auth/group") public GroupCredentials getGroupAuthenticationCredentials( - @Auth AuthenticatedAccount auth, + @ReadOnly @Auth AuthenticatedAccount auth, @QueryParam("redemptionStartSeconds") long startSeconds, @QueryParam("redemptionEndSeconds") long endSeconds, @QueryParam("pniAsServiceId") boolean pniAsServiceId) { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ChallengeController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ChallengeController.java index 360f70f7b..6263f8d30 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ChallengeController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ChallengeController.java @@ -42,6 +42,7 @@ import org.whispersystems.textsecuregcm.spam.Extract; import org.whispersystems.textsecuregcm.spam.FilterSpam; import org.whispersystems.textsecuregcm.spam.PushChallengeConfig; import org.whispersystems.textsecuregcm.spam.ScoreThreshold; +import org.whispersystems.websocket.auth.ReadOnly; @Path("/v1/challenge") @Tag(name = "Challenge") @@ -77,7 +78,7 @@ public class ChallengeController { @ApiResponse(responseCode = "429", description = "Too many attempts", headers = @Header( name = "Retry-After", description = "If present, an positive integer indicating the number of seconds before a subsequent attempt could succeed")) - public Response handleChallengeResponse(@Auth final AuthenticatedAccount auth, + public Response handleChallengeResponse(@ReadOnly @Auth final AuthenticatedAccount auth, @Valid final AnswerChallengeRequest answerRequest, @Context ContainerRequestContext requestContext, @HeaderParam(HttpHeaders.USER_AGENT) final String userAgent, @@ -163,7 +164,7 @@ public class ChallengeController { @ApiResponse(responseCode = "429", description = "Too many attempts", headers = @Header( name = "Retry-After", description = "If present, an positive integer indicating the number of seconds before a subsequent attempt could succeed")) - public Response requestPushChallenge(@Auth final AuthenticatedAccount auth, + public Response requestPushChallenge(@ReadOnly @Auth final AuthenticatedAccount auth, @Extract PushChallengeConfig pushChallengeConfig) { if (!pushChallengeConfig.pushPermitted()) { return Response.status(429).build(); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java index 1b2ea56c8..6e504344b 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java @@ -66,6 +66,8 @@ import org.whispersystems.textsecuregcm.storage.Device.DeviceCapabilities; import org.whispersystems.textsecuregcm.storage.DeviceSpec; import org.whispersystems.textsecuregcm.util.Util; import org.whispersystems.textsecuregcm.util.VerificationCode; +import org.whispersystems.websocket.auth.Mutable; +import org.whispersystems.websocket.auth.ReadOnly; @Path("/v1/devices") @Tag(name = "Devices") @@ -111,7 +113,7 @@ public class DeviceController { @GET @Produces(MediaType.APPLICATION_JSON) - public DeviceInfoList getDevices(@Auth AuthenticatedAccount auth) { + public DeviceInfoList getDevices(@ReadOnly @Auth AuthenticatedAccount auth) { List devices = new LinkedList<>(); for (Device device : auth.getAccount().getDevices()) { @@ -126,7 +128,7 @@ public class DeviceController { @Produces(MediaType.APPLICATION_JSON) @Path("/{device_id}") @ChangesDeviceEnabledState - public void removeDevice(@Auth AuthenticatedAccount auth, @PathParam("device_id") byte deviceId) { + public void removeDevice(@Mutable @Auth AuthenticatedAccount auth, @PathParam("device_id") byte deviceId) { if (auth.getAuthenticatedDevice().getId() != Device.PRIMARY_ID) { throw new WebApplicationException(Response.Status.UNAUTHORIZED); } @@ -141,7 +143,7 @@ public class DeviceController { @GET @Path("/provisioning/code") @Produces(MediaType.APPLICATION_JSON) - public VerificationCode createDeviceToken(@Auth AuthenticatedAccount auth) + public VerificationCode createDeviceToken(@ReadOnly @Auth AuthenticatedAccount auth) throws RateLimitExceededException, DeviceLimitExceededException { final Account account = auth.getAccount(); @@ -258,7 +260,7 @@ public class DeviceController { @PUT @Produces(MediaType.APPLICATION_JSON) @Path("/unauthenticated_delivery") - public void setUnauthenticatedDelivery(@Auth AuthenticatedAccount auth) { + public void setUnauthenticatedDelivery(@Mutable @Auth AuthenticatedAccount auth) { assert (auth.getAuthenticatedDevice() != null); // Deprecated } @@ -266,7 +268,7 @@ public class DeviceController { @PUT @Produces(MediaType.APPLICATION_JSON) @Path("/capabilities") - public void setCapabilities(@Auth AuthenticatedAccount auth, @NotNull @Valid DeviceCapabilities capabilities) { + public void setCapabilities(@Mutable @Auth AuthenticatedAccount auth, @NotNull @Valid DeviceCapabilities capabilities) { assert (auth.getAuthenticatedDevice() != null); final byte deviceId = auth.getAuthenticatedDevice().getId(); accounts.updateDevice(auth.getAccount(), deviceId, d -> d.setCapabilities(capabilities)); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DirectoryV2Controller.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DirectoryV2Controller.java index 5bd862cda..0ee567d4c 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DirectoryV2Controller.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DirectoryV2Controller.java @@ -18,6 +18,7 @@ import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; import org.whispersystems.textsecuregcm.configuration.DirectoryV2ClientConfiguration; +import org.whispersystems.websocket.auth.ReadOnly; @Path("/v2/directory") @Tag(name = "Directory") @@ -47,7 +48,7 @@ public class DirectoryV2Controller { @GET @Path("/auth") @Produces(MediaType.APPLICATION_JSON) - public Response getAuthToken(final @Auth AuthenticatedAccount auth) { + public Response getAuthToken(final @ReadOnly @Auth AuthenticatedAccount auth) { final UUID uuid = auth.getAccount().getUuid(); final ExternalServiceCredentials credentials = directoryServiceTokenGenerator.generateForUuid(uuid); return Response.ok().entity(credentials).build(); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DonationController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DonationController.java index c49b7629b..1c12ad3d6 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DonationController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DonationController.java @@ -36,6 +36,7 @@ import org.whispersystems.textsecuregcm.entities.RedeemReceiptRequest; import org.whispersystems.textsecuregcm.storage.AccountBadge; import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.RedeemedReceiptsManager; +import org.whispersystems.websocket.auth.Mutable; @Path("/v1/donation") @Tag(name = "Donations") @@ -74,7 +75,7 @@ public class DonationController { @Consumes(MediaType.APPLICATION_JSON) @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN}) public CompletionStage redeemReceipt( - @Auth final AuthenticatedAccount auth, + @Mutable @Auth final AuthenticatedAccount auth, @NotNull @Valid final RedeemReceiptRequest request) { return CompletableFuture.supplyAsync(() -> { ReceiptCredentialPresentation receiptCredentialPresentation; diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeepAliveController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeepAliveController.java index 96849ab62..44d0f6893 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeepAliveController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeepAliveController.java @@ -20,6 +20,7 @@ import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil; import org.whispersystems.textsecuregcm.push.ClientPresenceManager; +import org.whispersystems.websocket.auth.ReadOnly; import org.whispersystems.websocket.session.WebSocketSession; import org.whispersystems.websocket.session.WebSocketSessionContext; @@ -40,7 +41,7 @@ public class KeepAliveController { } @GET - public Response getKeepAlive(@Auth Optional maybeAuth, + public Response getKeepAlive(@ReadOnly @Auth Optional maybeAuth, @WebSocketSession WebSocketSessionContext context) { maybeAuth.ifPresent(auth -> { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java index aa881b3df..bd6bbf5b4 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java @@ -63,6 +63,7 @@ import org.whispersystems.textsecuregcm.storage.KeysManager; import org.whispersystems.textsecuregcm.util.HeaderUtils; import org.whispersystems.textsecuregcm.util.Util; import org.whispersystems.textsecuregcm.util.ua.ClientPlatform; +import org.whispersystems.websocket.auth.ReadOnly; @SuppressWarnings("OptionalUsedAsFieldOrParameterType") @Path("/v2/keys") @@ -94,7 +95,7 @@ public class KeysController { description = "Gets the number of one-time prekeys uploaded for this device and still available") @ApiResponse(responseCode = "200", description = "Body contains the number of available one-time prekeys for the device.", useReturnTypeSchema = true) @ApiResponse(responseCode = "401", description = "Account authentication check failed.") - public CompletableFuture getStatus(@Auth final AuthenticatedAccount auth, + public CompletableFuture getStatus(@ReadOnly @Auth final AuthenticatedAccount auth, @QueryParam("identity") @DefaultValue("aci") final IdentityType identityType, @HeaderParam(HttpHeaders.USER_AGENT) final String userAgent) { @@ -137,7 +138,8 @@ public class KeysController { @ApiResponse(responseCode = "401", description = "Account authentication check failed.") @ApiResponse(responseCode = "403", description = "Attempt to change identity key from a non-primary device.") @ApiResponse(responseCode = "422", description = "Invalid request format.") - public CompletableFuture setKeys(@Auth final AuthenticatedAccount auth, + public CompletableFuture setKeys( + @ReadOnly @Auth final AuthenticatedAccount auth, @RequestBody @NotNull @Valid final SetKeysRequest setKeysRequest, @Parameter(allowEmptyValue=true) @@ -230,7 +232,8 @@ public class KeysController { @ApiResponse(responseCode = "429", description = "Rate limit exceeded.", headers = @Header( name = "Retry-After", description = "If present, a positive integer indicating the number of seconds before a subsequent attempt could succeed")) - public PreKeyResponse getDeviceKeys(@Auth Optional auth, + public PreKeyResponse getDeviceKeys( + @ReadOnly @Auth Optional auth, @HeaderParam(HeaderUtils.UNIDENTIFIED_ACCESS_KEY) Optional accessKey, @Parameter(description="the account or phone-number identifier to retrieve keys for") @@ -341,7 +344,8 @@ public class KeysController { @ApiResponse(responseCode = "200", description = "Indicates that new prekey was successfully stored.") @ApiResponse(responseCode = "401", description = "Account authentication check failed.") @ApiResponse(responseCode = "422", description = "Invalid request format.") - public CompletableFuture setSignedKey(@Auth final AuthenticatedAccount auth, + public CompletableFuture setSignedKey( + @ReadOnly @Auth final AuthenticatedAccount auth, @Valid final ECSignedPreKey signedPreKey, @QueryParam("identity") @DefaultValue("aci") final IdentityType identityType) { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java index 7befad701..ee62b1c1d 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java @@ -129,6 +129,7 @@ import org.whispersystems.textsecuregcm.util.HeaderUtils; import org.whispersystems.textsecuregcm.util.Util; import org.whispersystems.textsecuregcm.websocket.WebSocketConnection; import org.whispersystems.websocket.Stories; +import org.whispersystems.websocket.auth.ReadOnly; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; @@ -235,7 +236,7 @@ public class MessageController { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @ManagedAsync - public Response sendMessage(@Auth Optional source, + public Response sendMessage(@ReadOnly @Auth Optional source, @HeaderParam(HeaderUtils.UNIDENTIFIED_ACCESS_KEY) Optional accessKey, @HeaderParam(HttpHeaders.USER_AGENT) String userAgent, @PathParam("destination") ServiceIdentifier destinationIdentifier, @@ -667,7 +668,7 @@ public class MessageController { @Timed @GET @Produces(MediaType.APPLICATION_JSON) - public CompletableFuture getPendingMessages(@Auth AuthenticatedAccount auth, + public CompletableFuture getPendingMessages(@ReadOnly @Auth AuthenticatedAccount auth, @HeaderParam(Stories.X_SIGNAL_RECEIVE_STORIES) String receiveStoriesHeader, @HeaderParam(HttpHeaders.USER_AGENT) String userAgent) { @@ -718,7 +719,7 @@ public class MessageController { @Timed @DELETE @Path("/uuid/{uuid}") - public CompletableFuture removePendingMessage(@Auth AuthenticatedAccount auth, @PathParam("uuid") UUID uuid) { + public CompletableFuture removePendingMessage(@ReadOnly @Auth AuthenticatedAccount auth, @PathParam("uuid") UUID uuid) { return messagesManager.delete( auth.getAccount().getUuid(), auth.getAuthenticatedDevice().getId(), @@ -749,7 +750,7 @@ public class MessageController { @Consumes(MediaType.APPLICATION_JSON) @Path("/report/{source}/{messageGuid}") public Response reportSpamMessage( - @Auth AuthenticatedAccount auth, + @ReadOnly @Auth AuthenticatedAccount auth, @PathParam("source") String source, @PathParam("messageGuid") UUID messageGuid, @Nullable @Valid SpamReport spamReport, diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/PaymentsController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/PaymentsController.java index bd9946d1d..4a00b15de 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/PaymentsController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/PaymentsController.java @@ -17,6 +17,7 @@ import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator import org.whispersystems.textsecuregcm.configuration.PaymentsServiceConfiguration; import org.whispersystems.textsecuregcm.currency.CurrencyConversionManager; import org.whispersystems.textsecuregcm.entities.CurrencyConversionEntityList; +import org.whispersystems.websocket.auth.ReadOnly; @Path("/v1/payments") @Tag(name = "Payments") @@ -42,14 +43,14 @@ public class PaymentsController { @GET @Path("/auth") @Produces(MediaType.APPLICATION_JSON) - public ExternalServiceCredentials getAuth(final @Auth AuthenticatedAccount auth) { + public ExternalServiceCredentials getAuth(final @ReadOnly @Auth AuthenticatedAccount auth) { return paymentsServiceCredentialsGenerator.generateForUuid(auth.getAccount().getUuid()); } @GET @Path("/conversions") @Produces(MediaType.APPLICATION_JSON) - public CurrencyConversionEntityList getConversions(final @Auth AuthenticatedAccount auth) { + public CurrencyConversionEntityList getConversions(final @ReadOnly @Auth AuthenticatedAccount auth) { return currencyManager.getCurrencyConversions().orElseThrow(); } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java index 5f1d83f8a..f061c8756 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java @@ -99,6 +99,8 @@ import org.whispersystems.textsecuregcm.util.HeaderUtils; import org.whispersystems.textsecuregcm.util.Pair; import org.whispersystems.textsecuregcm.util.ProfileHelper; import org.whispersystems.textsecuregcm.util.Util; +import org.whispersystems.websocket.auth.Mutable; +import org.whispersystems.websocket.auth.ReadOnly; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; @@ -162,7 +164,7 @@ public class ProfileController { @PUT @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) - public Response setProfile(@Auth AuthenticatedAccount auth, @NotNull @Valid CreateProfileRequest request) { + public Response setProfile(@Mutable @Auth AuthenticatedAccount auth, @NotNull @Valid CreateProfileRequest request) { final Optional currentProfile = profilesManager.get(auth.getAccount().getUuid(), request.version()); @@ -228,7 +230,7 @@ public class ProfileController { @Path("/{identifier}/{version}") @ManagedAsync public VersionedProfileResponse getProfile( - @Auth Optional auth, + @ReadOnly @Auth Optional auth, @HeaderParam(HeaderUtils.UNIDENTIFIED_ACCESS_KEY) Optional accessKey, @Context ContainerRequestContext containerRequestContext, @PathParam("identifier") AciServiceIdentifier accountIdentifier, @@ -248,7 +250,7 @@ public class ProfileController { @Produces(MediaType.APPLICATION_JSON) @Path("/{identifier}/{version}/{credentialRequest}") public CredentialProfileResponse getProfile( - @Auth Optional auth, + @ReadOnly @Auth Optional auth, @HeaderParam(HeaderUtils.UNIDENTIFIED_ACCESS_KEY) Optional accessKey, @Context ContainerRequestContext containerRequestContext, @PathParam("identifier") AciServiceIdentifier accountIdentifier, @@ -279,7 +281,7 @@ public class ProfileController { @Path("/{identifier}") @ManagedAsync public BaseProfileResponse getUnversionedProfile( - @Auth Optional auth, + @ReadOnly @Auth Optional auth, @HeaderParam(HeaderUtils.UNIDENTIFIED_ACCESS_KEY) Optional accessKey, @Context ContainerRequestContext containerRequestContext, @HeaderParam(HttpHeaders.USER_AGENT) String userAgent, diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProvisioningController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProvisioningController.java index 45228f77f..53b43c777 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProvisioningController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProvisioningController.java @@ -23,6 +23,7 @@ import org.whispersystems.textsecuregcm.entities.ProvisioningMessage; import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.push.ProvisioningManager; import org.whispersystems.textsecuregcm.websocket.ProvisioningAddress; +import org.whispersystems.websocket.auth.ReadOnly; @Path("/v1/provisioning") @Tag(name = "Provisioning") @@ -40,7 +41,7 @@ public class ProvisioningController { @PUT @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) - public void sendProvisioningMessage(@Auth AuthenticatedAccount auth, + public void sendProvisioningMessage(@ReadOnly @Auth AuthenticatedAccount auth, @PathParam("destination") String destinationName, @NotNull @Valid ProvisioningMessage message) throws RateLimitExceededException { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/RemoteConfigController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/RemoteConfigController.java index 65ea8e2e2..8e55c3932 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/RemoteConfigController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/RemoteConfigController.java @@ -28,6 +28,7 @@ import org.whispersystems.textsecuregcm.entities.UserRemoteConfigList; import org.whispersystems.textsecuregcm.storage.RemoteConfigsManager; import org.whispersystems.textsecuregcm.util.Conversions; import org.whispersystems.textsecuregcm.util.Util; +import org.whispersystems.websocket.auth.ReadOnly; @Path("/v1/config") @Tag(name = "Remote Config") @@ -51,7 +52,7 @@ public class RemoteConfigController { @GET @Produces(MediaType.APPLICATION_JSON) - public UserRemoteConfigList getAll(@Auth AuthenticatedAccount auth) { + public UserRemoteConfigList getAll(@ReadOnly @Auth AuthenticatedAccount auth) { try { MessageDigest digest = MessageDigest.getInstance("SHA1"); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureStorageController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureStorageController.java index a65f0aeb8..efa720474 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureStorageController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureStorageController.java @@ -15,6 +15,7 @@ import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration; +import org.whispersystems.websocket.auth.ReadOnly; @Path("/v1/storage") @Tag(name = "Secure Storage") @@ -36,7 +37,7 @@ public class SecureStorageController { @GET @Path("/auth") @Produces(MediaType.APPLICATION_JSON) - public ExternalServiceCredentials getAuth(@Auth AuthenticatedAccount auth) { + public ExternalServiceCredentials getAuth(@ReadOnly @Auth AuthenticatedAccount auth) { return storageServiceCredentialsGenerator.generateForUuid(auth.getAccount().getUuid()); } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureValueRecovery2Controller.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureValueRecovery2Controller.java index 7f3994cc1..9e0e6c51b 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureValueRecovery2Controller.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureValueRecovery2Controller.java @@ -34,6 +34,7 @@ import org.whispersystems.textsecuregcm.limits.RateLimitedByIp; import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; +import org.whispersystems.websocket.auth.ReadOnly; @Path("/v2/backup") @Tag(name = "Secure Value Recovery") @@ -77,7 +78,7 @@ public class SecureValueRecovery2Controller { ) @ApiResponse(responseCode = "200", description = "`JSON` with generated credentials.", useReturnTypeSchema = true) @ApiResponse(responseCode = "401", description = "Account authentication check failed.") - public ExternalServiceCredentials getAuth(@Auth final AuthenticatedAccount auth) { + public ExternalServiceCredentials getAuth(@ReadOnly @Auth final AuthenticatedAccount auth) { return backupServiceCredentialGenerator.generateFor(auth.getAccount().getUuid().toString()); } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureValueRecovery3Controller.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureValueRecovery3Controller.java index b759fce22..7cf8cf28f 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureValueRecovery3Controller.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureValueRecovery3Controller.java @@ -21,6 +21,7 @@ import org.whispersystems.textsecuregcm.limits.RateLimitedByIp; import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; +import org.whispersystems.websocket.auth.ReadOnly; import java.time.Clock; import java.util.List; @@ -78,7 +79,7 @@ public class SecureValueRecovery3Controller { ) @ApiResponse(responseCode = "200", description = "`JSON` with generated credentials.", useReturnTypeSchema = true) @ApiResponse(responseCode = "401", description = "Account authentication check failed.") - public ExternalServiceCredentials getAuth(@Auth final AuthenticatedAccount auth) { + public ExternalServiceCredentials getAuth(@ReadOnly @Auth final AuthenticatedAccount auth) { return backupServiceCredentialGenerator.generateFor(auth.getAccount().getUuid().toString()); } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/StickerController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/StickerController.java index 86ffec4de..37ae7cff2 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/StickerController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/StickerController.java @@ -28,6 +28,7 @@ import org.whispersystems.textsecuregcm.s3.PolicySigner; import org.whispersystems.textsecuregcm.s3.PostPolicyGenerator; import org.whispersystems.textsecuregcm.util.Constants; import org.whispersystems.textsecuregcm.util.Pair; +import org.whispersystems.websocket.auth.ReadOnly; @Path("/v1/sticker") @Tag(name = "Stickers") @@ -46,7 +47,7 @@ public class StickerController { @GET @Produces(MediaType.APPLICATION_JSON) @Path("/pack/form/{count}") - public StickerPackFormUploadAttributes getStickersForm(@Auth AuthenticatedAccount auth, + public StickerPackFormUploadAttributes getStickersForm(@ReadOnly @Auth AuthenticatedAccount auth, @PathParam("count") @Min(1) @Max(201) int stickerCount) throws RateLimitExceededException { rateLimiters.getStickerPackLimiter().validate(auth.getAccount().getUuid()); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java index 33e35cf9c..25e72f688 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java @@ -98,6 +98,7 @@ import org.whispersystems.textsecuregcm.subscriptions.SubscriptionCurrencyUtil; import org.whispersystems.textsecuregcm.subscriptions.SubscriptionProcessor; import org.whispersystems.textsecuregcm.subscriptions.SubscriptionProcessorManager; import org.whispersystems.textsecuregcm.util.ExactlySize; +import org.whispersystems.websocket.auth.ReadOnly; @Path("/v1/subscription") @io.swagger.v3.oas.annotations.tags.Tag(name = "Subscriptions") @@ -224,7 +225,7 @@ public class SubscriptionController { @Path("/{subscriberId}") @Produces(MediaType.APPLICATION_JSON) public CompletableFuture deleteSubscriber( - @Auth Optional authenticatedAccount, + @ReadOnly @Auth Optional authenticatedAccount, @PathParam("subscriberId") String subscriberId) { RequestData requestData = RequestData.process(authenticatedAccount, subscriberId, clock); return subscriptionManager.get(requestData.subscriberUser, requestData.hmac) @@ -246,7 +247,7 @@ public class SubscriptionController { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public CompletableFuture updateSubscriber( - @Auth Optional authenticatedAccount, + @ReadOnly @Auth Optional authenticatedAccount, @PathParam("subscriberId") String subscriberId) { RequestData requestData = RequestData.process(authenticatedAccount, subscriberId, clock); return subscriptionManager.get(requestData.subscriberUser, requestData.hmac) @@ -280,7 +281,7 @@ public class SubscriptionController { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public CompletableFuture createPaymentMethod( - @Auth Optional authenticatedAccount, + @ReadOnly @Auth Optional authenticatedAccount, @PathParam("subscriberId") String subscriberId, @QueryParam("type") @DefaultValue("CARD") PaymentMethod paymentMethodType) { @@ -334,7 +335,7 @@ public class SubscriptionController { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public CompletableFuture createPayPalPaymentMethod( - @Auth Optional authenticatedAccount, + @ReadOnly @Auth Optional authenticatedAccount, @PathParam("subscriberId") String subscriberId, @NotNull @Valid CreatePayPalBillingAgreementRequest request, @Context ContainerRequestContext containerRequestContext) { @@ -399,7 +400,7 @@ public class SubscriptionController { @Produces(MediaType.APPLICATION_JSON) @Deprecated // use /{subscriberId}/default_payment_method/{processor}/{paymentMethodId} public CompletableFuture setDefaultPaymentMethod( - @Auth Optional authenticatedAccount, + @ReadOnly @Auth Optional authenticatedAccount, @PathParam("subscriberId") String subscriberId, @PathParam("paymentMethodId") @NotEmpty String paymentMethodId) { RequestData requestData = RequestData.process(authenticatedAccount, subscriberId, clock); @@ -415,7 +416,7 @@ public class SubscriptionController { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public CompletableFuture setDefaultPaymentMethodWithProcessor( - @Auth Optional authenticatedAccount, + @ReadOnly @Auth Optional authenticatedAccount, @PathParam("subscriberId") String subscriberId, @PathParam("processor") SubscriptionProcessor processor, @PathParam("paymentMethodToken") @NotEmpty String paymentMethodToken) { @@ -446,7 +447,7 @@ public class SubscriptionController { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public CompletableFuture setSubscriptionLevel( - @Auth Optional authenticatedAccount, + @ReadOnly @Auth Optional authenticatedAccount, @PathParam("subscriberId") String subscriberId, @PathParam("level") long level, @PathParam("currency") String currency, @@ -873,7 +874,7 @@ public class SubscriptionController { @Path("/{subscriberId}") @Produces(MediaType.APPLICATION_JSON) public CompletableFuture getSubscriptionInformation( - @Auth Optional authenticatedAccount, + @ReadOnly @Auth Optional authenticatedAccount, @PathParam("subscriberId") String subscriberId) { RequestData requestData = RequestData.process(authenticatedAccount, subscriberId, clock); return subscriptionManager.get(requestData.subscriberUser, requestData.hmac) @@ -916,7 +917,7 @@ public class SubscriptionController { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public CompletableFuture createSubscriptionReceiptCredentials( - @Auth Optional authenticatedAccount, + @ReadOnly @Auth Optional authenticatedAccount, @HeaderParam(HttpHeaders.USER_AGENT) final String userAgent, @PathParam("subscriberId") String subscriberId, @NotNull @Valid GetReceiptCredentialsRequest request) { @@ -965,7 +966,7 @@ public class SubscriptionController { @Path("/{subscriberId}/default_payment_method_for_ideal/{setupIntentId}") @Produces(MediaType.APPLICATION_JSON) public CompletableFuture setDefaultPaymentMethodForIdeal( - @Auth Optional authenticatedAccount, + @ReadOnly @Auth Optional authenticatedAccount, @PathParam("subscriberId") String subscriberId, @PathParam("setupIntentId") @NotEmpty String setupIntentId) { RequestData requestData = RequestData.process(authenticatedAccount, subscriberId, clock);