diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java index 08ceb824b..f77dc45c8 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java @@ -33,6 +33,7 @@ import org.whispersystems.signalservice.api.payments.CurrencyConversions; import org.whispersystems.signalservice.api.profiles.ProfileAndCredential; import org.whispersystems.signalservice.api.profiles.SignalServiceProfileWrite; import org.whispersystems.signalservice.api.push.ACI; +import org.whispersystems.signalservice.api.push.AccountIdentifier; import org.whispersystems.signalservice.api.push.ContactTokenDetails; import org.whispersystems.signalservice.api.push.SignedPreKeyEntity; import org.whispersystems.signalservice.api.push.exceptions.NoContentException; @@ -408,6 +409,13 @@ public class SignalServiceAccountManager { return this.pushServiceSocket.getCurrentSignedPreKey(); } + /** + * @return True if the identifier corresponds to a registered user, otherwise false. + */ + public boolean isIdentifierRegistered(AccountIdentifier identifier) throws IOException { + return pushServiceSocket.isIdentifierRegistered(identifier); + } + /** * Checks whether a contact is currently registered with the server. * diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/ACI.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/ACI.java index d7db3bd71..2e4e27197 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/ACI.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/ACI.java @@ -16,12 +16,10 @@ import io.reactivex.rxjava3.annotations.NonNull; * An ACI is an "Account Identity". They're just UUIDs, but given multiple different things could be UUIDs, this wrapper exists to give us type safety around * this *specific type* of UUID. */ -public final class ACI { +public final class ACI extends AccountIdentifier { public static final ACI UNKNOWN = ACI.from(UuidUtil.UNKNOWN_UUID); - private final UUID uuid; - public static ACI from(UUID uuid) { return new ACI(uuid); } @@ -72,11 +70,7 @@ public final class ACI { } private ACI(UUID uuid) { - this.uuid = uuid; - } - - public UUID uuid() { - return uuid; + super(uuid); } public ByteString toByteString() { diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/AccountIdentifier.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/AccountIdentifier.java new file mode 100644 index 000000000..abf8b7378 --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/AccountIdentifier.java @@ -0,0 +1,24 @@ +package org.whispersystems.signalservice.api.push; + +import java.util.UUID; + +/** + * A wrapper around a UUID that represents an identifier for an account. Today, that is either an {@link ACI} or a {@link PNI}. + */ +public abstract class AccountIdentifier { + + protected final UUID uuid; + + protected AccountIdentifier(UUID uuid) { + this.uuid = uuid; + } + + public UUID uuid() { + return uuid; + } + + @Override + public String toString() { + return uuid.toString(); + } +} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/PNI.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/PNI.java new file mode 100644 index 000000000..f125e7e36 --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/PNI.java @@ -0,0 +1,32 @@ +package org.whispersystems.signalservice.api.push; + +import java.util.UUID; + +/** + * A PNI is a "Phone Number Identity". They're just UUIDs, but given multiple different things could be UUIDs, this wrapper exists to give us type safety around + * this *specific type* of UUID. + */ +public final class PNI extends AccountIdentifier { + + public static PNI from(UUID uuid) { + return new PNI(uuid); + } + + private PNI(UUID uuid) { + super(uuid); + } + + @Override + public int hashCode() { + return uuid.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (other instanceof PNI) { + return uuid.equals(((PNI) other).uuid); + } else { + return false; + } + } +} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java index bf7ff260c..1afb64033 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java @@ -52,6 +52,7 @@ import org.whispersystems.signalservice.api.profiles.ProfileAndCredential; import org.whispersystems.signalservice.api.profiles.SignalServiceProfile; import org.whispersystems.signalservice.api.profiles.SignalServiceProfileWrite; import org.whispersystems.signalservice.api.push.ACI; +import org.whispersystems.signalservice.api.push.AccountIdentifier; import org.whispersystems.signalservice.api.push.ContactTokenDetails; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.SignedPreKeyEntity; @@ -86,7 +87,6 @@ import org.whispersystems.signalservice.api.subscriptions.SubscriptionLevels; import org.whispersystems.signalservice.api.util.CredentialsProvider; import org.whispersystems.signalservice.api.util.Tls12SocketFactory; import org.whispersystems.signalservice.api.util.TlsProxySocketFactory; -import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl; import org.whispersystems.signalservice.internal.configuration.SignalProxy; import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration; @@ -178,20 +178,21 @@ public class PushServiceSocket { private static final String TAG = PushServiceSocket.class.getSimpleName(); - private static final String CREATE_ACCOUNT_SMS_PATH = "/v1/accounts/sms/code/%s?client=%s"; - private static final String CREATE_ACCOUNT_VOICE_PATH = "/v1/accounts/voice/code/%s"; - private static final String VERIFY_ACCOUNT_CODE_PATH = "/v1/accounts/code/%s"; - private static final String REGISTER_GCM_PATH = "/v1/accounts/gcm/"; - private static final String TURN_SERVER_INFO = "/v1/accounts/turn"; - private static final String SET_ACCOUNT_ATTRIBUTES = "/v1/accounts/attributes/"; - private static final String PIN_PATH = "/v1/accounts/pin/"; - private static final String REGISTRATION_LOCK_PATH = "/v1/accounts/registration_lock"; - private static final String REQUEST_PUSH_CHALLENGE = "/v1/accounts/fcm/preauth/%s/%s"; - private static final String WHO_AM_I = "/v1/accounts/whoami"; - private static final String SET_USERNAME_PATH = "/v1/accounts/username/%s"; - private static final String DELETE_USERNAME_PATH = "/v1/accounts/username"; - private static final String DELETE_ACCOUNT_PATH = "/v1/accounts/me"; - private static final String CHANGE_NUMBER_PATH = "/v1/accounts/number"; + private static final String CREATE_ACCOUNT_SMS_PATH = "/v1/accounts/sms/code/%s?client=%s"; + private static final String CREATE_ACCOUNT_VOICE_PATH = "/v1/accounts/voice/code/%s"; + private static final String VERIFY_ACCOUNT_CODE_PATH = "/v1/accounts/code/%s"; + private static final String REGISTER_GCM_PATH = "/v1/accounts/gcm/"; + private static final String TURN_SERVER_INFO = "/v1/accounts/turn"; + private static final String SET_ACCOUNT_ATTRIBUTES = "/v1/accounts/attributes/"; + private static final String PIN_PATH = "/v1/accounts/pin/"; + private static final String REGISTRATION_LOCK_PATH = "/v1/accounts/registration_lock"; + private static final String REQUEST_PUSH_CHALLENGE = "/v1/accounts/fcm/preauth/%s/%s"; + private static final String WHO_AM_I = "/v1/accounts/whoami"; + private static final String SET_USERNAME_PATH = "/v1/accounts/username/%s"; + private static final String DELETE_USERNAME_PATH = "/v1/accounts/username"; + private static final String DELETE_ACCOUNT_PATH = "/v1/accounts/me"; + private static final String CHANGE_NUMBER_PATH = "/v1/accounts/number"; + private static final String IDENTIFIER_REGISTERED_PATH = "/v1/accounts/account/%s"; private static final String PREKEY_METADATA_PATH = "/v2/keys/"; private static final String PREKEY_PATH = "/v2/keys/%s"; @@ -332,7 +333,7 @@ public class PushServiceSocket { public ACI getOwnAci() throws IOException { String body = makeServiceRequest(WHO_AM_I, "GET", null); WhoAmIResponse response = JsonUtil.fromJson(body, WhoAmIResponse.class); - Optional aci = ACI.parse(response.getUuid()); + Optional aci = ACI.parse(response.getAci()); if (aci.isPresent()) { return aci.get(); @@ -345,6 +346,15 @@ public class PushServiceSocket { return JsonUtil.fromJson(makeServiceRequest(WHO_AM_I, "GET", null), WhoAmIResponse.class); } + public boolean isIdentifierRegistered(AccountIdentifier identifier) throws IOException { + try { + makeServiceRequestWithoutAuthentication(String.format(IDENTIFIER_REGISTERED_PATH, identifier.toString()), "HEAD", null); + return true; + } catch (NotFoundException e) { + return false; + } + } + public CdshAuthResponse getCdshAuth() throws IOException { String body = makeServiceRequest(CDSH_AUTH, "GET", null); return JsonUtil.fromJsonResponse(body, CdshAuthResponse.class); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/WhoAmIResponse.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/WhoAmIResponse.java index 8ca14e141..3722e6d76 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/WhoAmIResponse.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/WhoAmIResponse.java @@ -6,13 +6,20 @@ public class WhoAmIResponse { @JsonProperty private String uuid; + @JsonProperty + private String pni; + @JsonProperty private String number; - public String getUuid() { + public String getAci() { return uuid; } + public String getPni() { + return pni; + } + public String getNumber() { return number; }