From: AsamK Date: Wed, 9 Feb 2022 18:07:23 +0000 (+0100) Subject: Add SignalAccountFiles as a central entry point X-Git-Tag: v0.10.4~23 X-Git-Url: https://git.nmode.ca/signal-cli/commitdiff_plain/ff6b733cd0448c05f4be5aad32895cc8c748ee79?ds=inline Add SignalAccountFiles as a central entry point --- diff --git a/lib/src/main/java/org/asamk/signal/manager/LibSignalLogger.java b/lib/src/main/java/org/asamk/signal/manager/LibSignalLogger.java index c14b0f13..ac686fc5 100644 --- a/lib/src/main/java/org/asamk/signal/manager/LibSignalLogger.java +++ b/lib/src/main/java/org/asamk/signal/manager/LibSignalLogger.java @@ -9,7 +9,7 @@ public class LibSignalLogger implements SignalProtocolLogger { private final static Logger logger = LoggerFactory.getLogger("LibSignal"); - public static void initLogger() { + static void initLogger() { SignalProtocolLoggerProvider.setProvider(new LibSignalLogger()); } diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index b7a97c46..9db509ad 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -1,6 +1,5 @@ package org.asamk.signal.manager; -import org.asamk.signal.manager.api.AccountCheckException; import org.asamk.signal.manager.api.AttachmentInvalidException; import org.asamk.signal.manager.api.Configuration; import org.asamk.signal.manager.api.Device; @@ -12,7 +11,6 @@ import org.asamk.signal.manager.api.InvalidStickerException; import org.asamk.signal.manager.api.Message; import org.asamk.signal.manager.api.MessageEnvelope; import org.asamk.signal.manager.api.NotMasterDeviceException; -import org.asamk.signal.manager.api.NotRegisteredException; import org.asamk.signal.manager.api.Pair; import org.asamk.signal.manager.api.RecipientIdentifier; import org.asamk.signal.manager.api.SendGroupMessageResults; @@ -23,16 +21,12 @@ import org.asamk.signal.manager.api.StickerPackUrl; import org.asamk.signal.manager.api.TypingAction; import org.asamk.signal.manager.api.UnregisteredRecipientException; import org.asamk.signal.manager.api.UpdateGroup; -import org.asamk.signal.manager.config.ServiceConfig; -import org.asamk.signal.manager.config.ServiceEnvironment; import org.asamk.signal.manager.groups.GroupId; import org.asamk.signal.manager.groups.GroupInviteLinkUrl; import org.asamk.signal.manager.groups.GroupNotFoundException; import org.asamk.signal.manager.groups.GroupSendingNotAllowedException; import org.asamk.signal.manager.groups.LastGroupAdminException; import org.asamk.signal.manager.groups.NotAGroupMemberException; -import org.asamk.signal.manager.storage.SignalAccount; -import org.asamk.signal.manager.storage.identities.TrustNewIdentity; import org.asamk.signal.manager.storage.recipients.Contact; import org.asamk.signal.manager.storage.recipients.Profile; import org.asamk.signal.manager.storage.recipients.RecipientAddress; @@ -51,45 +45,6 @@ import java.util.UUID; public interface Manager extends Closeable { - static Manager init( - String number, - File settingsPath, - ServiceEnvironment serviceEnvironment, - String userAgent, - TrustNewIdentity trustNewIdentity - ) throws IOException, NotRegisteredException, AccountCheckException { - var pathConfig = PathConfig.createDefault(settingsPath); - - if (!SignalAccount.userExists(pathConfig.dataPath(), number)) { - throw new NotRegisteredException(); - } - - var account = SignalAccount.load(pathConfig.dataPath(), number, true, trustNewIdentity); - - if (!account.isRegistered()) { - account.close(); - throw new NotRegisteredException(); - } - - account.initDatabase(); - final var serviceEnvironmentConfig = ServiceConfig.getServiceEnvironmentConfig(serviceEnvironment, userAgent); - - final var manager = new ManagerImpl(account, pathConfig, serviceEnvironmentConfig, userAgent); - - try { - manager.checkAccountState(); - } catch (IOException e) { - manager.close(); - throw new AccountCheckException("Error while checking account " + account + ": " + e.getMessage(), e); - } - - return manager; - } - - static void initLogger() { - LibSignalLogger.initLogger(); - } - static boolean isValidNumber(final String e164Number, final String countryCode) { return PhoneNumberFormatter.isValidNumber(e164Number, countryCode); } diff --git a/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java index c15f1612..742be520 100644 --- a/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java @@ -45,6 +45,7 @@ import org.asamk.signal.manager.groups.GroupNotFoundException; import org.asamk.signal.manager.groups.GroupSendingNotAllowedException; import org.asamk.signal.manager.groups.LastGroupAdminException; import org.asamk.signal.manager.groups.NotAGroupMemberException; +import org.asamk.signal.manager.helper.AccountFileUpdater; import org.asamk.signal.manager.helper.Context; import org.asamk.signal.manager.storage.SignalAccount; import org.asamk.signal.manager.storage.groups.GroupInfo; @@ -99,6 +100,7 @@ class ManagerImpl implements Manager { private final static Logger logger = LoggerFactory.getLogger(ManagerImpl.class); private SignalAccount account; + private final AccountFileUpdater accountFileUpdater; private final SignalDependencies dependencies; private final Context context; @@ -114,13 +116,15 @@ class ManagerImpl implements Manager { ManagerImpl( SignalAccount account, PathConfig pathConfig, + AccountFileUpdater accountFileUpdater, ServiceEnvironmentConfig serviceEnvironmentConfig, String userAgent ) { this.account = account; + this.accountFileUpdater = accountFileUpdater; final var credentialsProvider = new DynamicCredentialsProvider(account.getAci(), - account.getAccount(), + account.getNumber(), account.getPassword(), account.getDeviceId()); final var sessionLock = new SignalSessionLock() { @@ -142,7 +146,12 @@ class ManagerImpl implements Manager { final var attachmentStore = new AttachmentStore(pathConfig.attachmentsPath()); final var stickerPackStore = new StickerPackStore(pathConfig.stickerPacksPath()); - this.context = new Context(account, dependencies, avatarStore, attachmentStore, stickerPackStore); + this.context = new Context(account, + accountFileUpdater, + dependencies, + avatarStore, + attachmentStore, + stickerPackStore); this.context.getAccountHelper().setUnregisteredListener(this::close); this.context.getReceiveHelper().setAuthenticationFailureListener(this::close); this.context.getReceiveHelper().setCaughtUpWithOldMessagesListener(() -> { @@ -168,7 +177,7 @@ class ManagerImpl implements Manager { @Override public String getSelfNumber() { - return account.getAccount(); + return account.getNumber(); } void checkAccountState() throws IOException { @@ -179,7 +188,7 @@ class ManagerImpl implements Manager { public Map> areUsersRegistered(Set numbers) throws IOException { final var canonicalizedNumbers = numbers.stream().collect(Collectors.toMap(n -> n, n -> { try { - final var canonicalizedNumber = PhoneNumberFormatter.formatNumber(n, account.getAccount()); + final var canonicalizedNumber = PhoneNumberFormatter.formatNumber(n, account.getNumber()); if (!canonicalizedNumber.equals(n)) { logger.debug("Normalized number {} to {}.", n, canonicalizedNumber); } diff --git a/lib/src/main/java/org/asamk/signal/manager/ManagerLogger.java b/lib/src/main/java/org/asamk/signal/manager/ManagerLogger.java new file mode 100644 index 00000000..e5dc2c0d --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/ManagerLogger.java @@ -0,0 +1,8 @@ +package org.asamk.signal.manager; + +public class ManagerLogger { + + public static void initLogger() { + LibSignalLogger.initLogger(); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/MultiAccountManager.java b/lib/src/main/java/org/asamk/signal/manager/MultiAccountManager.java index 9f1cf2fd..15b60594 100644 --- a/lib/src/main/java/org/asamk/signal/manager/MultiAccountManager.java +++ b/lib/src/main/java/org/asamk/signal/manager/MultiAccountManager.java @@ -1,58 +1,13 @@ package org.asamk.signal.manager; -import org.asamk.signal.manager.api.AccountCheckException; -import org.asamk.signal.manager.api.NotRegisteredException; -import org.asamk.signal.manager.config.ServiceEnvironment; -import org.asamk.signal.manager.storage.identities.TrustNewIdentity; -import org.slf4j.LoggerFactory; -import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; - -import java.io.File; import java.io.IOException; import java.net.URI; -import java.util.Arrays; import java.util.List; -import java.util.Objects; import java.util.concurrent.TimeoutException; import java.util.function.Consumer; public interface MultiAccountManager extends AutoCloseable { - static MultiAccountManager init( - final File settingsPath, - final ServiceEnvironment serviceEnvironment, - final String userAgent, - final TrustNewIdentity trustNewIdentity - ) { - final var logger = LoggerFactory.getLogger(MultiAccountManager.class); - final var managers = getAllLocalAccountNumbers(settingsPath).stream().map(a -> { - try { - return Manager.init(a, settingsPath, serviceEnvironment, userAgent, trustNewIdentity); - } catch (NotRegisteredException | IOException | AccountCheckException e) { - logger.warn("Ignoring {}: {} ({})", a, e.getMessage(), e.getClass().getSimpleName()); - return null; - } - }).filter(Objects::nonNull).toList(); - - return new MultiAccountManagerImpl(managers, settingsPath, serviceEnvironment, userAgent); - } - - static List getAllLocalAccountNumbers(File settingsPath) { - var pathConfig = PathConfig.createDefault(settingsPath); - final var dataPath = pathConfig.dataPath(); - final var files = dataPath.listFiles(); - - if (files == null) { - return List.of(); - } - - return Arrays.stream(files) - .filter(File::isFile) - .map(File::getName) - .filter(file -> PhoneNumberFormatter.isValidNumber(file, null)) - .toList(); - } - List getAccountNumbers(); List getManagers(); diff --git a/lib/src/main/java/org/asamk/signal/manager/MultiAccountManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/MultiAccountManagerImpl.java index 4c67fd12..072e965b 100644 --- a/lib/src/main/java/org/asamk/signal/manager/MultiAccountManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/MultiAccountManagerImpl.java @@ -1,10 +1,8 @@ package org.asamk.signal.manager; -import org.asamk.signal.manager.config.ServiceEnvironment; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; import java.io.IOException; import java.net.URI; import java.util.ArrayList; @@ -25,21 +23,12 @@ class MultiAccountManagerImpl implements MultiAccountManager { private final Set> onManagerRemovedHandlers = new HashSet<>(); private final Set managers = new HashSet<>(); private final Map provisioningManagers = new HashMap<>(); - private final File settingsPath; - private final ServiceEnvironment serviceEnvironment; - private final String userAgent; - - public MultiAccountManagerImpl( - final Collection managers, - final File settingsPath, - final ServiceEnvironment serviceEnvironment, - final String userAgent - ) { + private final SignalAccountFiles signalAccountFiles; + + public MultiAccountManagerImpl(final Collection managers, final SignalAccountFiles signalAccountFiles) { + this.signalAccountFiles = signalAccountFiles; this.managers.addAll(managers); managers.forEach(m -> m.addClosedListener(() -> this.removeManager(m))); - this.settingsPath = settingsPath; - this.serviceEnvironment = serviceEnvironment; - this.userAgent = userAgent; } @Override @@ -99,9 +88,9 @@ class MultiAccountManagerImpl implements MultiAccountManager { } @Override - public Manager getManager(final String account) { + public Manager getManager(final String number) { synchronized (managers) { - return managers.stream().filter(m -> m.getSelfNumber().equals(account)).findFirst().orElse(null); + return managers.stream().filter(m -> m.getSelfNumber().equals(number)).findFirst().orElse(null); } } @@ -119,12 +108,12 @@ class MultiAccountManagerImpl implements MultiAccountManager { } private ProvisioningManager getNewProvisioningManager() { - return ProvisioningManager.init(settingsPath, serviceEnvironment, userAgent, this::addManager); + return signalAccountFiles.initProvisioningManager(this::addManager); } @Override - public RegistrationManager getNewRegistrationManager(String account) throws IOException { - return RegistrationManager.init(account, settingsPath, serviceEnvironment, userAgent, this::addManager); + public RegistrationManager getNewRegistrationManager(String number) throws IOException { + return signalAccountFiles.initRegistrationManager(number, this::addManager); } @Override diff --git a/lib/src/main/java/org/asamk/signal/manager/ProvisioningManager.java b/lib/src/main/java/org/asamk/signal/manager/ProvisioningManager.java index 81834220..09a329f5 100644 --- a/lib/src/main/java/org/asamk/signal/manager/ProvisioningManager.java +++ b/lib/src/main/java/org/asamk/signal/manager/ProvisioningManager.java @@ -1,36 +1,13 @@ package org.asamk.signal.manager; import org.asamk.signal.manager.api.UserAlreadyExistsException; -import org.asamk.signal.manager.config.ServiceConfig; -import org.asamk.signal.manager.config.ServiceEnvironment; -import java.io.File; import java.io.IOException; import java.net.URI; import java.util.concurrent.TimeoutException; -import java.util.function.Consumer; public interface ProvisioningManager { - static ProvisioningManager init( - File settingsPath, ServiceEnvironment serviceEnvironment, String userAgent - ) { - return init(settingsPath, serviceEnvironment, userAgent, null); - } - - static ProvisioningManager init( - File settingsPath, - ServiceEnvironment serviceEnvironment, - String userAgent, - Consumer newManagerListener - ) { - var pathConfig = PathConfig.createDefault(settingsPath); - - final var serviceConfiguration = ServiceConfig.getServiceEnvironmentConfig(serviceEnvironment, userAgent); - - return new ProvisioningManagerImpl(pathConfig, serviceConfiguration, userAgent, newManagerListener); - } - URI getDeviceLinkUri() throws TimeoutException, IOException; String finishDeviceLink(String deviceName) throws IOException, TimeoutException, UserAlreadyExistsException; diff --git a/lib/src/main/java/org/asamk/signal/manager/ProvisioningManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/ProvisioningManagerImpl.java index bfa51091..e0f1084b 100644 --- a/lib/src/main/java/org/asamk/signal/manager/ProvisioningManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/ProvisioningManagerImpl.java @@ -20,6 +20,7 @@ import org.asamk.signal.manager.api.UserAlreadyExistsException; import org.asamk.signal.manager.config.ServiceConfig; import org.asamk.signal.manager.config.ServiceEnvironmentConfig; import org.asamk.signal.manager.storage.SignalAccount; +import org.asamk.signal.manager.storage.accounts.AccountsStore; import org.asamk.signal.manager.storage.identities.TrustNewIdentity; import org.asamk.signal.manager.util.KeyUtils; import org.slf4j.Logger; @@ -47,6 +48,7 @@ class ProvisioningManagerImpl implements ProvisioningManager { private final ServiceEnvironmentConfig serviceEnvironmentConfig; private final String userAgent; private final Consumer newManagerListener; + private final AccountsStore accountsStore; private final SignalServiceAccountManager accountManager; private final IdentityKeyPair tempIdentityKey; @@ -57,12 +59,14 @@ class ProvisioningManagerImpl implements ProvisioningManager { PathConfig pathConfig, ServiceEnvironmentConfig serviceEnvironmentConfig, String userAgent, - final Consumer newManagerListener + final Consumer newManagerListener, + final AccountsStore accountsStore ) { this.pathConfig = pathConfig; this.serviceEnvironmentConfig = serviceEnvironmentConfig; this.userAgent = userAgent; this.newManagerListener = newManagerListener; + this.accountsStore = accountsStore; tempIdentityKey = KeyUtils.generateIdentityKeyPair(); registrationId = KeyHelper.generateRegistrationId(false); @@ -91,11 +95,23 @@ class ProvisioningManagerImpl implements ProvisioningManager { public String finishDeviceLink(String deviceName) throws IOException, TimeoutException, UserAlreadyExistsException { var ret = accountManager.getNewDeviceRegistration(tempIdentityKey); var number = ret.getNumber(); + var aci = ret.getAci(); logger.info("Received link information from {}, linking in progress ...", number); - if (SignalAccount.userExists(pathConfig.dataPath(), number) && !canRelinkExistingAccount(number)) { - throw new UserAlreadyExistsException(number, SignalAccount.getFileName(pathConfig.dataPath(), number)); + var accountPath = accountsStore.getPathByAci(aci); + if (accountPath == null) { + accountPath = accountsStore.getPathByNumber(number); + } + if (accountPath != null + && SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath) + && !canRelinkExistingAccount(accountPath)) { + throw new UserAlreadyExistsException(number, SignalAccount.getFileName(pathConfig.dataPath(), accountPath)); + } + if (accountPath == null) { + accountPath = accountsStore.addAccount(number, aci); + } else { + accountsStore.updateAccount(accountPath, number, aci); } var encryptedDeviceName = deviceName == null @@ -115,8 +131,9 @@ class ProvisioningManagerImpl implements ProvisioningManager { SignalAccount account = null; try { account = SignalAccount.createOrUpdateLinkedAccount(pathConfig.dataPath(), + accountPath, number, - ret.getAci(), + aci, password, encryptedDeviceName, deviceId, @@ -127,7 +144,12 @@ class ProvisioningManagerImpl implements ProvisioningManager { ManagerImpl m = null; try { - m = new ManagerImpl(account, pathConfig, serviceEnvironmentConfig, userAgent); + final var accountPathFinal = accountPath; + m = new ManagerImpl(account, + pathConfig, + (newNumber, newAci) -> accountsStore.updateAccount(accountPathFinal, newNumber, newAci), + serviceEnvironmentConfig, + userAgent); account = null; logger.debug("Refreshing pre keys"); @@ -162,10 +184,13 @@ class ProvisioningManagerImpl implements ProvisioningManager { } } - private boolean canRelinkExistingAccount(final String number) throws IOException { + private boolean canRelinkExistingAccount(final String accountPath) throws IOException { final SignalAccount signalAccount; try { - signalAccount = SignalAccount.load(pathConfig.dataPath(), number, false, TrustNewIdentity.ON_FIRST_USE); + signalAccount = SignalAccount.load(pathConfig.dataPath(), + accountPath, + false, + TrustNewIdentity.ON_FIRST_USE); } catch (IOException e) { logger.debug("Account in use or failed to load.", e); return false; @@ -177,7 +202,11 @@ class ProvisioningManagerImpl implements ProvisioningManager { return false; } - final var m = new ManagerImpl(signalAccount, pathConfig, serviceEnvironmentConfig, userAgent); + final var m = new ManagerImpl(signalAccount, + pathConfig, + (newNumber, newAci) -> accountsStore.updateAccount(accountPath, newNumber, newAci), + serviceEnvironmentConfig, + userAgent); try (m) { m.checkAccountState(); } catch (AuthorizationFailedException ignored) { diff --git a/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java b/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java index f094ed92..c1c318d9 100644 --- a/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java +++ b/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java @@ -3,60 +3,12 @@ package org.asamk.signal.manager; import org.asamk.signal.manager.api.CaptchaRequiredException; import org.asamk.signal.manager.api.IncorrectPinException; import org.asamk.signal.manager.api.PinLockedException; -import org.asamk.signal.manager.config.ServiceConfig; -import org.asamk.signal.manager.config.ServiceEnvironment; -import org.asamk.signal.manager.storage.SignalAccount; -import org.asamk.signal.manager.storage.identities.TrustNewIdentity; -import org.asamk.signal.manager.util.KeyUtils; -import org.whispersystems.libsignal.util.KeyHelper; import java.io.Closeable; -import java.io.File; import java.io.IOException; -import java.util.function.Consumer; public interface RegistrationManager extends Closeable { - static RegistrationManager init( - String number, File settingsPath, ServiceEnvironment serviceEnvironment, String userAgent - ) throws IOException { - return init(number, settingsPath, serviceEnvironment, userAgent, null); - } - - static RegistrationManager init( - String number, - File settingsPath, - ServiceEnvironment serviceEnvironment, - String userAgent, - Consumer newManagerListener - ) throws IOException { - var pathConfig = PathConfig.createDefault(settingsPath); - - final var serviceConfiguration = ServiceConfig.getServiceEnvironmentConfig(serviceEnvironment, userAgent); - if (!SignalAccount.userExists(pathConfig.dataPath(), number)) { - var identityKey = KeyUtils.generateIdentityKeyPair(); - var registrationId = KeyHelper.generateRegistrationId(false); - - var profileKey = KeyUtils.createProfileKey(); - var account = SignalAccount.create(pathConfig.dataPath(), - number, - identityKey, - registrationId, - profileKey, - TrustNewIdentity.ON_FIRST_USE); - - return new RegistrationManagerImpl(account, - pathConfig, - serviceConfiguration, - userAgent, - newManagerListener); - } - - var account = SignalAccount.load(pathConfig.dataPath(), number, true, TrustNewIdentity.ON_FIRST_USE); - - return new RegistrationManagerImpl(account, pathConfig, serviceConfiguration, userAgent, newManagerListener); - } - void register(boolean voiceVerification, String captcha) throws IOException, CaptchaRequiredException; void verifyAccount( diff --git a/lib/src/main/java/org/asamk/signal/manager/RegistrationManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/RegistrationManagerImpl.java index c48a2e99..81602122 100644 --- a/lib/src/main/java/org/asamk/signal/manager/RegistrationManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/RegistrationManagerImpl.java @@ -21,6 +21,7 @@ import org.asamk.signal.manager.api.IncorrectPinException; import org.asamk.signal.manager.api.PinLockedException; import org.asamk.signal.manager.config.ServiceConfig; import org.asamk.signal.manager.config.ServiceEnvironmentConfig; +import org.asamk.signal.manager.helper.AccountFileUpdater; import org.asamk.signal.manager.helper.PinHelper; import org.asamk.signal.manager.storage.SignalAccount; import org.asamk.signal.manager.util.Utils; @@ -59,16 +60,19 @@ class RegistrationManagerImpl implements RegistrationManager { private final SignalServiceAccountManager accountManager; private final PinHelper pinHelper; + private final AccountFileUpdater accountFileUpdater; RegistrationManagerImpl( SignalAccount account, PathConfig pathConfig, ServiceEnvironmentConfig serviceEnvironmentConfig, String userAgent, - Consumer newManagerListener + Consumer newManagerListener, + AccountFileUpdater accountFileUpdater ) { this.account = account; this.pathConfig = pathConfig; + this.accountFileUpdater = accountFileUpdater; this.serviceEnvironmentConfig = serviceEnvironmentConfig; this.userAgent = userAgent; this.newManagerListener = newManagerListener; @@ -82,7 +86,7 @@ class RegistrationManagerImpl implements RegistrationManager { this.accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(), new DynamicCredentialsProvider( // Using empty UUID, because registering doesn't work otherwise - null, account.getAccount(), account.getPassword(), SignalServiceAddress.DEFAULT_DEVICE_ID), + null, account.getNumber(), account.getPassword(), SignalServiceAddress.DEFAULT_DEVICE_ID), userAgent, groupsV2Operations, ServiceConfig.AUTOMATIC_NETWORK_RETRY); @@ -101,7 +105,7 @@ class RegistrationManagerImpl implements RegistrationManager { try { final var accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(), new DynamicCredentialsProvider(account.getAci(), - account.getAccount(), + account.getNumber(), account.getPassword(), account.getDeviceId()), userAgent, @@ -120,7 +124,11 @@ class RegistrationManagerImpl implements RegistrationManager { account.setRegistered(true); logger.info("Reactivated existing account, verify is not necessary."); if (newManagerListener != null) { - final var m = new ManagerImpl(account, pathConfig, serviceEnvironmentConfig, userAgent); + final var m = new ManagerImpl(account, + pathConfig, + accountFileUpdater, + serviceEnvironmentConfig, + userAgent); account = null; newManagerListener.accept(m); } @@ -187,11 +195,13 @@ class RegistrationManagerImpl implements RegistrationManager { } //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID))); - account.finishRegistration(ACI.parseOrNull(response.getUuid()), masterKey, pin); + final var aci = ACI.parseOrNull(response.getUuid()); + account.finishRegistration(aci, masterKey, pin); + accountFileUpdater.updateAccountIdentifiers(account.getNumber(), aci); ManagerImpl m = null; try { - m = new ManagerImpl(account, pathConfig, serviceEnvironmentConfig, userAgent); + m = new ManagerImpl(account, pathConfig, accountFileUpdater, serviceEnvironmentConfig, userAgent); account = null; m.refreshPreKeys(); diff --git a/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java b/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java new file mode 100644 index 00000000..f1d37e81 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java @@ -0,0 +1,152 @@ +package org.asamk.signal.manager; + +import org.asamk.signal.manager.api.AccountCheckException; +import org.asamk.signal.manager.api.NotRegisteredException; +import org.asamk.signal.manager.config.ServiceConfig; +import org.asamk.signal.manager.config.ServiceEnvironment; +import org.asamk.signal.manager.config.ServiceEnvironmentConfig; +import org.asamk.signal.manager.storage.SignalAccount; +import org.asamk.signal.manager.storage.accounts.AccountsStore; +import org.asamk.signal.manager.storage.identities.TrustNewIdentity; +import org.asamk.signal.manager.util.KeyUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.whispersystems.libsignal.util.KeyHelper; + +import java.io.File; +import java.io.IOException; +import java.util.Objects; +import java.util.Set; +import java.util.function.Consumer; + +public class SignalAccountFiles { + + private static final Logger logger = LoggerFactory.getLogger(MultiAccountManager.class); + + private final PathConfig pathConfig; + private final ServiceEnvironmentConfig serviceEnvironmentConfig; + private final String userAgent; + private final TrustNewIdentity trustNewIdentity; + private final AccountsStore accountsStore; + + public SignalAccountFiles( + final File settingsPath, + final ServiceEnvironment serviceEnvironment, + final String userAgent, + final TrustNewIdentity trustNewIdentity + ) { + this.pathConfig = PathConfig.createDefault(settingsPath); + this.serviceEnvironmentConfig = ServiceConfig.getServiceEnvironmentConfig(serviceEnvironment, userAgent); + this.userAgent = userAgent; + this.trustNewIdentity = trustNewIdentity; + this.accountsStore = new AccountsStore(pathConfig.dataPath()); + } + + public Set getAllLocalAccountNumbers() { + return accountsStore.getAllNumbers(); + } + + public MultiAccountManager initMultiAccountManager() { + final var managers = getAllLocalAccountNumbers().stream().map(a -> { + try { + return initManager(a); + } catch (NotRegisteredException | IOException | AccountCheckException e) { + logger.warn("Ignoring {}: {} ({})", a, e.getMessage(), e.getClass().getSimpleName()); + return null; + } + }).filter(Objects::nonNull).toList(); + + return new MultiAccountManagerImpl(managers, this); + } + + public Manager initManager(String number) throws IOException, NotRegisteredException, AccountCheckException { + final var accountPath = accountsStore.getPathByNumber(number); + if (accountPath == null || !SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)) { + throw new NotRegisteredException(); + } + + var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, trustNewIdentity); + if (!number.equals(account.getNumber())) { + account.close(); + throw new IOException("Number in account file doesn't match expected number: " + account.getNumber()); + } + + if (!account.isRegistered()) { + account.close(); + throw new NotRegisteredException(); + } + + account.initDatabase(); + + final var manager = new ManagerImpl(account, + pathConfig, + (newNumber, newAci) -> accountsStore.updateAccount(accountPath, newNumber, newAci), + serviceEnvironmentConfig, + userAgent); + + try { + manager.checkAccountState(); + } catch (IOException e) { + manager.close(); + throw new AccountCheckException("Error while checking account " + number + ": " + e.getMessage(), e); + } + + return manager; + } + + public ProvisioningManager initProvisioningManager() { + return initProvisioningManager(null); + } + + public ProvisioningManager initProvisioningManager(Consumer newManagerListener) { + return new ProvisioningManagerImpl(pathConfig, + serviceEnvironmentConfig, + userAgent, + newManagerListener, + accountsStore); + } + + public RegistrationManager initRegistrationManager(String number) throws IOException { + return initRegistrationManager(number, null); + } + + public RegistrationManager initRegistrationManager( + String number, Consumer newManagerListener + ) throws IOException { + final var accountPath = accountsStore.getPathByNumber(number); + if (accountPath == null || !SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)) { + final var newAccountPath = accountPath == null ? accountsStore.addAccount(number, null) : accountPath; + var identityKey = KeyUtils.generateIdentityKeyPair(); + var registrationId = KeyHelper.generateRegistrationId(false); + + var profileKey = KeyUtils.createProfileKey(); + var account = SignalAccount.create(pathConfig.dataPath(), + newAccountPath, + number, + identityKey, + registrationId, + profileKey, + trustNewIdentity); + + return new RegistrationManagerImpl(account, + pathConfig, + serviceEnvironmentConfig, + userAgent, + newManagerListener, + (newNumber, newAci) -> accountsStore.updateAccount(newAccountPath, newNumber, newAci)); + } + + var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, trustNewIdentity); + if (!number.equals(account.getNumber())) { + account.close(); + throw new IOException("Number in account file doesn't match expected number: " + account.getNumber()); + } + + return new RegistrationManagerImpl(account, + pathConfig, + serviceEnvironmentConfig, + userAgent, + newManagerListener, + (newNumber, newAci) -> accountsStore.updateAccount(accountPath, newNumber, newAci)); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/AccountFileUpdater.java b/lib/src/main/java/org/asamk/signal/manager/helper/AccountFileUpdater.java new file mode 100644 index 00000000..fb6e327c --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/helper/AccountFileUpdater.java @@ -0,0 +1,8 @@ +package org.asamk.signal.manager.helper; + +import org.whispersystems.signalservice.api.push.ACI; + +public interface AccountFileUpdater { + + void updateAccountIdentifiers(String number, ACI aci); +} 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 2b90a4fc..3e60ce74 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 @@ -52,7 +52,9 @@ public class AccountHelper { try { context.getPreKeyHelper().refreshPreKeysIfNecessary(); if (account.getAci() == null) { - account.setAci(ACI.parseOrNull(dependencies.getAccountManager().getWhoAmI().getAci())); + final var aci = ACI.parseOrNull(dependencies.getAccountManager().getWhoAmI().getAci()); + account.setAci(aci); + context.getAccountFileUpdater().updateAccountIdentifiers(account.getNumber(), aci); } updateAccountAttributes(); } catch (AuthorizationFailedException e) { diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/Context.java b/lib/src/main/java/org/asamk/signal/manager/helper/Context.java index 11de336e..522143ee 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/Context.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/Context.java @@ -4,8 +4,8 @@ import org.asamk.signal.manager.AttachmentStore; import org.asamk.signal.manager.AvatarStore; import org.asamk.signal.manager.JobExecutor; import org.asamk.signal.manager.SignalDependencies; -import org.asamk.signal.manager.storage.stickerPacks.StickerPackStore; import org.asamk.signal.manager.storage.SignalAccount; +import org.asamk.signal.manager.storage.stickerPacks.StickerPackStore; import java.util.function.Supplier; @@ -14,6 +14,7 @@ public class Context { private final Object LOCK = new Object(); private final SignalAccount account; + private final AccountFileUpdater accountFileUpdater; private final SignalDependencies dependencies; private final AvatarStore avatarStore; private final StickerPackStore stickerPackStore; @@ -40,12 +41,14 @@ public class Context { public Context( final SignalAccount account, + final AccountFileUpdater accountFileUpdater, final SignalDependencies dependencies, final AvatarStore avatarStore, final AttachmentStore attachmentStore, final StickerPackStore stickerPackStore ) { this.account = account; + this.accountFileUpdater = accountFileUpdater; this.dependencies = dependencies; this.avatarStore = avatarStore; this.stickerPackStore = stickerPackStore; @@ -57,6 +60,10 @@ public class Context { return account; } + public AccountFileUpdater getAccountFileUpdater() { + return accountFileUpdater; + } + public SignalDependencies getDependencies() { return dependencies; } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/StorageHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/StorageHelper.java index dfed3d43..6f136b3c 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/StorageHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/StorageHelper.java @@ -1,8 +1,8 @@ package org.asamk.signal.manager.helper; import org.asamk.signal.manager.SignalDependencies; -import org.asamk.signal.manager.api.TrustLevel; import org.asamk.signal.manager.api.PhoneNumberSharingMode; +import org.asamk.signal.manager.api.TrustLevel; import org.asamk.signal.manager.groups.GroupId; import org.asamk.signal.manager.storage.SignalAccount; import org.asamk.signal.manager.storage.recipients.Contact; @@ -188,7 +188,7 @@ public class StorageHelper { return; } - if (!accountRecord.getE164().equals(account.getAccount())) { + if (!accountRecord.getE164().equals(account.getNumber())) { // TODO implement changed number handling } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java b/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java index d026b25a..d77d51a2 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java @@ -3,8 +3,8 @@ package org.asamk.signal.manager.storage; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import org.asamk.signal.manager.api.TrustLevel; import org.asamk.signal.manager.api.Pair; +import org.asamk.signal.manager.api.TrustLevel; import org.asamk.signal.manager.groups.GroupId; import org.asamk.signal.manager.storage.configuration.ConfigurationStore; import org.asamk.signal.manager.storage.contacts.ContactsStore; @@ -87,7 +87,8 @@ public class SignalAccount implements Closeable { private int previousStorageVersion; private File dataPath; - private String account; + private String accountPath; + private String number; private ACI aci; private String encryptedDeviceName; private int deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID; @@ -132,23 +133,18 @@ public class SignalAccount implements Closeable { } public static SignalAccount load( - File dataPath, String account, boolean waitForLock, final TrustNewIdentity trustNewIdentity + File dataPath, String accountPath, boolean waitForLock, final TrustNewIdentity trustNewIdentity ) throws IOException { logger.trace("Opening account file"); - final var fileName = getFileName(dataPath, account); + final var fileName = getFileName(dataPath, accountPath); final var pair = openFileChannel(fileName, waitForLock); try { var signalAccount = new SignalAccount(pair.first(), pair.second()); logger.trace("Loading account file"); - signalAccount.load(dataPath, trustNewIdentity); + signalAccount.load(dataPath, accountPath, trustNewIdentity); logger.trace("Migrating legacy parts of account file"); signalAccount.migrateLegacyConfigs(); - if (!account.equals(signalAccount.getAccount())) { - throw new IOException("Number in account file doesn't match expected number: " - + signalAccount.getAccount()); - } - return signalAccount; } catch (Throwable e) { pair.second().close(); @@ -159,14 +155,15 @@ public class SignalAccount implements Closeable { public static SignalAccount create( File dataPath, - String account, + String accountPath, + String number, IdentityKeyPair identityKey, int registrationId, ProfileKey profileKey, final TrustNewIdentity trustNewIdentity ) throws IOException { IOUtils.createPrivateDirectories(dataPath); - var fileName = getFileName(dataPath, account); + var fileName = getFileName(dataPath, accountPath); if (!fileName.exists()) { IOUtils.createPrivateFile(fileName); } @@ -174,14 +171,15 @@ public class SignalAccount implements Closeable { final var pair = openFileChannel(fileName, true); var signalAccount = new SignalAccount(pair.first(), pair.second()); - signalAccount.account = account; + signalAccount.accountPath = accountPath; + signalAccount.number = number; signalAccount.profileKey = profileKey; signalAccount.dataPath = dataPath; signalAccount.identityKeyPair = identityKey; signalAccount.localRegistrationId = registrationId; signalAccount.trustNewIdentity = trustNewIdentity; - signalAccount.groupStore = new GroupStore(getGroupCachePath(dataPath, account), + signalAccount.groupStore = new GroupStore(getGroupCachePath(dataPath, accountPath), signalAccount.getRecipientStore(), signalAccount::saveGroupStore); signalAccount.stickerStore = new StickerStore(signalAccount::saveStickerStore); @@ -198,7 +196,8 @@ public class SignalAccount implements Closeable { public static SignalAccount createOrUpdateLinkedAccount( File dataPath, - String account, + String accountPath, + String number, ACI aci, String password, String encryptedDeviceName, @@ -209,10 +208,11 @@ public class SignalAccount implements Closeable { final TrustNewIdentity trustNewIdentity ) throws IOException { IOUtils.createPrivateDirectories(dataPath); - var fileName = getFileName(dataPath, account); + var fileName = getFileName(dataPath, accountPath); if (!fileName.exists()) { return createLinkedAccount(dataPath, - account, + accountPath, + number, aci, password, encryptedDeviceName, @@ -223,8 +223,8 @@ public class SignalAccount implements Closeable { trustNewIdentity); } - final var signalAccount = load(dataPath, account, true, trustNewIdentity); - signalAccount.setProvisioningData(account, aci, password, encryptedDeviceName, deviceId, profileKey); + final var signalAccount = load(dataPath, accountPath, true, trustNewIdentity); + signalAccount.setProvisioningData(number, aci, password, encryptedDeviceName, deviceId, profileKey); signalAccount.getRecipientStore().resolveRecipientTrusted(signalAccount.getSelfAddress()); signalAccount.getSessionStore().archiveAllSessions(); signalAccount.getSenderKeyStore().deleteAll(); @@ -246,7 +246,8 @@ public class SignalAccount implements Closeable { private static SignalAccount createLinkedAccount( File dataPath, - String account, + String accountPath, + String number, ACI aci, String password, String encryptedDeviceName, @@ -256,19 +257,20 @@ public class SignalAccount implements Closeable { ProfileKey profileKey, final TrustNewIdentity trustNewIdentity ) throws IOException { - var fileName = getFileName(dataPath, account); + var fileName = getFileName(dataPath, accountPath); IOUtils.createPrivateFile(fileName); final var pair = openFileChannel(fileName, true); var signalAccount = new SignalAccount(pair.first(), pair.second()); - signalAccount.setProvisioningData(account, aci, password, encryptedDeviceName, deviceId, profileKey); + signalAccount.setProvisioningData(number, aci, password, encryptedDeviceName, deviceId, profileKey); signalAccount.dataPath = dataPath; + signalAccount.accountPath = accountPath; signalAccount.identityKeyPair = identityKey; signalAccount.localRegistrationId = registrationId; signalAccount.trustNewIdentity = trustNewIdentity; - signalAccount.groupStore = new GroupStore(getGroupCachePath(dataPath, account), + signalAccount.groupStore = new GroupStore(getGroupCachePath(dataPath, accountPath), signalAccount.getRecipientStore(), signalAccount::saveGroupStore); signalAccount.stickerStore = new StickerStore(signalAccount::saveStickerStore); @@ -283,14 +285,14 @@ public class SignalAccount implements Closeable { } private void setProvisioningData( - final String account, + final String number, final ACI aci, final String password, final String encryptedDeviceName, final int deviceId, final ProfileKey profileKey ) { - this.account = account; + this.number = number; this.aci = aci; this.password = password; this.profileKey = profileKey; @@ -396,7 +398,7 @@ public class SignalAccount implements Closeable { return new File(getUserPath(dataPath, account), "account.db"); } - public static boolean userExists(File dataPath, String account) { + public static boolean accountFileExists(File dataPath, String account) { if (account == null) { return false; } @@ -405,9 +407,11 @@ public class SignalAccount implements Closeable { } private void load( - File dataPath, final TrustNewIdentity trustNewIdentity + File dataPath, String accountPath, final TrustNewIdentity trustNewIdentity ) throws IOException { - JsonNode rootNode; + this.dataPath = dataPath; + this.accountPath = accountPath; + final JsonNode rootNode; synchronized (fileChannel) { fileChannel.position(0); rootNode = jsonProcessor.readTree(Channels.newInputStream(fileChannel)); @@ -423,7 +427,7 @@ public class SignalAccount implements Closeable { previousStorageVersion = accountVersion; } - account = Utils.getNotNullNode(rootNode, "username").asText(); + number = Utils.getNotNullNode(rootNode, "username").asText(); if (rootNode.hasNonNull("password")) { password = rootNode.get("password").asText(); } @@ -501,7 +505,6 @@ public class SignalAccount implements Closeable { migratedLegacyConfig = true; } - this.dataPath = dataPath; this.identityKeyPair = identityKeyPair; this.localRegistrationId = registrationId; this.trustNewIdentity = trustNewIdentity; @@ -511,11 +514,11 @@ public class SignalAccount implements Closeable { if (rootNode.hasNonNull("groupStore")) { groupStoreStorage = jsonProcessor.convertValue(rootNode.get("groupStore"), GroupStore.Storage.class); groupStore = GroupStore.fromStorage(groupStoreStorage, - getGroupCachePath(dataPath, account), + getGroupCachePath(dataPath, accountPath), getRecipientStore(), this::saveGroupStore); } else { - groupStore = new GroupStore(getGroupCachePath(dataPath, account), + groupStore = new GroupStore(getGroupCachePath(dataPath, accountPath), getRecipientStore(), this::saveGroupStore); } @@ -732,7 +735,7 @@ public class SignalAccount implements Closeable { synchronized (fileChannel) { var rootNode = jsonProcessor.createObjectNode(); rootNode.put("version", CURRENT_STORAGE_VERSION) - .put("username", account) + .put("username", number) .put("uuid", aci == null ? null : aci.toString()) .put("deviceName", encryptedDeviceName) .put("deviceId", deviceId) @@ -821,22 +824,23 @@ public class SignalAccount implements Closeable { } private PreKeyStore getPreKeyStore() { - return getOrCreate(() -> preKeyStore, () -> preKeyStore = new PreKeyStore(getPreKeysPath(dataPath, account))); + return getOrCreate(() -> preKeyStore, + () -> preKeyStore = new PreKeyStore(getPreKeysPath(dataPath, accountPath))); } private SignedPreKeyStore getSignedPreKeyStore() { return getOrCreate(() -> signedPreKeyStore, - () -> signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, account))); + () -> signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, accountPath))); } public SessionStore getSessionStore() { return getOrCreate(() -> sessionStore, - () -> sessionStore = new SessionStore(getSessionsPath(dataPath, account), getRecipientStore())); + () -> sessionStore = new SessionStore(getSessionsPath(dataPath, accountPath), getRecipientStore())); } public IdentityKeyStore getIdentityKeyStore() { return getOrCreate(() -> identityKeyStore, - () -> identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, account), + () -> identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, accountPath), getRecipientStore(), identityKeyPair, localRegistrationId, @@ -853,7 +857,7 @@ public class SignalAccount implements Closeable { public RecipientStore getRecipientStore() { return getOrCreate(() -> recipientStore, - () -> recipientStore = RecipientStore.load(getRecipientsStoreFile(dataPath, account), + () -> recipientStore = RecipientStore.load(getRecipientsStoreFile(dataPath, accountPath), this::mergeRecipients)); } @@ -867,8 +871,8 @@ public class SignalAccount implements Closeable { public SenderKeyStore getSenderKeyStore() { return getOrCreate(() -> senderKeyStore, - () -> senderKeyStore = new SenderKeyStore(getSharedSenderKeysFile(dataPath, account), - getSenderKeysPath(dataPath, account), + () -> senderKeyStore = new SenderKeyStore(getSharedSenderKeysFile(dataPath, accountPath), + getSenderKeysPath(dataPath, accountPath), getRecipientStore()::resolveRecipientAddress, getRecipientStore())); } @@ -879,13 +883,13 @@ public class SignalAccount implements Closeable { public MessageCache getMessageCache() { return getOrCreate(() -> messageCache, - () -> messageCache = new MessageCache(getMessageCachePath(dataPath, account))); + () -> messageCache = new MessageCache(getMessageCachePath(dataPath, accountPath))); } public AccountDatabase getAccountDatabase() { return getOrCreate(() -> accountDatabase, () -> { try { - accountDatabase = AccountDatabase.init(getDatabaseFile(dataPath, account)); + accountDatabase = AccountDatabase.init(getDatabaseFile(dataPath, accountPath)); } catch (SQLException e) { throw new RuntimeException(e); } @@ -897,8 +901,8 @@ public class SignalAccount implements Closeable { () -> messageSendLogStore = new MessageSendLogStore(getRecipientStore(), getAccountDatabase())); } - public String getAccount() { - return account; + public String getNumber() { + return number; } public ACI getAci() { @@ -911,11 +915,11 @@ public class SignalAccount implements Closeable { } public SignalServiceAddress getSelfAddress() { - return new SignalServiceAddress(aci, account); + return new SignalServiceAddress(aci, number); } public RecipientAddress getSelfRecipientAddress() { - return new RecipientAddress(aci == null ? null : aci.uuid(), account); + return new RecipientAddress(aci == null ? null : aci.uuid(), number); } public RecipientId getSelfRecipientId() { diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStore.java new file mode 100644 index 00000000..ed6dec97 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStore.java @@ -0,0 +1,52 @@ +package org.asamk.signal.manager.storage.accounts; + +import org.whispersystems.signalservice.api.push.ACI; +import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; + +import java.io.File; +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +public class AccountsStore { + + private final File dataPath; + + public AccountsStore(final File dataPath) { + this.dataPath = dataPath; + } + + public Set getAllNumbers() { + final var files = dataPath.listFiles(); + + if (files == null) { + return Set.of(); + } + + return Arrays.stream(files) + .filter(File::isFile) + .map(File::getName) + .filter(file -> PhoneNumberFormatter.isValidNumber(file, null)) + .collect(Collectors.toSet()); + } + + public String getPathByNumber(String number) { + return number; + } + + public String getPathByAci(ACI aci) { + return null; + } + + public void updateAccount(String path, String number, ACI aci) { + // TODO remove number and uuid from all other accounts + if (!path.equals(number)) { + throw new UnsupportedOperationException("Updating number not supported yet"); + } + } + + public String addAccount(String number, ACI aci) { + // TODO remove number and uuid from all other accounts + return number; + } +} diff --git a/src/main/java/org/asamk/signal/App.java b/src/main/java/org/asamk/signal/App.java index 4e5b1e9f..5853e4b8 100644 --- a/src/main/java/org/asamk/signal/App.java +++ b/src/main/java/org/asamk/signal/App.java @@ -22,9 +22,8 @@ import org.asamk.signal.dbus.DbusMultiAccountManagerImpl; import org.asamk.signal.dbus.DbusProvisioningManagerImpl; import org.asamk.signal.dbus.DbusRegistrationManagerImpl; import org.asamk.signal.manager.Manager; -import org.asamk.signal.manager.MultiAccountManager; -import org.asamk.signal.manager.ProvisioningManager; import org.asamk.signal.manager.RegistrationManager; +import org.asamk.signal.manager.SignalAccountFiles; import org.asamk.signal.manager.api.AccountCheckException; import org.asamk.signal.manager.api.NotRegisteredException; import org.asamk.signal.manager.config.ServiceConfig; @@ -164,26 +163,27 @@ public class App { ? TrustNewIdentity.ON_FIRST_USE : trustNewIdentityCli == TrustNewIdentityCli.ALWAYS ? TrustNewIdentity.ALWAYS : TrustNewIdentity.NEVER; + final SignalAccountFiles signalAccountFiles = new SignalAccountFiles(configPath, + serviceEnvironment, + BaseConfig.USER_AGENT, + trustNewIdentity); + if (command instanceof ProvisioningCommand provisioningCommand) { if (account != null) { throw new UserErrorException("You cannot specify a account (phone number) when linking"); } - handleProvisioningCommand(provisioningCommand, configPath, serviceEnvironment, outputWriter); + handleProvisioningCommand(provisioningCommand, signalAccountFiles, outputWriter); return; } if (account == null) { if (command instanceof MultiLocalCommand multiLocalCommand) { - handleMultiLocalCommand(multiLocalCommand, - configPath, - serviceEnvironment, - outputWriter, - trustNewIdentity); + handleMultiLocalCommand(multiLocalCommand, signalAccountFiles, outputWriter); return; } - var accounts = MultiAccountManager.getAllLocalAccountNumbers(configPath); + var accounts = signalAccountFiles.getAllLocalAccountNumbers(); if (accounts.size() == 0) { throw new UserErrorException("No local users found, you first need to register or link an account"); } else if (accounts.size() > 1) { @@ -191,13 +191,13 @@ public class App { "Multiple users found, you need to specify an account (phone number) with -a"); } - account = accounts.get(0); + account = accounts.stream().findFirst().get(); } else if (!Manager.isValidNumber(account, null)) { throw new UserErrorException("Invalid account (phone number), make sure you include the country code."); } if (command instanceof RegistrationCommand registrationCommand) { - handleRegistrationCommand(registrationCommand, account, configPath, serviceEnvironment); + handleRegistrationCommand(registrationCommand, account, signalAccountFiles); return; } @@ -205,21 +205,15 @@ public class App { throw new UserErrorException("Command only works in multi-account mode"); } - handleLocalCommand((LocalCommand) command, - account, - configPath, - serviceEnvironment, - outputWriter, - trustNewIdentity); + handleLocalCommand((LocalCommand) command, account, signalAccountFiles, outputWriter); } private void handleProvisioningCommand( final ProvisioningCommand command, - final File configPath, - final ServiceEnvironment serviceEnvironment, + final SignalAccountFiles signalAccountFiles, final OutputWriter outputWriter ) throws CommandException { - var pm = ProvisioningManager.init(configPath, serviceEnvironment, BaseConfig.USER_AGENT); + var pm = signalAccountFiles.initProvisioningManager(); command.handleCommand(ns, pm, outputWriter); } @@ -240,22 +234,9 @@ public class App { } private void handleRegistrationCommand( - final RegistrationCommand command, - final String account, - final File configPath, - final ServiceEnvironment serviceEnvironment + final RegistrationCommand command, final String account, final SignalAccountFiles signalAccountFiles ) throws CommandException { - final RegistrationManager manager; - try { - manager = RegistrationManager.init(account, configPath, serviceEnvironment, BaseConfig.USER_AGENT); - } catch (Throwable e) { - throw new UnexpectedErrorException("Error loading or creating state file: " - + e.getMessage() - + " (" - + e.getClass().getSimpleName() - + ")", e); - } - try (manager) { + try (final var manager = loadRegistrationManager(account, signalAccountFiles)) { command.handleCommand(ns, manager); } catch (IOException e) { logger.warn("Cleanup failed", e); @@ -280,12 +261,10 @@ public class App { private void handleLocalCommand( final LocalCommand command, final String account, - final File configPath, - final ServiceEnvironment serviceEnvironment, - final OutputWriter outputWriter, - final TrustNewIdentity trustNewIdentity + final SignalAccountFiles signalAccountFiles, + final OutputWriter outputWriter ) throws CommandException { - try (var m = loadManager(account, configPath, serviceEnvironment, trustNewIdentity)) { + try (var m = loadManager(account, signalAccountFiles)) { command.handleCommand(ns, m, outputWriter); } catch (IOException e) { logger.warn("Cleanup failed", e); @@ -310,15 +289,10 @@ public class App { private void handleMultiLocalCommand( final MultiLocalCommand command, - final File configPath, - final ServiceEnvironment serviceEnvironment, - final OutputWriter outputWriter, - final TrustNewIdentity trustNewIdentity + final SignalAccountFiles signalAccountFiles, + final OutputWriter outputWriter ) throws CommandException { - try (var multiAccountManager = MultiAccountManager.init(configPath, - serviceEnvironment, - BaseConfig.USER_AGENT, - trustNewIdentity)) { + try (var multiAccountManager = signalAccountFiles.initMultiAccountManager()) { command.handleCommand(ns, multiAccountManager, outputWriter); } } @@ -336,15 +310,26 @@ public class App { } } + private RegistrationManager loadRegistrationManager( + final String account, final SignalAccountFiles signalAccountFiles + ) throws UnexpectedErrorException { + try { + return signalAccountFiles.initRegistrationManager(account); + } catch (Throwable e) { + throw new UnexpectedErrorException("Error loading or creating state file: " + + e.getMessage() + + " (" + + e.getClass().getSimpleName() + + ")", e); + } + } + private Manager loadManager( - final String account, - final File configPath, - final ServiceEnvironment serviceEnvironment, - final TrustNewIdentity trustNewIdentity + final String account, final SignalAccountFiles signalAccountFiles ) throws CommandException { logger.trace("Loading account file for {}", account); try { - return Manager.init(account, configPath, serviceEnvironment, BaseConfig.USER_AGENT, trustNewIdentity); + return signalAccountFiles.initManager(account); } catch (NotRegisteredException e) { throw new UserErrorException("User " + account + " is not registered."); } catch (AccountCheckException ace) { diff --git a/src/main/java/org/asamk/signal/Main.java b/src/main/java/org/asamk/signal/Main.java index f0b78590..14bdc4c9 100644 --- a/src/main/java/org/asamk/signal/Main.java +++ b/src/main/java/org/asamk/signal/Main.java @@ -27,7 +27,7 @@ import org.asamk.signal.commands.exceptions.IOErrorException; import org.asamk.signal.commands.exceptions.UnexpectedErrorException; import org.asamk.signal.commands.exceptions.UntrustedKeyErrorException; import org.asamk.signal.commands.exceptions.UserErrorException; -import org.asamk.signal.manager.Manager; +import org.asamk.signal.manager.ManagerLogger; import org.asamk.signal.util.SecurityProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.slf4j.bridge.SLF4JBridgeHandler; @@ -97,7 +97,7 @@ public class Main { if (verboseLevel > 0) { java.util.logging.Logger.getLogger("") .setLevel(verboseLevel > 2 ? java.util.logging.Level.FINEST : java.util.logging.Level.INFO); - Manager.initLogger(); + ManagerLogger.initLogger(); } SLF4JBridgeHandler.removeHandlersForRootLogger(); SLF4JBridgeHandler.install();