Retire AmbiguousIdentifier

This commit is contained in:
Jon Chambers
2021-08-27 13:40:46 -04:00
committed by GitHub
parent 1f815b49dd
commit d1735c7e57
30 changed files with 899 additions and 906 deletions

View File

@@ -41,11 +41,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials;
import org.whispersystems.textsecuregcm.auth.AuthorizationHeader;
import org.whispersystems.textsecuregcm.auth.BasicAuthorizationHeader;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.InvalidAuthorizationHeaderException;
import org.whispersystems.textsecuregcm.auth.StoredRegistrationLock;
import org.whispersystems.textsecuregcm.auth.StoredVerificationCode;
import org.whispersystems.textsecuregcm.auth.TurnToken;
@@ -326,79 +325,70 @@ public class AccountController {
@Produces(MediaType.APPLICATION_JSON)
@Path("/code/{verification_code}")
public AccountCreationResult verifyAccount(@PathParam("verification_code") String verificationCode,
@HeaderParam("Authorization") String authorizationHeader,
@HeaderParam("X-Signal-Agent") String signalAgent,
@HeaderParam("User-Agent") String userAgent,
@QueryParam("transfer") Optional<Boolean> availableForTransfer,
@Valid AccountAttributes accountAttributes)
@HeaderParam("Authorization") BasicAuthorizationHeader authorizationHeader,
@HeaderParam("X-Signal-Agent") String signalAgent,
@HeaderParam("User-Agent") String userAgent,
@QueryParam("transfer") Optional<Boolean> availableForTransfer,
@Valid AccountAttributes accountAttributes)
throws RateLimitExceededException, InterruptedException {
try {
AuthorizationHeader header = AuthorizationHeader.fromFullHeader(authorizationHeader);
String number = header.getIdentifier().getNumber();
String password = header.getPassword();
if (number == null) {
throw new WebApplicationException(400);
}
String number = authorizationHeader.getUsername();
String password = authorizationHeader.getPassword();
rateLimiters.getVerifyLimiter().validate(number);
rateLimiters.getVerifyLimiter().validate(number);
Optional<StoredVerificationCode> storedVerificationCode = pendingAccounts.getCodeForNumber(number);
Optional<StoredVerificationCode> storedVerificationCode = pendingAccounts.getCodeForNumber(number);
if (storedVerificationCode.isEmpty() || !storedVerificationCode.get().isValid(verificationCode)) {
throw new WebApplicationException(Response.status(403).build());
}
storedVerificationCode.flatMap(StoredVerificationCode::getTwilioVerificationSid)
.ifPresent(smsSender::reportVerificationSucceeded);
Optional<Account> existingAccount = accounts.get(number);
Optional<StoredRegistrationLock> existingRegistrationLock = existingAccount.map(Account::getRegistrationLock);
Optional<ExternalServiceCredentials> existingBackupCredentials = existingAccount.map(Account::getUuid)
.map(uuid -> backupServiceCredentialGenerator.generateFor(uuid.toString()));
if (existingRegistrationLock.isPresent() && existingRegistrationLock.get().requiresClientRegistrationLock()) {
rateLimiters.getVerifyLimiter().clear(number);
if (!Util.isEmpty(accountAttributes.getRegistrationLock())) {
rateLimiters.getPinLimiter().validate(number);
}
if (!existingRegistrationLock.get().verify(accountAttributes.getRegistrationLock())) {
throw new WebApplicationException(Response.status(423)
.entity(new RegistrationLockFailure(existingRegistrationLock.get().getTimeRemaining(),
existingRegistrationLock.get().needsFailureCredentials() ? existingBackupCredentials.orElseThrow() : null))
.build());
}
rateLimiters.getPinLimiter().clear(number);
}
if (availableForTransfer.orElse(false) && existingAccount.map(Account::isTransferSupported).orElse(false)) {
throw new WebApplicationException(Response.status(409).build());
}
Account account = accounts.create(number, password, signalAgent, accountAttributes);
{
metricRegistry.meter(name(AccountController.class, "verify", Util.getCountryCode(number))).mark();
final List<Tag> tags = new ArrayList<>();
tags.add(Tag.of(COUNTRY_CODE_TAG_NAME, Util.getCountryCode(number)));
tags.add(UserAgentTagUtil.getPlatformTag(userAgent));
tags.add(Tag.of(VERIFY_EXPERIMENT_TAG_NAME, String.valueOf(storedVerificationCode.get().getTwilioVerificationSid().isPresent())));
Metrics.counter(ACCOUNT_VERIFY_COUNTER_NAME, tags).increment();
Metrics.timer(name(AccountController.class, "verifyDuration"), tags)
.record(Instant.now().toEpochMilli() - storedVerificationCode.get().getTimestamp(), TimeUnit.MILLISECONDS);
}
return new AccountCreationResult(account.getUuid(), existingAccount.map(Account::isStorageSupported).orElse(false));
} catch (InvalidAuthorizationHeaderException e) {
logger.info("Bad Authorization Header", e);
throw new WebApplicationException(Response.status(401).build());
if (storedVerificationCode.isEmpty() || !storedVerificationCode.get().isValid(verificationCode)) {
throw new WebApplicationException(Response.status(403).build());
}
storedVerificationCode.flatMap(StoredVerificationCode::getTwilioVerificationSid)
.ifPresent(smsSender::reportVerificationSucceeded);
Optional<Account> existingAccount = accounts.get(number);
Optional<StoredRegistrationLock> existingRegistrationLock = existingAccount.map(Account::getRegistrationLock);
Optional<ExternalServiceCredentials> existingBackupCredentials = existingAccount.map(Account::getUuid)
.map(uuid -> backupServiceCredentialGenerator.generateFor(uuid.toString()));
if (existingRegistrationLock.isPresent() && existingRegistrationLock.get().requiresClientRegistrationLock()) {
rateLimiters.getVerifyLimiter().clear(number);
if (!Util.isEmpty(accountAttributes.getRegistrationLock())) {
rateLimiters.getPinLimiter().validate(number);
}
if (!existingRegistrationLock.get().verify(accountAttributes.getRegistrationLock())) {
throw new WebApplicationException(Response.status(423)
.entity(new RegistrationLockFailure(existingRegistrationLock.get().getTimeRemaining(),
existingRegistrationLock.get().needsFailureCredentials() ? existingBackupCredentials.orElseThrow() : null))
.build());
}
rateLimiters.getPinLimiter().clear(number);
}
if (availableForTransfer.orElse(false) && existingAccount.map(Account::isTransferSupported).orElse(false)) {
throw new WebApplicationException(Response.status(409).build());
}
Account account = accounts.create(number, password, signalAgent, accountAttributes);
{
metricRegistry.meter(name(AccountController.class, "verify", Util.getCountryCode(number))).mark();
final List<Tag> tags = new ArrayList<>();
tags.add(Tag.of(COUNTRY_CODE_TAG_NAME, Util.getCountryCode(number)));
tags.add(UserAgentTagUtil.getPlatformTag(userAgent));
tags.add(Tag.of(VERIFY_EXPERIMENT_TAG_NAME, String.valueOf(storedVerificationCode.get().getTwilioVerificationSid().isPresent())));
Metrics.counter(ACCOUNT_VERIFY_COUNTER_NAME, tags).increment();
Metrics.timer(name(AccountController.class, "verifyDuration"), tags)
.record(Instant.now().toEpochMilli() - storedVerificationCode.get().getTimestamp(), TimeUnit.MILLISECONDS);
}
return new AccountCreationResult(account.getUuid(), existingAccount.map(Account::isStorageSupported).orElse(false));
}
@Timed

View File

@@ -24,12 +24,9 @@ import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials;
import org.whispersystems.textsecuregcm.auth.AuthorizationHeader;
import org.whispersystems.textsecuregcm.auth.InvalidAuthorizationHeaderException;
import org.whispersystems.textsecuregcm.auth.BasicAuthorizationHeader;
import org.whispersystems.textsecuregcm.auth.StoredVerificationCode;
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
import org.whispersystems.textsecuregcm.entities.DeviceInfo;
@@ -51,8 +48,6 @@ import org.whispersystems.textsecuregcm.util.ua.UserAgentUtil;
@Path("/v1/devices")
public class DeviceController {
private final Logger logger = LoggerFactory.getLogger(DeviceController.class);
private static final int MAX_DEVICES = 6;
private final StoredVerificationCodeManager pendingDevices;
@@ -149,55 +144,52 @@ public class DeviceController {
@Consumes(MediaType.APPLICATION_JSON)
@Path("/{verification_code}")
public DeviceResponse verifyDeviceToken(@PathParam("verification_code") String verificationCode,
@HeaderParam("Authorization") String authorizationHeader,
@HeaderParam("User-Agent") String userAgent,
@Valid AccountAttributes accountAttributes)
@HeaderParam("Authorization") BasicAuthorizationHeader authorizationHeader,
@HeaderParam("User-Agent") String userAgent,
@Valid AccountAttributes accountAttributes)
throws RateLimitExceededException, DeviceLimitExceededException
{
try {
AuthorizationHeader header = AuthorizationHeader.fromFullHeader(authorizationHeader);
String number = header.getIdentifier().getNumber();
String password = header.getPassword();
if (number == null) throw new WebApplicationException(400);
String number = authorizationHeader.getUsername();
String password = authorizationHeader.getPassword();
rateLimiters.getVerifyDeviceLimiter().validate(number);
rateLimiters.getVerifyDeviceLimiter().validate(number);
Optional<StoredVerificationCode> storedVerificationCode = pendingDevices.getCodeForNumber(number);
Optional<StoredVerificationCode> storedVerificationCode = pendingDevices.getCodeForNumber(number);
if (!storedVerificationCode.isPresent() || !storedVerificationCode.get().isValid(verificationCode)) {
throw new WebApplicationException(Response.status(403).build());
}
if (!storedVerificationCode.isPresent() || !storedVerificationCode.get().isValid(verificationCode)) {
throw new WebApplicationException(Response.status(403).build());
}
Optional<Account> account = accounts.get(number);
Optional<Account> account = accounts.get(number);
if (!account.isPresent()) {
throw new WebApplicationException(Response.status(403).build());
}
if (!account.isPresent()) {
throw new WebApplicationException(Response.status(403).build());
}
int maxDeviceLimit = MAX_DEVICES;
int maxDeviceLimit = MAX_DEVICES;
if (maxDeviceConfiguration.containsKey(account.get().getNumber())) {
maxDeviceLimit = maxDeviceConfiguration.get(account.get().getNumber());
}
if (maxDeviceConfiguration.containsKey(account.get().getNumber())) {
maxDeviceLimit = maxDeviceConfiguration.get(account.get().getNumber());
}
if (account.get().getEnabledDeviceCount() >= maxDeviceLimit) {
throw new DeviceLimitExceededException(account.get().getDevices().size(), MAX_DEVICES);
}
if (account.get().getEnabledDeviceCount() >= maxDeviceLimit) {
throw new DeviceLimitExceededException(account.get().getDevices().size(), MAX_DEVICES);
}
final DeviceCapabilities capabilities = accountAttributes.getCapabilities();
if (capabilities != null && isCapabilityDowngrade(account.get(), capabilities, userAgent)) {
throw new WebApplicationException(Response.status(409).build());
}
final DeviceCapabilities capabilities = accountAttributes.getCapabilities();
if (capabilities != null && isCapabilityDowngrade(account.get(), capabilities, userAgent)) {
throw new WebApplicationException(Response.status(409).build());
}
Device device = new Device();
device.setName(accountAttributes.getName());
device.setAuthenticationCredentials(new AuthenticationCredentials(password));
device.setFetchesMessages(accountAttributes.getFetchesMessages());
device.setRegistrationId(accountAttributes.getRegistrationId());
device.setLastSeen(Util.todayInMillis());
device.setCreated(System.currentTimeMillis());
device.setCapabilities(accountAttributes.getCapabilities());
Device device = new Device();
device.setName(accountAttributes.getName());
device.setAuthenticationCredentials(new AuthenticationCredentials(password));
device.setFetchesMessages(accountAttributes.getFetchesMessages());
device.setRegistrationId(accountAttributes.getRegistrationId());
device.setLastSeen(Util.todayInMillis());
device.setCreated(System.currentTimeMillis());
device.setCapabilities(accountAttributes.getCapabilities());
accounts.update(account.get(), a -> {
device.setId(a.getNextDeviceId());
@@ -205,13 +197,9 @@ public class DeviceController {
a.addDevice(device);
});
pendingDevices.remove(number);
pendingDevices.remove(number);
return new DeviceResponse(device.getId());
} catch (InvalidAuthorizationHeaderException e) {
logger.info("Bad Authorization Header", e);
throw new WebApplicationException(Response.status(401).build());
}
return new DeviceResponse(device.getId());
}
@Timed

View File

@@ -15,6 +15,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
@@ -26,7 +27,6 @@ import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.whispersystems.textsecuregcm.auth.AmbiguousIdentifier;
import org.whispersystems.textsecuregcm.auth.Anonymous;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount;
@@ -63,7 +63,6 @@ public class KeysController {
private static final String SOURCE_COUNTRY_TAG_NAME = "sourceCountry";
private static final String INTERNATIONAL_TAG_NAME = "international";
private static final String PREKEY_TARGET_IDENTIFIER_TAG_NAME = "identifierType";
public KeysController(RateLimiters rateLimiters, KeysDynamoDb keysDynamoDb, AccountsManager accounts,
PreKeyRateLimiter preKeyRateLimiter,
@@ -119,20 +118,18 @@ public class KeysController {
@Produces(MediaType.APPLICATION_JSON)
public Response getDeviceKeys(@Auth Optional<AuthenticatedAccount> auth,
@HeaderParam(OptionalAccess.UNIDENTIFIED) Optional<Anonymous> accessKey,
@PathParam("identifier") AmbiguousIdentifier targetName,
@PathParam("identifier") UUID targetUuid,
@PathParam("device_id") String deviceId,
@HeaderParam("User-Agent") String userAgent)
throws RateLimitExceededException, RateLimitChallengeException, ServerRejectedException {
targetName.incrementRequestCounter("getDeviceKeys", userAgent);
if (auth.isEmpty() && accessKey.isEmpty()) {
if (!auth.isPresent() && !accessKey.isPresent()) {
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
}
final Optional<Account> account = auth.map(AuthenticatedAccount::getAccount);
Optional<Account> target = accounts.get(targetName);
Optional<Account> target = accounts.get(targetUuid);
OptionalAccess.verify(account, accessKey, target, deviceId);
assert (target.isPresent());
@@ -143,8 +140,7 @@ public class KeysController {
Metrics.counter(PREKEY_REQUEST_COUNTER_NAME, Tags.of(
SOURCE_COUNTRY_TAG_NAME, sourceCountryCode,
INTERNATIONAL_TAG_NAME, String.valueOf(!sourceCountryCode.equals(targetCountryCode)),
PREKEY_TARGET_IDENTIFIER_TAG_NAME, targetName.hasNumber() ? "number" : "uuid"
INTERNATIONAL_TAG_NAME, String.valueOf(!sourceCountryCode.equals(targetCountryCode))
)).increment();
}

View File

@@ -65,7 +65,6 @@ import javax.ws.rs.core.Response.Status;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.auth.AmbiguousIdentifier;
import org.whispersystems.textsecuregcm.auth.Anonymous;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.CombinedUnidentifiedSenderAccessKeys;
@@ -154,7 +153,6 @@ public class MessageController {
private static final String EPHEMERAL_TAG_NAME = "ephemeral";
private static final String SENDER_TYPE_TAG_NAME = "senderType";
private static final String SENDER_COUNTRY_TAG_NAME = "senderCountry";
private static final String DESTINATION_TYPE_TAG_NAME = "destinationType";
private static final long MAX_MESSAGE_SIZE = DataSize.kibibytes(256).toBytes();
@@ -202,17 +200,15 @@ public class MessageController {
@HeaderParam(OptionalAccess.UNIDENTIFIED) Optional<Anonymous> accessKey,
@HeaderParam("User-Agent") String userAgent,
@HeaderParam("X-Forwarded-For") String forwardedFor,
@PathParam("destination") AmbiguousIdentifier destinationName,
@PathParam("destination") UUID destinationUuid,
@Valid IncomingMessageList messages)
throws RateLimitExceededException, RateLimitChallengeException {
destinationName.incrementRequestCounter("sendMessage", userAgent);
if (source.isEmpty() && accessKey.isEmpty()) {
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
}
if (source.isPresent() && !source.get().getAccount().isFor(destinationName)) {
if (source.isPresent() && !source.get().getAccount().getUuid().equals(destinationUuid)) {
assert source.get().getAccount().getMasterDevice().isPresent();
final Device masterDevice = source.get().getAccount().getMasterDevice().get();
@@ -227,7 +223,7 @@ public class MessageController {
final String senderType;
if (source.isPresent() && !source.get().getAccount().isFor(destinationName)) {
if (source.isPresent() && !source.get().getAccount().getUuid().equals(destinationUuid)) {
identifiedMeter.mark();
senderType = "identified";
} else if (source.isEmpty()) {
@@ -257,12 +253,12 @@ public class MessageController {
}
try {
boolean isSyncMessage = source.isPresent() && source.get().getAccount().isFor(destinationName);
boolean isSyncMessage = source.isPresent() && source.get().getAccount().getUuid().equals(destinationUuid);
Optional<Account> destination;
if (!isSyncMessage) {
destination = accountsManager.get(destinationName);
destination = accountsManager.get(destinationUuid);
} else {
destination = source.map(AuthenticatedAccount::getAccount);
}
@@ -270,7 +266,7 @@ public class MessageController {
OptionalAccess.verify(source.map(AuthenticatedAccount::getAccount), accessKey, destination);
assert (destination.isPresent());
if (source.isPresent() && !source.get().getAccount().isFor(destinationName)) {
if (source.isPresent() && !source.get().getAccount().getUuid().equals(destinationUuid)) {
rateLimiters.getMessagesLimiter().validate(source.get().getAccount().getUuid(), destination.get().getUuid());
final String senderCountryCode = Util.getCountryCode(source.get().getAccount().getNumber());
@@ -320,8 +316,7 @@ public class MessageController {
final List<Tag> tags = List.of(UserAgentTagUtil.getPlatformTag(userAgent),
Tag.of(EPHEMERAL_TAG_NAME, String.valueOf(messages.isOnline())),
Tag.of(SENDER_TYPE_TAG_NAME, senderType),
Tag.of(DESTINATION_TYPE_TAG_NAME, destinationName.hasNumber() ? "e164" : "uuid"));
Tag.of(SENDER_TYPE_TAG_NAME, senderType));
for (IncomingMessage incomingMessage : messages.getMessages()) {
Optional<Device> destinationDevice = destination.get().getDevice(incomingMessage.getDestinationDeviceId());

View File

@@ -39,7 +39,6 @@ import org.signal.zkgroup.profiles.ProfileKeyCredentialResponse;
import org.signal.zkgroup.profiles.ServerZkProfileOperations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.auth.AmbiguousIdentifier;
import org.whispersystems.textsecuregcm.auth.Anonymous;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.OptionalAccess;
@@ -339,12 +338,10 @@ public class ProfileController {
public Profile getProfile(@Auth Optional<AuthenticatedAccount> auth,
@HeaderParam(OptionalAccess.UNIDENTIFIED) Optional<Anonymous> accessKey,
@HeaderParam("User-Agent") String userAgent,
@PathParam("identifier") AmbiguousIdentifier identifier,
@PathParam("identifier") UUID identifier,
@QueryParam("ca") boolean useCaCertificate)
throws RateLimitExceededException {
identifier.incrementRequestCounter("getProfile", userAgent);
if (auth.isEmpty() && accessKey.isEmpty()) {
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
}
@@ -356,12 +353,7 @@ public class ProfileController {
Optional<Account> accountProfile = accountsManager.get(identifier);
OptionalAccess.verify(auth.map(AuthenticatedAccount::getAccount), accessKey, accountProfile);
Optional<String> username = Optional.empty();
if (!identifier.hasNumber()) {
//noinspection OptionalGetWithoutIsPresent
username = usernamesManager.get(accountProfile.get().getUuid());
}
Optional<String> username = usernamesManager.get(accountProfile.get().getUuid());
return new Profile(accountProfile.get().getProfileName(),
null,