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

@ -12,13 +12,13 @@ import org.asamk.signal.commands.LocalCommand;
import org.asamk.signal.commands.MultiLocalCommand;
import org.asamk.signal.commands.ProvisioningCommand;
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.IOErrorException;
import org.asamk.signal.commands.exceptions.UnexpectedErrorException;
import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.dbus.DbusManagerImpl;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.MultiAccountManagerImpl;
import org.asamk.signal.manager.NotRegisteredException;
import org.asamk.signal.manager.ProvisioningManager;
import org.asamk.signal.manager.RegistrationManager;
@ -39,8 +39,6 @@ import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.util.ArrayList;
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;
@ -186,7 +184,7 @@ public class App {
throw new UserErrorException("No local users found, you first need to register or link an account");
} else if (accounts.size() > 1) {
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);
@ -237,8 +235,8 @@ public class App {
+ e.getClass().getSimpleName()
+ ")", e);
}
try (var m = manager) {
command.handleCommand(ns, m);
try (manager) {
command.handleCommand(ns, manager);
} catch (IOException e) {
logger.warn("Cleanup failed", e);
}
@ -268,73 +266,19 @@ public class App {
final TrustNewIdentity trustNewIdentity
) throws CommandException {
final var managers = new ArrayList<Manager>();
try {
for (String u : accounts) {
try {
managers.add(loadManager(u, dataPath, serviceEnvironment, trustNewIdentity));
} catch (CommandException e) {
logger.warn("Ignoring {}: {}", u, e.getMessage());
}
for (String a : accounts) {
try {
managers.add(loadManager(a, dataPath, serviceEnvironment, trustNewIdentity));
} catch (CommandException e) {
logger.warn("Ignoring {}: {}", a, e.getMessage());
}
}
command.handleCommand(ns, new SignalCreator() {
private final List<Consumer<Manager>> onManagerAddedHandlers = new ArrayList<>();
@Override
public List<String> getAccountNumbers() {
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();
}
try (var multiAccountManager = new MultiAccountManagerImpl(managers,
dataPath,
serviceEnvironment,
BaseConfig.USER_AGENT)) {
command.handleCommand(ns, multiAccountManager, outputWriter);
}
}

View file

@ -19,6 +19,7 @@ import org.asamk.signal.dbus.DbusSignalControlImpl;
import org.asamk.signal.dbus.DbusSignalImpl;
import org.asamk.signal.jsonrpc.SignalJsonRpcDispatcherHandler;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.MultiAccountManager;
import org.asamk.signal.util.IOUtils;
import org.freedesktop.dbus.connections.impl.DBusConnection;
import org.freedesktop.dbus.exceptions.DBusException;
@ -141,7 +142,7 @@ public class DaemonCommand implements MultiLocalCommand, LocalCommand {
@Override
public void handleCommand(
final Namespace ns, final SignalCreator c, final OutputWriter outputWriter
final Namespace ns, final MultiAccountManager c, final OutputWriter outputWriter
) throws CommandException {
logger.info("Starting daemon in multi-account mode");
final var noReceiveStdOut = Boolean.TRUE.equals(ns.getBoolean("no-receive-stdout"));
@ -220,7 +221,7 @@ public class DaemonCommand implements MultiLocalCommand, LocalCommand {
}
private void runSocketMultiAccount(
final SignalCreator c, final ServerSocketChannel serverChannel, final boolean noReceiveOnStart
final MultiAccountManager c, final ServerSocketChannel serverChannel, final boolean noReceiveOnStart
) {
runSocket(serverChannel, channel -> {
final var handler = getSignalJsonRpcDispatcherHandler(channel, noReceiveOnStart);
@ -276,7 +277,7 @@ public class DaemonCommand implements MultiLocalCommand, LocalCommand {
}
private void runDbusMultiAccount(
final SignalCreator c, final boolean noReceiveOnStart, final boolean isDbusSystem
final MultiAccountManager c, final boolean noReceiveOnStart, final boolean isDbusSystem
) throws UnexpectedErrorException {
runDbus(isDbusSystem, (connection, 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.commands.exceptions.CommandException;
import org.asamk.signal.manager.MultiAccountManager;
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.OutputType;
import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.manager.MultiAccountManager;
import java.util.List;
import java.util.Map;
@ -18,7 +19,7 @@ public interface JsonRpcMultiLocalCommand extends JsonRpcMultiCommand<Map<String
}
default void handleCommand(
Map<String, Object> request, SignalCreator c, JsonWriter jsonWriter
Map<String, Object> request, MultiAccountManager c, JsonWriter jsonWriter
) throws CommandException {
Namespace commandNamespace = new JsonRpcNamespace(request == null ? Map.of() : request);
handleCommand(commandNamespace, c, jsonWriter);

View file

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

View file

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

@ -1,24 +0,0 @@
package org.asamk.signal.commands;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.ProvisioningManager;
import org.asamk.signal.manager.RegistrationManager;
import java.io.IOException;
import java.util.List;
import java.util.function.Consumer;
public interface SignalCreator {
List<String> getAccountNumbers();
void addManager(Manager m);
void addOnManagerAddedHandler(Consumer<Manager> handler);
Manager getManager(String phoneNumber);
ProvisioningManager getNewProvisioningManager();
RegistrationManager getNewRegistrationManager(String username) throws IOException;
}

View file

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

View file

@ -32,8 +32,7 @@ public class VerifyCommand implements RegistrationCommand {
var pin = ns.getString("pin");
try {
final var manager = m.verifyAccount(verificationCode, pin);
manager.close();
m.verifyAccount(verificationCode, pin);
} catch (PinLockedException e) {
throw new UserErrorException(
"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.commands.exceptions.CommandException;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.MultiAccountManager;
import java.util.Map;
@ -23,7 +24,7 @@ public class VersionCommand implements JsonRpcSingleCommand<Void>, JsonRpcMultiC
@Override
public void handleCommand(
final Void request, final SignalCreator c, final JsonWriter jsonWriter
final Void request, final MultiAccountManager c, final JsonWriter jsonWriter
) throws CommandException {
outputVersion(jsonWriter);
}

View file

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

View file

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