Annotate authenticated endpoints with `@ReadOnly` or `@Mutable`

This commit is contained in:
Ravi Khadiwala 2024-02-14 16:54:42 -06:00 committed by ravi-signal
parent 26ffa19f36
commit df69d9f195
26 changed files with 119 additions and 83 deletions

View File

@ -65,6 +65,8 @@ import org.whispersystems.textsecuregcm.util.ExceptionUtils;
import org.whispersystems.textsecuregcm.util.HeaderUtils; import org.whispersystems.textsecuregcm.util.HeaderUtils;
import org.whispersystems.textsecuregcm.util.UsernameHashZkProofVerifier; import org.whispersystems.textsecuregcm.util.UsernameHashZkProofVerifier;
import org.whispersystems.textsecuregcm.util.Util; import org.whispersystems.textsecuregcm.util.Util;
import org.whispersystems.websocket.auth.Mutable;
import org.whispersystems.websocket.auth.ReadOnly;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType") @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Path("/v1/accounts") @Path("/v1/accounts")
@ -97,7 +99,7 @@ public class AccountController {
@GET @GET
@Path("/turn/") @Path("/turn/")
@Produces(MediaType.APPLICATION_JSON) @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()); rateLimiters.getTurnLimiter().validate(auth.getAccount().getUuid());
return turnTokenGenerator.generate(auth.getAccount().getUuid()); return turnTokenGenerator.generate(auth.getAccount().getUuid());
} }
@ -107,7 +109,7 @@ public class AccountController {
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@ChangesDeviceEnabledState @ChangesDeviceEnabledState
public void setGcmRegistrationId(@Auth AuthenticatedAccount auth, public void setGcmRegistrationId(@Mutable @Auth AuthenticatedAccount auth,
@NotNull @Valid GcmRegistrationId registrationId) { @NotNull @Valid GcmRegistrationId registrationId) {
final Account account = auth.getAccount(); final Account account = auth.getAccount();
@ -128,7 +130,7 @@ public class AccountController {
@DELETE @DELETE
@Path("/gcm/") @Path("/gcm/")
@ChangesDeviceEnabledState @ChangesDeviceEnabledState
public void deleteGcmRegistrationId(@Auth AuthenticatedAccount auth) { public void deleteGcmRegistrationId(@Mutable @Auth AuthenticatedAccount auth) {
Account account = auth.getAccount(); Account account = auth.getAccount();
Device device = auth.getAuthenticatedDevice(); Device device = auth.getAuthenticatedDevice();
@ -144,7 +146,7 @@ public class AccountController {
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@ChangesDeviceEnabledState @ChangesDeviceEnabledState
public void setApnRegistrationId(@Auth AuthenticatedAccount auth, public void setApnRegistrationId(@Mutable @Auth AuthenticatedAccount auth,
@NotNull @Valid ApnRegistrationId registrationId) { @NotNull @Valid ApnRegistrationId registrationId) {
final Account account = auth.getAccount(); final Account account = auth.getAccount();
@ -167,7 +169,7 @@ public class AccountController {
@DELETE @DELETE
@Path("/apn/") @Path("/apn/")
@ChangesDeviceEnabledState @ChangesDeviceEnabledState
public void deleteApnRegistrationId(@Auth AuthenticatedAccount auth) { public void deleteApnRegistrationId(@Mutable @Auth AuthenticatedAccount auth) {
Account account = auth.getAccount(); Account account = auth.getAccount();
Device device = auth.getAuthenticatedDevice(); Device device = auth.getAuthenticatedDevice();
@ -186,7 +188,7 @@ public class AccountController {
@PUT @PUT
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Path("/registration_lock") @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()); SaltedTokenHash credentials = SaltedTokenHash.generateFor(accountLock.getRegistrationLock());
accounts.update(auth.getAccount(), accounts.update(auth.getAccount(),
@ -195,13 +197,13 @@ public class AccountController {
@DELETE @DELETE
@Path("/registration_lock") @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)); accounts.update(auth.getAccount(), a -> a.setRegistrationLock(null, null));
} }
@PUT @PUT
@Path("/name/") @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(); Account account = auth.getAccount();
Device device = auth.getAuthenticatedDevice(); Device device = auth.getAuthenticatedDevice();
accounts.updateDevice(account, device.getId(), d -> d.setName(deviceName.getDeviceName())); accounts.updateDevice(account, device.getId(), d -> d.setName(deviceName.getDeviceName()));
@ -213,7 +215,7 @@ public class AccountController {
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@ChangesDeviceEnabledState @ChangesDeviceEnabledState
public void setAccountAttributes( public void setAccountAttributes(
@Auth AuthenticatedAccount auth, @Mutable @Auth AuthenticatedAccount auth,
@HeaderParam(HeaderUtils.X_SIGNAL_AGENT) String userAgent, @HeaderParam(HeaderUtils.X_SIGNAL_AGENT) String userAgent,
@NotNull @Valid AccountAttributes attributes) { @NotNull @Valid AccountAttributes attributes) {
final Account account = auth.getAccount(); final Account account = auth.getAccount();
@ -243,14 +245,14 @@ public class AccountController {
@Path("/me") @Path("/me")
@Deprecated() // use whoami @Deprecated() // use whoami
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public AccountIdentityResponse getMe(@Auth AuthenticatedAccount auth) { public AccountIdentityResponse getMe(@ReadOnly @Auth AuthenticatedAccount auth) {
return buildAccountIdentityResponse(auth); return buildAccountIdentityResponse(auth);
} }
@GET @GET
@Path("/whoami") @Path("/whoami")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public AccountIdentityResponse whoAmI(@Auth AuthenticatedAccount auth) { public AccountIdentityResponse whoAmI(@ReadOnly @Auth AuthenticatedAccount auth) {
return buildAccountIdentityResponse(auth); return buildAccountIdentityResponse(auth);
} }
@ -273,7 +275,7 @@ public class AccountController {
) )
@ApiResponse(responseCode = "204", description = "Username successfully deleted.", useReturnTypeSchema = true) @ApiResponse(responseCode = "204", description = "Username successfully deleted.", useReturnTypeSchema = true)
@ApiResponse(responseCode = "401", description = "Account authentication check failed.") @ApiResponse(responseCode = "401", description = "Account authentication check failed.")
public CompletableFuture<Response> deleteUsernameHash(@Auth final AuthenticatedAccount auth) { public CompletableFuture<Response> deleteUsernameHash(@Mutable @Auth final AuthenticatedAccount auth) {
return accounts.clearUsernameHash(auth.getAccount()) return accounts.clearUsernameHash(auth.getAccount())
.thenApply(Util.ASYNC_EMPTY_RESPONSE); .thenApply(Util.ASYNC_EMPTY_RESPONSE);
} }
@ -295,7 +297,7 @@ public class AccountController {
@ApiResponse(responseCode = "422", description = "Invalid request format.") @ApiResponse(responseCode = "422", description = "Invalid request format.")
@ApiResponse(responseCode = "429", description = "Ratelimited.") @ApiResponse(responseCode = "429", description = "Ratelimited.")
public CompletableFuture<ReserveUsernameHashResponse> reserveUsernameHash( public CompletableFuture<ReserveUsernameHashResponse> reserveUsernameHash(
@Auth final AuthenticatedAccount auth, @Mutable @Auth final AuthenticatedAccount auth,
@NotNull @Valid final ReserveUsernameHashRequest usernameRequest) throws RateLimitExceededException { @NotNull @Valid final ReserveUsernameHashRequest usernameRequest) throws RateLimitExceededException {
rateLimiters.getUsernameReserveLimiter().validate(auth.getAccount().getUuid()); rateLimiters.getUsernameReserveLimiter().validate(auth.getAccount().getUuid());
@ -335,7 +337,7 @@ public class AccountController {
@ApiResponse(responseCode = "422", description = "Invalid request format.") @ApiResponse(responseCode = "422", description = "Invalid request format.")
@ApiResponse(responseCode = "429", description = "Ratelimited.") @ApiResponse(responseCode = "429", description = "Ratelimited.")
public CompletableFuture<UsernameHashResponse> confirmUsernameHash( public CompletableFuture<UsernameHashResponse> confirmUsernameHash(
@Auth final AuthenticatedAccount auth, @Mutable @Auth final AuthenticatedAccount auth,
@NotNull @Valid final ConfirmUsernameHashRequest confirmRequest) { @NotNull @Valid final ConfirmUsernameHashRequest confirmRequest) {
try { try {
@ -380,7 +382,7 @@ public class AccountController {
@ApiResponse(responseCode = "400", description = "Request must not be authenticated.") @ApiResponse(responseCode = "400", description = "Request must not be authenticated.")
@ApiResponse(responseCode = "404", description = "Account not found for the given username.") @ApiResponse(responseCode = "404", description = "Account not found for the given username.")
public CompletableFuture<AccountIdentifierResponse> lookupUsernameHash( public CompletableFuture<AccountIdentifierResponse> lookupUsernameHash(
@Auth final Optional<AuthenticatedAccount> maybeAuthenticatedAccount, @ReadOnly @Auth final Optional<AuthenticatedAccount> maybeAuthenticatedAccount,
@PathParam("usernameHash") final String usernameHash) { @PathParam("usernameHash") final String usernameHash) {
requireNotAuthenticated(maybeAuthenticatedAccount); requireNotAuthenticated(maybeAuthenticatedAccount);
@ -419,7 +421,7 @@ public class AccountController {
@ApiResponse(responseCode = "422", description = "Invalid request format.") @ApiResponse(responseCode = "422", description = "Invalid request format.")
@ApiResponse(responseCode = "429", description = "Ratelimited.") @ApiResponse(responseCode = "429", description = "Ratelimited.")
public UsernameLinkHandle updateUsernameLink( public UsernameLinkHandle updateUsernameLink(
@Auth final AuthenticatedAccount auth, @Mutable @Auth final AuthenticatedAccount auth,
@NotNull @Valid final EncryptedUsername encryptedUsername) throws RateLimitExceededException { @NotNull @Valid final EncryptedUsername encryptedUsername) throws RateLimitExceededException {
// check ratelimiter for username link operations // check ratelimiter for username link operations
rateLimiters.forDescriptor(RateLimiters.For.USERNAME_LINK_OPERATION).validate(auth.getAccount().getUuid()); 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 = "204", description = "Username Link successfully deleted.", useReturnTypeSchema = true)
@ApiResponse(responseCode = "401", description = "Account authentication check failed.") @ApiResponse(responseCode = "401", description = "Account authentication check failed.")
@ApiResponse(responseCode = "429", description = "Ratelimited.") @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 // check ratelimiter for username link operations
rateLimiters.forDescriptor(RateLimiters.For.USERNAME_LINK_OPERATION).validate(auth.getAccount().getUuid()); rateLimiters.forDescriptor(RateLimiters.For.USERNAME_LINK_OPERATION).validate(auth.getAccount().getUuid());
clearUsernameLink(auth.getAccount()); clearUsernameLink(auth.getAccount());
@ -476,7 +478,7 @@ public class AccountController {
@ApiResponse(responseCode = "422", description = "Invalid request format.") @ApiResponse(responseCode = "422", description = "Invalid request format.")
@ApiResponse(responseCode = "429", description = "Ratelimited.") @ApiResponse(responseCode = "429", description = "Ratelimited.")
public CompletableFuture<EncryptedUsername> lookupUsernameLink( public CompletableFuture<EncryptedUsername> lookupUsernameLink(
@Auth final Optional<AuthenticatedAccount> maybeAuthenticatedAccount, @ReadOnly @Auth final Optional<AuthenticatedAccount> maybeAuthenticatedAccount,
@PathParam("uuid") final UUID usernameLinkHandle) { @PathParam("uuid") final UUID usernameLinkHandle) {
requireNotAuthenticated(maybeAuthenticatedAccount); requireNotAuthenticated(maybeAuthenticatedAccount);
@ -502,7 +504,7 @@ public class AccountController {
@Path("/account/{identifier}") @Path("/account/{identifier}")
@RateLimitedByIp(RateLimiters.For.CHECK_ACCOUNT_EXISTENCE) @RateLimitedByIp(RateLimiters.For.CHECK_ACCOUNT_EXISTENCE)
public Response accountExists( public Response accountExists(
@Auth final Optional<AuthenticatedAccount> authenticatedAccount, @ReadOnly @Auth final Optional<AuthenticatedAccount> authenticatedAccount,
@Parameter(description = "An ACI or PNI account identifier to check") @Parameter(description = "An ACI or PNI account identifier to check")
@PathParam("identifier") final ServiceIdentifier accountIdentifier) { @PathParam("identifier") final ServiceIdentifier accountIdentifier) {
@ -517,7 +519,7 @@ public class AccountController {
@DELETE @DELETE
@Path("/me") @Path("/me")
public CompletableFuture<Response> deleteAccount(@Auth AuthenticatedAccount auth) { public CompletableFuture<Response> deleteAccount(@Mutable @Auth AuthenticatedAccount auth) {
return accounts.delete(auth.getAccount(), AccountsManager.DeletionReason.USER_REQUEST).thenApply(Util.ASYNC_EMPTY_RESPONSE); return accounts.delete(auth.getAccount(), AccountsManager.DeletionReason.USER_REQUEST).thenApply(Util.ASYNC_EMPTY_RESPONSE);
} }

View File

@ -58,6 +58,8 @@ import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
import org.whispersystems.textsecuregcm.util.ua.UnrecognizedUserAgentException; import org.whispersystems.textsecuregcm.util.ua.UnrecognizedUserAgentException;
import org.whispersystems.textsecuregcm.util.ua.UserAgent; import org.whispersystems.textsecuregcm.util.ua.UserAgent;
import org.whispersystems.textsecuregcm.util.ua.UserAgentUtil; import org.whispersystems.textsecuregcm.util.ua.UserAgentUtil;
import org.whispersystems.websocket.auth.Mutable;
import org.whispersystems.websocket.auth.ReadOnly;
@Path("/v2/accounts") @Path("/v2/accounts")
@io.swagger.v3.oas.annotations.tags.Tag(name = "Account") @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( @ApiResponse(responseCode = "429", description = "Too many attempts", headers = @Header(
name = "Retry-After", name = "Retry-After",
description = "If present, an positive integer indicating the number of seconds before a subsequent attempt could succeed")) 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) @NotNull @Valid final ChangeNumberRequest request, @HeaderParam(HttpHeaders.USER_AGENT) final String userAgentString)
throws RateLimitExceededException, InterruptedException { throws RateLimitExceededException, InterruptedException {
@ -191,7 +193,8 @@ public class AccountControllerV2 {
content = @Content(schema = @Schema(implementation = MismatchedDevices.class))) 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.", @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))) 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) { @NotNull @Valid final PhoneNumberIdentityKeyDistributionRequest request) {
if (!authenticatedAccount.getAuthenticatedDevice().isPrimary()) { if (!authenticatedAccount.getAuthenticatedDevice().isPrimary()) {
@ -234,7 +237,7 @@ public class AccountControllerV2 {
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public void setPhoneNumberDiscoverability( public void setPhoneNumberDiscoverability(
@Auth AuthenticatedAccount auth, @Mutable @Auth AuthenticatedAccount auth,
@NotNull @Valid PhoneNumberDiscoverabilityRequest phoneNumberDiscoverability @NotNull @Valid PhoneNumberDiscoverabilityRequest phoneNumberDiscoverability
) { ) {
accountsManager.update(auth.getAccount(), a -> a.setDiscoverableByPhoneNumber( accountsManager.update(auth.getAccount(), a -> a.setDiscoverableByPhoneNumber(
@ -248,7 +251,7 @@ public class AccountControllerV2 {
@ApiResponse(responseCode = "200", @ApiResponse(responseCode = "200",
description = "Response with data report. A plain text representation is a field in the response.", description = "Response with data report. A plain text representation is a field in the response.",
useReturnTypeSchema = true) useReturnTypeSchema = true)
public AccountDataReportResponse getAccountDataReport(@Auth final AuthenticatedAccount auth) { public AccountDataReportResponse getAccountDataReport(@ReadOnly @Auth final AuthenticatedAccount auth) {
final Account account = auth.getAccount(); final Account account = auth.getAccount();

View File

@ -62,6 +62,8 @@ import org.whispersystems.textsecuregcm.util.ECPublicKeyAdapter;
import org.whispersystems.textsecuregcm.util.ExactlySize; import org.whispersystems.textsecuregcm.util.ExactlySize;
import org.whispersystems.textsecuregcm.util.ExceptionUtils; import org.whispersystems.textsecuregcm.util.ExceptionUtils;
import org.whispersystems.textsecuregcm.util.Util; 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.Flux;
import reactor.core.publisher.Mono; 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 = "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") @ApiResponse(responseCode = "429", description = "Rate limited. Too many attempts to change the backup-id have been made")
public CompletionStage<Response> setBackupId( public CompletionStage<Response> setBackupId(
@Auth final AuthenticatedAccount account, @Mutable @Auth final AuthenticatedAccount account,
@Valid @NotNull final SetBackupIdRequest setBackupIdRequest) throws RateLimitExceededException { @Valid @NotNull final SetBackupIdRequest setBackupIdRequest) throws RateLimitExceededException {
return this.backupAuthManager return this.backupAuthManager
.commitBackupId(account.getAccount(), setBackupIdRequest.backupAuthCredentialRequest) .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 = "404", description = "Could not find an existing blinded backup id")
@ApiResponse(responseCode = "429", description = "Rate limited.") @ApiResponse(responseCode = "429", description = "Rate limited.")
public CompletionStage<BackupAuthCredentialsResponse> getBackupZKCredentials( public CompletionStage<BackupAuthCredentialsResponse> getBackupZKCredentials(
@Auth AuthenticatedAccount auth, @ReadOnly @Auth AuthenticatedAccount auth,
@NotNull @QueryParam("redemptionStartSeconds") Long startSeconds, @NotNull @QueryParam("redemptionStartSeconds") Long startSeconds,
@NotNull @QueryParam("redemptionEndSeconds") Long endSeconds) { @NotNull @QueryParam("redemptionEndSeconds") Long endSeconds) {
@ -212,7 +214,7 @@ public class ArchiveController {
@ApiResponse(responseCode = "429", description = "Rate limited.") @ApiResponse(responseCode = "429", description = "Rate limited.")
@ApiResponseZkAuth @ApiResponseZkAuth
public CompletionStage<ReadAuthResponse> readAuth( public CompletionStage<ReadAuthResponse> readAuth(
@Auth final Optional<AuthenticatedAccount> account, @ReadOnly @Auth final Optional<AuthenticatedAccount> account,
@Parameter(description = BackupAuthCredentialPresentationHeader.DESCRIPTION, schema = @Schema(implementation = String.class)) @Parameter(description = BackupAuthCredentialPresentationHeader.DESCRIPTION, schema = @Schema(implementation = String.class))
@NotNull @NotNull
@ -256,7 +258,7 @@ public class ArchiveController {
@ApiResponse(responseCode = "429", description = "Rate limited.") @ApiResponse(responseCode = "429", description = "Rate limited.")
@ApiResponseZkAuth @ApiResponseZkAuth
public CompletionStage<BackupInfoResponse> backupInfo( public CompletionStage<BackupInfoResponse> backupInfo(
@Auth final Optional<AuthenticatedAccount> account, @ReadOnly @Auth final Optional<AuthenticatedAccount> account,
@Parameter(description = BackupAuthCredentialPresentationHeader.DESCRIPTION, schema = @Schema(implementation = String.class)) @Parameter(description = BackupAuthCredentialPresentationHeader.DESCRIPTION, schema = @Schema(implementation = String.class))
@NotNull @NotNull
@ -300,7 +302,7 @@ public class ArchiveController {
@ApiResponse(responseCode = "204", description = "The public key was set") @ApiResponse(responseCode = "204", description = "The public key was set")
@ApiResponse(responseCode = "429", description = "Rate limited.") @ApiResponse(responseCode = "429", description = "Rate limited.")
public CompletionStage<Response> setPublicKey( public CompletionStage<Response> setPublicKey(
@Auth final Optional<AuthenticatedAccount> account, @ReadOnly @Auth final Optional<AuthenticatedAccount> account,
@Parameter(description = BackupAuthCredentialPresentationHeader.DESCRIPTION, schema = @Schema(implementation = String.class)) @Parameter(description = BackupAuthCredentialPresentationHeader.DESCRIPTION, schema = @Schema(implementation = String.class))
@NotNull @NotNull
@ -337,7 +339,7 @@ public class ArchiveController {
@ApiResponse(responseCode = "429", description = "Rate limited.") @ApiResponse(responseCode = "429", description = "Rate limited.")
@ApiResponseZkAuth @ApiResponseZkAuth
public CompletionStage<MessageBackupResponse> backup( public CompletionStage<MessageBackupResponse> backup(
@Auth final Optional<AuthenticatedAccount> account, @ReadOnly @Auth final Optional<AuthenticatedAccount> account,
@Parameter(description = BackupAuthCredentialPresentationHeader.DESCRIPTION, schema = @Schema(implementation = String.class)) @Parameter(description = BackupAuthCredentialPresentationHeader.DESCRIPTION, schema = @Schema(implementation = String.class))
@NotNull @NotNull
@ -425,7 +427,8 @@ public class ArchiveController {
@ApiResponse(responseCode = "410", description = "The source object was not found.") @ApiResponse(responseCode = "410", description = "The source object was not found.")
@ApiResponse(responseCode = "429", description = "Rate limited.") @ApiResponse(responseCode = "429", description = "Rate limited.")
@ApiResponseZkAuth @ApiResponseZkAuth
public CompletionStage<CopyMediaResponse> copyMedia(@Auth final Optional<AuthenticatedAccount> account, public CompletionStage<CopyMediaResponse> copyMedia(
@ReadOnly @Auth final Optional<AuthenticatedAccount> account,
@Parameter(description = BackupAuthCredentialPresentationHeader.DESCRIPTION, schema = @Schema(implementation = String.class)) @Parameter(description = BackupAuthCredentialPresentationHeader.DESCRIPTION, schema = @Schema(implementation = String.class))
@NotNull @NotNull
@ -533,7 +536,7 @@ public class ArchiveController {
@ApiResponse(responseCode = "429", description = "Rate limited.") @ApiResponse(responseCode = "429", description = "Rate limited.")
@ApiResponseZkAuth @ApiResponseZkAuth
public CompletionStage<Response> copyMedia( public CompletionStage<Response> copyMedia(
@Auth final Optional<AuthenticatedAccount> account, @ReadOnly @Auth final Optional<AuthenticatedAccount> account,
@Parameter(description = BackupAuthCredentialPresentationHeader.DESCRIPTION, schema = @Schema(implementation = String.class)) @Parameter(description = BackupAuthCredentialPresentationHeader.DESCRIPTION, schema = @Schema(implementation = String.class))
@NotNull @NotNull
@ -599,7 +602,7 @@ public class ArchiveController {
@ApiResponse(responseCode = "429", description = "Rate limited.") @ApiResponse(responseCode = "429", description = "Rate limited.")
@ApiResponseZkAuth @ApiResponseZkAuth
public CompletionStage<Response> refresh( public CompletionStage<Response> refresh(
@Auth final Optional<AuthenticatedAccount> account, @ReadOnly @Auth final Optional<AuthenticatedAccount> account,
@Parameter(description = BackupAuthCredentialPresentationHeader.DESCRIPTION, schema = @Schema(implementation = String.class)) @Parameter(description = BackupAuthCredentialPresentationHeader.DESCRIPTION, schema = @Schema(implementation = String.class))
@NotNull @NotNull
@ -656,7 +659,7 @@ public class ArchiveController {
@ApiResponse(responseCode = "429", description = "Rate limited.") @ApiResponse(responseCode = "429", description = "Rate limited.")
@ApiResponseZkAuth @ApiResponseZkAuth
public CompletionStage<ListResponse> listMedia( public CompletionStage<ListResponse> listMedia(
@Auth final Optional<AuthenticatedAccount> account, @ReadOnly @Auth final Optional<AuthenticatedAccount> account,
@Parameter(description = BackupAuthCredentialPresentationHeader.DESCRIPTION, schema = @Schema(implementation = String.class)) @Parameter(description = BackupAuthCredentialPresentationHeader.DESCRIPTION, schema = @Schema(implementation = String.class))
@NotNull @NotNull
@ -709,7 +712,7 @@ public class ArchiveController {
@ApiResponse(responseCode = "429", description = "Rate limited.") @ApiResponse(responseCode = "429", description = "Rate limited.")
@ApiResponseZkAuth @ApiResponseZkAuth
public CompletionStage<Response> deleteMedia( public CompletionStage<Response> deleteMedia(
@Auth final Optional<AuthenticatedAccount> account, @ReadOnly @Auth final Optional<AuthenticatedAccount> account,
@Parameter(description = BackupAuthCredentialPresentationHeader.DESCRIPTION, schema = @Schema(implementation = String.class)) @Parameter(description = BackupAuthCredentialPresentationHeader.DESCRIPTION, schema = @Schema(implementation = String.class))
@NotNull @NotNull

View File

@ -17,6 +17,7 @@ import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.configuration.ArtServiceConfiguration; import org.whispersystems.textsecuregcm.configuration.ArtServiceConfiguration;
import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.websocket.auth.ReadOnly;
@Path("/v1/art") @Path("/v1/art")
@Tag(name = "Art") @Tag(name = "Art")
@ -42,7 +43,7 @@ public class ArtController {
@GET @GET
@Path("/auth") @Path("/auth")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public ExternalServiceCredentials getAuth(final @Auth AuthenticatedAccount auth) public ExternalServiceCredentials getAuth(final @ReadOnly @Auth AuthenticatedAccount auth)
throws RateLimitExceededException { throws RateLimitExceededException {
final UUID uuid = auth.getAccount().getUuid(); final UUID uuid = auth.getAccount().getUuid();
rateLimiters.forDescriptor(RateLimiters.For.EXTERNAL_SERVICE_CREDENTIALS).validate(uuid); rateLimiters.forDescriptor(RateLimiters.For.EXTERNAL_SERVICE_CREDENTIALS).validate(uuid);

View File

@ -29,6 +29,7 @@ import org.whispersystems.textsecuregcm.s3.PolicySigner;
import org.whispersystems.textsecuregcm.s3.PostPolicyGenerator; import org.whispersystems.textsecuregcm.s3.PostPolicyGenerator;
import org.whispersystems.textsecuregcm.util.Conversions; import org.whispersystems.textsecuregcm.util.Conversions;
import org.whispersystems.textsecuregcm.util.Pair; import org.whispersystems.textsecuregcm.util.Pair;
import org.whispersystems.websocket.auth.ReadOnly;
@Path("/v2/attachments") @Path("/v2/attachments")
@Tag(name = "Attachments") @Tag(name = "Attachments")
@ -51,7 +52,7 @@ public class AttachmentControllerV2 {
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Path("/form/upload") @Path("/form/upload")
public AttachmentDescriptorV2 getAttachmentUploadForm( public AttachmentDescriptorV2 getAttachmentUploadForm(
@Auth AuthenticatedAccount auth, @ReadOnly @Auth AuthenticatedAccount auth,
@HeaderParam(HttpHeaders.USER_AGENT) String userAgent) @HeaderParam(HttpHeaders.USER_AGENT) String userAgent)
throws RateLimitExceededException { throws RateLimitExceededException {
rateLimiter.validate(auth.getAccount().getUuid()); rateLimiter.validate(auth.getAccount().getUuid());

View File

@ -23,6 +23,7 @@ import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.entities.AttachmentDescriptorV3; import org.whispersystems.textsecuregcm.entities.AttachmentDescriptorV3;
import org.whispersystems.textsecuregcm.limits.RateLimiter; import org.whispersystems.textsecuregcm.limits.RateLimiter;
import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.websocket.auth.ReadOnly;
@Path("/v3/attachments") @Path("/v3/attachments")
@Tag(name = "Attachments") @Tag(name = "Attachments")
@ -47,7 +48,7 @@ public class AttachmentControllerV3 {
@GET @GET
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Path("/form/upload") @Path("/form/upload")
public AttachmentDescriptorV3 getAttachmentUploadForm(@Auth AuthenticatedAccount auth) public AttachmentDescriptorV3 getAttachmentUploadForm(@ReadOnly @Auth AuthenticatedAccount auth)
throws RateLimitExceededException { throws RateLimitExceededException {
rateLimiter.validate(auth.getAccount().getUuid()); rateLimiter.validate(auth.getAccount().getUuid());
final String key = generateAttachmentKey(); final String key = generateAttachmentKey();

View File

@ -26,6 +26,7 @@ import org.whispersystems.textsecuregcm.entities.AttachmentDescriptorV3;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager; import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.limits.RateLimiter; import org.whispersystems.textsecuregcm.limits.RateLimiter;
import org.whispersystems.textsecuregcm.limits.RateLimiters; 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( @ApiResponse(responseCode = "429", description = "Too many attempts", headers = @Header(
name = "Retry-After", name = "Retry-After",
description = "If present, an positive integer indicating the number of seconds before a subsequent attempt could succeed")) 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 { throws RateLimitExceededException {
rateLimiter.validate(auth.getAccount().getUuid()); rateLimiter.validate(auth.getAccount().getUuid());
final String key = generateAttachmentKey(); final String key = generateAttachmentKey();

View File

@ -20,6 +20,7 @@ import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.entities.CreateCallLinkCredential; import org.whispersystems.textsecuregcm.entities.CreateCallLinkCredential;
import org.whispersystems.textsecuregcm.entities.GetCreateCallLinkCredentialsRequest; import org.whispersystems.textsecuregcm.entities.GetCreateCallLinkCredentialsRequest;
import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.websocket.auth.ReadOnly;
@Path("/v1/call-link") @Path("/v1/call-link")
@io.swagger.v3.oas.annotations.tags.Tag(name = "CallLink") @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 = "422", description = "Invalid request format.")
@ApiResponse(responseCode = "429", description = "Ratelimited.") @ApiResponse(responseCode = "429", description = "Ratelimited.")
public CreateCallLinkCredential getCreateAuth( public CreateCallLinkCredential getCreateAuth(
final @Auth AuthenticatedAccount auth, final @ReadOnly @Auth AuthenticatedAccount auth,
final @NotNull @Valid GetCreateCallLinkCredentialsRequest request final @NotNull @Valid GetCreateCallLinkCredentialsRequest request
) throws RateLimitExceededException { ) throws RateLimitExceededException {

View File

@ -25,6 +25,7 @@ import org.whispersystems.textsecuregcm.calls.routing.TurnServerOptions;
import org.whispersystems.textsecuregcm.calls.routing.TurnCallRouter; import org.whispersystems.textsecuregcm.calls.routing.TurnCallRouter;
import org.whispersystems.textsecuregcm.filters.RemoteAddressFilter; import org.whispersystems.textsecuregcm.filters.RemoteAddressFilter;
import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.websocket.auth.ReadOnly;
import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name; import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
@ -64,7 +65,7 @@ public class CallRoutingController {
@ApiResponse(responseCode = "422", description = "Invalid request format.") @ApiResponse(responseCode = "422", description = "Invalid request format.")
@ApiResponse(responseCode = "429", description = "Ratelimited.") @ApiResponse(responseCode = "429", description = "Ratelimited.")
public TurnToken getCallingRelays( public TurnToken getCallingRelays(
final @Auth AuthenticatedAccount auth, final @ReadOnly @Auth AuthenticatedAccount auth,
@Context ContainerRequestContext requestContext @Context ContainerRequestContext requestContext
) throws RateLimitExceededException { ) throws RateLimitExceededException {
UUID aci = auth.getAccount().getUuid(); UUID aci = auth.getAccount().getUuid();

View File

@ -39,6 +39,7 @@ import org.whispersystems.textsecuregcm.auth.CertificateGenerator;
import org.whispersystems.textsecuregcm.entities.DeliveryCertificate; import org.whispersystems.textsecuregcm.entities.DeliveryCertificate;
import org.whispersystems.textsecuregcm.entities.GroupCredentials; import org.whispersystems.textsecuregcm.entities.GroupCredentials;
import org.whispersystems.textsecuregcm.identity.IdentityType; import org.whispersystems.textsecuregcm.identity.IdentityType;
import org.whispersystems.websocket.auth.ReadOnly;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType") @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Path("/v1/certificate") @Path("/v1/certificate")
@ -69,7 +70,7 @@ public class CertificateController {
@GET @GET
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Path("/delivery") @Path("/delivery")
public DeliveryCertificate getDeliveryCertificate(@Auth AuthenticatedAccount auth, public DeliveryCertificate getDeliveryCertificate(@ReadOnly @Auth AuthenticatedAccount auth,
@QueryParam("includeE164") @DefaultValue("true") boolean includeE164) @QueryParam("includeE164") @DefaultValue("true") boolean includeE164)
throws InvalidKeyException { throws InvalidKeyException {
@ -88,7 +89,7 @@ public class CertificateController {
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Path("/auth/group") @Path("/auth/group")
public GroupCredentials getGroupAuthenticationCredentials( public GroupCredentials getGroupAuthenticationCredentials(
@Auth AuthenticatedAccount auth, @ReadOnly @Auth AuthenticatedAccount auth,
@QueryParam("redemptionStartSeconds") long startSeconds, @QueryParam("redemptionStartSeconds") long startSeconds,
@QueryParam("redemptionEndSeconds") long endSeconds, @QueryParam("redemptionEndSeconds") long endSeconds,
@QueryParam("pniAsServiceId") boolean pniAsServiceId) { @QueryParam("pniAsServiceId") boolean pniAsServiceId) {

View File

@ -42,6 +42,7 @@ import org.whispersystems.textsecuregcm.spam.Extract;
import org.whispersystems.textsecuregcm.spam.FilterSpam; import org.whispersystems.textsecuregcm.spam.FilterSpam;
import org.whispersystems.textsecuregcm.spam.PushChallengeConfig; import org.whispersystems.textsecuregcm.spam.PushChallengeConfig;
import org.whispersystems.textsecuregcm.spam.ScoreThreshold; import org.whispersystems.textsecuregcm.spam.ScoreThreshold;
import org.whispersystems.websocket.auth.ReadOnly;
@Path("/v1/challenge") @Path("/v1/challenge")
@Tag(name = "Challenge") @Tag(name = "Challenge")
@ -77,7 +78,7 @@ public class ChallengeController {
@ApiResponse(responseCode = "429", description = "Too many attempts", headers = @Header( @ApiResponse(responseCode = "429", description = "Too many attempts", headers = @Header(
name = "Retry-After", name = "Retry-After",
description = "If present, an positive integer indicating the number of seconds before a subsequent attempt could succeed")) 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, @Valid final AnswerChallengeRequest answerRequest,
@Context ContainerRequestContext requestContext, @Context ContainerRequestContext requestContext,
@HeaderParam(HttpHeaders.USER_AGENT) final String userAgent, @HeaderParam(HttpHeaders.USER_AGENT) final String userAgent,
@ -163,7 +164,7 @@ public class ChallengeController {
@ApiResponse(responseCode = "429", description = "Too many attempts", headers = @Header( @ApiResponse(responseCode = "429", description = "Too many attempts", headers = @Header(
name = "Retry-After", name = "Retry-After",
description = "If present, an positive integer indicating the number of seconds before a subsequent attempt could succeed")) 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) { @Extract PushChallengeConfig pushChallengeConfig) {
if (!pushChallengeConfig.pushPermitted()) { if (!pushChallengeConfig.pushPermitted()) {
return Response.status(429).build(); return Response.status(429).build();

View File

@ -66,6 +66,8 @@ import org.whispersystems.textsecuregcm.storage.Device.DeviceCapabilities;
import org.whispersystems.textsecuregcm.storage.DeviceSpec; import org.whispersystems.textsecuregcm.storage.DeviceSpec;
import org.whispersystems.textsecuregcm.util.Util; import org.whispersystems.textsecuregcm.util.Util;
import org.whispersystems.textsecuregcm.util.VerificationCode; import org.whispersystems.textsecuregcm.util.VerificationCode;
import org.whispersystems.websocket.auth.Mutable;
import org.whispersystems.websocket.auth.ReadOnly;
@Path("/v1/devices") @Path("/v1/devices")
@Tag(name = "Devices") @Tag(name = "Devices")
@ -111,7 +113,7 @@ public class DeviceController {
@GET @GET
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public DeviceInfoList getDevices(@Auth AuthenticatedAccount auth) { public DeviceInfoList getDevices(@ReadOnly @Auth AuthenticatedAccount auth) {
List<DeviceInfo> devices = new LinkedList<>(); List<DeviceInfo> devices = new LinkedList<>();
for (Device device : auth.getAccount().getDevices()) { for (Device device : auth.getAccount().getDevices()) {
@ -126,7 +128,7 @@ public class DeviceController {
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Path("/{device_id}") @Path("/{device_id}")
@ChangesDeviceEnabledState @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) { if (auth.getAuthenticatedDevice().getId() != Device.PRIMARY_ID) {
throw new WebApplicationException(Response.Status.UNAUTHORIZED); throw new WebApplicationException(Response.Status.UNAUTHORIZED);
} }
@ -141,7 +143,7 @@ public class DeviceController {
@GET @GET
@Path("/provisioning/code") @Path("/provisioning/code")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public VerificationCode createDeviceToken(@Auth AuthenticatedAccount auth) public VerificationCode createDeviceToken(@ReadOnly @Auth AuthenticatedAccount auth)
throws RateLimitExceededException, DeviceLimitExceededException { throws RateLimitExceededException, DeviceLimitExceededException {
final Account account = auth.getAccount(); final Account account = auth.getAccount();
@ -258,7 +260,7 @@ public class DeviceController {
@PUT @PUT
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Path("/unauthenticated_delivery") @Path("/unauthenticated_delivery")
public void setUnauthenticatedDelivery(@Auth AuthenticatedAccount auth) { public void setUnauthenticatedDelivery(@Mutable @Auth AuthenticatedAccount auth) {
assert (auth.getAuthenticatedDevice() != null); assert (auth.getAuthenticatedDevice() != null);
// Deprecated // Deprecated
} }
@ -266,7 +268,7 @@ public class DeviceController {
@PUT @PUT
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Path("/capabilities") @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); assert (auth.getAuthenticatedDevice() != null);
final byte deviceId = auth.getAuthenticatedDevice().getId(); final byte deviceId = auth.getAuthenticatedDevice().getId();
accounts.updateDevice(auth.getAccount(), deviceId, d -> d.setCapabilities(capabilities)); accounts.updateDevice(auth.getAccount(), deviceId, d -> d.setCapabilities(capabilities));

View File

@ -18,6 +18,7 @@ import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.configuration.DirectoryV2ClientConfiguration; import org.whispersystems.textsecuregcm.configuration.DirectoryV2ClientConfiguration;
import org.whispersystems.websocket.auth.ReadOnly;
@Path("/v2/directory") @Path("/v2/directory")
@Tag(name = "Directory") @Tag(name = "Directory")
@ -47,7 +48,7 @@ public class DirectoryV2Controller {
@GET @GET
@Path("/auth") @Path("/auth")
@Produces(MediaType.APPLICATION_JSON) @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 UUID uuid = auth.getAccount().getUuid();
final ExternalServiceCredentials credentials = directoryServiceTokenGenerator.generateForUuid(uuid); final ExternalServiceCredentials credentials = directoryServiceTokenGenerator.generateForUuid(uuid);
return Response.ok().entity(credentials).build(); return Response.ok().entity(credentials).build();

View File

@ -36,6 +36,7 @@ import org.whispersystems.textsecuregcm.entities.RedeemReceiptRequest;
import org.whispersystems.textsecuregcm.storage.AccountBadge; import org.whispersystems.textsecuregcm.storage.AccountBadge;
import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.RedeemedReceiptsManager; import org.whispersystems.textsecuregcm.storage.RedeemedReceiptsManager;
import org.whispersystems.websocket.auth.Mutable;
@Path("/v1/donation") @Path("/v1/donation")
@Tag(name = "Donations") @Tag(name = "Donations")
@ -74,7 +75,7 @@ public class DonationController {
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN}) @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
public CompletionStage<Response> redeemReceipt( public CompletionStage<Response> redeemReceipt(
@Auth final AuthenticatedAccount auth, @Mutable @Auth final AuthenticatedAccount auth,
@NotNull @Valid final RedeemReceiptRequest request) { @NotNull @Valid final RedeemReceiptRequest request) {
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
ReceiptCredentialPresentation receiptCredentialPresentation; ReceiptCredentialPresentation receiptCredentialPresentation;

View File

@ -20,6 +20,7 @@ import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil; import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.websocket.auth.ReadOnly;
import org.whispersystems.websocket.session.WebSocketSession; import org.whispersystems.websocket.session.WebSocketSession;
import org.whispersystems.websocket.session.WebSocketSessionContext; import org.whispersystems.websocket.session.WebSocketSessionContext;
@ -40,7 +41,7 @@ public class KeepAliveController {
} }
@GET @GET
public Response getKeepAlive(@Auth Optional<AuthenticatedAccount> maybeAuth, public Response getKeepAlive(@ReadOnly @Auth Optional<AuthenticatedAccount> maybeAuth,
@WebSocketSession WebSocketSessionContext context) { @WebSocketSession WebSocketSessionContext context) {
maybeAuth.ifPresent(auth -> { maybeAuth.ifPresent(auth -> {

View File

@ -63,6 +63,7 @@ import org.whispersystems.textsecuregcm.storage.KeysManager;
import org.whispersystems.textsecuregcm.util.HeaderUtils; import org.whispersystems.textsecuregcm.util.HeaderUtils;
import org.whispersystems.textsecuregcm.util.Util; import org.whispersystems.textsecuregcm.util.Util;
import org.whispersystems.textsecuregcm.util.ua.ClientPlatform; import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
import org.whispersystems.websocket.auth.ReadOnly;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType") @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Path("/v2/keys") @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") 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 = "200", description = "Body contains the number of available one-time prekeys for the device.", useReturnTypeSchema = true)
@ApiResponse(responseCode = "401", description = "Account authentication check failed.") @ApiResponse(responseCode = "401", description = "Account authentication check failed.")
public CompletableFuture<PreKeyCount> getStatus(@Auth final AuthenticatedAccount auth, public CompletableFuture<PreKeyCount> getStatus(@ReadOnly @Auth final AuthenticatedAccount auth,
@QueryParam("identity") @DefaultValue("aci") final IdentityType identityType, @QueryParam("identity") @DefaultValue("aci") final IdentityType identityType,
@HeaderParam(HttpHeaders.USER_AGENT) final String userAgent) { @HeaderParam(HttpHeaders.USER_AGENT) final String userAgent) {
@ -137,7 +138,8 @@ public class KeysController {
@ApiResponse(responseCode = "401", description = "Account authentication check failed.") @ApiResponse(responseCode = "401", description = "Account authentication check failed.")
@ApiResponse(responseCode = "403", description = "Attempt to change identity key from a non-primary device.") @ApiResponse(responseCode = "403", description = "Attempt to change identity key from a non-primary device.")
@ApiResponse(responseCode = "422", description = "Invalid request format.") @ApiResponse(responseCode = "422", description = "Invalid request format.")
public CompletableFuture<Response> setKeys(@Auth final AuthenticatedAccount auth, public CompletableFuture<Response> setKeys(
@ReadOnly @Auth final AuthenticatedAccount auth,
@RequestBody @NotNull @Valid final SetKeysRequest setKeysRequest, @RequestBody @NotNull @Valid final SetKeysRequest setKeysRequest,
@Parameter(allowEmptyValue=true) @Parameter(allowEmptyValue=true)
@ -230,7 +232,8 @@ public class KeysController {
@ApiResponse(responseCode = "429", description = "Rate limit exceeded.", headers = @Header( @ApiResponse(responseCode = "429", description = "Rate limit exceeded.", headers = @Header(
name = "Retry-After", name = "Retry-After",
description = "If present, a positive integer indicating the number of seconds before a subsequent attempt could succeed")) description = "If present, a positive integer indicating the number of seconds before a subsequent attempt could succeed"))
public PreKeyResponse getDeviceKeys(@Auth Optional<AuthenticatedAccount> auth, public PreKeyResponse getDeviceKeys(
@ReadOnly @Auth Optional<AuthenticatedAccount> auth,
@HeaderParam(HeaderUtils.UNIDENTIFIED_ACCESS_KEY) Optional<Anonymous> accessKey, @HeaderParam(HeaderUtils.UNIDENTIFIED_ACCESS_KEY) Optional<Anonymous> accessKey,
@Parameter(description="the account or phone-number identifier to retrieve keys for") @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 = "200", description = "Indicates that new prekey was successfully stored.")
@ApiResponse(responseCode = "401", description = "Account authentication check failed.") @ApiResponse(responseCode = "401", description = "Account authentication check failed.")
@ApiResponse(responseCode = "422", description = "Invalid request format.") @ApiResponse(responseCode = "422", description = "Invalid request format.")
public CompletableFuture<Response> setSignedKey(@Auth final AuthenticatedAccount auth, public CompletableFuture<Response> setSignedKey(
@ReadOnly @Auth final AuthenticatedAccount auth,
@Valid final ECSignedPreKey signedPreKey, @Valid final ECSignedPreKey signedPreKey,
@QueryParam("identity") @DefaultValue("aci") final IdentityType identityType) { @QueryParam("identity") @DefaultValue("aci") final IdentityType identityType) {

View File

@ -129,6 +129,7 @@ import org.whispersystems.textsecuregcm.util.HeaderUtils;
import org.whispersystems.textsecuregcm.util.Util; import org.whispersystems.textsecuregcm.util.Util;
import org.whispersystems.textsecuregcm.websocket.WebSocketConnection; import org.whispersystems.textsecuregcm.websocket.WebSocketConnection;
import org.whispersystems.websocket.Stories; import org.whispersystems.websocket.Stories;
import org.whispersystems.websocket.auth.ReadOnly;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Scheduler;
@ -235,7 +236,7 @@ public class MessageController {
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@ManagedAsync @ManagedAsync
public Response sendMessage(@Auth Optional<AuthenticatedAccount> source, public Response sendMessage(@ReadOnly @Auth Optional<AuthenticatedAccount> source,
@HeaderParam(HeaderUtils.UNIDENTIFIED_ACCESS_KEY) Optional<Anonymous> accessKey, @HeaderParam(HeaderUtils.UNIDENTIFIED_ACCESS_KEY) Optional<Anonymous> accessKey,
@HeaderParam(HttpHeaders.USER_AGENT) String userAgent, @HeaderParam(HttpHeaders.USER_AGENT) String userAgent,
@PathParam("destination") ServiceIdentifier destinationIdentifier, @PathParam("destination") ServiceIdentifier destinationIdentifier,
@ -667,7 +668,7 @@ public class MessageController {
@Timed @Timed
@GET @GET
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public CompletableFuture<OutgoingMessageEntityList> getPendingMessages(@Auth AuthenticatedAccount auth, public CompletableFuture<OutgoingMessageEntityList> getPendingMessages(@ReadOnly @Auth AuthenticatedAccount auth,
@HeaderParam(Stories.X_SIGNAL_RECEIVE_STORIES) String receiveStoriesHeader, @HeaderParam(Stories.X_SIGNAL_RECEIVE_STORIES) String receiveStoriesHeader,
@HeaderParam(HttpHeaders.USER_AGENT) String userAgent) { @HeaderParam(HttpHeaders.USER_AGENT) String userAgent) {
@ -718,7 +719,7 @@ public class MessageController {
@Timed @Timed
@DELETE @DELETE
@Path("/uuid/{uuid}") @Path("/uuid/{uuid}")
public CompletableFuture<Response> removePendingMessage(@Auth AuthenticatedAccount auth, @PathParam("uuid") UUID uuid) { public CompletableFuture<Response> removePendingMessage(@ReadOnly @Auth AuthenticatedAccount auth, @PathParam("uuid") UUID uuid) {
return messagesManager.delete( return messagesManager.delete(
auth.getAccount().getUuid(), auth.getAccount().getUuid(),
auth.getAuthenticatedDevice().getId(), auth.getAuthenticatedDevice().getId(),
@ -749,7 +750,7 @@ public class MessageController {
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@Path("/report/{source}/{messageGuid}") @Path("/report/{source}/{messageGuid}")
public Response reportSpamMessage( public Response reportSpamMessage(
@Auth AuthenticatedAccount auth, @ReadOnly @Auth AuthenticatedAccount auth,
@PathParam("source") String source, @PathParam("source") String source,
@PathParam("messageGuid") UUID messageGuid, @PathParam("messageGuid") UUID messageGuid,
@Nullable @Valid SpamReport spamReport, @Nullable @Valid SpamReport spamReport,

View File

@ -17,6 +17,7 @@ import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator
import org.whispersystems.textsecuregcm.configuration.PaymentsServiceConfiguration; import org.whispersystems.textsecuregcm.configuration.PaymentsServiceConfiguration;
import org.whispersystems.textsecuregcm.currency.CurrencyConversionManager; import org.whispersystems.textsecuregcm.currency.CurrencyConversionManager;
import org.whispersystems.textsecuregcm.entities.CurrencyConversionEntityList; import org.whispersystems.textsecuregcm.entities.CurrencyConversionEntityList;
import org.whispersystems.websocket.auth.ReadOnly;
@Path("/v1/payments") @Path("/v1/payments")
@Tag(name = "Payments") @Tag(name = "Payments")
@ -42,14 +43,14 @@ public class PaymentsController {
@GET @GET
@Path("/auth") @Path("/auth")
@Produces(MediaType.APPLICATION_JSON) @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()); return paymentsServiceCredentialsGenerator.generateForUuid(auth.getAccount().getUuid());
} }
@GET @GET
@Path("/conversions") @Path("/conversions")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public CurrencyConversionEntityList getConversions(final @Auth AuthenticatedAccount auth) { public CurrencyConversionEntityList getConversions(final @ReadOnly @Auth AuthenticatedAccount auth) {
return currencyManager.getCurrencyConversions().orElseThrow(); return currencyManager.getCurrencyConversions().orElseThrow();
} }
} }

View File

@ -99,6 +99,8 @@ import org.whispersystems.textsecuregcm.util.HeaderUtils;
import org.whispersystems.textsecuregcm.util.Pair; import org.whispersystems.textsecuregcm.util.Pair;
import org.whispersystems.textsecuregcm.util.ProfileHelper; import org.whispersystems.textsecuregcm.util.ProfileHelper;
import org.whispersystems.textsecuregcm.util.Util; 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.S3Client;
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
@ -162,7 +164,7 @@ public class ProfileController {
@PUT @PUT
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Consumes(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<VersionedProfile> currentProfile = profilesManager.get(auth.getAccount().getUuid(), final Optional<VersionedProfile> currentProfile = profilesManager.get(auth.getAccount().getUuid(),
request.version()); request.version());
@ -228,7 +230,7 @@ public class ProfileController {
@Path("/{identifier}/{version}") @Path("/{identifier}/{version}")
@ManagedAsync @ManagedAsync
public VersionedProfileResponse getProfile( public VersionedProfileResponse getProfile(
@Auth Optional<AuthenticatedAccount> auth, @ReadOnly @Auth Optional<AuthenticatedAccount> auth,
@HeaderParam(HeaderUtils.UNIDENTIFIED_ACCESS_KEY) Optional<Anonymous> accessKey, @HeaderParam(HeaderUtils.UNIDENTIFIED_ACCESS_KEY) Optional<Anonymous> accessKey,
@Context ContainerRequestContext containerRequestContext, @Context ContainerRequestContext containerRequestContext,
@PathParam("identifier") AciServiceIdentifier accountIdentifier, @PathParam("identifier") AciServiceIdentifier accountIdentifier,
@ -248,7 +250,7 @@ public class ProfileController {
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Path("/{identifier}/{version}/{credentialRequest}") @Path("/{identifier}/{version}/{credentialRequest}")
public CredentialProfileResponse getProfile( public CredentialProfileResponse getProfile(
@Auth Optional<AuthenticatedAccount> auth, @ReadOnly @Auth Optional<AuthenticatedAccount> auth,
@HeaderParam(HeaderUtils.UNIDENTIFIED_ACCESS_KEY) Optional<Anonymous> accessKey, @HeaderParam(HeaderUtils.UNIDENTIFIED_ACCESS_KEY) Optional<Anonymous> accessKey,
@Context ContainerRequestContext containerRequestContext, @Context ContainerRequestContext containerRequestContext,
@PathParam("identifier") AciServiceIdentifier accountIdentifier, @PathParam("identifier") AciServiceIdentifier accountIdentifier,
@ -279,7 +281,7 @@ public class ProfileController {
@Path("/{identifier}") @Path("/{identifier}")
@ManagedAsync @ManagedAsync
public BaseProfileResponse getUnversionedProfile( public BaseProfileResponse getUnversionedProfile(
@Auth Optional<AuthenticatedAccount> auth, @ReadOnly @Auth Optional<AuthenticatedAccount> auth,
@HeaderParam(HeaderUtils.UNIDENTIFIED_ACCESS_KEY) Optional<Anonymous> accessKey, @HeaderParam(HeaderUtils.UNIDENTIFIED_ACCESS_KEY) Optional<Anonymous> accessKey,
@Context ContainerRequestContext containerRequestContext, @Context ContainerRequestContext containerRequestContext,
@HeaderParam(HttpHeaders.USER_AGENT) String userAgent, @HeaderParam(HttpHeaders.USER_AGENT) String userAgent,

View File

@ -23,6 +23,7 @@ import org.whispersystems.textsecuregcm.entities.ProvisioningMessage;
import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.push.ProvisioningManager; import org.whispersystems.textsecuregcm.push.ProvisioningManager;
import org.whispersystems.textsecuregcm.websocket.ProvisioningAddress; import org.whispersystems.textsecuregcm.websocket.ProvisioningAddress;
import org.whispersystems.websocket.auth.ReadOnly;
@Path("/v1/provisioning") @Path("/v1/provisioning")
@Tag(name = "Provisioning") @Tag(name = "Provisioning")
@ -40,7 +41,7 @@ public class ProvisioningController {
@PUT @PUT
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public void sendProvisioningMessage(@Auth AuthenticatedAccount auth, public void sendProvisioningMessage(@ReadOnly @Auth AuthenticatedAccount auth,
@PathParam("destination") String destinationName, @PathParam("destination") String destinationName,
@NotNull @Valid ProvisioningMessage message) @NotNull @Valid ProvisioningMessage message)
throws RateLimitExceededException { throws RateLimitExceededException {

View File

@ -28,6 +28,7 @@ import org.whispersystems.textsecuregcm.entities.UserRemoteConfigList;
import org.whispersystems.textsecuregcm.storage.RemoteConfigsManager; import org.whispersystems.textsecuregcm.storage.RemoteConfigsManager;
import org.whispersystems.textsecuregcm.util.Conversions; import org.whispersystems.textsecuregcm.util.Conversions;
import org.whispersystems.textsecuregcm.util.Util; import org.whispersystems.textsecuregcm.util.Util;
import org.whispersystems.websocket.auth.ReadOnly;
@Path("/v1/config") @Path("/v1/config")
@Tag(name = "Remote Config") @Tag(name = "Remote Config")
@ -51,7 +52,7 @@ public class RemoteConfigController {
@GET @GET
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public UserRemoteConfigList getAll(@Auth AuthenticatedAccount auth) { public UserRemoteConfigList getAll(@ReadOnly @Auth AuthenticatedAccount auth) {
try { try {
MessageDigest digest = MessageDigest.getInstance("SHA1"); MessageDigest digest = MessageDigest.getInstance("SHA1");

View File

@ -15,6 +15,7 @@ import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration; import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration;
import org.whispersystems.websocket.auth.ReadOnly;
@Path("/v1/storage") @Path("/v1/storage")
@Tag(name = "Secure Storage") @Tag(name = "Secure Storage")
@ -36,7 +37,7 @@ public class SecureStorageController {
@GET @GET
@Path("/auth") @Path("/auth")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public ExternalServiceCredentials getAuth(@Auth AuthenticatedAccount auth) { public ExternalServiceCredentials getAuth(@ReadOnly @Auth AuthenticatedAccount auth) {
return storageServiceCredentialsGenerator.generateForUuid(auth.getAccount().getUuid()); return storageServiceCredentialsGenerator.generateForUuid(auth.getAccount().getUuid());
} }
} }

View File

@ -34,6 +34,7 @@ import org.whispersystems.textsecuregcm.limits.RateLimitedByIp;
import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.websocket.auth.ReadOnly;
@Path("/v2/backup") @Path("/v2/backup")
@Tag(name = "Secure Value Recovery") @Tag(name = "Secure Value Recovery")
@ -77,7 +78,7 @@ public class SecureValueRecovery2Controller {
) )
@ApiResponse(responseCode = "200", description = "`JSON` with generated credentials.", useReturnTypeSchema = true) @ApiResponse(responseCode = "200", description = "`JSON` with generated credentials.", useReturnTypeSchema = true)
@ApiResponse(responseCode = "401", description = "Account authentication check failed.") @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()); return backupServiceCredentialGenerator.generateFor(auth.getAccount().getUuid().toString());
} }

View File

@ -21,6 +21,7 @@ import org.whispersystems.textsecuregcm.limits.RateLimitedByIp;
import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.websocket.auth.ReadOnly;
import java.time.Clock; import java.time.Clock;
import java.util.List; import java.util.List;
@ -78,7 +79,7 @@ public class SecureValueRecovery3Controller {
) )
@ApiResponse(responseCode = "200", description = "`JSON` with generated credentials.", useReturnTypeSchema = true) @ApiResponse(responseCode = "200", description = "`JSON` with generated credentials.", useReturnTypeSchema = true)
@ApiResponse(responseCode = "401", description = "Account authentication check failed.") @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()); return backupServiceCredentialGenerator.generateFor(auth.getAccount().getUuid().toString());
} }

View File

@ -28,6 +28,7 @@ import org.whispersystems.textsecuregcm.s3.PolicySigner;
import org.whispersystems.textsecuregcm.s3.PostPolicyGenerator; import org.whispersystems.textsecuregcm.s3.PostPolicyGenerator;
import org.whispersystems.textsecuregcm.util.Constants; import org.whispersystems.textsecuregcm.util.Constants;
import org.whispersystems.textsecuregcm.util.Pair; import org.whispersystems.textsecuregcm.util.Pair;
import org.whispersystems.websocket.auth.ReadOnly;
@Path("/v1/sticker") @Path("/v1/sticker")
@Tag(name = "Stickers") @Tag(name = "Stickers")
@ -46,7 +47,7 @@ public class StickerController {
@GET @GET
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Path("/pack/form/{count}") @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) @PathParam("count") @Min(1) @Max(201) int stickerCount)
throws RateLimitExceededException { throws RateLimitExceededException {
rateLimiters.getStickerPackLimiter().validate(auth.getAccount().getUuid()); rateLimiters.getStickerPackLimiter().validate(auth.getAccount().getUuid());

View File

@ -98,6 +98,7 @@ import org.whispersystems.textsecuregcm.subscriptions.SubscriptionCurrencyUtil;
import org.whispersystems.textsecuregcm.subscriptions.SubscriptionProcessor; import org.whispersystems.textsecuregcm.subscriptions.SubscriptionProcessor;
import org.whispersystems.textsecuregcm.subscriptions.SubscriptionProcessorManager; import org.whispersystems.textsecuregcm.subscriptions.SubscriptionProcessorManager;
import org.whispersystems.textsecuregcm.util.ExactlySize; import org.whispersystems.textsecuregcm.util.ExactlySize;
import org.whispersystems.websocket.auth.ReadOnly;
@Path("/v1/subscription") @Path("/v1/subscription")
@io.swagger.v3.oas.annotations.tags.Tag(name = "Subscriptions") @io.swagger.v3.oas.annotations.tags.Tag(name = "Subscriptions")
@ -224,7 +225,7 @@ public class SubscriptionController {
@Path("/{subscriberId}") @Path("/{subscriberId}")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public CompletableFuture<Response> deleteSubscriber( public CompletableFuture<Response> deleteSubscriber(
@Auth Optional<AuthenticatedAccount> authenticatedAccount, @ReadOnly @Auth Optional<AuthenticatedAccount> authenticatedAccount,
@PathParam("subscriberId") String subscriberId) { @PathParam("subscriberId") String subscriberId) {
RequestData requestData = RequestData.process(authenticatedAccount, subscriberId, clock); RequestData requestData = RequestData.process(authenticatedAccount, subscriberId, clock);
return subscriptionManager.get(requestData.subscriberUser, requestData.hmac) return subscriptionManager.get(requestData.subscriberUser, requestData.hmac)
@ -246,7 +247,7 @@ public class SubscriptionController {
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public CompletableFuture<Response> updateSubscriber( public CompletableFuture<Response> updateSubscriber(
@Auth Optional<AuthenticatedAccount> authenticatedAccount, @ReadOnly @Auth Optional<AuthenticatedAccount> authenticatedAccount,
@PathParam("subscriberId") String subscriberId) { @PathParam("subscriberId") String subscriberId) {
RequestData requestData = RequestData.process(authenticatedAccount, subscriberId, clock); RequestData requestData = RequestData.process(authenticatedAccount, subscriberId, clock);
return subscriptionManager.get(requestData.subscriberUser, requestData.hmac) return subscriptionManager.get(requestData.subscriberUser, requestData.hmac)
@ -280,7 +281,7 @@ public class SubscriptionController {
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public CompletableFuture<Response> createPaymentMethod( public CompletableFuture<Response> createPaymentMethod(
@Auth Optional<AuthenticatedAccount> authenticatedAccount, @ReadOnly @Auth Optional<AuthenticatedAccount> authenticatedAccount,
@PathParam("subscriberId") String subscriberId, @PathParam("subscriberId") String subscriberId,
@QueryParam("type") @DefaultValue("CARD") PaymentMethod paymentMethodType) { @QueryParam("type") @DefaultValue("CARD") PaymentMethod paymentMethodType) {
@ -334,7 +335,7 @@ public class SubscriptionController {
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public CompletableFuture<Response> createPayPalPaymentMethod( public CompletableFuture<Response> createPayPalPaymentMethod(
@Auth Optional<AuthenticatedAccount> authenticatedAccount, @ReadOnly @Auth Optional<AuthenticatedAccount> authenticatedAccount,
@PathParam("subscriberId") String subscriberId, @PathParam("subscriberId") String subscriberId,
@NotNull @Valid CreatePayPalBillingAgreementRequest request, @NotNull @Valid CreatePayPalBillingAgreementRequest request,
@Context ContainerRequestContext containerRequestContext) { @Context ContainerRequestContext containerRequestContext) {
@ -399,7 +400,7 @@ public class SubscriptionController {
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Deprecated // use /{subscriberId}/default_payment_method/{processor}/{paymentMethodId} @Deprecated // use /{subscriberId}/default_payment_method/{processor}/{paymentMethodId}
public CompletableFuture<Response> setDefaultPaymentMethod( public CompletableFuture<Response> setDefaultPaymentMethod(
@Auth Optional<AuthenticatedAccount> authenticatedAccount, @ReadOnly @Auth Optional<AuthenticatedAccount> authenticatedAccount,
@PathParam("subscriberId") String subscriberId, @PathParam("subscriberId") String subscriberId,
@PathParam("paymentMethodId") @NotEmpty String paymentMethodId) { @PathParam("paymentMethodId") @NotEmpty String paymentMethodId) {
RequestData requestData = RequestData.process(authenticatedAccount, subscriberId, clock); RequestData requestData = RequestData.process(authenticatedAccount, subscriberId, clock);
@ -415,7 +416,7 @@ public class SubscriptionController {
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public CompletableFuture<Response> setDefaultPaymentMethodWithProcessor( public CompletableFuture<Response> setDefaultPaymentMethodWithProcessor(
@Auth Optional<AuthenticatedAccount> authenticatedAccount, @ReadOnly @Auth Optional<AuthenticatedAccount> authenticatedAccount,
@PathParam("subscriberId") String subscriberId, @PathParam("subscriberId") String subscriberId,
@PathParam("processor") SubscriptionProcessor processor, @PathParam("processor") SubscriptionProcessor processor,
@PathParam("paymentMethodToken") @NotEmpty String paymentMethodToken) { @PathParam("paymentMethodToken") @NotEmpty String paymentMethodToken) {
@ -446,7 +447,7 @@ public class SubscriptionController {
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public CompletableFuture<Response> setSubscriptionLevel( public CompletableFuture<Response> setSubscriptionLevel(
@Auth Optional<AuthenticatedAccount> authenticatedAccount, @ReadOnly @Auth Optional<AuthenticatedAccount> authenticatedAccount,
@PathParam("subscriberId") String subscriberId, @PathParam("subscriberId") String subscriberId,
@PathParam("level") long level, @PathParam("level") long level,
@PathParam("currency") String currency, @PathParam("currency") String currency,
@ -873,7 +874,7 @@ public class SubscriptionController {
@Path("/{subscriberId}") @Path("/{subscriberId}")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public CompletableFuture<Response> getSubscriptionInformation( public CompletableFuture<Response> getSubscriptionInformation(
@Auth Optional<AuthenticatedAccount> authenticatedAccount, @ReadOnly @Auth Optional<AuthenticatedAccount> authenticatedAccount,
@PathParam("subscriberId") String subscriberId) { @PathParam("subscriberId") String subscriberId) {
RequestData requestData = RequestData.process(authenticatedAccount, subscriberId, clock); RequestData requestData = RequestData.process(authenticatedAccount, subscriberId, clock);
return subscriptionManager.get(requestData.subscriberUser, requestData.hmac) return subscriptionManager.get(requestData.subscriberUser, requestData.hmac)
@ -916,7 +917,7 @@ public class SubscriptionController {
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public CompletableFuture<Response> createSubscriptionReceiptCredentials( public CompletableFuture<Response> createSubscriptionReceiptCredentials(
@Auth Optional<AuthenticatedAccount> authenticatedAccount, @ReadOnly @Auth Optional<AuthenticatedAccount> authenticatedAccount,
@HeaderParam(HttpHeaders.USER_AGENT) final String userAgent, @HeaderParam(HttpHeaders.USER_AGENT) final String userAgent,
@PathParam("subscriberId") String subscriberId, @PathParam("subscriberId") String subscriberId,
@NotNull @Valid GetReceiptCredentialsRequest request) { @NotNull @Valid GetReceiptCredentialsRequest request) {
@ -965,7 +966,7 @@ public class SubscriptionController {
@Path("/{subscriberId}/default_payment_method_for_ideal/{setupIntentId}") @Path("/{subscriberId}/default_payment_method_for_ideal/{setupIntentId}")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public CompletableFuture<Response> setDefaultPaymentMethodForIdeal( public CompletableFuture<Response> setDefaultPaymentMethodForIdeal(
@Auth Optional<AuthenticatedAccount> authenticatedAccount, @ReadOnly @Auth Optional<AuthenticatedAccount> authenticatedAccount,
@PathParam("subscriberId") String subscriberId, @PathParam("subscriberId") String subscriberId,
@PathParam("setupIntentId") @NotEmpty String setupIntentId) { @PathParam("setupIntentId") @NotEmpty String setupIntentId) {
RequestData requestData = RequestData.process(authenticatedAccount, subscriberId, clock); RequestData requestData = RequestData.process(authenticatedAccount, subscriberId, clock);