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