]> nmode's Git Repositories - signal-cli/blobdiff - lib/src/main/java/org/asamk/signal/manager/internal/ProvisioningManagerImpl.java
Refactor manager lib package structure
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / internal / ProvisioningManagerImpl.java
diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/ProvisioningManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/internal/ProvisioningManagerImpl.java
new file mode 100644 (file)
index 0000000..d372a27
--- /dev/null
@@ -0,0 +1,244 @@
+/*
+  Copyright (C) 2015-2022 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.internal;
+
+import org.asamk.signal.manager.Manager;
+import org.asamk.signal.manager.ProvisioningManager;
+import org.asamk.signal.manager.Settings;
+import org.asamk.signal.manager.api.UserAlreadyExistsException;
+import org.asamk.signal.manager.config.ServiceConfig;
+import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
+import org.asamk.signal.manager.storage.SignalAccount;
+import org.asamk.signal.manager.storage.accounts.AccountsStore;
+import org.asamk.signal.manager.util.KeyUtils;
+import org.signal.libsignal.protocol.IdentityKeyPair;
+import org.signal.libsignal.protocol.util.KeyHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.whispersystems.signalservice.api.SignalServiceAccountManager;
+import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
+import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
+import org.whispersystems.signalservice.api.push.SignalServiceAddress;
+import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
+import org.whispersystems.signalservice.api.util.DeviceNameUtil;
+import org.whispersystems.signalservice.internal.push.ConfirmCodeMessage;
+import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.channels.OverlappingFileLockException;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
+
+import static org.asamk.signal.manager.config.ServiceConfig.getCapabilities;
+
+public class ProvisioningManagerImpl implements ProvisioningManager {
+
+    private final static Logger logger = LoggerFactory.getLogger(ProvisioningManagerImpl.class);
+
+    private final PathConfig pathConfig;
+    private final ServiceEnvironmentConfig serviceEnvironmentConfig;
+    private final String userAgent;
+    private final Consumer<Manager> newManagerListener;
+    private final AccountsStore accountsStore;
+
+    private final SignalServiceAccountManager accountManager;
+    private final IdentityKeyPair tempIdentityKey;
+    private final int registrationId;
+    private final int pniRegistrationId;
+    private final String password;
+
+    public ProvisioningManagerImpl(
+            PathConfig pathConfig,
+            ServiceEnvironmentConfig serviceEnvironmentConfig,
+            String userAgent,
+            final Consumer<Manager> newManagerListener,
+            final AccountsStore accountsStore
+    ) {
+        this.pathConfig = pathConfig;
+        this.serviceEnvironmentConfig = serviceEnvironmentConfig;
+        this.userAgent = userAgent;
+        this.newManagerListener = newManagerListener;
+        this.accountsStore = accountsStore;
+
+        tempIdentityKey = KeyUtils.generateIdentityKeyPair();
+        registrationId = KeyHelper.generateRegistrationId(false);
+        pniRegistrationId = KeyHelper.generateRegistrationId(false);
+        password = KeyUtils.createPassword();
+        GroupsV2Operations groupsV2Operations;
+        try {
+            groupsV2Operations = new GroupsV2Operations(ClientZkOperations.create(serviceEnvironmentConfig.getSignalServiceConfiguration()),
+                    ServiceConfig.GROUP_MAX_SIZE);
+        } catch (Throwable ignored) {
+            groupsV2Operations = null;
+        }
+        accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
+                new DynamicCredentialsProvider(null, null, null, password, SignalServiceAddress.DEFAULT_DEVICE_ID),
+                userAgent,
+                groupsV2Operations,
+                ServiceConfig.AUTOMATIC_NETWORK_RETRY);
+    }
+
+    @Override
+    public URI getDeviceLinkUri() throws TimeoutException, IOException {
+        var deviceUuid = accountManager.getNewDeviceUuid();
+
+        return new DeviceLinkInfo(deviceUuid, tempIdentityKey.getPublicKey().getPublicKey()).createDeviceLinkUri();
+    }
+
+    @Override
+    public String finishDeviceLink(String deviceName) throws IOException, TimeoutException, UserAlreadyExistsException {
+        var ret = accountManager.getNewDeviceRegistration(tempIdentityKey);
+        var number = ret.getNumber();
+        var aci = ret.getAci();
+        var pni = ret.getPni();
+
+        logger.info("Received link information from {}, linking in progress ...", number);
+
+        var accountPath = accountsStore.getPathByAci(aci);
+        if (accountPath == null) {
+            accountPath = accountsStore.getPathByNumber(number);
+        }
+        if (accountPath != null
+                && SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)
+                && !canRelinkExistingAccount(accountPath)) {
+            throw new UserAlreadyExistsException(number, SignalAccount.getFileName(pathConfig.dataPath(), accountPath));
+        }
+        if (accountPath == null) {
+            accountPath = accountsStore.addAccount(number, aci);
+        } else {
+            accountsStore.updateAccount(accountPath, number, aci);
+        }
+
+        var encryptedDeviceName = deviceName == null
+                ? null
+                : DeviceNameUtil.encryptDeviceName(deviceName, ret.getAciIdentity().getPrivateKey());
+
+        logger.debug("Finishing new device registration");
+        var deviceId = accountManager.finishNewDeviceRegistration(ret.getProvisioningCode(),
+                new ConfirmCodeMessage(false,
+                        true,
+                        registrationId,
+                        pniRegistrationId,
+                        encryptedDeviceName,
+                        getCapabilities(false)));
+
+        // Create new account with the synced identity
+        var profileKey = ret.getProfileKey() == null ? KeyUtils.createProfileKey() : ret.getProfileKey();
+
+        SignalAccount account = null;
+        try {
+            account = SignalAccount.createOrUpdateLinkedAccount(pathConfig.dataPath(),
+                    accountPath,
+                    number,
+                    serviceEnvironmentConfig.getType(),
+                    aci,
+                    pni,
+                    password,
+                    encryptedDeviceName,
+                    deviceId,
+                    ret.getAciIdentity(),
+                    ret.getPniIdentity(),
+                    registrationId,
+                    pniRegistrationId,
+                    profileKey,
+                    Settings.DEFAULT);
+            account.getConfigurationStore().setReadReceipts(ret.isReadReceipts());
+
+            ManagerImpl m = null;
+            try {
+                m = new ManagerImpl(account,
+                        pathConfig,
+                        new AccountFileUpdaterImpl(accountsStore, accountPath),
+                        serviceEnvironmentConfig,
+                        userAgent);
+                account = null;
+
+                logger.debug("Refreshing pre keys");
+                try {
+                    m.refreshPreKeys();
+                } catch (Exception e) {
+                    logger.error("Failed to refresh pre keys.", e);
+                }
+
+                logger.debug("Requesting sync data");
+                try {
+                    m.requestAllSyncData();
+                } catch (Exception e) {
+                    logger.error(
+                            "Failed to request sync messages from linked device, data can be requested again with `sendSyncRequest`.",
+                            e);
+                }
+
+                if (newManagerListener != null) {
+                    newManagerListener.accept(m);
+                    m = null;
+                }
+                return number;
+            } finally {
+                if (m != null) {
+                    m.close();
+                }
+            }
+        } finally {
+            if (account != null) {
+                account.close();
+            }
+        }
+    }
+
+    private boolean canRelinkExistingAccount(final String accountPath) throws IOException {
+        final SignalAccount signalAccount;
+        try {
+            signalAccount = SignalAccount.load(pathConfig.dataPath(), accountPath, false, Settings.DEFAULT);
+        } catch (IOException e) {
+            logger.debug("Account in use or failed to load.", e);
+            return false;
+        } catch (OverlappingFileLockException e) {
+            logger.debug("Account in use.", e);
+            return false;
+        }
+
+        try (signalAccount) {
+            if (signalAccount.isPrimaryDevice()) {
+                logger.debug("Account is a primary device.");
+                return false;
+            }
+            if (signalAccount.isRegistered()
+                    && signalAccount.getServiceEnvironment() != null
+                    && signalAccount.getServiceEnvironment() != serviceEnvironmentConfig.getType()) {
+                logger.debug("Account is registered in another environment: {}.",
+                        signalAccount.getServiceEnvironment());
+                return false;
+            }
+
+            final var m = new ManagerImpl(signalAccount,
+                    pathConfig,
+                    new AccountFileUpdaterImpl(accountsStore, accountPath),
+                    serviceEnvironmentConfig,
+                    userAgent);
+            try (m) {
+                m.checkAccountState();
+            } catch (AuthorizationFailedException ignored) {
+                return true;
+            }
+
+            logger.debug("Account is still successfully linked.");
+            return false;
+        }
+    }
+}