diff --git a/doc/numberless-accounts.md b/doc/numberless-accounts.md index e4b7e752..4bd773b7 100644 --- a/doc/numberless-accounts.md +++ b/doc/numberless-accounts.md @@ -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 diff --git a/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java b/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java index 231aacc7..6834bff2 100644 --- a/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java +++ b/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java @@ -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 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 { diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java index 2dfc4cac..6ada2fd7 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java @@ -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 result = - runSuspendBlocking(cont -> api.setAccountAttributes(account.getAccountAttributesV2(), cont)); + final RequestResult 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"); diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java index 9564830c..bd1188af 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java @@ -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, diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/ProvisioningManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/internal/ProvisioningManagerImpl.java index a748611c..aca7e3f3 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/ProvisioningManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/ProvisioningManagerImpl.java @@ -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 result = - runSuspendBlocking(cont -> registrationApi.registerAsSecondaryDevice(account.getAci(), - account.getPassword(), - provisioningCode, - deviceAttributes, - toRegistrationPreKeys(aciPreKeys), - toRegistrationPreKeys(pniPreKeys), - null, - cont)); + final RequestResult 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 result + final RequestResult 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 diff --git a/lib/src/main/java/org/asamk/signal/manager/util/PhoneNumberFormatter.java b/lib/src/main/java/org/asamk/signal/manager/util/PhoneNumberFormatter.java index d96bb457..57176c76 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/PhoneNumberFormatter.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/PhoneNumberFormatter.java @@ -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(); diff --git a/lib/src/test/java/org/asamk/signal/manager/helper/NumberlessGroupTest.java b/lib/src/test/java/org/asamk/signal/manager/helper/NumberlessGroupTest.java index 8bf8a0cd..a8733273 100644 --- a/lib/src/test/java/org/asamk/signal/manager/helper/NumberlessGroupTest.java +++ b/lib/src/test/java/org/asamk/signal/manager/helper/NumberlessGroupTest.java @@ -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())); } } } diff --git a/lib/src/test/java/org/asamk/signal/manager/internal/NumberlessProvisioningTest.java b/lib/src/test/java/org/asamk/signal/manager/internal/NumberlessProvisioningTest.java index c3ad6d71..6e143cb9 100644 --- a/lib/src/test/java/org/asamk/signal/manager/internal/NumberlessProvisioningTest.java +++ b/lib/src/test/java/org/asamk/signal/manager/internal/NumberlessProvisioningTest.java @@ -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")); } } diff --git a/lib/src/test/java/org/asamk/signal/manager/storage/NumberlessAccountTest.java b/lib/src/test/java/org/asamk/signal/manager/storage/NumberlessAccountTest.java index 5e8f54be..f637702b 100644 --- a/lib/src/test/java/org/asamk/signal/manager/storage/NumberlessAccountTest.java +++ b/lib/src/test/java/org/asamk/signal/manager/storage/NumberlessAccountTest.java @@ -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); } diff --git a/lib/src/test/java/org/asamk/signal/manager/storage/accounts/AccountsStoreTest.java b/lib/src/test/java/org/asamk/signal/manager/storage/accounts/AccountsStoreTest.java index 32d032d7..2ed5eb31 100644 --- a/lib/src/test/java/org/asamk/signal/manager/storage/accounts/AccountsStoreTest.java +++ b/lib/src/test/java/org/asamk/signal/manager/storage/accounts/AccountsStoreTest.java @@ -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); diff --git a/src/main/java/org/asamk/signal/App.java b/src/main/java/org/asamk/signal/App.java index be8aab1b..aa2441dd 100644 --- a/src/main/java/org/asamk/signal/App.java +++ b/src/main/java/org/asamk/signal/App.java @@ -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(); } diff --git a/src/main/java/org/asamk/signal/commands/ListAccountsCommand.java b/src/main/java/org/asamk/signal/commands/ListAccountsCommand.java index 333a4443..c979fae4 100644 --- a/src/main/java/org/asamk/signal/commands/ListAccountsCommand.java +++ b/src/main/java/org/asamk/signal/commands/ListAccountsCommand.java @@ -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 -> { diff --git a/src/test/java/org/asamk/signal/json/NumberlessAccountOutputTest.java b/src/test/java/org/asamk/signal/json/NumberlessAccountOutputTest.java index 15a2bd09..015b6ded 100644 --- a/src/test/java/org/asamk/signal/json/NumberlessAccountOutputTest.java +++ b/src/test/java/org/asamk/signal/json/NumberlessAccountOutputTest.java @@ -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);