Retire authenticated device getters

This commit is contained in:
Jon Chambers 2025-06-23 09:10:30 -05:00 committed by GitHub
parent 68b84dd56b
commit 483404a67f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 181 additions and 254 deletions

View File

@ -1,26 +0,0 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Device;
import java.time.Instant;
import java.util.UUID;
public interface AccountAndAuthenticatedDeviceHolder {
UUID getAccountIdentifier();
byte getDeviceId();
Instant getPrimaryDeviceLastSeen();
@Deprecated(forRemoval = true)
Account getAccount();
@Deprecated(forRemoval = true)
Device getAuthenticatedDevice();
}

View File

@ -15,10 +15,12 @@ import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.Tags;
import java.time.Clock; import java.time.Clock;
import java.time.Duration; import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.whispersystems.textsecuregcm.identity.IdentityType;
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.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.Device;
@ -112,7 +114,9 @@ public class AccountAuthenticator implements Authenticator<BasicCredentials, Aut
device.get(), device.get(),
SaltedTokenHash.generateFor(basicCredentials.getPassword())); // new credentials have current version SaltedTokenHash.generateFor(basicCredentials.getPassword())); // new credentials have current version
} }
return Optional.of(new AuthenticatedDevice(authenticatedAccount, device.get())); return Optional.of(new AuthenticatedDevice(authenticatedAccount.getIdentifier(IdentityType.ACI),
device.get().getId(),
Instant.ofEpochMilli(authenticatedAccount.getPrimaryDevice().getLastSeen())));
} else { } else {
failureReason = "incorrectPassword"; failureReason = "incorrectPassword";
return Optional.empty(); return Optional.empty();

View File

@ -9,46 +9,9 @@ import java.security.Principal;
import java.time.Instant; import java.time.Instant;
import java.util.UUID; import java.util.UUID;
import javax.security.auth.Subject; import javax.security.auth.Subject;
import org.whispersystems.textsecuregcm.identity.IdentityType;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Device;
public class AuthenticatedDevice implements Principal, AccountAndAuthenticatedDeviceHolder { public record AuthenticatedDevice(UUID accountIdentifier, byte deviceId, Instant primaryDeviceLastSeen)
implements Principal {
private final Account account;
private final Device device;
public AuthenticatedDevice(final Account account, final Device device) {
this.account = account;
this.device = device;
}
@Override
public Account getAccount() {
return account;
}
@Override
public Device getAuthenticatedDevice() {
return device;
}
@Override
public UUID getAccountIdentifier() {
return account.getIdentifier(IdentityType.ACI);
}
@Override
public byte getDeviceId() {
return device.getId();
}
@Override
public Instant getPrimaryDeviceLastSeen() {
return Instant.ofEpochMilli(account.getPrimaryDevice().getLastSeen());
}
// Principal implementation
@Override @Override
public String getName() { public String getName() {

View File

@ -47,11 +47,9 @@ public class IdlePrimaryDeviceAuthenticatedWebSocketUpgradeFilter implements
// No action needed if the connection is unauthenticated (in which case we don't know when we've last seen the // No action needed if the connection is unauthenticated (in which case we don't know when we've last seen the
// primary device) or if the authenticated device IS the primary device // primary device) or if the authenticated device IS the primary device
authenticated authenticated
.filter(authenticatedDevice -> authenticatedDevice.getDeviceId() != Device.PRIMARY_ID) .filter(authenticatedDevice -> authenticatedDevice.deviceId() != Device.PRIMARY_ID)
.ifPresent(authenticatedDevice -> { .ifPresent(authenticatedDevice -> {
final Instant primaryDeviceLastSeen = authenticatedDevice.getPrimaryDeviceLastSeen(); if (authenticatedDevice.primaryDeviceLastSeen().isBefore(clock.instant().minus(minIdleDuration))) {
if (primaryDeviceLastSeen.isBefore(clock.instant().minus(minIdleDuration))) {
response.addHeader(ALERT_HEADER, IDLE_PRIMARY_DEVICE_ALERT); response.addHeader(ALERT_HEADER, IDLE_PRIMARY_DEVICE_ALERT);
IDLE_PRIMARY_WARNING_COUNTER.increment(); IDLE_PRIMARY_WARNING_COUNTER.increment();
} }

View File

@ -98,10 +98,10 @@ public class AccountController {
public void setGcmRegistrationId(@Auth AuthenticatedDevice auth, public void setGcmRegistrationId(@Auth AuthenticatedDevice auth,
@NotNull @Valid GcmRegistrationId registrationId) { @NotNull @Valid GcmRegistrationId registrationId) {
final Account account = accounts.getByAccountIdentifier(auth.getAccountIdentifier()) final Account account = accounts.getByAccountIdentifier(auth.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED));
final Device device = account.getDevice(auth.getDeviceId()) final Device device = account.getDevice(auth.deviceId())
.orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED));
if (Objects.equals(device.getGcmId(), registrationId.gcmRegistrationId())) { if (Objects.equals(device.getGcmId(), registrationId.gcmRegistrationId())) {
@ -118,10 +118,10 @@ public class AccountController {
@DELETE @DELETE
@Path("/gcm/") @Path("/gcm/")
public void deleteGcmRegistrationId(@Auth AuthenticatedDevice auth) { public void deleteGcmRegistrationId(@Auth AuthenticatedDevice auth) {
final Account account = accounts.getByAccountIdentifier(auth.getAccountIdentifier()) final Account account = accounts.getByAccountIdentifier(auth.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED));
final Device device = account.getDevice(auth.getDeviceId()) final Device device = account.getDevice(auth.deviceId())
.orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED));
accounts.updateDevice(account, device.getId(), d -> { accounts.updateDevice(account, device.getId(), d -> {
@ -138,10 +138,10 @@ public class AccountController {
public void setApnRegistrationId(@Auth AuthenticatedDevice auth, public void setApnRegistrationId(@Auth AuthenticatedDevice auth,
@NotNull @Valid ApnRegistrationId registrationId) { @NotNull @Valid ApnRegistrationId registrationId) {
final Account account = accounts.getByAccountIdentifier(auth.getAccountIdentifier()) final Account account = accounts.getByAccountIdentifier(auth.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED));
final Device device = account.getDevice(auth.getDeviceId()) final Device device = account.getDevice(auth.deviceId())
.orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED));
// Unlike FCM tokens, we need current "last updated" timestamps for APNs tokens and so update device records // Unlike FCM tokens, we need current "last updated" timestamps for APNs tokens and so update device records
@ -156,10 +156,10 @@ public class AccountController {
@DELETE @DELETE
@Path("/apn/") @Path("/apn/")
public void deleteApnRegistrationId(@Auth AuthenticatedDevice auth) { public void deleteApnRegistrationId(@Auth AuthenticatedDevice auth) {
final Account account = accounts.getByAccountIdentifier(auth.getAccountIdentifier()) final Account account = accounts.getByAccountIdentifier(auth.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED));
final Device device = account.getDevice(auth.getDeviceId()) final Device device = account.getDevice(auth.deviceId())
.orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED));
accounts.updateDevice(account, device.getId(), d -> { accounts.updateDevice(account, device.getId(), d -> {
@ -179,7 +179,7 @@ public class AccountController {
public void setRegistrationLock(@Auth AuthenticatedDevice auth, @NotNull @Valid RegistrationLock accountLock) { public void setRegistrationLock(@Auth AuthenticatedDevice auth, @NotNull @Valid RegistrationLock accountLock) {
final SaltedTokenHash credentials = SaltedTokenHash.generateFor(accountLock.getRegistrationLock()); final SaltedTokenHash credentials = SaltedTokenHash.generateFor(accountLock.getRegistrationLock());
final Account account = accounts.getByAccountIdentifier(auth.getAccountIdentifier()) final Account account = accounts.getByAccountIdentifier(auth.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED));
accounts.update(account, accounts.update(account,
@ -189,7 +189,7 @@ public class AccountController {
@DELETE @DELETE
@Path("/registration_lock") @Path("/registration_lock")
public void removeRegistrationLock(@Auth AuthenticatedDevice auth) { public void removeRegistrationLock(@Auth AuthenticatedDevice auth) {
final Account account = accounts.getByAccountIdentifier(auth.getAccountIdentifier()) final Account account = accounts.getByAccountIdentifier(auth.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED));
accounts.update(account, a -> a.setRegistrationLock(null, null)); accounts.update(account, a -> a.setRegistrationLock(null, null));
@ -215,16 +215,16 @@ public class AccountController {
requiredMode = Schema.RequiredMode.NOT_REQUIRED) requiredMode = Schema.RequiredMode.NOT_REQUIRED)
final Byte deviceId) { final Byte deviceId) {
final Account account = accounts.getByAccountIdentifier(auth.getAccountIdentifier()) final Account account = accounts.getByAccountIdentifier(auth.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED));
final byte targetDeviceId = deviceId == null ? auth.getDeviceId() : deviceId; final byte targetDeviceId = deviceId == null ? auth.deviceId() : deviceId;
if (account.getDevice(targetDeviceId).isEmpty()) { if (account.getDevice(targetDeviceId).isEmpty()) {
throw new NotFoundException(); throw new NotFoundException();
} }
final boolean mayChangeName = auth.getDeviceId() == Device.PRIMARY_ID || auth.getDeviceId() == targetDeviceId; final boolean mayChangeName = auth.deviceId() == Device.PRIMARY_ID || auth.deviceId() == targetDeviceId;
if (!mayChangeName) { if (!mayChangeName) {
throw new ForbiddenException(); throw new ForbiddenException();
@ -241,11 +241,11 @@ public class AccountController {
@Auth AuthenticatedDevice auth, @Auth AuthenticatedDevice 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 = accounts.getByAccountIdentifier(auth.getAccountIdentifier()) final Account account = accounts.getByAccountIdentifier(auth.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED));
final Account updatedAccount = accounts.update(account, a -> { final Account updatedAccount = accounts.update(account, a -> {
a.getDevice(auth.getDeviceId()).ifPresent(d -> { a.getDevice(auth.deviceId()).ifPresent(d -> {
d.setFetchesMessages(attributes.getFetchesMessages()); d.setFetchesMessages(attributes.getFetchesMessages());
d.setName(attributes.getName()); d.setName(attributes.getName());
d.setLastSeen(Util.todayInMillis()); d.setLastSeen(Util.todayInMillis());
@ -270,7 +270,7 @@ public class AccountController {
@Path("/whoami") @Path("/whoami")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public AccountIdentityResponse whoAmI(@Auth final AuthenticatedDevice auth) { public AccountIdentityResponse whoAmI(@Auth final AuthenticatedDevice auth) {
final Account account = accounts.getByAccountIdentifier(auth.getAccountIdentifier()) final Account account = accounts.getByAccountIdentifier(auth.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED));
return AccountIdentityResponseBuilder.fromAccount(account); return AccountIdentityResponseBuilder.fromAccount(account);
@ -288,7 +288,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 AuthenticatedDevice auth) { public CompletableFuture<Response> deleteUsernameHash(@Auth final AuthenticatedDevice auth) {
final Account account = accounts.getByAccountIdentifier(auth.getAccountIdentifier()) final Account account = accounts.getByAccountIdentifier(auth.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED));
return accounts.clearUsernameHash(account) return accounts.clearUsernameHash(account)
@ -315,10 +315,10 @@ public class AccountController {
@Auth final AuthenticatedDevice auth, @Auth final AuthenticatedDevice auth,
@NotNull @Valid final ReserveUsernameHashRequest usernameRequest) throws RateLimitExceededException { @NotNull @Valid final ReserveUsernameHashRequest usernameRequest) throws RateLimitExceededException {
final Account account = accounts.getByAccountIdentifier(auth.getAccountIdentifier()) final Account account = accounts.getByAccountIdentifier(auth.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED));
rateLimiters.getUsernameReserveLimiter().validate(auth.getAccountIdentifier()); rateLimiters.getUsernameReserveLimiter().validate(auth.accountIdentifier());
for (final byte[] hash : usernameRequest.usernameHashes()) { for (final byte[] hash : usernameRequest.usernameHashes()) {
if (hash.length != USERNAME_HASH_LENGTH) { if (hash.length != USERNAME_HASH_LENGTH) {
@ -358,7 +358,7 @@ public class AccountController {
@Auth final AuthenticatedDevice auth, @Auth final AuthenticatedDevice auth,
@NotNull @Valid final ConfirmUsernameHashRequest confirmRequest) { @NotNull @Valid final ConfirmUsernameHashRequest confirmRequest) {
final Account account = accounts.getByAccountIdentifier(auth.getAccountIdentifier()) final Account account = accounts.getByAccountIdentifier(auth.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED));
try { try {
@ -446,9 +446,9 @@ public class AccountController {
@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.getAccountIdentifier()); rateLimiters.forDescriptor(RateLimiters.For.USERNAME_LINK_OPERATION).validate(auth.accountIdentifier());
final Account account = accounts.getByAccountIdentifier(auth.getAccountIdentifier()) final Account account = accounts.getByAccountIdentifier(auth.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED));
// check if username hash is set for the account // check if username hash is set for the account
@ -480,9 +480,9 @@ public class AccountController {
@ApiResponse(responseCode = "429", description = "Ratelimited.") @ApiResponse(responseCode = "429", description = "Ratelimited.")
public void deleteUsernameLink(@Auth final AuthenticatedDevice auth) throws RateLimitExceededException { public void deleteUsernameLink(@Auth final AuthenticatedDevice auth) throws RateLimitExceededException {
// check ratelimiter for username link operations // check ratelimiter for username link operations
rateLimiters.forDescriptor(RateLimiters.For.USERNAME_LINK_OPERATION).validate(auth.getAccountIdentifier()); rateLimiters.forDescriptor(RateLimiters.For.USERNAME_LINK_OPERATION).validate(auth.accountIdentifier());
final Account account = accounts.getByAccountIdentifier(auth.getAccountIdentifier()) final Account account = accounts.getByAccountIdentifier(auth.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED));
clearUsernameLink(account); clearUsernameLink(account);
@ -547,7 +547,7 @@ public class AccountController {
@DELETE @DELETE
@Path("/me") @Path("/me")
public CompletableFuture<Response> deleteAccount(@Auth AuthenticatedDevice auth) { public CompletableFuture<Response> deleteAccount(@Auth AuthenticatedDevice auth) {
final Account account = accounts.getByAccountIdentifier(auth.getAccountIdentifier()) final Account account = accounts.getByAccountIdentifier(auth.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED));
return accounts.delete(account, AccountsManager.DeletionReason.USER_REQUEST).thenApply(Util.ASYNC_EMPTY_RESPONSE); return accounts.delete(account, AccountsManager.DeletionReason.USER_REQUEST).thenApply(Util.ASYNC_EMPTY_RESPONSE);

View File

@ -105,7 +105,7 @@ public class AccountControllerV2 {
@HeaderParam(HttpHeaders.USER_AGENT) final String userAgentString, @HeaderParam(HttpHeaders.USER_AGENT) final String userAgentString,
@Context final ContainerRequestContext requestContext) throws RateLimitExceededException, InterruptedException { @Context final ContainerRequestContext requestContext) throws RateLimitExceededException, InterruptedException {
if (authenticatedDevice.getDeviceId() != Device.PRIMARY_ID) { if (authenticatedDevice.deviceId() != Device.PRIMARY_ID) {
throw new ForbiddenException(); throw new ForbiddenException();
} }
@ -115,7 +115,7 @@ public class AccountControllerV2 {
final String number = request.number(); final String number = request.number();
final Account account = accountsManager.getByAccountIdentifier(authenticatedDevice.getAccountIdentifier()) final Account account = accountsManager.getByAccountIdentifier(authenticatedDevice.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
// Only verify and check reglock if there's a data change to be made... // Only verify and check reglock if there's a data change to be made...
@ -191,7 +191,7 @@ public class AccountControllerV2 {
@HeaderParam(HttpHeaders.USER_AGENT) @Nullable final String userAgentString, @HeaderParam(HttpHeaders.USER_AGENT) @Nullable final String userAgentString,
@NotNull @Valid final PhoneNumberIdentityKeyDistributionRequest request) { @NotNull @Valid final PhoneNumberIdentityKeyDistributionRequest request) {
if (authenticatedDevice.getDeviceId() != Device.PRIMARY_ID) { if (authenticatedDevice.deviceId() != Device.PRIMARY_ID) {
throw new ForbiddenException(); throw new ForbiddenException();
} }
@ -199,7 +199,7 @@ public class AccountControllerV2 {
throw new WebApplicationException("Invalid signature", 422); throw new WebApplicationException("Invalid signature", 422);
} }
final Account account = accountsManager.getByAccountIdentifier(authenticatedDevice.getAccountIdentifier()) final Account account = accountsManager.getByAccountIdentifier(authenticatedDevice.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
try { try {
@ -243,7 +243,7 @@ public class AccountControllerV2 {
@Auth AuthenticatedDevice auth, @Auth AuthenticatedDevice auth,
@NotNull @Valid PhoneNumberDiscoverabilityRequest phoneNumberDiscoverability) { @NotNull @Valid PhoneNumberDiscoverabilityRequest phoneNumberDiscoverability) {
final Account account = accountsManager.getByAccountIdentifier(auth.getAccountIdentifier()) final Account account = accountsManager.getByAccountIdentifier(auth.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
accountsManager.update(account, a -> a.setDiscoverableByPhoneNumber( accountsManager.update(account, a -> a.setDiscoverableByPhoneNumber(
@ -259,7 +259,7 @@ public class AccountControllerV2 {
useReturnTypeSchema = true) useReturnTypeSchema = true)
public AccountDataReportResponse getAccountDataReport(@Auth final AuthenticatedDevice auth) { public AccountDataReportResponse getAccountDataReport(@Auth final AuthenticatedDevice auth) {
final Account account = accountsManager.getByAccountIdentifier(auth.getAccountIdentifier()) final Account account = accountsManager.getByAccountIdentifier(auth.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
return new AccountDataReportResponse(UUID.randomUUID(), Instant.now(), return new AccountDataReportResponse(UUID.randomUUID(), Instant.now(),

View File

@ -147,12 +147,12 @@ public class ArchiveController {
@Auth final AuthenticatedDevice authenticatedDevice, @Auth final AuthenticatedDevice authenticatedDevice,
@Valid @NotNull final SetBackupIdRequest setBackupIdRequest) throws RateLimitExceededException { @Valid @NotNull final SetBackupIdRequest setBackupIdRequest) throws RateLimitExceededException {
return accountsManager.getByAccountIdentifierAsync(authenticatedDevice.getAccountIdentifier()) return accountsManager.getByAccountIdentifierAsync(authenticatedDevice.accountIdentifier())
.thenCompose(maybeAccount -> { .thenCompose(maybeAccount -> {
final Account account = maybeAccount final Account account = maybeAccount
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
final Device device = account.getDevice(authenticatedDevice.getDeviceId()) final Device device = account.getDevice(authenticatedDevice.deviceId())
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
return backupAuthManager return backupAuthManager
@ -206,7 +206,7 @@ public class ArchiveController {
@Auth final AuthenticatedDevice authenticatedDevice, @Auth final AuthenticatedDevice authenticatedDevice,
@Valid @NotNull final RedeemBackupReceiptRequest redeemBackupReceiptRequest) { @Valid @NotNull final RedeemBackupReceiptRequest redeemBackupReceiptRequest) {
return accountsManager.getByAccountIdentifierAsync(authenticatedDevice.getAccountIdentifier()) return accountsManager.getByAccountIdentifierAsync(authenticatedDevice.accountIdentifier())
.thenCompose(maybeAccount -> { .thenCompose(maybeAccount -> {
final Account account = maybeAccount final Account account = maybeAccount
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
@ -280,7 +280,7 @@ public class ArchiveController {
final Map<BackupCredentialType, List<BackupAuthCredentialsResponse.BackupAuthCredential>> credentialsByType = final Map<BackupCredentialType, List<BackupAuthCredentialsResponse.BackupAuthCredential>> credentialsByType =
new ConcurrentHashMap<>(); new ConcurrentHashMap<>();
return accountsManager.getByAccountIdentifierAsync(authenticatedDevice.getAccountIdentifier()) return accountsManager.getByAccountIdentifierAsync(authenticatedDevice.accountIdentifier())
.thenCompose(maybeAccount -> { .thenCompose(maybeAccount -> {
final Account account = maybeAccount final Account account = maybeAccount
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));

View File

@ -79,9 +79,9 @@ public class AttachmentControllerV4 {
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 AuthenticatedDevice auth) public AttachmentDescriptorV3 getAttachmentUploadForm(@Auth AuthenticatedDevice auth)
throws RateLimitExceededException { throws RateLimitExceededException {
rateLimiter.validate(auth.getAccountIdentifier()); rateLimiter.validate(auth.accountIdentifier());
final String key = generateAttachmentKey(); final String key = generateAttachmentKey();
final boolean useCdn3 = this.experimentEnrollmentManager.isEnrolled(auth.getAccountIdentifier(), CDN3_EXPERIMENT_NAME); final boolean useCdn3 = this.experimentEnrollmentManager.isEnrolled(auth.accountIdentifier(), CDN3_EXPERIMENT_NAME);
int cdn = useCdn3 ? 3 : 2; int cdn = useCdn3 ? 3 : 2;
final AttachmentGenerator.Descriptor descriptor = this.attachmentGenerators.get(cdn).generateAttachment(key); final AttachmentGenerator.Descriptor descriptor = this.attachmentGenerators.get(cdn).generateAttachment(key);
return new AttachmentDescriptorV3(cdn, key, descriptor.headers(), descriptor.signedUploadLocation()); return new AttachmentDescriptorV3(cdn, key, descriptor.headers(), descriptor.signedUploadLocation());

View File

@ -55,7 +55,7 @@ public class CallLinkController {
final @NotNull @Valid GetCreateCallLinkCredentialsRequest request final @NotNull @Valid GetCreateCallLinkCredentialsRequest request
) throws RateLimitExceededException { ) throws RateLimitExceededException {
rateLimiters.getCreateCallLinkLimiter().validate(auth.getAccountIdentifier()); rateLimiters.getCreateCallLinkLimiter().validate(auth.accountIdentifier());
final Instant truncatedDayTimestamp = Instant.now().truncatedTo(ChronoUnit.DAYS); final Instant truncatedDayTimestamp = Instant.now().truncatedTo(ChronoUnit.DAYS);
@ -67,7 +67,7 @@ public class CallLinkController {
} }
return new CreateCallLinkCredential( return new CreateCallLinkCredential(
createCallLinkCredentialRequest.issueCredential(new ServiceId.Aci(auth.getAccountIdentifier()), truncatedDayTimestamp, genericServerSecretParams).serialize(), createCallLinkCredentialRequest.issueCredential(new ServiceId.Aci(auth.accountIdentifier()), truncatedDayTimestamp, genericServerSecretParams).serialize(),
truncatedDayTimestamp.getEpochSecond() truncatedDayTimestamp.getEpochSecond()
); );
} }

View File

@ -57,7 +57,7 @@ public class CallRoutingControllerV2 {
public GetCallingRelaysResponse getCallingRelays(final @Auth AuthenticatedDevice auth) public GetCallingRelaysResponse getCallingRelays(final @Auth AuthenticatedDevice auth)
throws RateLimitExceededException, IOException { throws RateLimitExceededException, IOException {
rateLimiters.getCallEndpointLimiter().validate(auth.getAccountIdentifier()); rateLimiters.getCallEndpointLimiter().validate(auth.accountIdentifier());
try { try {
return new GetCallingRelaysResponse(List.of(cloudflareTurnCredentialsManager.retrieveFromCloudflare())); return new GetCallingRelaysResponse(List.of(cloudflareTurnCredentialsManager.retrieveFromCloudflare()));

View File

@ -82,11 +82,11 @@ public class CertificateController {
Metrics.counter(GENERATE_DELIVERY_CERTIFICATE_COUNTER_NAME, INCLUDE_E164_TAG_NAME, String.valueOf(includeE164)) Metrics.counter(GENERATE_DELIVERY_CERTIFICATE_COUNTER_NAME, INCLUDE_E164_TAG_NAME, String.valueOf(includeE164))
.increment(); .increment();
final Account account = accountsManager.getByAccountIdentifier(auth.getAccountIdentifier()) final Account account = accountsManager.getByAccountIdentifier(auth.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
return new DeliveryCertificate( return new DeliveryCertificate(
certificateGenerator.createFor(account, auth.getDeviceId(), includeE164)); certificateGenerator.createFor(account, auth.deviceId(), includeE164));
} }
@GET @GET
@ -110,7 +110,7 @@ public class CertificateController {
throw new BadRequestException(); throw new BadRequestException();
} }
final Account account = accountsManager.getByAccountIdentifier(auth.getAccountIdentifier()) final Account account = accountsManager.getByAccountIdentifier(auth.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
final List<GroupCredentials.GroupCredential> credentials = new ArrayList<>(); final List<GroupCredentials.GroupCredential> credentials = new ArrayList<>();

View File

@ -87,7 +87,7 @@ public class ChallengeController {
@Context ContainerRequestContext requestContext, @Context ContainerRequestContext requestContext,
@HeaderParam(HttpHeaders.USER_AGENT) final String userAgent) throws RateLimitExceededException, IOException { @HeaderParam(HttpHeaders.USER_AGENT) final String userAgent) throws RateLimitExceededException, IOException {
final Account account = accountsManager.getByAccountIdentifier(auth.getAccountIdentifier()) final Account account = accountsManager.getByAccountIdentifier(auth.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
Tags tags = Tags.of(UserAgentTagUtil.getPlatformTag(userAgent)); Tags tags = Tags.of(UserAgentTagUtil.getPlatformTag(userAgent));
@ -174,7 +174,7 @@ public class ChallengeController {
public Response requestPushChallenge(@Auth final AuthenticatedDevice auth, public Response requestPushChallenge(@Auth final AuthenticatedDevice auth,
@Context ContainerRequestContext requestContext) { @Context ContainerRequestContext requestContext) {
final Account account = accountsManager.getByAccountIdentifier(auth.getAccountIdentifier()) final Account account = accountsManager.getByAccountIdentifier(auth.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
final ChallengeConstraints constraints = challengeConstraintChecker.challengeConstraints(requestContext, account); final ChallengeConstraints constraints = challengeConstraintChecker.challengeConstraints(requestContext, account);

View File

@ -100,9 +100,9 @@ public class DeviceCheckController {
public ChallengeResponse attestChallenge(@Auth AuthenticatedDevice authenticatedDevice) public ChallengeResponse attestChallenge(@Auth AuthenticatedDevice authenticatedDevice)
throws RateLimitExceededException { throws RateLimitExceededException {
rateLimiters.forDescriptor(RateLimiters.For.DEVICE_CHECK_CHALLENGE) rateLimiters.forDescriptor(RateLimiters.For.DEVICE_CHECK_CHALLENGE)
.validate(authenticatedDevice.getAccountIdentifier()); .validate(authenticatedDevice.accountIdentifier());
final Account account = accountsManager.getByAccountIdentifier(authenticatedDevice.getAccountIdentifier()) final Account account = accountsManager.getByAccountIdentifier(authenticatedDevice.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
return new ChallengeResponse(deviceCheckManager.createChallenge( return new ChallengeResponse(deviceCheckManager.createChallenge(
@ -141,7 +141,7 @@ public class DeviceCheckController {
@RequestBody(description = "The attestation data, created by [attestKey](https://developer.apple.com/documentation/devicecheck/dcappattestservice/attestkey(_:clientdatahash:completionhandler:))") @RequestBody(description = "The attestation data, created by [attestKey](https://developer.apple.com/documentation/devicecheck/dcappattestservice/attestkey(_:clientdatahash:completionhandler:))")
@NotNull final byte[] attestation) { @NotNull final byte[] attestation) {
final Account account = accountsManager.getByAccountIdentifier(authenticatedDevice.getAccountIdentifier()) final Account account = accountsManager.getByAccountIdentifier(authenticatedDevice.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
try { try {
@ -182,9 +182,9 @@ public class DeviceCheckController {
implementation = String.class)) implementation = String.class))
@QueryParam("action") Action action) throws RateLimitExceededException { @QueryParam("action") Action action) throws RateLimitExceededException {
rateLimiters.forDescriptor(RateLimiters.For.DEVICE_CHECK_CHALLENGE) rateLimiters.forDescriptor(RateLimiters.For.DEVICE_CHECK_CHALLENGE)
.validate(authenticatedDevice.getAccountIdentifier()); .validate(authenticatedDevice.accountIdentifier());
final Account account = accountsManager.getByAccountIdentifier(authenticatedDevice.getAccountIdentifier()) final Account account = accountsManager.getByAccountIdentifier(authenticatedDevice.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
return new ChallengeResponse(deviceCheckManager.createChallenge(toChallengeType(action), account)); return new ChallengeResponse(deviceCheckManager.createChallenge(toChallengeType(action), account));
@ -229,7 +229,7 @@ public class DeviceCheckController {
@RequestBody(description = "The assertion created by [generateAssertion](https://developer.apple.com/documentation/devicecheck/dcappattestservice/generateassertion(_:clientdatahash:completionhandler:))") @RequestBody(description = "The assertion created by [generateAssertion](https://developer.apple.com/documentation/devicecheck/dcappattestservice/generateassertion(_:clientdatahash:completionhandler:))")
@NotNull final byte[] assertion) { @NotNull final byte[] assertion) {
final Account account = accountsManager.getByAccountIdentifier(authenticatedDevice.getAccountIdentifier()) final Account account = accountsManager.getByAccountIdentifier(authenticatedDevice.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
try { try {

View File

@ -151,7 +151,7 @@ public class DeviceController {
public DeviceInfoList getDevices(@Auth AuthenticatedDevice auth) { public DeviceInfoList getDevices(@Auth AuthenticatedDevice auth) {
// Devices may change their own names (and primary devices may change the names of linked devices) and so the device // Devices may change their own names (and primary devices may change the names of linked devices) and so the device
// state associated with the authenticated account may be stale. Fetch a fresh copy to compensate. // state associated with the authenticated account may be stale. Fetch a fresh copy to compensate.
return accounts.getByAccountIdentifier(auth.getAccountIdentifier()) return accounts.getByAccountIdentifier(auth.accountIdentifier())
.map(account -> new DeviceInfoList(account.getDevices().stream() .map(account -> new DeviceInfoList(account.getDevices().stream()
.map(DeviceInfo::forDevice) .map(DeviceInfo::forDevice)
.toList())) .toList()))
@ -163,7 +163,7 @@ public class DeviceController {
@Path("/{device_id}") @Path("/{device_id}")
@ChangesLinkedDevices @ChangesLinkedDevices
public void removeDevice(@Auth AuthenticatedDevice auth, @PathParam("device_id") byte deviceId) { public void removeDevice(@Auth AuthenticatedDevice auth, @PathParam("device_id") byte deviceId) {
if (auth.getDeviceId() != Device.PRIMARY_ID && auth.getDeviceId() != deviceId) { if (auth.deviceId() != Device.PRIMARY_ID && auth.deviceId() != deviceId) {
throw new WebApplicationException(Response.Status.UNAUTHORIZED); throw new WebApplicationException(Response.Status.UNAUTHORIZED);
} }
@ -171,7 +171,7 @@ public class DeviceController {
throw new ForbiddenException(); throw new ForbiddenException();
} }
final Account account = accounts.getByAccountIdentifier(auth.getAccountIdentifier()) final Account account = accounts.getByAccountIdentifier(auth.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
accounts.removeDevice(account, deviceId).join(); accounts.removeDevice(account, deviceId).join();
@ -208,7 +208,7 @@ public class DeviceController {
public LinkDeviceToken createDeviceToken(@Auth AuthenticatedDevice auth) public LinkDeviceToken createDeviceToken(@Auth AuthenticatedDevice auth)
throws RateLimitExceededException, DeviceLimitExceededException { throws RateLimitExceededException, DeviceLimitExceededException {
final Account account = accounts.getByAccountIdentifier(auth.getAccountIdentifier()) final Account account = accounts.getByAccountIdentifier(auth.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
rateLimiters.getAllocateDeviceLimiter().validate(account.getUuid()); rateLimiters.getAllocateDeviceLimiter().validate(account.getUuid());
@ -223,7 +223,7 @@ public class DeviceController {
throw new DeviceLimitExceededException(account.getDevices().size(), maxDeviceLimit); throw new DeviceLimitExceededException(account.getDevices().size(), maxDeviceLimit);
} }
if (auth.getDeviceId() != Device.PRIMARY_ID) { if (auth.deviceId() != Device.PRIMARY_ID) {
throw new WebApplicationException(Response.Status.UNAUTHORIZED); throw new WebApplicationException(Response.Status.UNAUTHORIZED);
} }
@ -367,8 +367,8 @@ public class DeviceController {
final AtomicInteger linkedDeviceListenerCounter = getCounterForLinkedDeviceListeners(userAgent); final AtomicInteger linkedDeviceListenerCounter = getCounterForLinkedDeviceListeners(userAgent);
linkedDeviceListenerCounter.incrementAndGet(); linkedDeviceListenerCounter.incrementAndGet();
return rateLimiters.getWaitForLinkedDeviceLimiter().validateAsync(authenticatedDevice.getAccountIdentifier()) return rateLimiters.getWaitForLinkedDeviceLimiter().validateAsync(authenticatedDevice.accountIdentifier())
.thenCompose(ignored -> accounts.getByAccountIdentifierAsync(authenticatedDevice.getAccountIdentifier())) .thenCompose(ignored -> accounts.getByAccountIdentifierAsync(authenticatedDevice.accountIdentifier()))
.thenCompose(maybeAccount -> { .thenCompose(maybeAccount -> {
final Account account = maybeAccount.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); final Account account = maybeAccount.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
@ -376,8 +376,8 @@ public class DeviceController {
.thenApply(sample -> new Pair<>(account, sample)); .thenApply(sample -> new Pair<>(account, sample));
}) })
.thenCompose(accountAndSample -> accounts.waitForNewLinkedDevice( .thenCompose(accountAndSample -> accounts.waitForNewLinkedDevice(
authenticatedDevice.getAccountIdentifier(), authenticatedDevice.accountIdentifier(),
accountAndSample.first().getDevice(authenticatedDevice.getDeviceId()) accountAndSample.first().getDevice(authenticatedDevice.deviceId())
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)), .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)),
tokenIdentifier, tokenIdentifier,
Duration.ofSeconds(timeoutSeconds)) Duration.ofSeconds(timeoutSeconds))
@ -414,10 +414,10 @@ public class DeviceController {
@NotNull @NotNull
final Map<String, Boolean> capabilities) { final Map<String, Boolean> capabilities) {
final Account account = accounts.getByAccountIdentifier(auth.getAccountIdentifier()) final Account account = accounts.getByAccountIdentifier(auth.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
accounts.updateDevice(account, auth.getDeviceId(), accounts.updateDevice(account, auth.deviceId(),
d -> d.setCapabilities(DeviceCapabilityAdapter.mapToSet(capabilities))); d -> d.setCapabilities(DeviceCapabilityAdapter.mapToSet(capabilities)));
} }
@ -438,10 +438,10 @@ public class DeviceController {
public CompletableFuture<Void> setPublicKey(@Auth final AuthenticatedDevice auth, public CompletableFuture<Void> setPublicKey(@Auth final AuthenticatedDevice auth,
final SetPublicKeyRequest setPublicKeyRequest) { final SetPublicKeyRequest setPublicKeyRequest) {
final Account account = accounts.getByAccountIdentifier(auth.getAccountIdentifier()) final Account account = accounts.getByAccountIdentifier(auth.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
return clientPublicKeysManager.setPublicKey(account, auth.getDeviceId(), setPublicKeyRequest.publicKey()); return clientPublicKeysManager.setPublicKey(account, auth.deviceId(), setPublicKeyRequest.publicKey());
} }
private static boolean isCapabilityDowngrade(final Account account, final Set<DeviceCapability> capabilities) { private static boolean isCapabilityDowngrade(final Account account, final Set<DeviceCapability> capabilities) {
@ -536,8 +536,8 @@ public class DeviceController {
@NotNull @Valid final TransferArchiveUploadedRequest transferArchiveUploadedRequest) { @NotNull @Valid final TransferArchiveUploadedRequest transferArchiveUploadedRequest) {
return rateLimiters.getUploadTransferArchiveLimiter() return rateLimiters.getUploadTransferArchiveLimiter()
.validateAsync(authenticatedDevice.getAccountIdentifier()) .validateAsync(authenticatedDevice.accountIdentifier())
.thenCompose(ignored -> accounts.getByAccountIdentifierAsync(authenticatedDevice.getAccountIdentifier())) .thenCompose(ignored -> accounts.getByAccountIdentifierAsync(authenticatedDevice.accountIdentifier()))
.thenCompose(maybeAccount -> { .thenCompose(maybeAccount -> {
final Account account = maybeAccount.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); final Account account = maybeAccount.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
@ -582,10 +582,10 @@ public class DeviceController {
@HeaderParam(HttpHeaders.USER_AGENT) @Nullable String userAgent) { @HeaderParam(HttpHeaders.USER_AGENT) @Nullable String userAgent) {
final String rateLimiterKey = authenticatedDevice.getAccountIdentifier() + ":" + authenticatedDevice.getDeviceId(); final String rateLimiterKey = authenticatedDevice.accountIdentifier() + ":" + authenticatedDevice.deviceId();
return rateLimiters.getWaitForTransferArchiveLimiter().validateAsync(rateLimiterKey) return rateLimiters.getWaitForTransferArchiveLimiter().validateAsync(rateLimiterKey)
.thenCompose(ignored -> accounts.getByAccountIdentifierAsync(authenticatedDevice.getAccountIdentifier())) .thenCompose(ignored -> accounts.getByAccountIdentifierAsync(authenticatedDevice.accountIdentifier()))
.thenCompose(maybeAccount -> { .thenCompose(maybeAccount -> {
final Account account = maybeAccount.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); final Account account = maybeAccount.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
@ -593,7 +593,7 @@ public class DeviceController {
.thenApply(sample -> new Pair<>(account, sample)); .thenApply(sample -> new Pair<>(account, sample));
}) })
.thenCompose(accountAndSample -> accounts.waitForTransferArchive(accountAndSample.first(), .thenCompose(accountAndSample -> accounts.waitForTransferArchive(accountAndSample.first(),
accountAndSample.first().getDevice(authenticatedDevice.getDeviceId()) accountAndSample.first().getDevice(authenticatedDevice.deviceId())
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)), .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)),
Duration.ofSeconds(timeoutSeconds)) Duration.ofSeconds(timeoutSeconds))
.thenApply(maybeTransferArchive -> maybeTransferArchive .thenApply(maybeTransferArchive -> maybeTransferArchive

View File

@ -56,6 +56,6 @@ public class DirectoryV2Controller {
) )
@ApiResponse(responseCode = "200", description = "`JSON` with generated credentials.", useReturnTypeSchema = true) @ApiResponse(responseCode = "200", description = "`JSON` with generated credentials.", useReturnTypeSchema = true)
public ExternalServiceCredentials getAuthToken(final @Auth AuthenticatedDevice auth) { public ExternalServiceCredentials getAuthToken(final @Auth AuthenticatedDevice auth) {
return directoryServiceTokenGenerator.generateForUuid(auth.getAccountIdentifier()); return directoryServiceTokenGenerator.generateForUuid(auth.accountIdentifier());
} }
} }

View File

@ -120,12 +120,12 @@ public class DonationController {
.build()); .build());
} }
return accountsManager.getByAccountIdentifierAsync(auth.getAccountIdentifier()) return accountsManager.getByAccountIdentifierAsync(auth.accountIdentifier())
.thenCompose(maybeAccount -> { .thenCompose(maybeAccount -> {
final Account account = maybeAccount.orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED)); final Account account = maybeAccount.orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED));
return redeemedReceiptsManager.put( return redeemedReceiptsManager.put(
receiptSerial, receiptExpiration.getEpochSecond(), receiptLevel, auth.getAccountIdentifier()) receiptSerial, receiptExpiration.getEpochSecond(), receiptLevel, auth.accountIdentifier())
.thenCompose(receiptMatched -> { .thenCompose(receiptMatched -> {
if (!receiptMatched) { if (!receiptMatched) {
return CompletableFuture.completedFuture(Response.status(Status.BAD_REQUEST) return CompletableFuture.completedFuture(Response.status(Status.BAD_REQUEST)

View File

@ -48,12 +48,12 @@ public class KeepAliveController {
@WebSocketSession WebSocketSessionContext context) { @WebSocketSession WebSocketSessionContext context) {
maybeAuth.ifPresent(auth -> { maybeAuth.ifPresent(auth -> {
if (!webSocketConnectionEventManager.isLocallyPresent(auth.getAccountIdentifier(), auth.getDeviceId())) { if (!webSocketConnectionEventManager.isLocallyPresent(auth.accountIdentifier(), auth.deviceId())) {
final Duration age = Duration.between(context.getClient().getCreated(), Instant.now()); final Duration age = Duration.between(context.getClient().getCreated(), Instant.now());
logger.debug("***** No local subscription found for {}::{}; age = {}ms, User-Agent = {}", logger.debug("***** No local subscription found for {}::{}; age = {}ms, User-Agent = {}",
auth.getAccountIdentifier(), auth.getDeviceId(), age.toMillis(), auth.accountIdentifier(), auth.deviceId(), age.toMillis(),
context.getClient().getUserAgent()); context.getClient().getUserAgent());
context.getClient().close(1000, "OK"); context.getClient().close(1000, "OK");

View File

@ -113,15 +113,15 @@ public class KeysController {
public CompletableFuture<PreKeyCount> getStatus(@Auth final AuthenticatedDevice auth, public CompletableFuture<PreKeyCount> getStatus(@Auth final AuthenticatedDevice auth,
@QueryParam("identity") @DefaultValue("aci") final IdentityType identityType) { @QueryParam("identity") @DefaultValue("aci") final IdentityType identityType) {
return accounts.getByAccountIdentifierAsync(auth.getAccountIdentifier()) return accounts.getByAccountIdentifierAsync(auth.accountIdentifier())
.thenCompose(maybeAccount -> { .thenCompose(maybeAccount -> {
final Account account = maybeAccount.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); final Account account = maybeAccount.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
final CompletableFuture<Integer> ecCountFuture = final CompletableFuture<Integer> ecCountFuture =
keysManager.getEcCount(account.getIdentifier(identityType), auth.getDeviceId()); keysManager.getEcCount(account.getIdentifier(identityType), auth.deviceId());
final CompletableFuture<Integer> pqCountFuture = final CompletableFuture<Integer> pqCountFuture =
keysManager.getPqCount(account.getIdentifier(identityType), auth.getDeviceId()); keysManager.getPqCount(account.getIdentifier(identityType), auth.deviceId());
return ecCountFuture.thenCombine(pqCountFuture, PreKeyCount::new); return ecCountFuture.thenCombine(pqCountFuture, PreKeyCount::new);
}); });
@ -147,12 +147,12 @@ public class KeysController {
@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) {
return accounts.getByAccountIdentifierAsync(auth.getAccountIdentifier()) return accounts.getByAccountIdentifierAsync(auth.accountIdentifier())
.thenCompose(maybeAccount -> { .thenCompose(maybeAccount -> {
final Account account = maybeAccount final Account account = maybeAccount
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
final Device device = account.getDevice(auth.getDeviceId()) final Device device = account.getDevice(auth.deviceId())
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
final UUID identifier = account.getIdentifier(identityType); final UUID identifier = account.getIdentifier(identityType);
@ -160,7 +160,7 @@ public class KeysController {
checkSignedPreKeySignatures(setKeysRequest, account.getIdentityKey(identityType), userAgent); checkSignedPreKeySignatures(setKeysRequest, account.getIdentityKey(identityType), userAgent);
final Tag platformTag = UserAgentTagUtil.getPlatformTag(userAgent); final Tag platformTag = UserAgentTagUtil.getPlatformTag(userAgent);
final Tag primaryDeviceTag = Tag.of(PRIMARY_DEVICE_TAG_NAME, String.valueOf(auth.getDeviceId() == Device.PRIMARY_ID)); final Tag primaryDeviceTag = Tag.of(PRIMARY_DEVICE_TAG_NAME, String.valueOf(auth.deviceId() == Device.PRIMARY_ID));
final Tag identityTypeTag = Tag.of(IDENTITY_TYPE_TAG_NAME, identityType.name()); final Tag identityTypeTag = Tag.of(IDENTITY_TYPE_TAG_NAME, identityType.name());
final List<CompletableFuture<Void>> storeFutures = new ArrayList<>(4); final List<CompletableFuture<Void>> storeFutures = new ArrayList<>(4);
@ -267,12 +267,12 @@ public class KeysController {
@Auth final AuthenticatedDevice auth, @Auth final AuthenticatedDevice auth,
@RequestBody @NotNull @Valid final CheckKeysRequest checkKeysRequest) { @RequestBody @NotNull @Valid final CheckKeysRequest checkKeysRequest) {
return accounts.getByAccountIdentifierAsync(auth.getAccountIdentifier()) return accounts.getByAccountIdentifierAsync(auth.accountIdentifier())
.thenCompose(maybeAccount -> { .thenCompose(maybeAccount -> {
final Account account = maybeAccount.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); final Account account = maybeAccount.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
final UUID identifier = account.getIdentifier(checkKeysRequest.identityType()); final UUID identifier = account.getIdentifier(checkKeysRequest.identityType());
final byte deviceId = auth.getDeviceId(); final byte deviceId = auth.deviceId();
final CompletableFuture<Optional<ECSignedPreKey>> ecSignedPreKeyFuture = final CompletableFuture<Optional<ECSignedPreKey>> ecSignedPreKeyFuture =
keysManager.getEcSignedPreKey(identifier, deviceId); keysManager.getEcSignedPreKey(identifier, deviceId);
@ -361,7 +361,7 @@ public class KeysController {
} }
final Optional<Account> account = maybeAuthenticatedDevice final Optional<Account> account = maybeAuthenticatedDevice
.map(authenticatedDevice -> accounts.getByAccountIdentifier(authenticatedDevice.getAccountIdentifier()) .map(authenticatedDevice -> accounts.getByAccountIdentifier(authenticatedDevice.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED))); .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)));
final Optional<Account> maybeTarget = accounts.getByServiceIdentifier(targetIdentifier); final Optional<Account> maybeTarget = accounts.getByServiceIdentifier(targetIdentifier);
@ -383,7 +383,7 @@ public class KeysController {
if (account.isPresent()) { if (account.isPresent()) {
rateLimiters.getPreKeysLimiter().validate( rateLimiters.getPreKeysLimiter().validate(
account.get().getUuid() + "." + maybeAuthenticatedDevice.get().getDeviceId() + "__" + targetIdentifier.uuid() account.get().getUuid() + "." + maybeAuthenticatedDevice.get().deviceId() + "__" + targetIdentifier.uuid()
+ "." + deviceId); + "." + deviceId);
} }

View File

@ -274,7 +274,7 @@ public class MessageController {
sendStoryMessage(destinationIdentifier, messages, context); sendStoryMessage(destinationIdentifier, messages, context);
} else if (source.isPresent()) { } else if (source.isPresent()) {
final AuthenticatedDevice authenticatedDevice = source.get(); final AuthenticatedDevice authenticatedDevice = source.get();
final Account account = accountsManager.getByAccountIdentifier(authenticatedDevice.getAccountIdentifier()) final Account account = accountsManager.getByAccountIdentifier(authenticatedDevice.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED));
if (account.isIdentifiedBy(destinationIdentifier)) { if (account.isIdentifiedBy(destinationIdentifier)) {
@ -304,7 +304,7 @@ public class MessageController {
final Account destination = final Account destination =
accountsManager.getByServiceIdentifier(destinationIdentifier).orElseThrow(NotFoundException::new); accountsManager.getByServiceIdentifier(destinationIdentifier).orElseThrow(NotFoundException::new);
rateLimiters.getMessagesLimiter().validate(source.getAccountIdentifier(), destination.getUuid()); rateLimiters.getMessagesLimiter().validate(source.accountIdentifier(), destination.getUuid());
sendIndividualMessage(destination, sendIndividualMessage(destination,
destinationIdentifier, destinationIdentifier,
@ -423,8 +423,8 @@ public class MessageController {
try { try {
return message.toEnvelope( return message.toEnvelope(
destinationIdentifier, destinationIdentifier,
sender != null ? new AciServiceIdentifier(sender.getAccountIdentifier()) : null, sender != null ? new AciServiceIdentifier(sender.accountIdentifier()) : null,
sender != null ? sender.getDeviceId() : null, sender != null ? sender.deviceId() : null,
messages.timestamp() == 0 ? System.currentTimeMillis() : messages.timestamp(), messages.timestamp() == 0 ? System.currentTimeMillis() : messages.timestamp(),
isStory, isStory,
messages.online(), messages.online(),
@ -440,7 +440,7 @@ public class MessageController {
.collect(Collectors.toMap(IncomingMessage::destinationDeviceId, IncomingMessage::destinationRegistrationId)); .collect(Collectors.toMap(IncomingMessage::destinationDeviceId, IncomingMessage::destinationRegistrationId));
final Optional<Byte> syncMessageSenderDeviceId = messageType == MessageType.SYNC final Optional<Byte> syncMessageSenderDeviceId = messageType == MessageType.SYNC
? Optional.ofNullable(sender).map(AuthenticatedDevice::getDeviceId) ? Optional.ofNullable(sender).map(AuthenticatedDevice::deviceId)
: Optional.empty(); : Optional.empty();
try { try {
@ -762,10 +762,10 @@ public class MessageController {
@HeaderParam(WebsocketHeaders.X_SIGNAL_RECEIVE_STORIES) String receiveStoriesHeader, @HeaderParam(WebsocketHeaders.X_SIGNAL_RECEIVE_STORIES) String receiveStoriesHeader,
@HeaderParam(HttpHeaders.USER_AGENT) String userAgent) { @HeaderParam(HttpHeaders.USER_AGENT) String userAgent) {
return accountsManager.getByAccountIdentifierAsync(auth.getAccountIdentifier()) return accountsManager.getByAccountIdentifierAsync(auth.accountIdentifier())
.thenCompose(maybeAccount -> { .thenCompose(maybeAccount -> {
final Account account = maybeAccount.orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED)); final Account account = maybeAccount.orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED));
final Device device = account.getDevice(auth.getDeviceId()) final Device device = account.getDevice(auth.deviceId())
.orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED));
final boolean shouldReceiveStories = WebsocketHeaders.parseReceiveStoriesHeader(receiveStoriesHeader); final boolean shouldReceiveStories = WebsocketHeaders.parseReceiveStoriesHeader(receiveStoriesHeader);
@ -773,7 +773,7 @@ public class MessageController {
pushNotificationManager.handleMessagesRetrieved(account, device, userAgent); pushNotificationManager.handleMessagesRetrieved(account, device, userAgent);
return messagesManager.getMessagesForDevice( return messagesManager.getMessagesForDevice(
auth.getAccountIdentifier(), auth.accountIdentifier(),
device, device,
false) false)
.map(messagesAndHasMore -> { .map(messagesAndHasMore -> {
@ -788,7 +788,7 @@ public class MessageController {
messageMetrics.measureAccountOutgoingMessageUuidMismatches(account, outgoingMessageEntity); messageMetrics.measureAccountOutgoingMessageUuidMismatches(account, outgoingMessageEntity);
messageMetrics.measureOutgoingMessageLatency(outgoingMessageEntity.serverTimestamp(), messageMetrics.measureOutgoingMessageLatency(outgoingMessageEntity.serverTimestamp(),
"rest", "rest",
auth.getDeviceId() == Device.PRIMARY_ID, auth.deviceId() == Device.PRIMARY_ID,
outgoingMessageEntity.urgent(), outgoingMessageEntity.urgent(),
// Messages fetched via this endpoint (as opposed to WebSocketConnection) are never ephemeral // Messages fetched via this endpoint (as opposed to WebSocketConnection) are never ephemeral
// because, by definition, the client doesn't have a "live" connection via which to receive // because, by definition, the client doesn't have a "live" connection via which to receive
@ -804,8 +804,8 @@ public class MessageController {
.record(estimateMessageListSizeBytes(messages)); .record(estimateMessageListSizeBytes(messages));
if (!messages.messages().isEmpty()) { if (!messages.messages().isEmpty()) {
messageDeliveryLoopMonitor.recordDeliveryAttempt(auth.getAccountIdentifier(), messageDeliveryLoopMonitor.recordDeliveryAttempt(auth.accountIdentifier(),
auth.getDeviceId(), auth.deviceId(),
messages.messages().getFirst().guid(), messages.messages().getFirst().guid(),
userAgent, userAgent,
"rest"); "rest");
@ -838,14 +838,14 @@ public class MessageController {
@DELETE @DELETE
@Path("/uuid/{uuid}") @Path("/uuid/{uuid}")
public CompletableFuture<Response> removePendingMessage(@Auth AuthenticatedDevice auth, @PathParam("uuid") UUID uuid) { public CompletableFuture<Response> removePendingMessage(@Auth AuthenticatedDevice auth, @PathParam("uuid") UUID uuid) {
final Account account = accountsManager.getByAccountIdentifier(auth.getAccountIdentifier()) final Account account = accountsManager.getByAccountIdentifier(auth.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED));
final Device device = account.getDevice(auth.getDeviceId()) final Device device = account.getDevice(auth.deviceId())
.orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Status.UNAUTHORIZED));
return messagesManager.delete( return messagesManager.delete(
auth.getAccountIdentifier(), auth.accountIdentifier(),
device, device,
uuid, uuid,
null) null)
@ -857,7 +857,7 @@ public class MessageController {
&& removedMessage.envelopeType() != Type.SERVER_DELIVERY_RECEIPT) { && removedMessage.envelopeType() != Type.SERVER_DELIVERY_RECEIPT) {
if (removedMessage.sourceServiceId().get() instanceof AciServiceIdentifier aciServiceIdentifier) { if (removedMessage.sourceServiceId().get() instanceof AciServiceIdentifier aciServiceIdentifier) {
try { try {
receiptSender.sendReceipt(removedMessage.destinationServiceId(), auth.getDeviceId(), receiptSender.sendReceipt(removedMessage.destinationServiceId(), auth.deviceId(),
aciServiceIdentifier, removedMessage.clientTimestamp()); aciServiceIdentifier, removedMessage.clientTimestamp());
} catch (Exception e) { } catch (Exception e) {
logger.warn("Failed to send delivery receipt", e); logger.warn("Failed to send delivery receipt", e);
@ -914,7 +914,7 @@ public class MessageController {
} }
} }
UUID spamReporterUuid = auth.getAccountIdentifier(); UUID spamReporterUuid = auth.accountIdentifier();
// spam report token is optional, but if provided ensure it is non-empty. // spam report token is optional, but if provided ensure it is non-empty.
final Optional<byte[]> maybeSpamReportToken = final Optional<byte[]> maybeSpamReportToken =

View File

@ -43,7 +43,7 @@ public class PaymentsController {
@Path("/auth") @Path("/auth")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public ExternalServiceCredentials getAuth(final @Auth AuthenticatedDevice auth) { public ExternalServiceCredentials getAuth(final @Auth AuthenticatedDevice auth) {
return paymentsServiceCredentialsGenerator.generateForUuid(auth.getAccountIdentifier()); return paymentsServiceCredentialsGenerator.generateForUuid(auth.accountIdentifier());
} }
@GET @GET

View File

@ -153,11 +153,11 @@ public class ProfileController {
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
public Response setProfile(@Auth AuthenticatedDevice auth, @NotNull @Valid CreateProfileRequest request) { public Response setProfile(@Auth AuthenticatedDevice auth, @NotNull @Valid CreateProfileRequest request) {
final Account account = accountsManager.getByAccountIdentifier(auth.getAccountIdentifier()) final Account account = accountsManager.getByAccountIdentifier(auth.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)); .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
final Optional<VersionedProfile> currentProfile = final Optional<VersionedProfile> currentProfile =
profilesManager.get(auth.getAccountIdentifier(), request.version()); profilesManager.get(auth.accountIdentifier(), request.version());
if (request.paymentAddress() != null && request.paymentAddress().length != 0) { if (request.paymentAddress() != null && request.paymentAddress().length != 0) {
final boolean hasDisallowedPrefix = final boolean hasDisallowedPrefix =
@ -181,7 +181,7 @@ public class ProfileController {
case UPDATE -> ProfileHelper.generateAvatarObjectName(); case UPDATE -> ProfileHelper.generateAvatarObjectName();
}; };
profilesManager.set(auth.getAccountIdentifier(), profilesManager.set(auth.accountIdentifier(),
new VersionedProfile( new VersionedProfile(
request.version(), request.version(),
request.name(), request.name(),
@ -228,7 +228,7 @@ public class ProfileController {
final Optional<Account> maybeRequester = final Optional<Account> maybeRequester =
maybeAuthenticatedDevice.map( maybeAuthenticatedDevice.map(
authenticatedDevice -> accountsManager.getByAccountIdentifier(authenticatedDevice.getAccountIdentifier()) authenticatedDevice -> accountsManager.getByAccountIdentifier(authenticatedDevice.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED))); .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)));
final Account targetAccount = verifyPermissionToReceiveProfile(maybeRequester, accessKey, accountIdentifier, "getVersionedProfile", userAgent); final Account targetAccount = verifyPermissionToReceiveProfile(maybeRequester, accessKey, accountIdentifier, "getVersionedProfile", userAgent);
@ -260,7 +260,7 @@ public class ProfileController {
final Optional<Account> maybeRequester = final Optional<Account> maybeRequester =
maybeAuthenticatedDevice.map( maybeAuthenticatedDevice.map(
authenticatedDevice -> accountsManager.getByAccountIdentifier(authenticatedDevice.getAccountIdentifier()) authenticatedDevice -> accountsManager.getByAccountIdentifier(authenticatedDevice.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED))); .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)));
final Account targetAccount = verifyPermissionToReceiveProfile(maybeRequester, accessKey, accountIdentifier, "credentialRequest", userAgent); final Account targetAccount = verifyPermissionToReceiveProfile(maybeRequester, accessKey, accountIdentifier, "credentialRequest", userAgent);
@ -290,7 +290,7 @@ public class ProfileController {
final Optional<Account> maybeRequester = final Optional<Account> maybeRequester =
maybeAuthenticatedDevice.map( maybeAuthenticatedDevice.map(
authenticatedDevice -> accountsManager.getByAccountIdentifier(authenticatedDevice.getAccountIdentifier()) authenticatedDevice -> accountsManager.getByAccountIdentifier(authenticatedDevice.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED))); .orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED)));
final Account targetAccount; final Account targetAccount;

View File

@ -92,7 +92,7 @@ public class ProvisioningController {
throw new WebApplicationException(Response.Status.BAD_REQUEST); throw new WebApplicationException(Response.Status.BAD_REQUEST);
} }
rateLimiters.getMessagesLimiter().validate(auth.getAccountIdentifier()); rateLimiters.getMessagesLimiter().validate(auth.accountIdentifier());
final boolean subscriberPresent = final boolean subscriberPresent =
provisioningManager.sendProvisioningMessage(provisioningAddress, Base64.getMimeDecoder().decode(message.body())); provisioningManager.sendProvisioningMessage(provisioningAddress, Base64.getMimeDecoder().decode(message.body()));

View File

@ -72,7 +72,7 @@ public class RemoteConfigController {
return new UserRemoteConfigList(Stream.concat(remoteConfigsManager.getAll().stream().map(config -> { return new UserRemoteConfigList(Stream.concat(remoteConfigsManager.getAll().stream().map(config -> {
final byte[] hashKey = config.getHashKey() != null ? config.getHashKey().getBytes(StandardCharsets.UTF_8) final byte[] hashKey = config.getHashKey() != null ? config.getHashKey().getBytes(StandardCharsets.UTF_8)
: config.getName().getBytes(StandardCharsets.UTF_8); : config.getName().getBytes(StandardCharsets.UTF_8);
boolean inBucket = isInBucket(digest, auth.getAccountIdentifier(), hashKey, config.getPercentage(), boolean inBucket = isInBucket(digest, auth.accountIdentifier(), hashKey, config.getPercentage(),
config.getUuids()); config.getUuids());
return new UserRemoteConfig(config.getName(), inBucket, return new UserRemoteConfig(config.getName(), inBucket,
inBucket ? config.getValue() : config.getDefaultValue()); inBucket ? config.getValue() : config.getDefaultValue());

View File

@ -47,6 +47,6 @@ public class SecureStorageController {
) )
@ApiResponse(responseCode = "200", description = "`JSON` with generated credentials.", useReturnTypeSchema = true) @ApiResponse(responseCode = "200", description = "`JSON` with generated credentials.", useReturnTypeSchema = true)
public ExternalServiceCredentials getAuth(@Auth AuthenticatedDevice auth) { public ExternalServiceCredentials getAuth(@Auth AuthenticatedDevice auth) {
return storageServiceCredentialsGenerator.generateForUuid(auth.getAccountIdentifier()); return storageServiceCredentialsGenerator.generateForUuid(auth.accountIdentifier());
} }
} }

View File

@ -78,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 AuthenticatedDevice auth) { public ExternalServiceCredentials getAuth(@Auth final AuthenticatedDevice auth) {
return backupServiceCredentialGenerator.generateFor(auth.getAccountIdentifier().toString()); return backupServiceCredentialGenerator.generateFor(auth.accountIdentifier().toString());
} }

View File

@ -49,7 +49,7 @@ public class StickerController {
public StickerPackFormUploadAttributes getStickersForm(@Auth AuthenticatedDevice auth, public StickerPackFormUploadAttributes getStickersForm(@Auth AuthenticatedDevice 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.getAccountIdentifier()); rateLimiters.getStickerPackLimiter().validate(auth.accountIdentifier());
ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC); ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);
String packId = generatePackId(); String packId = generatePackId();

View File

@ -97,7 +97,7 @@ public class RestDeprecationFilter implements ContainerRequestFilter {
} }
if (securityContext.getUserPrincipal() instanceof AuthenticatedDevice authenticatedDevice) { if (securityContext.getUserPrincipal() instanceof AuthenticatedDevice authenticatedDevice) {
return experimentEnrollmentManager.isEnrolled(authenticatedDevice.getAccountIdentifier(), AUTHENTICATED_EXPERIMENT_NAME); return experimentEnrollmentManager.isEnrolled(authenticatedDevice.accountIdentifier(), AUTHENTICATED_EXPERIMENT_NAME);
} else { } else {
log.error("Security context was not null but user principal was of type {}", securityContext.getUserPrincipal().getClass().getName()); log.error("Security context was not null but user principal was of type {}", securityContext.getUserPrincipal().getClass().getName());
return false; return false;

View File

@ -102,11 +102,11 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener {
if (authenticated) { if (authenticated) {
final AuthenticatedDevice auth = context.getAuthenticated(AuthenticatedDevice.class); final AuthenticatedDevice auth = context.getAuthenticated(AuthenticatedDevice.class);
final Optional<Account> maybeAuthenticatedAccount = accountsManager.getByAccountIdentifier(auth.getAccountIdentifier()); final Optional<Account> maybeAuthenticatedAccount = accountsManager.getByAccountIdentifier(auth.accountIdentifier());
final Optional<Device> maybeAuthenticatedDevice = maybeAuthenticatedAccount.flatMap(account -> account.getDevice(auth.getDeviceId()));; final Optional<Device> maybeAuthenticatedDevice = maybeAuthenticatedAccount.flatMap(account -> account.getDevice(auth.deviceId()));;
if (maybeAuthenticatedAccount.isEmpty() || maybeAuthenticatedDevice.isEmpty()) { if (maybeAuthenticatedAccount.isEmpty() || maybeAuthenticatedDevice.isEmpty()) {
log.warn("{}:{} not found when opening authenticated WebSocket", auth.getAccountIdentifier(), auth.getDeviceId()); log.warn("{}:{} not found when opening authenticated WebSocket", auth.accountIdentifier(), auth.deviceId());
context.getClient().close(1011, "Unexpected error initializing connection"); context.getClient().close(1011, "Unexpected error initializing connection");
return; return;
@ -131,7 +131,7 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener {
// receive push notifications for inbound messages. We should do this first because, at this point, the // receive push notifications for inbound messages. We should do this first because, at this point, the
// connection has already closed and attempts to actually deliver a message via the connection will not succeed. // connection has already closed and attempts to actually deliver a message via the connection will not succeed.
// It's preferable to start sending push notifications as soon as possible. // It's preferable to start sending push notifications as soon as possible.
webSocketConnectionEventManager.handleClientDisconnected(auth.getAccountIdentifier(), auth.getDeviceId()); webSocketConnectionEventManager.handleClientDisconnected(auth.accountIdentifier(), auth.deviceId());
// Finally, stop trying to deliver messages and send a push notification if the connection is aware of any // Finally, stop trying to deliver messages and send a push notification if the connection is aware of any
// undelivered messages. // undelivered messages.
@ -147,7 +147,7 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener {
// Finally, we register this client's presence, which suppresses push notifications. We do this last because // Finally, we register this client's presence, which suppresses push notifications. We do this last because
// receiving extra push notifications is generally preferable to missing out on a push notification. // receiving extra push notifications is generally preferable to missing out on a push notification.
webSocketConnectionEventManager.handleClientConnected(auth.getAccountIdentifier(), auth.getDeviceId(), connection); webSocketConnectionEventManager.handleClientConnected(auth.accountIdentifier(), auth.deviceId(), connection);
} catch (final Exception e) { } catch (final Exception e) {
log.warn("Failed to initialize websocket", e); log.warn("Failed to initialize websocket", e);
context.getClient().close(1011, "Unexpected error initializing connection"); context.getClient().close(1011, "Unexpected error initializing connection");

View File

@ -31,6 +31,7 @@ import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.provider.ValueSource;
import org.junitpioneer.jupiter.cartesian.CartesianTest; import org.junitpioneer.jupiter.cartesian.CartesianTest;
import org.whispersystems.textsecuregcm.identity.IdentityType;
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.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.Device;
@ -78,8 +79,8 @@ class AccountAuthenticatorTest {
void testUpdateLastSeenMiddleOfDay() { void testUpdateLastSeenMiddleOfDay() {
clock.pin(Instant.ofEpochMilli(currentTime)); clock.pin(Instant.ofEpochMilli(currentTime));
final Device device1 = acct1.getDevices().stream().findFirst().get(); final Device device1 = acct1.getDevices().stream().findFirst().orElseThrow();
final Device device2 = acct2.getDevices().stream().findFirst().get(); final Device device2 = acct2.getDevices().stream().findFirst().orElseThrow();
final Account updatedAcct1 = accountAuthenticator.updateLastSeen(acct1, device1); final Account updatedAcct1 = accountAuthenticator.updateLastSeen(acct1, device1);
final Account updatedAcct2 = accountAuthenticator.updateLastSeen(acct2, device2); final Account updatedAcct2 = accountAuthenticator.updateLastSeen(acct2, device2);
@ -98,8 +99,8 @@ class AccountAuthenticatorTest {
void testUpdateLastSeenStartOfDay() { void testUpdateLastSeenStartOfDay() {
clock.pin(Instant.ofEpochMilli(today)); clock.pin(Instant.ofEpochMilli(today));
final Device device1 = acct1.getDevices().stream().findFirst().get(); final Device device1 = acct1.getDevices().stream().findFirst().orElseThrow();
final Device device2 = acct2.getDevices().stream().findFirst().get(); final Device device2 = acct2.getDevices().stream().findFirst().orElseThrow();
final Account updatedAcct1 = accountAuthenticator.updateLastSeen(acct1, device1); final Account updatedAcct1 = accountAuthenticator.updateLastSeen(acct1, device1);
final Account updatedAcct2 = accountAuthenticator.updateLastSeen(acct2, device2); final Account updatedAcct2 = accountAuthenticator.updateLastSeen(acct2, device2);
@ -118,8 +119,8 @@ class AccountAuthenticatorTest {
void testUpdateLastSeenEndOfDay() { void testUpdateLastSeenEndOfDay() {
clock.pin(Instant.ofEpochMilli(today + 86_400_000L - 1)); clock.pin(Instant.ofEpochMilli(today + 86_400_000L - 1));
final Device device1 = acct1.getDevices().stream().findFirst().get(); final Device device1 = acct1.getDevices().stream().findFirst().orElseThrow();
final Device device2 = acct2.getDevices().stream().findFirst().get(); final Device device2 = acct2.getDevices().stream().findFirst().orElseThrow();
final Account updatedAcct1 = accountAuthenticator.updateLastSeen(acct1, device1); final Account updatedAcct1 = accountAuthenticator.updateLastSeen(acct1, device1);
final Account updatedAcct2 = accountAuthenticator.updateLastSeen(acct2, device2); final Account updatedAcct2 = accountAuthenticator.updateLastSeen(acct2, device2);
@ -138,7 +139,7 @@ class AccountAuthenticatorTest {
void testNeverWriteYesterday() { void testNeverWriteYesterday() {
clock.pin(Instant.ofEpochMilli(today)); clock.pin(Instant.ofEpochMilli(today));
final Device device = oldAccount.getDevices().stream().findFirst().get(); final Device device = oldAccount.getDevices().stream().findFirst().orElseThrow();
accountAuthenticator.updateLastSeen(oldAccount, device); accountAuthenticator.updateLastSeen(oldAccount, device);
@ -160,7 +161,9 @@ class AccountAuthenticatorTest {
clock.unpin(); clock.unpin();
when(accountsManager.getByAccountIdentifier(uuid)).thenReturn(Optional.of(account)); when(accountsManager.getByAccountIdentifier(uuid)).thenReturn(Optional.of(account));
when(account.getUuid()).thenReturn(uuid); when(account.getUuid()).thenReturn(uuid);
when(account.getIdentifier(IdentityType.ACI)).thenReturn(uuid);
when(account.getDevice(deviceId)).thenReturn(Optional.of(device)); when(account.getDevice(deviceId)).thenReturn(Optional.of(device));
when(account.getPrimaryDevice()).thenReturn(device);
when(device.getId()).thenReturn(deviceId); when(device.getId()).thenReturn(deviceId);
when(device.getAuthTokenHash()).thenReturn(credentials); when(device.getAuthTokenHash()).thenReturn(credentials);
when(credentials.verify(password)).thenReturn(true); when(credentials.verify(password)).thenReturn(true);
@ -170,9 +173,9 @@ class AccountAuthenticatorTest {
accountAuthenticator.authenticate(new BasicCredentials(uuid.toString(), password)); accountAuthenticator.authenticate(new BasicCredentials(uuid.toString(), password));
assertThat(maybeAuthenticatedAccount).isPresent(); assertThat(maybeAuthenticatedAccount).isPresent();
assertThat(maybeAuthenticatedAccount.get().getAccount().getUuid()).isEqualTo(uuid); assertThat(maybeAuthenticatedAccount.orElseThrow().accountIdentifier()).isEqualTo(uuid);
assertThat(maybeAuthenticatedAccount.get().getAuthenticatedDevice()).isEqualTo(device); assertThat(maybeAuthenticatedAccount.orElseThrow().deviceId()).isEqualTo(device.getId());
verify(accountsManager, never()).updateDeviceAuthentication(any(), any(), any());; verify(accountsManager, never()).updateDeviceAuthentication(any(), any(), any());
} }
@Test @Test
@ -188,7 +191,9 @@ class AccountAuthenticatorTest {
clock.unpin(); clock.unpin();
when(accountsManager.getByAccountIdentifier(uuid)).thenReturn(Optional.of(account)); when(accountsManager.getByAccountIdentifier(uuid)).thenReturn(Optional.of(account));
when(account.getUuid()).thenReturn(uuid); when(account.getUuid()).thenReturn(uuid);
when(account.getIdentifier(IdentityType.ACI)).thenReturn(uuid);
when(account.getDevice(deviceId)).thenReturn(Optional.of(device)); when(account.getDevice(deviceId)).thenReturn(Optional.of(device));
when(account.getPrimaryDevice()).thenReturn(device);
when(device.getId()).thenReturn(deviceId); when(device.getId()).thenReturn(deviceId);
when(device.getAuthTokenHash()).thenReturn(credentials); when(device.getAuthTokenHash()).thenReturn(credentials);
when(credentials.verify(password)).thenReturn(true); when(credentials.verify(password)).thenReturn(true);
@ -198,15 +203,13 @@ class AccountAuthenticatorTest {
accountAuthenticator.authenticate(new BasicCredentials(uuid + "." + deviceId, password)); accountAuthenticator.authenticate(new BasicCredentials(uuid + "." + deviceId, password));
assertThat(maybeAuthenticatedAccount).isPresent(); assertThat(maybeAuthenticatedAccount).isPresent();
assertThat(maybeAuthenticatedAccount.get().getAccount().getUuid()).isEqualTo(uuid); assertThat(maybeAuthenticatedAccount.orElseThrow().accountIdentifier()).isEqualTo(uuid);
assertThat(maybeAuthenticatedAccount.get().getAuthenticatedDevice()).isEqualTo(device); assertThat(maybeAuthenticatedAccount.orElseThrow().deviceId()).isEqualTo(device.getId());
verify(accountsManager, never()).updateDeviceAuthentication(any(), any(), any()); verify(accountsManager, never()).updateDeviceAuthentication(any(), any(), any());
} }
@CartesianTest @CartesianTest
void testAuthenticateEnabled( void testAuthenticateEnabled(
@CartesianTest.Values(booleans = {true, false}) final boolean accountEnabled,
@CartesianTest.Values(booleans = {true, false}) final boolean deviceEnabled,
@CartesianTest.Values(booleans = {true, false}) final boolean authenticatedDeviceIsPrimary) { @CartesianTest.Values(booleans = {true, false}) final boolean authenticatedDeviceIsPrimary) {
final UUID uuid = UUID.randomUUID(); final UUID uuid = UUID.randomUUID();
final byte deviceId = (byte) (authenticatedDeviceIsPrimary ? 1 : 2); final byte deviceId = (byte) (authenticatedDeviceIsPrimary ? 1 : 2);
@ -219,7 +222,9 @@ class AccountAuthenticatorTest {
clock.unpin(); clock.unpin();
when(accountsManager.getByAccountIdentifier(uuid)).thenReturn(Optional.of(account)); when(accountsManager.getByAccountIdentifier(uuid)).thenReturn(Optional.of(account));
when(account.getUuid()).thenReturn(uuid); when(account.getUuid()).thenReturn(uuid);
when(account.getIdentifier(IdentityType.ACI)).thenReturn(uuid);
when(account.getDevice(deviceId)).thenReturn(Optional.of(authenticatedDevice)); when(account.getDevice(deviceId)).thenReturn(Optional.of(authenticatedDevice));
when(account.getPrimaryDevice()).thenReturn(authenticatedDevice);
when(authenticatedDevice.getId()).thenReturn(deviceId); when(authenticatedDevice.getId()).thenReturn(deviceId);
when(authenticatedDevice.getAuthTokenHash()).thenReturn(credentials); when(authenticatedDevice.getAuthTokenHash()).thenReturn(credentials);
when(credentials.verify(password)).thenReturn(true); when(credentials.verify(password)).thenReturn(true);
@ -235,9 +240,8 @@ class AccountAuthenticatorTest {
accountAuthenticator.authenticate(new BasicCredentials(identifier, password)); accountAuthenticator.authenticate(new BasicCredentials(identifier, password));
assertThat(maybeAuthenticatedAccount).isPresent(); assertThat(maybeAuthenticatedAccount).isPresent();
assertThat(maybeAuthenticatedAccount.get().getAccount().getUuid()).isEqualTo(uuid); assertThat(maybeAuthenticatedAccount.orElseThrow().accountIdentifier()).isEqualTo(uuid);
assertThat(maybeAuthenticatedAccount.get().getAuthenticatedDevice()).isEqualTo(authenticatedDevice); assertThat(maybeAuthenticatedAccount.orElseThrow().deviceId()).isEqualTo(authenticatedDevice.getId());
} }
@Test @Test
@ -253,7 +257,9 @@ class AccountAuthenticatorTest {
clock.unpin(); clock.unpin();
when(accountsManager.getByAccountIdentifier(uuid)).thenReturn(Optional.of(account)); when(accountsManager.getByAccountIdentifier(uuid)).thenReturn(Optional.of(account));
when(account.getUuid()).thenReturn(uuid); when(account.getUuid()).thenReturn(uuid);
when(account.getIdentifier(IdentityType.ACI)).thenReturn(uuid);
when(account.getDevice(deviceId)).thenReturn(Optional.of(device)); when(account.getDevice(deviceId)).thenReturn(Optional.of(device));
when(account.getPrimaryDevice()).thenReturn(device);
when(device.getId()).thenReturn(deviceId); when(device.getId()).thenReturn(deviceId);
when(device.getAuthTokenHash()).thenReturn(credentials); when(device.getAuthTokenHash()).thenReturn(credentials);
when(credentials.verify(password)).thenReturn(true); when(credentials.verify(password)).thenReturn(true);
@ -263,8 +269,8 @@ class AccountAuthenticatorTest {
accountAuthenticator.authenticate(new BasicCredentials(uuid.toString(), password)); accountAuthenticator.authenticate(new BasicCredentials(uuid.toString(), password));
assertThat(maybeAuthenticatedAccount).isPresent(); assertThat(maybeAuthenticatedAccount).isPresent();
assertThat(maybeAuthenticatedAccount.get().getAccount().getUuid()).isEqualTo(uuid); assertThat(maybeAuthenticatedAccount.orElseThrow().accountIdentifier()).isEqualTo(uuid);
assertThat(maybeAuthenticatedAccount.get().getAuthenticatedDevice()).isEqualTo(device); assertThat(maybeAuthenticatedAccount.orElseThrow().deviceId()).isEqualTo(device.getId());
verify(accountsManager, times(1)).updateDeviceAuthentication( verify(accountsManager, times(1)).updateDeviceAuthentication(
any(), // this won't be 'account', because it'll already be updated by updateDeviceLastSeen any(), // this won't be 'account', because it'll already be updated by updateDeviceLastSeen
eq(device), any()); eq(device), any());
@ -288,7 +294,9 @@ class AccountAuthenticatorTest {
clock.unpin(); clock.unpin();
when(accountsManager.getByAccountIdentifier(uuid)).thenReturn(Optional.of(account)); when(accountsManager.getByAccountIdentifier(uuid)).thenReturn(Optional.of(account));
when(account.getUuid()).thenReturn(uuid); when(account.getUuid()).thenReturn(uuid);
when(account.getIdentifier(IdentityType.ACI)).thenReturn(uuid);
when(account.getDevice(deviceId)).thenReturn(Optional.of(device)); when(account.getDevice(deviceId)).thenReturn(Optional.of(device));
when(account.getPrimaryDevice()).thenReturn(device);
when(device.getId()).thenReturn(deviceId); when(device.getId()).thenReturn(deviceId);
when(device.getAuthTokenHash()).thenReturn(credentials); when(device.getAuthTokenHash()).thenReturn(credentials);
when(credentials.verify(password)).thenReturn(true); when(credentials.verify(password)).thenReturn(true);
@ -314,7 +322,9 @@ class AccountAuthenticatorTest {
clock.unpin(); clock.unpin();
when(accountsManager.getByAccountIdentifier(uuid)).thenReturn(Optional.of(account)); when(accountsManager.getByAccountIdentifier(uuid)).thenReturn(Optional.of(account));
when(account.getUuid()).thenReturn(uuid); when(account.getUuid()).thenReturn(uuid);
when(account.getIdentifier(IdentityType.ACI)).thenReturn(uuid);
when(account.getDevice(deviceId)).thenReturn(Optional.of(device)); when(account.getDevice(deviceId)).thenReturn(Optional.of(device));
when(account.getPrimaryDevice()).thenReturn(device);
when(device.getId()).thenReturn(deviceId); when(device.getId()).thenReturn(deviceId);
when(device.getAuthTokenHash()).thenReturn(credentials); when(device.getAuthTokenHash()).thenReturn(credentials);
when(credentials.verify(password)).thenReturn(true); when(credentials.verify(password)).thenReturn(true);

View File

@ -8,12 +8,12 @@ package org.whispersystems.textsecuregcm.auth;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.eclipse.jetty.websocket.server.JettyServerUpgradeRequest; import org.eclipse.jetty.websocket.server.JettyServerUpgradeRequest;
import org.eclipse.jetty.websocket.server.JettyServerUpgradeResponse; import org.eclipse.jetty.websocket.server.JettyServerUpgradeResponse;
@ -21,7 +21,6 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.util.TestClock; import org.whispersystems.textsecuregcm.util.TestClock;
@ -59,26 +58,8 @@ class IdlePrimaryDeviceAuthenticatedWebSocketUpgradeFilterTest {
} }
private static List<Arguments> handleAuthentication() { private static List<Arguments> handleAuthentication() {
final Device activePrimaryDevice = mock(Device.class); final Instant activePrimaryDeviceLastSeen = CLOCK.instant();
when(activePrimaryDevice.getId()).thenReturn(Device.PRIMARY_ID); final Instant idlePrimaryDeviceLastSeen = CLOCK.instant().minus(MIN_IDLE_DURATION).minusSeconds(1);
when(activePrimaryDevice.isPrimary()).thenReturn(true);
when(activePrimaryDevice.getLastSeen()).thenReturn(CLOCK.millis());
final Device minIdlePrimaryDevice = mock(Device.class);
when(minIdlePrimaryDevice.getId()).thenReturn(Device.PRIMARY_ID);
when(minIdlePrimaryDevice.isPrimary()).thenReturn(true);
when(minIdlePrimaryDevice.getLastSeen())
.thenReturn(CLOCK.instant().minus(MIN_IDLE_DURATION).minusSeconds(1).toEpochMilli());
final Device linkedDevice = mock(Device.class);
when(linkedDevice.getId()).thenReturn((byte) (Device.PRIMARY_ID + 1));
when(linkedDevice.isPrimary()).thenReturn(false);
final Account accountWithActivePrimaryDevice = mock(Account.class);
when(accountWithActivePrimaryDevice.getPrimaryDevice()).thenReturn(activePrimaryDevice);
final Account accountWithMinIdlePrimaryDevice = mock(Account.class);
when(accountWithMinIdlePrimaryDevice.getPrimaryDevice()).thenReturn(minIdlePrimaryDevice);
return List.of( return List.of(
Arguments.argumentSet("Anonymous", Arguments.argumentSet("Anonymous",
@ -86,19 +67,19 @@ class IdlePrimaryDeviceAuthenticatedWebSocketUpgradeFilterTest {
null), null),
Arguments.argumentSet("Authenticated as active primary device", Arguments.argumentSet("Authenticated as active primary device",
new AuthenticatedDevice(accountWithActivePrimaryDevice, activePrimaryDevice), new AuthenticatedDevice(UUID.randomUUID(), Device.PRIMARY_ID, activePrimaryDeviceLastSeen),
null), null),
Arguments.argumentSet("Authenticated as idle primary device", Arguments.argumentSet("Authenticated as idle primary device",
new AuthenticatedDevice(accountWithMinIdlePrimaryDevice, minIdlePrimaryDevice), new AuthenticatedDevice(UUID.randomUUID(), Device.PRIMARY_ID, idlePrimaryDeviceLastSeen),
null), null),
Arguments.argumentSet("Authenticated as linked device with active primary device", Arguments.argumentSet("Authenticated as linked device with active primary device",
new AuthenticatedDevice(accountWithActivePrimaryDevice, linkedDevice), new AuthenticatedDevice(UUID.randomUUID(), (byte) (Device.PRIMARY_ID + 1), activePrimaryDeviceLastSeen),
null), null),
Arguments.argumentSet("Authenticated as linked device with min-idle primary device", Arguments.argumentSet("Authenticated as linked device with idle primary device",
new AuthenticatedDevice(accountWithMinIdlePrimaryDevice, linkedDevice), new AuthenticatedDevice(UUID.randomUUID(), (byte) (Device.PRIMARY_ID + 1), idlePrimaryDeviceLastSeen),
IdlePrimaryDeviceAuthenticatedWebSocketUpgradeFilter.IDLE_PRIMARY_DEVICE_ALERT) IdlePrimaryDeviceAuthenticatedWebSocketUpgradeFilter.IDLE_PRIMARY_DEVICE_ALERT)
); );
} }

View File

@ -39,7 +39,7 @@ class DirectoryControllerV2Test {
when(account.getIdentifier(IdentityType.ACI)).thenReturn(uuid); when(account.getIdentifier(IdentityType.ACI)).thenReturn(uuid);
final ExternalServiceCredentials credentials = controller.getAuthToken( final ExternalServiceCredentials credentials = controller.getAuthToken(
new AuthenticatedDevice(account, mock(Device.class))); new AuthenticatedDevice(uuid, Device.PRIMARY_ID, Instant.now()));
assertEquals("d369bc712e2e0dd36258", credentials.username()); assertEquals("d369bc712e2e0dd36258", credentials.username());
assertEquals("1633738643:4433b0fab41f25f79dd4", credentials.password()); assertEquals("1633738643:4433b0fab41f25f79dd4", credentials.password());

View File

@ -14,6 +14,7 @@ import com.google.common.net.HttpHeaders;
import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.SecurityContext; import jakarta.ws.rs.core.SecurityContext;
import java.net.URI; import java.net.URI;
import java.time.Instant;
import java.util.Random; import java.util.Random;
import java.util.UUID; import java.util.UUID;
import org.glassfish.jersey.server.ContainerRequest; import org.glassfish.jersey.server.ContainerRequest;
@ -23,7 +24,6 @@ import org.junit.jupiter.params.provider.ValueSource;
import org.whispersystems.textsecuregcm.auth.AuthenticatedDevice; import org.whispersystems.textsecuregcm.auth.AuthenticatedDevice;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager; import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import org.whispersystems.textsecuregcm.tests.util.FakeDynamicConfigurationManager; import org.whispersystems.textsecuregcm.tests.util.FakeDynamicConfigurationManager;
@ -39,10 +39,8 @@ class RestDeprecationFilterTest {
final RestDeprecationFilter filter = new RestDeprecationFilter(dynamicConfigurationManager, experimentEnrollmentManager); final RestDeprecationFilter filter = new RestDeprecationFilter(dynamicConfigurationManager, experimentEnrollmentManager);
final Account account = new Account();
account.setUuid(UUID.randomUUID());
final SecurityContext securityContext = mock(SecurityContext.class); final SecurityContext securityContext = mock(SecurityContext.class);
when(securityContext.getUserPrincipal()).thenReturn(new AuthenticatedDevice(account, new Device())); when(securityContext.getUserPrincipal()).thenReturn(new AuthenticatedDevice(UUID.randomUUID(), Device.PRIMARY_ID, Instant.now()));
final ContainerRequest req = new ContainerRequest(null, new URI("/some/uri"), "GET", securityContext, null, null); final ContainerRequest req = new ContainerRequest(null, new URI("/some/uri"), "GET", securityContext, null, null);
req.getHeaders().add(HttpHeaders.USER_AGENT, "Signal-Android/100.0.0"); req.getHeaders().add(HttpHeaders.USER_AGENT, "Signal-Android/100.0.0");
@ -60,17 +58,15 @@ class RestDeprecationFilterTest {
experiments: experiments:
restDeprecation: restDeprecation:
uuidEnrollmentPercentage: 100 uuidEnrollmentPercentage: 100
""", """,
DynamicConfiguration.class); DynamicConfiguration.class);
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager = new FakeDynamicConfigurationManager<>(config); final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager = new FakeDynamicConfigurationManager<>(config);
final ExperimentEnrollmentManager experimentEnrollmentManager = new ExperimentEnrollmentManager(dynamicConfigurationManager); final ExperimentEnrollmentManager experimentEnrollmentManager = new ExperimentEnrollmentManager(dynamicConfigurationManager);
final RestDeprecationFilter filter = new RestDeprecationFilter(dynamicConfigurationManager, experimentEnrollmentManager); final RestDeprecationFilter filter = new RestDeprecationFilter(dynamicConfigurationManager, experimentEnrollmentManager);
final Account account = new Account();
account.setUuid(UUID.randomUUID());
final SecurityContext securityContext = mock(SecurityContext.class); final SecurityContext securityContext = mock(SecurityContext.class);
when(securityContext.getUserPrincipal()).thenReturn(new AuthenticatedDevice(account, new Device())); when(securityContext.getUserPrincipal()).thenReturn(new AuthenticatedDevice(UUID.randomUUID(), Device.PRIMARY_ID, Instant.now()));
final ContainerRequest req = new ContainerRequest(null, new URI("/some/uri"), "GET", securityContext, null, null); final ContainerRequest req = new ContainerRequest(null, new URI("/some/uri"), "GET", securityContext, null, null);
req.getHeaders().add(HttpHeaders.USER_AGENT, "Signal-Android/100.0.0"); req.getHeaders().add(HttpHeaders.USER_AGENT, "Signal-Android/100.0.0");
@ -90,7 +86,7 @@ class RestDeprecationFilterTest {
experiments: experiments:
restDeprecation: restDeprecation:
uuidEnrollmentPercentage: 100 uuidEnrollmentPercentage: 100
""", """,
DynamicConfiguration.class); DynamicConfiguration.class);
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager = new FakeDynamicConfigurationManager<>(config); final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager = new FakeDynamicConfigurationManager<>(config);
final ExperimentEnrollmentManager experimentEnrollmentManager = new ExperimentEnrollmentManager(dynamicConfigurationManager); final ExperimentEnrollmentManager experimentEnrollmentManager = new ExperimentEnrollmentManager(dynamicConfigurationManager);
@ -116,17 +112,15 @@ class RestDeprecationFilterTest {
experiments: experiments:
restDeprecation: restDeprecation:
enrollmentPercentage: 100 enrollmentPercentage: 100
""", """,
DynamicConfiguration.class); DynamicConfiguration.class);
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager = new FakeDynamicConfigurationManager<>(config); final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager = new FakeDynamicConfigurationManager<>(config);
final ExperimentEnrollmentManager experimentEnrollmentManager = new ExperimentEnrollmentManager(dynamicConfigurationManager); final ExperimentEnrollmentManager experimentEnrollmentManager = new ExperimentEnrollmentManager(dynamicConfigurationManager);
final RestDeprecationFilter filter = new RestDeprecationFilter(dynamicConfigurationManager, experimentEnrollmentManager); final RestDeprecationFilter filter = new RestDeprecationFilter(dynamicConfigurationManager, experimentEnrollmentManager);
final Account account = new Account();
account.setUuid(UUID.randomUUID());
final SecurityContext securityContext = mock(SecurityContext.class); final SecurityContext securityContext = mock(SecurityContext.class);
when(securityContext.getUserPrincipal()).thenReturn(new AuthenticatedDevice(account, new Device())); when(securityContext.getUserPrincipal()).thenReturn(new AuthenticatedDevice(UUID.randomUUID(), Device.PRIMARY_ID, Instant.now()));
final ContainerRequest req = new ContainerRequest(null, new URI("/some/path"), "GET", securityContext, null, null); final ContainerRequest req = new ContainerRequest(null, new URI("/some/path"), "GET", securityContext, null, null);
req.getHeaders().putSingle(HttpHeaders.USER_AGENT, "Signal-Android/10.9.15"); req.getHeaders().putSingle(HttpHeaders.USER_AGENT, "Signal-Android/10.9.15");
@ -152,7 +146,7 @@ class RestDeprecationFilterTest {
ANDROID: ANDROID:
minimumRestFreeVersion: 10.10.10 minimumRestFreeVersion: 10.10.10
universalRolloutPercent: 70 universalRolloutPercent: 70
""", """,
DynamicConfiguration.class); DynamicConfiguration.class);
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager = new FakeDynamicConfigurationManager<>(config); final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager = new FakeDynamicConfigurationManager<>(config);
final ExperimentEnrollmentManager experimentEnrollmentManager = new ExperimentEnrollmentManager(dynamicConfigurationManager); final ExperimentEnrollmentManager experimentEnrollmentManager = new ExperimentEnrollmentManager(dynamicConfigurationManager);

View File

@ -14,7 +14,9 @@ import static org.mockito.Mockito.when;
import com.google.common.net.HttpHeaders; import com.google.common.net.HttpHeaders;
import com.google.i18n.phonenumbers.PhoneNumberUtil; import com.google.i18n.phonenumbers.PhoneNumberUtil;
import io.dropwizard.auth.basic.BasicCredentials; import io.dropwizard.auth.basic.BasicCredentials;
import java.time.Instant;
import java.util.Optional; import java.util.Optional;
import java.util.UUID;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.eclipse.jetty.websocket.api.UpgradeRequest; import org.eclipse.jetty.websocket.api.UpgradeRequest;
@ -24,7 +26,6 @@ import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.whispersystems.textsecuregcm.auth.AccountAuthenticator; import org.whispersystems.textsecuregcm.auth.AccountAuthenticator;
import org.whispersystems.textsecuregcm.auth.AuthenticatedDevice; import org.whispersystems.textsecuregcm.auth.AuthenticatedDevice;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.util.HeaderUtils; import org.whispersystems.textsecuregcm.util.HeaderUtils;
import org.whispersystems.websocket.auth.InvalidCredentialsException; import org.whispersystems.websocket.auth.InvalidCredentialsException;
@ -50,7 +51,7 @@ class WebSocketAccountAuthenticatorTest {
accountAuthenticator = mock(AccountAuthenticator.class); accountAuthenticator = mock(AccountAuthenticator.class);
when(accountAuthenticator.authenticate(eq(new BasicCredentials(VALID_USER, VALID_PASSWORD)))) when(accountAuthenticator.authenticate(eq(new BasicCredentials(VALID_USER, VALID_PASSWORD))))
.thenReturn(Optional.of(new AuthenticatedDevice(mock(Account.class), mock(Device.class)))); .thenReturn(Optional.of(new AuthenticatedDevice(UUID.randomUUID(), Device.PRIMARY_ID, Instant.now())));
when(accountAuthenticator.authenticate(eq(new BasicCredentials(INVALID_USER, INVALID_PASSWORD)))) when(accountAuthenticator.authenticate(eq(new BasicCredentials(INVALID_USER, INVALID_PASSWORD))))
.thenReturn(Optional.empty()); .thenReturn(Optional.empty());

View File

@ -33,6 +33,7 @@ import io.lettuce.core.RedisException;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.Duration; import java.time.Duration;
import java.time.Instant;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -79,7 +80,8 @@ import reactor.test.publisher.TestPublisher;
class WebSocketConnectionTest { class WebSocketConnectionTest {
private static final String VALID_USER = "+14152222222"; private static final String VALID_E164 = "+14152222222";
private static final UUID VALID_UUID = UUID.randomUUID();
private static final int SOURCE_DEVICE_ID = 1; private static final int SOURCE_DEVICE_ID = 1;
@ -127,8 +129,8 @@ class WebSocketConnectionTest {
mock(ExperimentEnrollmentManager.class)); mock(ExperimentEnrollmentManager.class));
WebSocketSessionContext sessionContext = mock(WebSocketSessionContext.class); WebSocketSessionContext sessionContext = mock(WebSocketSessionContext.class);
when(accountAuthenticator.authenticate(eq(new BasicCredentials(VALID_USER, VALID_PASSWORD)))) when(accountAuthenticator.authenticate(eq(new BasicCredentials(VALID_E164, VALID_PASSWORD))))
.thenReturn(Optional.of(new AuthenticatedDevice(account, device))); .thenReturn(Optional.of(new AuthenticatedDevice(VALID_UUID, Device.PRIMARY_ID, Instant.now())));
Optional<AuthenticatedDevice> account = webSocketAuthenticator.authenticate(upgradeRequest); Optional<AuthenticatedDevice> account = webSocketAuthenticator.authenticate(upgradeRequest);
when(sessionContext.getAuthenticated()).thenReturn(account.orElse(null)); when(sessionContext.getAuthenticated()).thenReturn(account.orElse(null));