mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 10:30:38 +00:00
Add SignalAccountFiles as a central entry point
This commit is contained in:
parent
1db7f8d76e
commit
ff6b733cd0
19 changed files with 398 additions and 304 deletions
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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<String, Pair<String, UUID>> areUsersRegistered(Set<String> 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);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package org.asamk.signal.manager;
|
||||
|
||||
public class ManagerLogger {
|
||||
|
||||
public static void initLogger() {
|
||||
LibSignalLogger.initLogger();
|
||||
}
|
||||
}
|
|
@ -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<String> 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<String> getAccountNumbers();
|
||||
|
||||
List<Manager> getManagers();
|
||||
|
|
|
@ -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<Consumer<Manager>> onManagerRemovedHandlers = new HashSet<>();
|
||||
private final Set<Manager> managers = new HashSet<>();
|
||||
private final Map<URI, ProvisioningManager> provisioningManagers = new HashMap<>();
|
||||
private final File settingsPath;
|
||||
private final ServiceEnvironment serviceEnvironment;
|
||||
private final String userAgent;
|
||||
private final SignalAccountFiles signalAccountFiles;
|
||||
|
||||
public MultiAccountManagerImpl(
|
||||
final Collection<Manager> managers,
|
||||
final File settingsPath,
|
||||
final ServiceEnvironment serviceEnvironment,
|
||||
final String userAgent
|
||||
) {
|
||||
public MultiAccountManagerImpl(final Collection<Manager> 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
|
||||
|
|
|
@ -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<Manager> 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;
|
||||
|
|
|
@ -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<Manager> 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<Manager> newManagerListener
|
||||
final Consumer<Manager> 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) {
|
||||
|
|
|
@ -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<Manager> 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(
|
||||
|
|
|
@ -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<Manager> newManagerListener
|
||||
Consumer<Manager> 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();
|
||||
|
|
|
@ -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<String> 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<Manager> 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<Manager> 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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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<String> 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;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue