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 @Override
public void submitRateLimitRecaptchaChallenge(String challenge, String captcha) throws IOException { public void submitRateLimitRecaptchaChallenge(String challenge, String captcha) throws IOException {
captcha = captcha == null ? null : captcha.replace("signalcaptcha://", "");
dependencies.getAccountManager().submitRateLimitRecaptchaChallenge(challenge, captcha); dependencies.getAccountManager().submitRateLimitRecaptchaChallenge(challenge, captcha);
} }

View file

@ -1,24 +1,23 @@
package org.asamk.signal.commands; package org.asamk.signal.manager;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.ProvisioningManager;
import org.asamk.signal.manager.RegistrationManager;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
public interface SignalCreator { public interface MultiAccountManager extends AutoCloseable {
List<String> getAccountNumbers(); List<String> getAccountNumbers();
void addManager(Manager m);
void addOnManagerAddedHandler(Consumer<Manager> handler); void addOnManagerAddedHandler(Consumer<Manager> handler);
void addOnManagerRemovedHandler(Consumer<Manager> handler);
Manager getManager(String phoneNumber); Manager getManager(String phoneNumber);
ProvisioningManager getNewProvisioningManager(); ProvisioningManager getNewProvisioningManager();
RegistrationManager getNewRegistrationManager(String username) throws IOException; 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.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 class ProvisioningManager { public class ProvisioningManager {
@ -46,16 +47,23 @@ public class ProvisioningManager {
private final PathConfig pathConfig; private final PathConfig pathConfig;
private final ServiceEnvironmentConfig serviceEnvironmentConfig; private final ServiceEnvironmentConfig serviceEnvironmentConfig;
private final String userAgent; private final String userAgent;
private final Consumer<Manager> newManagerListener;
private final SignalServiceAccountManager accountManager; private final SignalServiceAccountManager accountManager;
private final IdentityKeyPair tempIdentityKey; private final IdentityKeyPair tempIdentityKey;
private final int registrationId; private final int registrationId;
private final String password; 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.pathConfig = pathConfig;
this.serviceEnvironmentConfig = serviceEnvironmentConfig; this.serviceEnvironmentConfig = serviceEnvironmentConfig;
this.userAgent = userAgent; this.userAgent = userAgent;
this.newManagerListener = newManagerListener;
tempIdentityKey = KeyUtils.generateIdentityKeyPair(); tempIdentityKey = KeyUtils.generateIdentityKeyPair();
registrationId = KeyHelper.generateRegistrationId(false); registrationId = KeyHelper.generateRegistrationId(false);
@ -75,12 +83,21 @@ public class ProvisioningManager {
public static ProvisioningManager init( public static ProvisioningManager init(
File settingsPath, ServiceEnvironment serviceEnvironment, String userAgent 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); var pathConfig = PathConfig.createDefault(settingsPath);
final var serviceConfiguration = ServiceConfig.getServiceEnvironmentConfig(serviceEnvironment, userAgent); 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 { public URI getDeviceLinkUri() throws TimeoutException, IOException {
@ -89,7 +106,7 @@ public class ProvisioningManager {
return new DeviceLinkInfo(deviceUuid, tempIdentityKey.getPublicKey().getPublicKey()).createDeviceLinkUri(); 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 ret = accountManager.getNewDeviceRegistration(tempIdentityKey);
var number = ret.getNumber(); 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`."); "Failed to request sync messages from linked device, data can be requested again with `sendSyncRequest`.");
} }
final var result = m; if (newManagerListener != null) {
account = null; newManagerListener.accept(m);
m = null; m = null;
}
return result; return number;
} finally { } finally {
if (m != null) { if (m != null) {
m.close(); m.close();

View file

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

View file

@ -12,13 +12,13 @@ import org.asamk.signal.commands.LocalCommand;
import org.asamk.signal.commands.MultiLocalCommand; import org.asamk.signal.commands.MultiLocalCommand;
import org.asamk.signal.commands.ProvisioningCommand; import org.asamk.signal.commands.ProvisioningCommand;
import org.asamk.signal.commands.RegistrationCommand; import org.asamk.signal.commands.RegistrationCommand;
import org.asamk.signal.commands.SignalCreator;
import org.asamk.signal.commands.exceptions.CommandException; import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.commands.exceptions.IOErrorException; 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.UserErrorException; import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.dbus.DbusManagerImpl; import org.asamk.signal.dbus.DbusManagerImpl;
import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.MultiAccountManagerImpl;
import org.asamk.signal.manager.NotRegisteredException; import org.asamk.signal.manager.NotRegisteredException;
import org.asamk.signal.manager.ProvisioningManager; import org.asamk.signal.manager.ProvisioningManager;
import org.asamk.signal.manager.RegistrationManager; import org.asamk.signal.manager.RegistrationManager;
@ -39,8 +39,6 @@ import java.io.OutputStreamWriter;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static net.sourceforge.argparse4j.DefaultSettings.VERSION_0_9_0_DEFAULT_SETTINGS; import static net.sourceforge.argparse4j.DefaultSettings.VERSION_0_9_0_DEFAULT_SETTINGS;
@ -186,7 +184,7 @@ public class App {
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) {
throw new UserErrorException( throw new UserErrorException(
"Multiple users found, you need to specify a account (phone number) with -u"); "Multiple users found, you need to specify an account (phone number) with -a");
} }
account = accounts.get(0); account = accounts.get(0);
@ -237,8 +235,8 @@ public class App {
+ e.getClass().getSimpleName() + e.getClass().getSimpleName()
+ ")", e); + ")", e);
} }
try (var m = manager) { try (manager) {
command.handleCommand(ns, m); command.handleCommand(ns, manager);
} catch (IOException e) { } catch (IOException e) {
logger.warn("Cleanup failed", e); logger.warn("Cleanup failed", e);
} }
@ -268,73 +266,19 @@ public class App {
final TrustNewIdentity trustNewIdentity final TrustNewIdentity trustNewIdentity
) throws CommandException { ) throws CommandException {
final var managers = new ArrayList<Manager>(); final var managers = new ArrayList<Manager>();
try { for (String a : accounts) {
for (String u : accounts) { try {
try { managers.add(loadManager(a, dataPath, serviceEnvironment, trustNewIdentity));
managers.add(loadManager(u, dataPath, serviceEnvironment, trustNewIdentity)); } catch (CommandException e) {
} catch (CommandException e) { logger.warn("Ignoring {}: {}", a, e.getMessage());
logger.warn("Ignoring {}: {}", u, e.getMessage());
}
} }
}
command.handleCommand(ns, new SignalCreator() { try (var multiAccountManager = new MultiAccountManagerImpl(managers,
private final List<Consumer<Manager>> onManagerAddedHandlers = new ArrayList<>(); dataPath,
serviceEnvironment,
@Override BaseConfig.USER_AGENT)) {
public List<String> getAccountNumbers() { command.handleCommand(ns, multiAccountManager, outputWriter);
synchronized (managers) {
return managers.stream().map(Manager::getSelfNumber).collect(Collectors.toList());
}
}
@Override
public void addManager(final Manager m) {
synchronized (managers) {
if (!managers.contains(m)) {
managers.add(m);
for (final var handler : onManagerAddedHandlers) {
handler.accept(m);
}
}
}
}
@Override
public void addOnManagerAddedHandler(final Consumer<Manager> handler) {
onManagerAddedHandlers.add(handler);
}
@Override
public Manager getManager(final String phoneNumber) {
synchronized (managers) {
return managers.stream()
.filter(m -> m.getSelfNumber().equals(phoneNumber))
.findFirst()
.orElse(null);
}
}
@Override
public ProvisioningManager getNewProvisioningManager() {
return ProvisioningManager.init(dataPath, serviceEnvironment, BaseConfig.USER_AGENT);
}
@Override
public RegistrationManager getNewRegistrationManager(String account) throws IOException {
return RegistrationManager.init(account, dataPath, serviceEnvironment, BaseConfig.USER_AGENT);
}
}, outputWriter);
} finally {
synchronized (managers) {
for (var m : managers) {
try {
m.close();
} catch (IOException e) {
logger.warn("Cleanup failed", e);
}
}
managers.clear();
}
} }
} }

View file

@ -19,6 +19,7 @@ import org.asamk.signal.dbus.DbusSignalControlImpl;
import org.asamk.signal.dbus.DbusSignalImpl; import org.asamk.signal.dbus.DbusSignalImpl;
import org.asamk.signal.jsonrpc.SignalJsonRpcDispatcherHandler; import org.asamk.signal.jsonrpc.SignalJsonRpcDispatcherHandler;
import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.MultiAccountManager;
import org.asamk.signal.util.IOUtils; import org.asamk.signal.util.IOUtils;
import org.freedesktop.dbus.connections.impl.DBusConnection; import org.freedesktop.dbus.connections.impl.DBusConnection;
import org.freedesktop.dbus.exceptions.DBusException; import org.freedesktop.dbus.exceptions.DBusException;
@ -141,7 +142,7 @@ public class DaemonCommand implements MultiLocalCommand, LocalCommand {
@Override @Override
public void handleCommand( public void handleCommand(
final Namespace ns, final SignalCreator c, final OutputWriter outputWriter final Namespace ns, final MultiAccountManager c, final OutputWriter outputWriter
) throws CommandException { ) throws CommandException {
logger.info("Starting daemon in multi-account mode"); logger.info("Starting daemon in multi-account mode");
final var noReceiveStdOut = Boolean.TRUE.equals(ns.getBoolean("no-receive-stdout")); final var noReceiveStdOut = Boolean.TRUE.equals(ns.getBoolean("no-receive-stdout"));
@ -220,7 +221,7 @@ public class DaemonCommand implements MultiLocalCommand, LocalCommand {
} }
private void runSocketMultiAccount( private void runSocketMultiAccount(
final SignalCreator c, final ServerSocketChannel serverChannel, final boolean noReceiveOnStart final MultiAccountManager c, final ServerSocketChannel serverChannel, final boolean noReceiveOnStart
) { ) {
runSocket(serverChannel, channel -> { runSocket(serverChannel, channel -> {
final var handler = getSignalJsonRpcDispatcherHandler(channel, noReceiveOnStart); final var handler = getSignalJsonRpcDispatcherHandler(channel, noReceiveOnStart);
@ -276,7 +277,7 @@ public class DaemonCommand implements MultiLocalCommand, LocalCommand {
} }
private void runDbusMultiAccount( private void runDbusMultiAccount(
final SignalCreator c, final boolean noReceiveOnStart, final boolean isDbusSystem final MultiAccountManager c, final boolean noReceiveOnStart, final boolean isDbusSystem
) throws UnexpectedErrorException { ) throws UnexpectedErrorException {
runDbus(isDbusSystem, (connection, objectPath) -> { runDbus(isDbusSystem, (connection, objectPath) -> {
final var signalControl = new DbusSignalControlImpl(c, objectPath); final var signalControl = new DbusSignalControlImpl(c, objectPath);

View file

@ -2,8 +2,9 @@ package org.asamk.signal.commands;
import org.asamk.signal.JsonWriter; import org.asamk.signal.JsonWriter;
import org.asamk.signal.commands.exceptions.CommandException; import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.manager.MultiAccountManager;
public interface JsonRpcMultiCommand<T> extends JsonRpcCommand<T> { public interface JsonRpcMultiCommand<T> extends JsonRpcCommand<T> {
void handleCommand(T request, SignalCreator c, JsonWriter jsonWriter) throws CommandException; void handleCommand(T request, MultiAccountManager c, JsonWriter jsonWriter) throws CommandException;
} }

View file

@ -7,6 +7,7 @@ import net.sourceforge.argparse4j.inf.Namespace;
import org.asamk.signal.JsonWriter; import org.asamk.signal.JsonWriter;
import org.asamk.signal.OutputType; import org.asamk.signal.OutputType;
import org.asamk.signal.commands.exceptions.CommandException; import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.manager.MultiAccountManager;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -18,7 +19,7 @@ public interface JsonRpcMultiLocalCommand extends JsonRpcMultiCommand<Map<String
} }
default void handleCommand( default void handleCommand(
Map<String, Object> request, SignalCreator c, JsonWriter jsonWriter Map<String, Object> request, MultiAccountManager c, JsonWriter jsonWriter
) throws CommandException { ) throws CommandException {
Namespace commandNamespace = new JsonRpcNamespace(request == null ? Map.of() : request); Namespace commandNamespace = new JsonRpcNamespace(request == null ? Map.of() : request);
handleCommand(commandNamespace, c, jsonWriter); handleCommand(commandNamespace, c, jsonWriter);

View file

@ -43,9 +43,8 @@ public class LinkCommand implements ProvisioningCommand {
} }
try { try {
writer.println("{}", m.getDeviceLinkUri()); writer.println("{}", m.getDeviceLinkUri());
try (var manager = m.finishDeviceLink(deviceName)) { var number = m.finishDeviceLink(deviceName);
writer.println("Associated with: {}", manager.getSelfNumber()); writer.println("Associated with: {}", number);
}
} catch (TimeoutException e) { } catch (TimeoutException e) {
throw new UserErrorException("Link request timed out, please try again."); throw new UserErrorException("Link request timed out, please try again.");
} catch (IOException e) { } catch (IOException e) {

View file

@ -7,6 +7,7 @@ import org.asamk.signal.JsonWriter;
import org.asamk.signal.OutputWriter; import org.asamk.signal.OutputWriter;
import org.asamk.signal.PlainTextWriter; import org.asamk.signal.PlainTextWriter;
import org.asamk.signal.commands.exceptions.CommandException; import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.manager.MultiAccountManager;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -24,7 +25,7 @@ public class ListAccountsCommand implements JsonRpcMultiLocalCommand {
@Override @Override
public void handleCommand( public void handleCommand(
final Namespace ns, final SignalCreator c, final OutputWriter outputWriter final Namespace ns, final MultiAccountManager c, final OutputWriter outputWriter
) throws CommandException { ) throws CommandException {
final var accountNumbers = c.getAccountNumbers(); final var accountNumbers = c.getAccountNumbers();
if (outputWriter instanceof JsonWriter jsonWriter) { if (outputWriter instanceof JsonWriter jsonWriter) {

View file

@ -4,8 +4,9 @@ import net.sourceforge.argparse4j.inf.Namespace;
import org.asamk.signal.OutputWriter; import org.asamk.signal.OutputWriter;
import org.asamk.signal.commands.exceptions.CommandException; import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.manager.MultiAccountManager;
public interface MultiLocalCommand extends CliCommand { public interface MultiLocalCommand extends CliCommand {
void handleCommand(Namespace ns, SignalCreator c, OutputWriter outputWriter) throws CommandException; void handleCommand(Namespace ns, MultiAccountManager c, OutputWriter outputWriter) throws CommandException;
} }

View file

@ -32,8 +32,7 @@ public class SubmitRateLimitChallengeCommand implements JsonRpcLocalCommand {
@Override @Override
public void handleCommand(final Namespace ns, final Manager m, OutputWriter outputWriter) throws CommandException { public void handleCommand(final Namespace ns, final Manager m, OutputWriter outputWriter) throws CommandException {
final var challenge = ns.getString("challenge"); final var challenge = ns.getString("challenge");
final var captchaString = ns.getString("captcha"); final var captcha = ns.getString("captcha");
final var captcha = captchaString == null ? null : captchaString.replace("signalcaptcha://", "");
try { try {
m.submitRateLimitRecaptchaChallenge(challenge, captcha); m.submitRateLimitRecaptchaChallenge(challenge, captcha);

View file

@ -32,8 +32,7 @@ public class VerifyCommand implements RegistrationCommand {
var pin = ns.getString("pin"); var pin = ns.getString("pin");
try { try {
final var manager = m.verifyAccount(verificationCode, pin); m.verifyAccount(verificationCode, pin);
manager.close();
} catch (PinLockedException e) { } catch (PinLockedException e) {
throw new UserErrorException( throw new UserErrorException(
"Verification failed! This number is locked with a pin. Hours remaining until reset: " "Verification failed! This number is locked with a pin. Hours remaining until reset: "

View file

@ -4,6 +4,7 @@ import org.asamk.signal.BaseConfig;
import org.asamk.signal.JsonWriter; import org.asamk.signal.JsonWriter;
import org.asamk.signal.commands.exceptions.CommandException; import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.MultiAccountManager;
import java.util.Map; import java.util.Map;
@ -23,7 +24,7 @@ public class VersionCommand implements JsonRpcSingleCommand<Void>, JsonRpcMultiC
@Override @Override
public void handleCommand( public void handleCommand(
final Void request, final SignalCreator c, final JsonWriter jsonWriter final Void request, final MultiAccountManager c, final JsonWriter jsonWriter
) throws CommandException { ) throws CommandException {
outputVersion(jsonWriter); outputVersion(jsonWriter);
} }

View file

@ -3,8 +3,8 @@ package org.asamk.signal.dbus;
import org.asamk.SignalControl; import org.asamk.SignalControl;
import org.asamk.signal.BaseConfig; import org.asamk.signal.BaseConfig;
import org.asamk.signal.DbusConfig; import org.asamk.signal.DbusConfig;
import org.asamk.signal.commands.SignalCreator;
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.ProvisioningManager;
import org.asamk.signal.manager.RegistrationManager; import org.asamk.signal.manager.RegistrationManager;
import org.asamk.signal.manager.UserAlreadyExists; import org.asamk.signal.manager.UserAlreadyExists;
@ -21,11 +21,11 @@ import java.util.stream.Collectors;
public class DbusSignalControlImpl implements org.asamk.SignalControl { public class DbusSignalControlImpl implements org.asamk.SignalControl {
private final SignalCreator c; private final MultiAccountManager c;
private final String objectPath; private final String objectPath;
public DbusSignalControlImpl(final SignalCreator c, final String objectPath) { public DbusSignalControlImpl(final MultiAccountManager c, final String objectPath) {
this.c = c; this.c = c;
this.objectPath = objectPath; this.objectPath = objectPath;
} }
@ -75,8 +75,7 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl {
final String number, final String verificationCode, final String pin final String number, final String verificationCode, final String pin
) throws Error.Failure, Error.InvalidNumber { ) throws Error.Failure, Error.InvalidNumber {
try (final RegistrationManager registrationManager = c.getNewRegistrationManager(number)) { try (final RegistrationManager registrationManager = c.getNewRegistrationManager(number)) {
final Manager manager = registrationManager.verifyAccount(verificationCode, pin); registrationManager.verifyAccount(verificationCode, pin);
c.addManager(manager);
} catch (IOException | PinLockedException | IncorrectPinException e) { } catch (IOException | PinLockedException | IncorrectPinException e) {
throw new SignalControl.Error.Failure(e.getClass().getSimpleName() + " " + e.getMessage()); throw new SignalControl.Error.Failure(e.getClass().getSimpleName() + " " + e.getMessage());
} }
@ -89,8 +88,7 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl {
final URI deviceLinkUri = provisioningManager.getDeviceLinkUri(); final URI deviceLinkUri = provisioningManager.getDeviceLinkUri();
new Thread(() -> { new Thread(() -> {
try { try {
final Manager manager = provisioningManager.finishDeviceLink(newDeviceName); provisioningManager.finishDeviceLink(newDeviceName);
c.addManager(manager);
} catch (IOException | TimeoutException | UserAlreadyExists e) { } catch (IOException | TimeoutException | UserAlreadyExists e) {
e.printStackTrace(); e.printStackTrace();
} }

View file

@ -129,9 +129,7 @@ public class DbusSignalImpl implements Signal {
} }
@Override @Override
public void submitRateLimitChallenge(String challenge, String captchaString) { public void submitRateLimitChallenge(String challenge, String captcha) {
final var captcha = captchaString == null ? null : captchaString.replace("signalcaptcha://", "");
try { try {
m.submitRateLimitRecaptchaChallenge(challenge, captcha); m.submitRateLimitRecaptchaChallenge(challenge, captcha);
} catch (IOException e) { } catch (IOException e) {

View file

@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.JsonMappingException;
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 com.fasterxml.jackson.databind.node.ContainerNode; import com.fasterxml.jackson.databind.node.ContainerNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.asamk.signal.JsonReceiveMessageHandler; import org.asamk.signal.JsonReceiveMessageHandler;
import org.asamk.signal.JsonWriter; import org.asamk.signal.JsonWriter;
@ -13,12 +14,12 @@ import org.asamk.signal.commands.Command;
import org.asamk.signal.commands.Commands; import org.asamk.signal.commands.Commands;
import org.asamk.signal.commands.JsonRpcMultiCommand; import org.asamk.signal.commands.JsonRpcMultiCommand;
import org.asamk.signal.commands.JsonRpcSingleCommand; import org.asamk.signal.commands.JsonRpcSingleCommand;
import org.asamk.signal.commands.SignalCreator;
import org.asamk.signal.commands.exceptions.CommandException; import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.commands.exceptions.IOErrorException; import org.asamk.signal.commands.exceptions.IOErrorException;
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.Manager;
import org.asamk.signal.manager.MultiAccountManager;
import org.asamk.signal.util.Util; import org.asamk.signal.util.Util;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -42,7 +43,7 @@ public class SignalJsonRpcDispatcherHandler {
private final JsonRpcReader jsonRpcReader; private final JsonRpcReader jsonRpcReader;
private final boolean noReceiveOnStart; private final boolean noReceiveOnStart;
private SignalCreator c; private MultiAccountManager c;
private final Map<Manager, Manager.ReceiveMessageHandler> receiveHandlers = new HashMap<>(); private final Map<Manager, Manager.ReceiveMessageHandler> receiveHandlers = new HashMap<>();
private Manager m; private Manager m;
@ -56,7 +57,7 @@ public class SignalJsonRpcDispatcherHandler {
this.jsonRpcReader = new JsonRpcReader(jsonRpcSender, lineSupplier); this.jsonRpcReader = new JsonRpcReader(jsonRpcSender, lineSupplier);
} }
public void handleConnection(final SignalCreator c) { public void handleConnection(final MultiAccountManager c) {
this.c = c; this.c = c;
if (!noReceiveOnStart) { if (!noReceiveOnStart) {
@ -120,19 +121,19 @@ public class SignalJsonRpcDispatcherHandler {
) throws JsonRpcException { ) throws JsonRpcException {
var command = getCommand(method); var command = getCommand(method);
// TODO implement register, verify, link // TODO implement register, verify, link
if (c != null && command instanceof JsonRpcMultiCommand<?> jsonRpcCommand) { if (c != null) {
return runCommand(objectMapper, params, new MultiCommandRunnerImpl<>(c, jsonRpcCommand)); if (command instanceof JsonRpcMultiCommand<?> jsonRpcCommand) {
return runCommand(objectMapper, params, new MultiCommandRunnerImpl<>(c, jsonRpcCommand));
}
} }
if (command instanceof JsonRpcSingleCommand<?> jsonRpcCommand) { if (command instanceof JsonRpcSingleCommand<?> jsonRpcCommand) {
if (m != null) { if (m != null) {
return runCommand(objectMapper, params, new CommandRunnerImpl<>(m, jsonRpcCommand)); return runCommand(objectMapper, params, new CommandRunnerImpl<>(m, jsonRpcCommand));
} }
if (params.has("account")) { final var manager = getManagerFromParams(params);
Manager manager = c.getManager(params.get("account").asText()); if (manager != null) {
if (manager != null) { return runCommand(objectMapper, params, new CommandRunnerImpl<>(manager, jsonRpcCommand));
return runCommand(objectMapper, params, new CommandRunnerImpl<>(manager, jsonRpcCommand));
}
} else { } else {
throw new JsonRpcException(new JsonRpcResponse.Error(JsonRpcResponse.Error.INVALID_PARAMS, throw new JsonRpcException(new JsonRpcResponse.Error(JsonRpcResponse.Error.INVALID_PARAMS,
"Method requires valid account parameter", "Method requires valid account parameter",
@ -145,6 +146,15 @@ public class SignalJsonRpcDispatcherHandler {
null)); null));
} }
private Manager getManagerFromParams(final ContainerNode<?> params) {
if (params.has("account")) {
final var manager = c.getManager(params.get("account").asText());
((ObjectNode) params).remove("account");
return manager;
}
return null;
}
private Command getCommand(final String method) { private Command getCommand(final String method) {
if ("subscribeReceive".equals(method)) { if ("subscribeReceive".equals(method)) {
return new SubscribeReceiveCommand(); return new SubscribeReceiveCommand();
@ -169,7 +179,7 @@ public class SignalJsonRpcDispatcherHandler {
} }
private record MultiCommandRunnerImpl<T>( private record MultiCommandRunnerImpl<T>(
SignalCreator c, JsonRpcMultiCommand<T> command MultiAccountManager c, JsonRpcMultiCommand<T> command
) implements CommandRunner<T> { ) implements CommandRunner<T> {
@Override @Override