]> nmode's Git Repositories - signal-cli/commitdiff
Refactor Manager to always have a valid SignalAccount instance
authorAsamK <asamk@gmx.de>
Mon, 11 May 2020 16:07:37 +0000 (18:07 +0200)
committerAsamK <asamk@gmx.de>
Mon, 11 May 2020 16:07:37 +0000 (18:07 +0200)
Extract ProvisioningManager to link new devices

src/main/java/org/asamk/signal/Main.java
src/main/java/org/asamk/signal/commands/LinkCommand.java
src/main/java/org/asamk/signal/commands/ProvisioningCommand.java [new file with mode: 0644]
src/main/java/org/asamk/signal/commands/VerifyCommand.java
src/main/java/org/asamk/signal/manager/Manager.java
src/main/java/org/asamk/signal/manager/PathConfig.java [new file with mode: 0644]
src/main/java/org/asamk/signal/manager/ProvisioningManager.java [new file with mode: 0644]
src/main/java/org/asamk/signal/storage/SignalAccount.java

index b2defd81642ce0aae74201afeb9deb088bc95a0d..0958b9a809ac6d386b4d8aa8a5be4e0793f9b3b9 100644 (file)
@@ -31,7 +31,9 @@ import org.asamk.signal.commands.Commands;
 import org.asamk.signal.commands.DbusCommand;
 import org.asamk.signal.commands.ExtendedDbusCommand;
 import org.asamk.signal.commands.LocalCommand;
+import org.asamk.signal.commands.ProvisioningCommand;
 import org.asamk.signal.manager.Manager;
+import org.asamk.signal.manager.ProvisioningManager;
 import org.asamk.signal.manager.ServiceConfig;
 import org.asamk.signal.util.IOUtils;
 import org.asamk.signal.util.SecurityProvider;
