mirror of
https://github.com/AsamK/signal-cli.git
synced 2026-10-30 09:30:20 +00:00
Align numberless account support with project style
This commit is contained in:
parent
c0b8f390be
commit
29140bbf64
@ -1,8 +1,8 @@
|
||||
# Linking an account without a phone number
|
||||
|
||||
This branch adds secondary-device linking for accounts registered without a phone
|
||||
number, such as those created in Signal Android 8.28. It does not implement creating
|
||||
or recovering a primary numberless account using an Account Key.
|
||||
signal-cli can link as a secondary device to an account registered without a phone
|
||||
number, such as those created in Signal Android 8.28. Creating or recovering a
|
||||
primary numberless account using an Account Key is not supported.
|
||||
|
||||
Run `signal-cli link -n signal-cli`, then scan the QR code from the mobile app's
|
||||
Linked devices screen. The linking URI advertises `capabilities=nopni`. If linking
|
||||
@ -27,6 +27,10 @@ Receive events use the ACI in their `account` field when there is no phone numbe
|
||||
REST wrappers must understand those nullable numbers and ACI selectors before they
|
||||
can be assumed compatible.
|
||||
|
||||
Numberless accounts are supported through the CLI and JSON-RPC interfaces. The
|
||||
D-Bus interface still assumes phone-number-based account identifiers and does not
|
||||
support numberless accounts.
|
||||
|
||||
## Validation
|
||||
|
||||
Run `./gradlew test installDist` with JDK 25. Regression tests cover the linking
|
||||
|
||||
@ -25,6 +25,7 @@ import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class SignalAccountFiles {
|
||||
|
||||
@ -66,9 +67,10 @@ public class SignalAccountFiles {
|
||||
}
|
||||
|
||||
public Set<String> getAllLocalAccountIdentifiers() throws IOException {
|
||||
return accountsStore.getAllAccounts().stream()
|
||||
return accountsStore.getAllAccounts()
|
||||
.stream()
|
||||
.map(a -> a.number() != null ? a.number() : a.uuid())
|
||||
.collect(java.util.stream.Collectors.toSet());
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public MultiAccountManager initMultiAccountManager() throws IOException {
|
||||
|
||||
@ -29,6 +29,7 @@ import org.signal.libsignal.protocol.util.KeyHelper;
|
||||
import org.signal.libsignal.usernames.BaseUsernameException;
|
||||
import org.signal.libsignal.usernames.Username;
|
||||
import org.signal.network.api.AccountApiV2;
|
||||
import org.signal.network.api.AccountApiV2.SetAccountAttributesError;
|
||||
import org.signal.network.exceptions.NonSuccessfulResponseCodeException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -143,8 +144,8 @@ public class AccountHelper {
|
||||
final var number = whoAmI.getNumber();
|
||||
final var aci = ACI.parseOrThrow(whoAmI.getAci());
|
||||
final var pni = whoAmI.getPni() == null ? null : PNI.parseOrThrow(whoAmI.getPni());
|
||||
if (Objects.equals(number, account.getNumber()) && aci.equals(account.getAci())
|
||||
&& Objects.equals(pni, account.getPni())) {
|
||||
if (Objects.equals(number, account.getNumber()) && aci.equals(account.getAci()) && Objects.equals(pni,
|
||||
account.getPni())) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -570,14 +571,16 @@ public class AccountHelper {
|
||||
|
||||
public void updateAccountAttributes() throws IOException {
|
||||
if (account.getNumber() != null) {
|
||||
handleResponseException(dependencies.getAccountApi().setAccountAttributes(account.getAccountAttributes(null)));
|
||||
handleResponseException(dependencies.getAccountApi()
|
||||
.setAccountAttributes(account.getAccountAttributes(null)));
|
||||
return;
|
||||
}
|
||||
final var api = new AccountApiV2(dependencies.getAuthenticatedSignalWebSocket());
|
||||
final RequestResult<Unit, ? extends AccountApiV2.SetAccountAttributesError> result =
|
||||
runSuspendBlocking(cont -> api.setAccountAttributes(account.getAccountAttributesV2(), cont));
|
||||
final RequestResult<Unit, ? extends SetAccountAttributesError> result = runSuspendBlocking(cont -> api.setAccountAttributes(
|
||||
account.getAccountAttributesV2(),
|
||||
cont));
|
||||
if (result instanceof RequestResult.NonSuccess<?> failure) {
|
||||
if (failure.getError() instanceof AccountApiV2.SetAccountAttributesError.Unauthorized) {
|
||||
if (failure.getError() instanceof SetAccountAttributesError.Unauthorized) {
|
||||
throw new AuthorizationFailedException(401, "Authorization failed!");
|
||||
}
|
||||
throw new IOException("Account attribute update rate limited; try again later");
|
||||
|
||||
@ -886,8 +886,8 @@ public class SendHelper {
|
||||
|
||||
var address = context.getRecipientHelper().resolveSignalServiceAddress(recipientId);
|
||||
try {
|
||||
final boolean includePniSignature = account.getPni() != null
|
||||
&& account.getRecipientStore().needsPniSignature(recipientId);
|
||||
final boolean includePniSignature = account.getPni() != null && account.getRecipientStore()
|
||||
.needsPniSignature(recipientId);
|
||||
try {
|
||||
return s.send(messageSender,
|
||||
address,
|
||||
|
||||
@ -35,6 +35,8 @@ import org.signal.libsignal.protocol.IdentityKeyPair;
|
||||
import org.signal.libsignal.protocol.ecc.ECPrivateKey;
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||
import org.signal.network.api.RegistrationApiV2;
|
||||
import org.signal.network.api.RegistrationApiV2.LinkDeviceResponse;
|
||||
import org.signal.network.api.RegistrationApiV2.RegisterAsLinkedDeviceError;
|
||||
import org.signal.network.rest.SignalRestClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -162,7 +164,8 @@ public class ProvisioningManagerImpl implements ProvisioningManager, Closeable {
|
||||
final var accountExists = accountPath != null && SignalAccount.accountFileExists(pathConfig.dataPath(),
|
||||
accountPath);
|
||||
if (accountExists && !canRelinkExistingAccount(accountPath)) {
|
||||
throw new UserAlreadyExistsException(identifier, SignalAccount.getFileName(pathConfig.dataPath(), accountPath));
|
||||
throw new UserAlreadyExistsException(identifier,
|
||||
SignalAccount.getFileName(pathConfig.dataPath(), accountPath));
|
||||
}
|
||||
if (accountPath == null) {
|
||||
accountPath = accountsStore.addAccount(number, aci);
|
||||
@ -179,7 +182,7 @@ public class ProvisioningManagerImpl implements ProvisioningManager, Closeable {
|
||||
pniIdentity = pni == null
|
||||
? null
|
||||
: new IdentityKeyPair(new IdentityKey(msg.pniIdentityKeyPublic.toByteArray()),
|
||||
new ECPrivateKey(msg.pniIdentityKeyPrivate.toByteArray()));
|
||||
new ECPrivateKey(msg.pniIdentityKeyPrivate.toByteArray()));
|
||||
profileKey = msg.profileKey == null
|
||||
? KeyUtils.createProfileKey()
|
||||
: new ProfileKey(msg.profileKey.toByteArray());
|
||||
@ -232,9 +235,14 @@ public class ProvisioningManagerImpl implements ProvisioningManager, Closeable {
|
||||
: generatePreKeysForType(account.getAccountData(ServiceIdType.PNI));
|
||||
|
||||
logger.debug("Finishing new device registration");
|
||||
final var registrationApi = new RegistrationApiV2(new SignalRestClient(
|
||||
serviceEnvironmentConfig.signalServiceConfiguration(), userAgent), false);
|
||||
final var deviceId = registerLinkedDevice(registrationApi, account, msg.provisioningCode, aciPreKeys, pniPreKeys);
|
||||
final var restClient = new SignalRestClient(serviceEnvironmentConfig.signalServiceConfiguration(),
|
||||
userAgent);
|
||||
final var registrationApi = new RegistrationApiV2(restClient, false);
|
||||
final var deviceId = registerLinkedDevice(registrationApi,
|
||||
account,
|
||||
msg.provisioningCode,
|
||||
aciPreKeys,
|
||||
pniPreKeys);
|
||||
|
||||
account.finishLinking(deviceId, aciPreKeys, pniPreKeys);
|
||||
linkingFinished = true;
|
||||
@ -291,8 +299,10 @@ public class ProvisioningManagerImpl implements ProvisioningManager, Closeable {
|
||||
if (message.number != null) {
|
||||
return PNI.parseOrThrow(message.pni, message.pniBinary);
|
||||
}
|
||||
if (message.pni != null || message.pniBinary != null
|
||||
|| message.pniIdentityKeyPublic != null || message.pniIdentityKeyPrivate != null) {
|
||||
if (message.pni != null
|
||||
|| message.pniBinary != null
|
||||
|| message.pniIdentityKeyPublic != null
|
||||
|| message.pniIdentityKeyPrivate != null) {
|
||||
throw new IOException("Provisioning message has PNI material without a phone number");
|
||||
}
|
||||
if (message.authCredentialSalt == null || message.authCredentialSalt.size() == 0) {
|
||||
@ -314,32 +324,32 @@ public class ProvisioningManagerImpl implements ProvisioningManager, Closeable {
|
||||
attrs.getPniRegistrationId(),
|
||||
attrs.getName(),
|
||||
attrs.getCapabilities());
|
||||
final RequestResult<RegistrationApiV2.LinkDeviceResponse, ? extends RegistrationApiV2.RegisterAsLinkedDeviceError> result =
|
||||
runSuspendBlocking(cont -> registrationApi.registerAsSecondaryDevice(account.getAci(),
|
||||
account.getPassword(),
|
||||
provisioningCode,
|
||||
deviceAttributes,
|
||||
toRegistrationPreKeys(aciPreKeys),
|
||||
toRegistrationPreKeys(pniPreKeys),
|
||||
null,
|
||||
cont));
|
||||
final RequestResult<LinkDeviceResponse, ? extends RegisterAsLinkedDeviceError> result = runSuspendBlocking(cont -> registrationApi.registerAsSecondaryDevice(
|
||||
account.getAci(),
|
||||
account.getPassword(),
|
||||
provisioningCode,
|
||||
deviceAttributes,
|
||||
toRegistrationPreKeys(aciPreKeys),
|
||||
toRegistrationPreKeys(pniPreKeys),
|
||||
null,
|
||||
cont));
|
||||
return getLinkedDeviceId(result);
|
||||
}
|
||||
|
||||
static int getLinkedDeviceId(
|
||||
final RequestResult<RegistrationApiV2.LinkDeviceResponse, ? extends RegistrationApiV2.RegisterAsLinkedDeviceError> result
|
||||
final RequestResult<LinkDeviceResponse, ? extends RegisterAsLinkedDeviceError> result
|
||||
) throws IOException {
|
||||
if (result instanceof RequestResult.NonSuccess<?> failure) {
|
||||
throw switch (failure.getError()) {
|
||||
case RegistrationApiV2.RegisterAsLinkedDeviceError.IncorrectVerification ignored ->
|
||||
case RegisterAsLinkedDeviceError.IncorrectVerification ignored ->
|
||||
new AuthorizationFailedException(403, "Device verification failed");
|
||||
case RegistrationApiV2.RegisterAsLinkedDeviceError.MissingCapability ignored ->
|
||||
case RegisterAsLinkedDeviceError.MissingCapability ignored ->
|
||||
new IOException("Linked device is missing a required account capability");
|
||||
case RegistrationApiV2.RegisterAsLinkedDeviceError.MaxLinkedDevices ignored ->
|
||||
case RegisterAsLinkedDeviceError.MaxLinkedDevices ignored ->
|
||||
new IOException("Account has reached its linked device limit");
|
||||
case RegistrationApiV2.RegisterAsLinkedDeviceError.InvalidRequest ignored ->
|
||||
case RegisterAsLinkedDeviceError.InvalidRequest ignored ->
|
||||
new IOException("Signal rejected the device linking request");
|
||||
case RegistrationApiV2.RegisterAsLinkedDeviceError.RateLimited ignored ->
|
||||
case RegisterAsLinkedDeviceError.RateLimited ignored ->
|
||||
new IOException("Device linking rate limited; try again later");
|
||||
default -> new IOException("Unexpected device linking response");
|
||||
};
|
||||
@ -348,8 +358,11 @@ public class ProvisioningManagerImpl implements ProvisioningManager, Closeable {
|
||||
}
|
||||
|
||||
static RegistrationApiV2.PreKeyCollection toRegistrationPreKeys(final PreKeyCollection preKeys) {
|
||||
return preKeys == null ? null : new RegistrationApiV2.PreKeyCollection(preKeys.getIdentityKey(),
|
||||
preKeys.getSignedPreKey(), preKeys.getLastResortKyberPreKey());
|
||||
return preKeys == null
|
||||
? null
|
||||
: new RegistrationApiV2.PreKeyCollection(preKeys.getIdentityKey(),
|
||||
preKeys.getSignedPreKey(),
|
||||
preKeys.getLastResortKyberPreKey());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -44,7 +44,8 @@ public class PhoneNumberFormatter {
|
||||
|
||||
if (localNumber == null) {
|
||||
if (!number.startsWith("+")) {
|
||||
throw new InvalidNumberException("Use an international number including the country code for a numberless account.");
|
||||
throw new InvalidNumberException(
|
||||
"Use an international number including the country code for a numberless account.");
|
||||
}
|
||||
try {
|
||||
final var util = PhoneNumberUtil.getInstance();
|
||||
|
||||
@ -24,18 +24,34 @@ class NumberlessGroupTest {
|
||||
void findsOurGroupMembershipByAciWhenPniIsAbsent() throws Exception {
|
||||
final var aci = ACI.parseOrThrow("11111111-1111-4111-8111-111111111111");
|
||||
final var otherAci = ACI.parseOrThrow("22222222-2222-4222-8222-222222222222");
|
||||
try (final var account = SignalAccount.createLinkedAccount(directory.toFile(), "account",
|
||||
ServiceEnvironment.STAGING, Settings.DEFAULT);
|
||||
final var context = new Context(account, null, null, null, null, null)) {
|
||||
account.setProvisioningData(null, aci, null, "test-password", new byte[]{1},
|
||||
KeyUtils.generateIdentityKeyPair(), null, KeyUtils.createProfileKey(), null, new byte[32], null);
|
||||
try (final var account = SignalAccount.createLinkedAccount(directory.toFile(),
|
||||
"account",
|
||||
ServiceEnvironment.STAGING,
|
||||
Settings.DEFAULT); final var context = new Context(account, null, null, null, null, null)) {
|
||||
account.setProvisioningData(null,
|
||||
aci,
|
||||
null,
|
||||
"test-password",
|
||||
new byte[]{1},
|
||||
KeyUtils.generateIdentityKeyPair(),
|
||||
null,
|
||||
KeyUtils.createProfileKey(),
|
||||
null,
|
||||
new byte[32],
|
||||
null);
|
||||
final var otherMember = new DecryptedMember.Builder().aciBytes(otherAci.toByteString())
|
||||
.joinedAtRevision(1).build();
|
||||
.joinedAtRevision(1)
|
||||
.build();
|
||||
final var selfMember = new DecryptedMember.Builder().aciBytes(aci.toByteString())
|
||||
.joinedAtRevision(4).build();
|
||||
final var group = new DecryptedGroup.Builder().revision(9).members(List.of(otherMember, selfMember)).build();
|
||||
.joinedAtRevision(4)
|
||||
.build();
|
||||
final var group = new DecryptedGroup.Builder().revision(9)
|
||||
.members(List.of(otherMember, selfMember))
|
||||
.build();
|
||||
assertEquals(4, context.getGroupV2Helper().findRevisionWeWereAdded(group));
|
||||
assertEquals(9, context.getGroupV2Helper().findRevisionWeWereAdded(group.newBuilder().members(List.of(otherMember)).build()));
|
||||
assertEquals(9,
|
||||
context.getGroupV2Helper()
|
||||
.findRevisionWeWereAdded(group.newBuilder().members(List.of(otherMember)).build()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ import org.signal.core.models.ServiceId.ACI;
|
||||
import org.signal.core.models.ServiceId.PNI;
|
||||
import org.signal.libsignal.net.RequestResult;
|
||||
import org.signal.network.api.RegistrationApiV2;
|
||||
import org.signal.network.api.RegistrationApiV2.RegisterAsLinkedDeviceError;
|
||||
import org.signal.network.rest.SignalRestClient;
|
||||
import org.whispersystems.signalservice.api.push.ServiceIdType;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
|
||||
@ -24,6 +25,7 @@ import java.nio.file.Path;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import okhttp3.Credentials;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Protocol;
|
||||
@ -50,8 +52,8 @@ class NumberlessProvisioningTest {
|
||||
@Test
|
||||
void numberlessProvisioningRequiresGroupCredentialSalt() {
|
||||
assertThrows(IOException.class, () -> ProvisioningManagerImpl.parsePni(new ProvisionMessage.Builder().build()));
|
||||
assertThrows(IOException.class, () -> ProvisioningManagerImpl.parsePni(new ProvisionMessage.Builder()
|
||||
.authCredentialSalt(ByteString.EMPTY).build()));
|
||||
final var emptySalt = new ProvisionMessage.Builder().authCredentialSalt(ByteString.EMPTY).build();
|
||||
assertThrows(IOException.class, () -> ProvisioningManagerImpl.parsePni(emptySalt));
|
||||
assertNull(ProvisioningManagerImpl.toRegistrationPreKeys(null));
|
||||
}
|
||||
|
||||
@ -60,10 +62,12 @@ class NumberlessProvisioningTest {
|
||||
final var message = new ProvisionMessage.Builder().authCredentialSalt(ByteString.of(new byte[32]));
|
||||
assertNull(ProvisioningManagerImpl.parsePni(message.build()));
|
||||
assertThrows(IOException.class, () -> ProvisioningManagerImpl.parsePni(message.pni(PNI_ID.toString()).build()));
|
||||
assertThrows(IllegalArgumentException.class, () -> ProvisioningManagerImpl.parsePni(
|
||||
new ProvisionMessage.Builder().number("+12025550123").build()));
|
||||
assertEquals(PNI_ID, ProvisioningManagerImpl.parsePni(new ProvisionMessage.Builder()
|
||||
.number("+12025550123").pni(PNI_ID.toString()).build()));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> ProvisioningManagerImpl.parsePni(new ProvisionMessage.Builder().number("+12025550123").build()));
|
||||
assertEquals(PNI_ID,
|
||||
ProvisioningManagerImpl.parsePni(new ProvisionMessage.Builder().number("+12025550123")
|
||||
.pni(PNI_ID.toString())
|
||||
.build()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -85,29 +89,57 @@ class NumberlessProvisioningTest {
|
||||
final var buffer = new Buffer();
|
||||
chain.request().body().writeTo(buffer);
|
||||
body.set(mapper.readTree(buffer.readUtf8()));
|
||||
return new Response.Builder().request(chain.request()).protocol(Protocol.HTTP_1_1)
|
||||
.code(200).message("OK")
|
||||
.body(ResponseBody.create("{\"deviceId\":2}", MediaType.get("application/json"))).build();
|
||||
return new Response.Builder().request(chain.request())
|
||||
.protocol(Protocol.HTTP_1_1)
|
||||
.code(200)
|
||||
.message("OK")
|
||||
.body(ResponseBody.create("{\"deviceId\":2}", MediaType.get("application/json")))
|
||||
.build();
|
||||
}).build();
|
||||
final var config = ServiceConfig.getServiceEnvironmentConfig(ServiceEnvironment.STAGING, "signal-cli-test");
|
||||
final var restClient = new SignalRestClient(config.signalServiceConfiguration(), "signal-cli-test",
|
||||
null, false, 1000L, new SecureRandom(), client);
|
||||
final var restClient = new SignalRestClient(config.signalServiceConfiguration(),
|
||||
"signal-cli-test",
|
||||
null,
|
||||
false,
|
||||
1000L,
|
||||
new SecureRandom(),
|
||||
client);
|
||||
final var api = new RegistrationApiV2(restClient, false);
|
||||
|
||||
try (final var account = SignalAccount.createLinkedAccount(directory.toFile(), "account",
|
||||
ServiceEnvironment.STAGING, Settings.DEFAULT)) {
|
||||
account.setProvisioningData(number, ACI_ID, pni, "test-password", new byte[]{1},
|
||||
KeyUtils.generateIdentityKeyPair(), pni == null ? null : KeyUtils.generateIdentityKeyPair(),
|
||||
KeyUtils.createProfileKey(), null, new byte[32], null);
|
||||
try (final var account = SignalAccount.createLinkedAccount(directory.toFile(),
|
||||
"account",
|
||||
ServiceEnvironment.STAGING,
|
||||
Settings.DEFAULT)) {
|
||||
account.setProvisioningData(number,
|
||||
ACI_ID,
|
||||
pni,
|
||||
"test-password",
|
||||
new byte[]{1},
|
||||
KeyUtils.generateIdentityKeyPair(),
|
||||
pni == null ? null : KeyUtils.generateIdentityKeyPair(),
|
||||
KeyUtils.createProfileKey(),
|
||||
null,
|
||||
new byte[32],
|
||||
null);
|
||||
final var aciKeys = KeyUtils.generatePreKeysForType(account.getAccountData(ServiceIdType.ACI));
|
||||
final var pniKeys = pni == null ? null : KeyUtils.generatePreKeysForType(account.getAccountData(ServiceIdType.PNI));
|
||||
final var deviceId = ProvisioningManagerImpl.registerLinkedDevice(api, account, "test-code", aciKeys, pniKeys);
|
||||
final var pniKeys = pni == null
|
||||
? null
|
||||
: KeyUtils.generatePreKeysForType(account.getAccountData(ServiceIdType.PNI));
|
||||
final var deviceId = ProvisioningManagerImpl.registerLinkedDevice(api,
|
||||
account,
|
||||
"test-code",
|
||||
aciKeys,
|
||||
pniKeys);
|
||||
|
||||
assertEquals(2, deviceId);
|
||||
assertEquals("PUT", request.get().method());
|
||||
assertEquals("/v1/devices/link", request.get().url().encodedPath());
|
||||
assertEquals(okhttp3.Credentials.basic(ACI_ID.toString(), "test-password"), request.get().header("Authorization"));
|
||||
assertTrue(body.get().path("accountAttributes").path("capabilities").path("optionalPhoneNumber").asBoolean());
|
||||
assertEquals(Credentials.basic(ACI_ID.toString(), "test-password"), request.get().header("Authorization"));
|
||||
assertTrue(body.get()
|
||||
.path("accountAttributes")
|
||||
.path("capabilities")
|
||||
.path("optionalPhoneNumber")
|
||||
.asBoolean());
|
||||
assertTrue(body.get().hasNonNull("aciSignedPreKey"));
|
||||
assertTrue(body.get().hasNonNull("aciPqLastResortPreKey"));
|
||||
assertEquals(pni != null, body.get().has("pniSignedPreKey"));
|
||||
@ -122,10 +154,12 @@ class NumberlessProvisioningTest {
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void linkingErrorsAreReportedWithoutCrashingOrIncludingServerBody() {
|
||||
assertThrows(AuthorizationFailedException.class, () -> ProvisioningManagerImpl.getLinkedDeviceId(
|
||||
new RequestResult.NonSuccess<>(RegistrationApiV2.RegisterAsLinkedDeviceError.IncorrectVerification.INSTANCE)));
|
||||
final var error = assertThrows(IOException.class, () -> ProvisioningManagerImpl.getLinkedDeviceId(
|
||||
new RequestResult.NonSuccess<>(new RegistrationApiV2.RegisterAsLinkedDeviceError.InvalidRequest("secret payload"))));
|
||||
final var incorrectVerification = RegisterAsLinkedDeviceError.IncorrectVerification.INSTANCE;
|
||||
assertThrows(AuthorizationFailedException.class,
|
||||
() -> ProvisioningManagerImpl.getLinkedDeviceId(new RequestResult.NonSuccess<>(incorrectVerification)));
|
||||
final var invalidRequest = new RegisterAsLinkedDeviceError.InvalidRequest("secret payload");
|
||||
final var error = assertThrows(IOException.class,
|
||||
() -> ProvisioningManagerImpl.getLinkedDeviceId(new RequestResult.NonSuccess<>(invalidRequest)));
|
||||
assertFalse(error.getMessage().contains("secret payload"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,10 +27,21 @@ class NumberlessAccountTest {
|
||||
final var identity = KeyUtils.generateIdentityKeyPair();
|
||||
final var salt = new byte[32];
|
||||
salt[0] = 42;
|
||||
try (final var account = SignalAccount.createLinkedAccount(directory.toFile(), "account",
|
||||
ServiceEnvironment.STAGING, Settings.DEFAULT)) {
|
||||
account.setProvisioningData(null, aci, null, "test-password", new byte[]{1}, identity,
|
||||
null, KeyUtils.createProfileKey(), null, salt, null);
|
||||
try (final var account = SignalAccount.createLinkedAccount(directory.toFile(),
|
||||
"account",
|
||||
ServiceEnvironment.STAGING,
|
||||
Settings.DEFAULT)) {
|
||||
account.setProvisioningData(null,
|
||||
aci,
|
||||
null,
|
||||
"test-password",
|
||||
new byte[]{1},
|
||||
identity,
|
||||
null,
|
||||
KeyUtils.createProfileKey(),
|
||||
null,
|
||||
salt,
|
||||
null);
|
||||
account.finishLinking(2, KeyUtils.generatePreKeysForType(account.getAccountData(ServiceIdType.ACI)), null);
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.signal.core.models.ServiceId.ACI;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Set;
|
||||
|
||||
@ -29,7 +30,8 @@ class AccountsStoreTest {
|
||||
assertEquals(path, reopened.getPathByAci(aci));
|
||||
assertNull(reopened.getPathByNumber(null));
|
||||
assertEquals(Set.of("+12025550123"), reopened.getAllNumbers());
|
||||
assertTrue(new AccountsStore(directory.toFile(), ServiceEnvironment.LIVE, ignored -> null).getAllAccounts().isEmpty());
|
||||
assertTrue(new AccountsStore(directory.toFile(), ServiceEnvironment.LIVE, ignored -> null).getAllAccounts()
|
||||
.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -37,7 +39,7 @@ class AccountsStoreTest {
|
||||
final var store = new AccountsStore(directory.toFile(), ServiceEnvironment.STAGING, path -> null);
|
||||
final var aci = ACI.parseOrThrow("11111111-1111-4111-8111-111111111111");
|
||||
final var oldPath = store.addAccount(null, aci);
|
||||
java.nio.file.Files.createFile(directory.resolve(oldPath));
|
||||
Files.createFile(directory.resolve(oldPath));
|
||||
final var newPath = store.addAccount(null, null);
|
||||
store.updateAccount(newPath, null, aci);
|
||||
|
||||
|
||||
@ -215,7 +215,8 @@ public class App {
|
||||
if (accounts.isEmpty()) {
|
||||
throw new UserErrorException("No local users found, you first need to register or link an account");
|
||||
} else if (accounts.size() > 1) {
|
||||
throw new UserErrorException("Multiple users found, you need to specify an account (phone number or ACI) with -a");
|
||||
throw new UserErrorException(
|
||||
"Multiple users found, you need to specify an account (phone number or ACI) with -a");
|
||||
}
|
||||
return accounts.stream().findFirst().get();
|
||||
}
|
||||
|
||||
@ -30,7 +30,9 @@ public class ListAccountsCommand implements JsonRpcMultiLocalCommand {
|
||||
final var managers = c.getManagers();
|
||||
switch (outputWriter) {
|
||||
case JsonWriter jsonWriter -> {
|
||||
final var jsonAccounts = managers.stream().map(m -> new JsonAccount(m.getSelfNumber(), m.getSelfACI())).toList();
|
||||
final var jsonAccounts = managers.stream()
|
||||
.map(m -> new JsonAccount(m.getSelfNumber(), m.getSelfACI()))
|
||||
.toList();
|
||||
jsonWriter.write(jsonAccounts);
|
||||
}
|
||||
case PlainTextWriter plainTextWriter -> {
|
||||
|
||||
@ -52,7 +52,8 @@ class NumberlessAccountOutputTest {
|
||||
}
|
||||
|
||||
private static Manager manager(final String number) {
|
||||
return (Manager) Proxy.newProxyInstance(Manager.class.getClassLoader(), new Class<?>[]{Manager.class},
|
||||
return (Manager) Proxy.newProxyInstance(Manager.class.getClassLoader(),
|
||||
new Class<?>[]{Manager.class},
|
||||
(proxy, method, args) -> {
|
||||
if (method.isDefault()) {
|
||||
return InvocationHandler.invokeDefault(proxy, method, args);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user