Add MultiAccountManager

This commit is contained in:
AsamK 2021-11-11 13:29:32 +01:00
parent 6261934dda
commit 4a1af0786c
18 changed files with 221 additions and 131 deletions

View file

@ -403,6 +403,8 @@ public class ManagerImpl implements Manager {
@Override
public void submitRateLimitRecaptchaChallenge(String challenge, String captcha) throws IOException {
captcha = captcha == null ? null : captcha.replace("signalcaptcha://", "");
dependencies.getAccountManager().submitRateLimitRecaptchaChallenge(challenge, captcha);
}

View file

@ -0,0 +1,23 @@
package org.asamk.signal.manager;
import java.io.IOException;
import java.util.List;
import java.util.function.Consumer;
public interface MultiAccountManager extends AutoCloseable {
List<String> getAccountNumbers();
void addOnManagerAddedHandler(Consumer<Manager> handler);
void addOnManagerRemovedHandler(Consumer<Manager> handler);
Manager getManager(String phoneNumber);
ProvisioningManager getNewProvisioningManager();
RegistrationManager getNewRegistrationManager(String username) throws IOException;
@Override
void close();
}

View file

@ -0,0 +1,104 @@
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.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
public class MultiAccountManagerImpl implements MultiAccountManager {
private final static Logger logger = LoggerFactory.getLogger(MultiAccountManagerImpl.class);
private final Set<Consumer<Manager>> onManagerAddedHandlers = new HashSet<>();
private final Set<Consumer<Manager>> onManagerRemovedHandlers = new HashSet<>();
private final Set<Manager> managers = new HashSet<>();
private final File dataPath;
private final ServiceEnvironment serviceEnvironment;
private final String userAgent;
public MultiAccountManagerImpl(
final Collection<Manager> managers,
final File dataPath,
final ServiceEnvironment serviceEnvironment,
final String userAgent
) {
this.managers.addAll(managers);
this.dataPath = dataPath;
this.serviceEnvironment = serviceEnvironment;
this.userAgent = userAgent;
}
@Override
public List<String> getAccountNumbers() {
synchronized (managers) {
return managers.stream().map(Manager::getSelfNumber).collect(Collectors.toList());
}
}
void addManager(final Manager m) {
synchronized (managers) {
if (managers.contains(m)) {
return;
}
managers.add(m);
}
synchronized (onManagerAddedHandlers) {
for (final var handler : onManagerAddedHandlers) {
handler.accept(m);
}
}
}
@Override
public void addOnManagerAddedHandler(final Consumer<Manager> handler) {
synchronized (onManagerAddedHandlers) {
onManagerAddedHandlers.add(handler);
}
}
@Override
public void addOnManagerRemovedHandler(final Consumer<Manager> handler) {
synchronized (onManagerRemovedHandlers) {
onManagerRemovedHandlers.add(handler);
}
}
@Override
public Manager getManager(final String account) {
synchronized (managers) {
return managers.stream().filter(m -> m.getSelfNumber().equals(account)).findFirst().orElse(null);
}
}
@Override
public ProvisioningManager getNewProvisioningManager() {
return ProvisioningManager.init(dataPath, serviceEnvironment, userAgent, this::addManager);
}
@Override
public RegistrationManager getNewRegistrationManager(String account) throws IOException {
return RegistrationManager.init(account, dataPath, serviceEnvironment, userAgent, this::addManager);
}
@Override
public void close() {
synchronized (managers) {
for (var m : managers) {
try {
m.close();
} catch (IOException e) {
logger.warn("Cleanup failed", e);
}
}
managers.clear();
}
}
}

View file

@ -38,6 +38,7 @@ import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
public class ProvisioningManager {
@ -46,16 +47,23 @@ public class ProvisioningManager {
private final PathConfig pathConfig;
private final ServiceEnvironmentConfig serviceEnvironmentConfig;
private final String userAgent;
private final Consumer<Manager> newManagerListener;
private final SignalServiceAccountManager accountManager;
private final IdentityKeyPair tempIdentityKey;
private final int registrationId;
private final String password;
ProvisioningManager(PathConfig pathConfig, ServiceEnvironmentConfig serviceEnvironmentConfig, String userAgent) {
ProvisioningManager(
PathConfig pathConfig,
ServiceEnvironmentConfig serviceEnvironmentConfig,
String userAgent,
final Consumer<Manager> newManagerListener
) {
this.pathConfig = pathConfig;
this.serviceEnvironmentConfig = serviceEnvironmentConfig;
this.userAgent = userAgent;
this.newManagerListener = newManagerListener;
tempIdentityKey = KeyUtils.generateIdentityKeyPair();
registrationId = KeyHelper.generateRegistrationId(false);
@ -75,12 +83,21 @@ public class ProvisioningManager {
public static ProvisioningManager init(
File settingsPath, ServiceEnvironment serviceEnvironment, String userAgent
) {
return init(settingsPath, serviceEnvironment, userAgent, null);
}
public 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 ProvisioningManager(pathConfig, serviceConfiguration, userAgent);
return new ProvisioningManager(pathConfig, serviceConfiguration, userAgent, newManagerListener);
}
public URI getDeviceLinkUri() throws TimeoutException, IOException {
@ -89,7 +106,7 @@ public class ProvisioningManager {
return new DeviceLinkInfo(deviceUuid, tempIdentityKey.getPublicKey().getPublicKey()).createDeviceLinkUri();
}
public Manager finishDeviceLink(String deviceName) throws IOException, TimeoutException, UserAlreadyExists {
public String finishDeviceLink(String deviceName) throws IOException, TimeoutException, UserAlreadyExists {
var ret = accountManager.getNewDeviceRegistration(tempIdentityKey);
var number = ret.getNumber();
@ -145,11 +162,11 @@ public class ProvisioningManager {
"Failed to request sync messages from linked device, data can be requested again with `sendSyncRequest`.");
}
final var result = m;
account = null;
m = null;
return result;
if (newManagerListener != null) {
newManagerListener.accept(m);
m = null;
}
return number;
} finally {
if (m != null) {
m.close();

View file

@ -49,6 +49,7 @@ import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.function.Consumer;
public class RegistrationManager implements Closeable {
@ -58,6 +59,7 @@ public class RegistrationManager implements Closeable {
private final PathConfig pathConfig;
private final ServiceEnvironmentConfig serviceEnvironmentConfig;
private final String userAgent;
private final Consumer<Manager> newManagerListener;
private final SignalServiceAccountManager accountManager;
private final PinHelper pinHelper;
@ -66,12 +68,14 @@ public class RegistrationManager implements Closeable {
SignalAccount account,
PathConfig pathConfig,
ServiceEnvironmentConfig serviceEnvironmentConfig,
String userAgent
String userAgent,
Consumer<Manager> newManagerListener
) {
this.account = account;
this.pathConfig = pathConfig;
this.serviceEnvironmentConfig = serviceEnvironmentConfig;
this.userAgent = userAgent;
this.newManagerListener = newManagerListener;
GroupsV2Operations groupsV2Operations;
try {
@ -96,6 +100,16 @@ public class RegistrationManager implements Closeable {
public static RegistrationManager init(
String number, File settingsPath, ServiceEnvironment serviceEnvironment, String userAgent
) throws IOException {
return init(number, settingsPath, serviceEnvironment, userAgent, null);
}
public static RegistrationManager init(
String number,
File settingsPath,
ServiceEnvironment serviceEnvironment,
String userAgent,
Consumer<Manager> newManagerListener
) throws IOException {
var pathConfig = PathConfig.createDefault(settingsPath);
@ -112,15 +126,16 @@ public class RegistrationManager implements Closeable {
profileKey,
TrustNewIdentity.ON_FIRST_USE);
return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent);
return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent, newManagerListener);
}
var account = SignalAccount.load(pathConfig.dataPath(), number, true, TrustNewIdentity.ON_FIRST_USE);
return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent);
return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent, newManagerListener);
}
public void register(boolean voiceVerification, String captcha) throws IOException, CaptchaRequiredException {
captcha = captcha == null ? null : captcha.replace("signalcaptcha://", "");
final ServiceResponse<RequestVerificationCodeResponse> response;
if (voiceVerification) {
response = accountManager.requestVoiceVerificationCode(Utils.getDefaultLocale(),
@ -140,7 +155,7 @@ public class RegistrationManager implements Closeable {
}
}
public Manager verifyAccount(
public void verifyAccount(
String verificationCode, String pin
) throws IOException, PinLockedException, IncorrectPinException {
verificationCode = verificationCode.replace("-", "");
@ -196,10 +211,10 @@ public class RegistrationManager implements Closeable {
logger.warn("Failed to set default profile: {}", e.getMessage());
}
final var result = m;
m = null;
return result;
if (newManagerListener != null) {
newManagerListener.accept(m);
m = null;
}
} finally {
if (m != null) {
m.close();