@@ -69,13 +71,13 @@ public class Main {
 
     private static int handleCommands(Namespace ns) {
         final String username = ns.getString("username");
-        Manager m;
+        Manager m = null;
+        ProvisioningManager pm = null;
         Signal ts;
         DBusConnection dBusConn = null;
         try {
             if (ns.getBoolean("dbus") || ns.getBoolean("dbus_system")) {
                 try {
-                    m = null;
                     DBusConnection.DBusBusType busType;
                     if (ns.getBoolean("dbus_system")) {
                         busType = DBusConnection.DBusBusType.SYSTEM;
@@ -102,19 +104,23 @@ public class Main {
                     dataPath = getDefaultDataPath();
                 }
 
-                m = new Manager(username, dataPath, ServiceConfig.createDefaultServiceConfiguration(BaseConfig.USER_AGENT), BaseConfig.USER_AGENT);
-                ts = m;
-                try {
-                    m.init();
-                } catch (AuthorizationFailedException e) {
-                    if (!"register".equals(ns.getString("command"))) {
-                        // Register command should still be possible, if current authorization fails
-                        System.err.println("Authorization failed, was the number registered elsewhere?");
+                if (username == null) {
+                    pm = new ProvisioningManager(dataPath, ServiceConfig.createDefaultServiceConfiguration(BaseConfig.USER_AGENT), BaseConfig.USER_AGENT);
+                    ts = null;
+                } else {
+                    try {
+                        m = Manager.init(username, dataPath, ServiceConfig.createDefaultServiceConfiguration(BaseConfig.USER_AGENT), BaseConfig.USER_AGENT);
+                    } catch (AuthorizationFailedException e) {
+                        if (!"register".equals(ns.getString("command"))) {
+                            // Register command should still be possible, if current authorization fails
+                            System.err.println("Authorization failed, was the number registered elsewhere?");
+                            return 2;
+                        }
+                    } catch (Throwable e) {
+                        System.err.println("Error loading state file: " + e.getMessage());
                         return 2;
                     }
-                } catch (Exception e) {
-                    System.err.println("Error loading state file: " + e.getMessage());
-                    return 2;
+                    ts = m;
                 }
             }
 
@@ -135,6 +141,8 @@ public class Main {
                 } else {
                     if (command instanceof LocalCommand) {
                         return ((LocalCommand) command).handleCommand(ns, m);
+                    } else if (command instanceof ProvisioningCommand) {
+                        return ((ProvisioningCommand) command).handleCommand(ns, pm);
                     } else if (command instanceof DbusCommand) {
                         return ((DbusCommand) command).handleCommand(ns, ts);
                     } else {
index 2a2d4c4b06db6d728718addb6e965a92b8953817..917b674b26e70ab172fcf4818facb0d264513dc3 100644 (file)
@@ -4,7 +4,7 @@ import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
 import org.asamk.signal.UserAlreadyExists;
-import org.asamk.signal.manager.Manager;
+import org.asamk.signal.manager.ProvisioningManager;
 import org.whispersystems.libsignal.InvalidKeyException;
 
 import java.io.IOException;
@@ -12,7 +12,7 @@ import java.util.concurrent.TimeoutException;
 
 import static org.asamk.signal.util.ErrorUtils.handleAssertionError;
 
-public class LinkCommand implements LocalCommand {
+public class LinkCommand implements ProvisioningCommand {
 
     @Override
     public void attachToSubparser(final Subparser subparser) {
@@ -21,15 +21,15 @@ public class LinkCommand implements LocalCommand {
     }
 
     @Override
-    public int handleCommand(final Namespace ns, final Manager m) {
+    public int handleCommand(final Namespace ns, final ProvisioningManager m) {
         String deviceName = ns.getString("name");
         if (deviceName == null) {
             deviceName = "cli";
         }
         try {
             System.out.println(m.getDeviceLinkUri());
-            m.finishDeviceLink(deviceName);
-            System.out.println("Associated with: " + m.getUsername());
+            String username = m.finishDeviceLink(deviceName);
+            System.out.println("Associated with: " + username);
         } catch (TimeoutException e) {
             System.err.println("Link request timed out, please try again.");
             return 3;
diff --git a/src/main/java/org/asamk/signal/commands/ProvisioningCommand.java b/src/main/java/org/asamk/signal/commands/ProvisioningCommand.java
new file mode 100644 (file)
index 0000000..12a612f
--- /dev/null
@@ -0,0 +1,10 @@
+package org.asamk.signal.commands;
+
+import net.sourceforge.argparse4j.inf.Namespace;
+
+import org.asamk.signal.manager.ProvisioningManager;
+
+public interface ProvisioningCommand extends Command {
+
+    int handleCommand(Namespace ns, ProvisioningManager m);
+}
index 9d99c0d209c4e086de48c996696b760f098fa4b3..0f3363252bbd204b6abedfd0edaeae07a44c2c08 100644 (file)
@@ -20,10 +20,6 @@ public class VerifyCommand implements LocalCommand {
 
     @Override
     public int handleCommand(final Namespace ns, final Manager m) {
-        if (!m.userHasKeys()) {
-            System.err.println("User has no keys, first call register.");
-            return 1;
-        }
         if (m.isRegistered()) {
             System.err.println("User registration is already verified");
             return 1;
index ab4ac6cbd92215fa44d2b6908bd3959f1b4252ae..aa5c2dba5c32137a148cef69aab4234e944dab3d 100644 (file)
@@ -24,7 +24,6 @@ import org.asamk.signal.GroupNotFoundException;
 import org.asamk.signal.NotAGroupMemberException;
 import org.asamk.signal.StickerPackInvalidException;
 import org.asamk.signal.TrustLevel;
-import org.asamk.signal.UserAlreadyExists;
 import org.asamk.signal.storage.SignalAccount;
 import org.asamk.signal.storage.contacts.ContactInfo;
 import org.asamk.signal.storage.groups.GroupInfo;
@@ -149,44 +148,40 @@ import java.util.zip.ZipFile;
 
 public class Manager implements Signal {
 
-    private final String settingsPath;
-    private final String dataPath;
-    private final String attachmentsPath;
-    private final String avatarsPath;
     private final SleepTimer timer = new UptimeSleepTimer();
     private final SignalServiceConfiguration serviceConfiguration;
     private final String userAgent;
 
-    private SignalAccount account;
-    private String username;
+    private final SignalAccount account;
+    private final PathConfig pathConfig;
     private SignalServiceAccountManager accountManager;
     private SignalServiceMessagePipe messagePipe = null;
     private SignalServiceMessagePipe unidentifiedMessagePipe = null;
 
-    public Manager(String username, String settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent) {
-        this.username = username;
-        this.settingsPath = settingsPath;
-        this.dataPath = this.settingsPath + "/data";
-        this.attachmentsPath = this.settingsPath + "/attachments";
-        this.avatarsPath = this.settingsPath + "/avatars";
+    public Manager(SignalAccount account, PathConfig pathConfig, SignalServiceConfiguration serviceConfiguration, String userAgent) {
+        this.account = account;
+        this.pathConfig = pathConfig;
         this.serviceConfiguration = serviceConfiguration;
         this.userAgent = userAgent;
+        this.accountManager = createSignalServiceAccountManager();
+
+        this.account.setResolver(this::resolveSignalServiceAddress);
     }
 
     public String getUsername() {
-        return username;
+        return account.getUsername();
     }
 
     public SignalServiceAddress getSelfAddress() {
         return account.getSelfAddress();
     }
 
-    private SignalServiceAccountManager getSignalServiceAccountManager() {
+    private SignalServiceAccountManager createSignalServiceAccountManager() {
         return new SignalServiceAccountManager(serviceConfiguration, account.getUuid(), account.getUsername(), account.getPassword(), account.getDeviceId(), userAgent, timer);
     }
 
-    private IdentityKey getIdentity() {
-        return account.getSignalProtocolStore().getIdentityKeyPair().getPublicKey();
+    private IdentityKeyPair getIdentityKeyPair() {
+        return account.getSignalProtocolStore().getIdentityKeyPair();
     }
 
     public int getDeviceId() {
@@ -194,7 +189,7 @@ public class Manager implements Signal {
     }
 
     private String getMessageCachePath() {
-        return this.dataPath + "/" + username + ".d/msg-cache";
+        return pathConfig.getDataPath() + "/" + account.getUsername() + ".d/msg-cache";
     }
 
     private String getMessageCachePath(String sender) {
@@ -211,30 +206,28 @@ public class Manager implements Signal {
         return new File(cachePath + "/" + now + "_" + timestamp);
     }
 
-    public boolean userHasKeys() {
-        return account != null && account.getSignalProtocolStore() != null;
-    }
+    public static Manager init(String username, String settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent) throws IOException {
+        PathConfig pathConfig = PathConfig.createDefault(settingsPath);
 
-    public void init() throws IOException {
-        if (!SignalAccount.userExists(dataPath, username)) {
-            return;
-        }
-        account = SignalAccount.load(dataPath, username);
-        account.setResolver(this::resolveSignalServiceAddress);
+        if (!SignalAccount.userExists(pathConfig.getDataPath(), username)) {
+            IdentityKeyPair identityKey = KeyHelper.generateIdentityKeyPair();
+            int registrationId = KeyHelper.generateRegistrationId(false);
 
-        migrateLegacyConfigs();
+            ProfileKey profileKey = KeyUtils.createProfileKey();
+            SignalAccount account = SignalAccount.create(pathConfig.getDataPath(), username, identityKey, registrationId, profileKey);
+            account.save();
 
-        accountManager = getSignalServiceAccountManager();
-        if (account.isRegistered()) {
-            if (accountManager.getPreKeysCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) {
-                refreshPreKeys();
-                account.save();
-            }
-            if (account.getUuid() == null) {
-                account.setUuid(accountManager.getOwnUuid());
-                account.save();
-            }
+            return new Manager(account, pathConfig, serviceConfiguration, userAgent);
         }
+
+        SignalAccount account = SignalAccount.load(pathConfig.getDataPath(), username);
+
+        Manager m = new Manager(account, pathConfig, serviceConfiguration, userAgent);
+
+        m.migrateLegacyConfigs();
+        m.checkAccountState();
+
+        return m;
     }
 
     private void migrateLegacyConfigs() {
@@ -246,7 +239,7 @@ public class Manager implements Signal {
                 File attachmentFile = getAttachmentFile(new SignalServiceAttachmentRemoteId(g.getAvatarId()));
                 if (!avatarFile.exists() && attachmentFile.exists()) {
                     try {
-                        IOUtils.createPrivateDirectories(avatarsPath);
+                        IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath());
                         Files.copy(attachmentFile.toPath(), avatarFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
                     } catch (Exception e) {
                         // Ignore
@@ -263,31 +256,29 @@ public class Manager implements Signal {
         }
     }
 
-    private void createNewIdentity() throws IOException {
-        IdentityKeyPair identityKey = KeyHelper.generateIdentityKeyPair();
-        int registrationId = KeyHelper.generateRegistrationId(false);
-        if (username == null) {
-            account = SignalAccount.createTemporaryAccount(identityKey, registrationId);
-            account.setResolver(this::resolveSignalServiceAddress);
-        } else {
-            ProfileKey profileKey = KeyUtils.createProfileKey();
-            account = SignalAccount.create(dataPath, username, identityKey, registrationId, profileKey);
-            account.setResolver(this::resolveSignalServiceAddress);
-            account.save();
+    private void checkAccountState() throws IOException {
+        if (account.isRegistered()) {
+            if (accountManager.getPreKeysCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) {
+                refreshPreKeys();
+                account.save();
+            }
+            if (account.getUuid() == null) {
+                account.setUuid(accountManager.getOwnUuid());
+                account.save();
+            }
         }
     }
 
     public boolean isRegistered() {
-        return account != null && account.isRegistered();
+        return account.isRegistered();
     }
 
     public void register(boolean voiceVerification) throws IOException {
-        if (account == null) {
-            createNewIdentity();
-        }
         account.setPassword(KeyUtils.createPassword());
+
+        // Resetting UUID, because registering doesn't work otherwise
         account.setUuid(null);
-        accountManager = getSignalServiceAccountManager();
+        accountManager = createSignalServiceAccountManager();
 
         if (voiceVerification) {
             accountManager.requestVoiceVerificationCode(Locale.getDefault(), Optional.absent(), Optional.absent());
@@ -327,52 +318,6 @@ public class Manager implements Signal {
         account.save();
     }
 
-    public String getDeviceLinkUri() throws TimeoutException, IOException {
-        if (account == null) {
-            createNewIdentity();
-        }
-        account.setPassword(KeyUtils.createPassword());
-        accountManager = getSignalServiceAccountManager();
-        String uuid = accountManager.getNewDeviceUuid();
-
-        return Utils.createDeviceLinkUri(new Utils.DeviceLinkInfo(uuid, getIdentity().getPublicKey()));
-    }
-
-    public void finishDeviceLink(String deviceName) throws IOException, InvalidKeyException, TimeoutException, UserAlreadyExists {
-        account.setSignalingKey(KeyUtils.createSignalingKey());
-        SignalServiceAccountManager.NewDeviceRegistrationReturn ret = accountManager.finishNewDeviceRegistration(account.getSignalProtocolStore().getIdentityKeyPair(), account.getSignalingKey(), false, true, account.getSignalProtocolStore().getLocalRegistrationId(), deviceName);
-
-        username = ret.getNumber();
-        // TODO do this check before actually registering
-        if (SignalAccount.userExists(dataPath, username)) {
-            throw new UserAlreadyExists(username, SignalAccount.getFileName(dataPath, username));
-        }
-
-        // Create new account with the synced identity
-        byte[] profileKeyBytes = ret.getProfileKey();
-        ProfileKey profileKey;
-        if (profileKeyBytes == null) {
-            profileKey = KeyUtils.createProfileKey();
-        } else {
-            try {
-                profileKey = new ProfileKey(profileKeyBytes);
-            } catch (InvalidInputException e) {
-                throw new IOException("Received invalid profileKey", e);
-            }
-        }
-        account = SignalAccount.createLinkedAccount(dataPath, username, ret.getUuid(), account.getPassword(), ret.getDeviceId(), ret.getIdentity(), account.getSignalProtocolStore().getLocalRegistrationId(), account.getSignalingKey(), profileKey);
-        account.setResolver(this::resolveSignalServiceAddress);
-
-        refreshPreKeys();
-
-        requestSyncGroups();
-        requestSyncContacts();
-        requestSyncBlocked();
-        requestSyncConfiguration();
-
-        account.save();
-    }
-
     public List<DeviceInfo> getLinkedDevices() throws IOException {
         List<DeviceInfo> devices = accountManager.getDevices();
         account.setMultiDevice(devices.size() > 1);
@@ -394,7 +339,7 @@ public class Manager implements Signal {
     }
 
     private void addDevice(String deviceIdentifier, ECPublicKey deviceKey) throws IOException, InvalidKeyException {
-        IdentityKeyPair identityKeyPair = account.getSignalProtocolStore().getIdentityKeyPair();
+        IdentityKeyPair identityKeyPair = getIdentityKeyPair();
         String verificationCode = accountManager.getNewDeviceVerificationCode();
 
         accountManager.addDevice(deviceIdentifier, deviceKey, identityKeyPair, Optional.of(account.getProfileKey().serialize()), verificationCode);
@@ -447,7 +392,7 @@ public class Manager implements Signal {
         account.setRegistered(true);
         account.setUuid(uuid);
         account.setRegistrationLockPin(pin);
-        account.getSignalProtocolStore().saveIdentity(account.getSelfAddress(), account.getSignalProtocolStore().getIdentityKeyPair().getPublicKey(), TrustLevel.TRUSTED_VERIFIED);
+        account.getSignalProtocolStore().saveIdentity(account.getSelfAddress(), getIdentityKeyPair().getPublicKey(), TrustLevel.TRUSTED_VERIFIED);
 
         refreshPreKeys();
         account.save();
@@ -464,12 +409,12 @@ public class Manager implements Signal {
         account.save();
     }
 
-    private void refreshPreKeys() throws IOException {
+    void refreshPreKeys() throws IOException {
         List<PreKeyRecord> oneTimePreKeys = generatePreKeys();
-        final IdentityKeyPair identityKeyPair = account.getSignalProtocolStore().getIdentityKeyPair();
+        final IdentityKeyPair identityKeyPair = getIdentityKeyPair();
         SignedPreKeyRecord signedPreKeyRecord = generateSignedPreKey(identityKeyPair);
 
-        accountManager.setPreKeys(getIdentity(), signedPreKeyRecord, oneTimePreKeys);
+        accountManager.setPreKeys(identityKeyPair.getPublicKey(), signedPreKeyRecord, oneTimePreKeys);
     }
 
     private SignalServiceMessageReceiver getMessageReceiver() {
@@ -629,7 +574,7 @@ public class Manager implements Signal {
         }
 
         if (avatarFile != null) {
-            IOUtils.createPrivateDirectories(avatarsPath);
+            IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath());
             File aFile = getGroupAvatarFile(g.groupId);
             Files.copy(Paths.get(avatarFile), aFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
         }
@@ -984,7 +929,7 @@ public class Manager implements Signal {
         }
     }
 
-    private void requestSyncGroups() throws IOException {
+    void requestSyncGroups() throws IOException {
         SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder().setType(SignalServiceProtos.SyncMessage.Request.Type.GROUPS).build();
         SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
         try {
@@ -994,7 +939,7 @@ public class Manager implements Signal {
         }
     }
 
-    private void requestSyncContacts() throws IOException {
+    void requestSyncContacts() throws IOException {
         SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder().setType(SignalServiceProtos.SyncMessage.Request.Type.CONTACTS).build();
         SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
         try {
@@ -1004,7 +949,7 @@ public class Manager implements Signal {
         }
     }
 
-    private void requestSyncBlocked() throws IOException {
+    void requestSyncBlocked() throws IOException {
         SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder().setType(SignalServiceProtos.SyncMessage.Request.Type.BLOCKED).build();
         SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
         try {
@@ -1014,7 +959,7 @@ public class Manager implements Signal {
         }
     }
 
-    private void requestSyncConfiguration() throws IOException {
+    void requestSyncConfiguration() throws IOException {
         SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder().setType(SignalServiceProtos.SyncMessage.Request.Type.CONFIGURATION).build();
         SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
         try {
@@ -1750,11 +1695,11 @@ public class Manager implements Signal {
     }
 
     private File getContactAvatarFile(String number) {
-        return new File(avatarsPath, "contact-" + number);
+        return new File(pathConfig.getAvatarsPath(), "contact-" + number);
     }
 
     private File retrieveContactAvatarAttachment(SignalServiceAttachment attachment, String number) throws IOException, InvalidMessageException, MissingConfigurationException {
-        IOUtils.createPrivateDirectories(avatarsPath);
+        IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath());
         if (attachment.isPointer()) {
             SignalServiceAttachmentPointer pointer = attachment.asPointer();
             return retrieveAttachment(pointer, getContactAvatarFile(number), false);
@@ -1765,11 +1710,11 @@ public class Manager implements Signal {
     }
 
     private File getGroupAvatarFile(byte[] groupId) {
-        return new File(avatarsPath, "group-" + Base64.encodeBytes(groupId).replace("/", "_"));
+        return new File(pathConfig.getAvatarsPath(), "group-" + Base64.encodeBytes(groupId).replace("/", "_"));
     }
 
     private File retrieveGroupAvatarAttachment(SignalServiceAttachment attachment, byte[] groupId) throws IOException, InvalidMessageException, MissingConfigurationException {
-        IOUtils.createPrivateDirectories(avatarsPath);
+        IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath());
         if (attachment.isPointer()) {
             SignalServiceAttachmentPointer pointer = attachment.asPointer();
             return retrieveAttachment(pointer, getGroupAvatarFile(groupId), false);
@@ -1780,11 +1725,11 @@ public class Manager implements Signal {
     }
 
     public File getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId) {
-        return new File(attachmentsPath, attachmentId.toString());
+        return new File(pathConfig.getAttachmentsPath(), attachmentId.toString());
     }
 
     private File retrieveAttachment(SignalServiceAttachmentPointer pointer) throws IOException, InvalidMessageException, MissingConfigurationException {
-        IOUtils.createPrivateDirectories(attachmentsPath);
+        IOUtils.createPrivateDirectories(pathConfig.getAttachmentsPath());
         return retrieveAttachment(pointer, getAttachmentFile(pointer.getRemoteId()), true);
     }
 
@@ -2054,7 +1999,11 @@ public class Manager implements Signal {
     }
 
     public String computeSafetyNumber(SignalServiceAddress theirAddress, IdentityKey theirIdentityKey) {
-        return Utils.computeSafetyNumber(account.getSelfAddress(), getIdentity(), theirAddress, theirIdentityKey);
+        return Utils.computeSafetyNumber(account.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress, theirIdentityKey);
+    }
+
+    void saveAccount() {
+        account.save();
     }
 
     public SignalServiceAddress canonicalizeAndResolveSignalServiceAddress(String identifier) throws InvalidNumberException {
diff --git a/src/main/java/org/asamk/signal/manager/PathConfig.java b/src/main/java/org/asamk/signal/manager/PathConfig.java
new file mode 100644 (file)
index 0000000..2c2d938
--- /dev/null
@@ -0,0 +1,34 @@
+package org.asamk.signal.manager;
+
+public class PathConfig {
+
+    private final String dataPath;
+    private final String attachmentsPath;
+    private final String avatarsPath;
+
+    public static PathConfig createDefault(final String settingsPath) {
+        return new PathConfig(
+                settingsPath + "/data",
+                settingsPath + "/attachments",
+                settingsPath + "/avatars"
+        );
+    }
+
+    private PathConfig(final String dataPath, final String attachmentsPath, final String avatarsPath) {
+        this.dataPath = dataPath;
+        this.attachmentsPath = attachmentsPath;
+        this.avatarsPath = avatarsPath;
+    }
+
+    public String getDataPath() {
+        return dataPath;
+    }
+
+    public String getAttachmentsPath() {
+        return attachmentsPath;
+    }
+
+    public String getAvatarsPath() {
+        return avatarsPath;
+    }
+}
diff --git a/src/main/java/org/asamk/signal/manager/ProvisioningManager.java b/src/main/java/org/asamk/signal/manager/ProvisioningManager.java
new file mode 100644 (file)
index 0000000..61d3315
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+  Copyright (C) 2015-2020 AsamK and contributors
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package org.asamk.signal.manager;
+
+import org.asamk.signal.UserAlreadyExists;
+import org.asamk.signal.storage.SignalAccount;
+import org.signal.zkgroup.InvalidInputException;
+import org.signal.zkgroup.profiles.ProfileKey;
+import org.whispersystems.libsignal.IdentityKeyPair;
+import org.whispersystems.libsignal.InvalidKeyException;
+import org.whispersystems.libsignal.util.KeyHelper;
+import org.whispersystems.signalservice.api.SignalServiceAccountManager;
+import org.whispersystems.signalservice.api.push.SignalServiceAddress;
+import org.whispersystems.signalservice.api.util.SleepTimer;
+import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
+import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+public class ProvisioningManager {
+
+    private final PathConfig pathConfig;
+    private final SignalServiceConfiguration serviceConfiguration;
+    private final String userAgent;
+
+    private final SignalServiceAccountManager accountManager;
+    private final IdentityKeyPair identityKey;
+    private final int registrationId;
+    private final String password;
+
+    public ProvisioningManager(String settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent) {
+        this.pathConfig = PathConfig.createDefault(settingsPath);
+        this.serviceConfiguration = serviceConfiguration;
+        this.userAgent = userAgent;
+
+        identityKey = KeyHelper.generateIdentityKeyPair();
+        registrationId = KeyHelper.generateRegistrationId(false);
+        password = KeyUtils.createPassword();
+        final SleepTimer timer = new UptimeSleepTimer();
+        accountManager = new SignalServiceAccountManager(serviceConfiguration, null, null, password, SignalServiceAddress.DEFAULT_DEVICE_ID, userAgent, timer);
+    }
+
+    public String getDeviceLinkUri() throws TimeoutException, IOException {
+        String deviceUuid = accountManager.getNewDeviceUuid();
+
+        return Utils.createDeviceLinkUri(new Utils.DeviceLinkInfo(deviceUuid, identityKey.getPublicKey().getPublicKey()));
+    }
+
+    public String finishDeviceLink(String deviceName) throws IOException, InvalidKeyException, TimeoutException, UserAlreadyExists {
+        String signalingKey = KeyUtils.createSignalingKey();
+        SignalServiceAccountManager.NewDeviceRegistrationReturn ret = accountManager.finishNewDeviceRegistration(identityKey, signalingKey, false, true, registrationId, deviceName);
+
+        String username = ret.getNumber();
+        // TODO do this check before actually registering
+        if (SignalAccount.userExists(pathConfig.getDataPath(), username)) {
+            throw new UserAlreadyExists(username, SignalAccount.getFileName(pathConfig.getDataPath(), username));
+        }
+
+        // Create new account with the synced identity
+        byte[] profileKeyBytes = ret.getProfileKey();
+        ProfileKey profileKey;
+        if (profileKeyBytes == null) {
+            profileKey = KeyUtils.createProfileKey();
+        } else {
+            try {
+                profileKey = new ProfileKey(profileKeyBytes);
+            } catch (InvalidInputException e) {
+                throw new IOException("Received invalid profileKey", e);
+            }
+        }
+        SignalAccount account = SignalAccount.createLinkedAccount(pathConfig.getDataPath(), username, ret.getUuid(), password, ret.getDeviceId(), ret.getIdentity(), registrationId, signalingKey, profileKey);
+        account.save();
+
+        Manager m = new Manager(account, pathConfig, serviceConfiguration, userAgent);
+
+        m.refreshPreKeys();
+
+        m.requestSyncGroups();
+        m.requestSyncContacts();
+        m.requestSyncBlocked();
+        m.requestSyncConfiguration();
+
+        m.saveAccount();
+
+        return username;
+    }
+}
index f03bac3a9d0bf85edc4a347a67601fe3184c1b80..16ad21220a9a3ee0f35bba5283d9178ae14e9026 100644 (file)
@@ -121,15 +121,6 @@ public class SignalAccount {
         return account;
     }
 
-    public static SignalAccount createTemporaryAccount(IdentityKeyPair identityKey, int registrationId) {
-        SignalAccount account = new SignalAccount();
-
-        account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId);
-        account.registered = false;
-
-        return account;
-    }
-
     public static String getFileName(String dataPath, String username) {
         return dataPath + "/" + username;
     }