]> nmode's Git Repositories - signal-cli/commitdiff
Add command to delete local account data
authorAsamK <asamk@gmx.de>
Mon, 16 May 2022 10:20:23 +0000 (12:20 +0200)
committerAsamK <asamk@gmx.de>
Mon, 16 May 2022 10:27:43 +0000 (12:27 +0200)
Fixes #912

13 files changed:
lib/src/main/java/org/asamk/signal/manager/AccountFileUpdaterImpl.java [new file with mode: 0644]
lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java
lib/src/main/java/org/asamk/signal/manager/ProvisioningManagerImpl.java
lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java
lib/src/main/java/org/asamk/signal/manager/RegistrationManagerImpl.java
lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java
lib/src/main/java/org/asamk/signal/manager/helper/AccountFileUpdater.java
lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java
lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStore.java
man/signal-cli.1.adoc
src/main/java/org/asamk/signal/commands/Commands.java
src/main/java/org/asamk/signal/commands/DeleteLocalAccountDataCommand.java [new file with mode: 0644]
src/main/java/org/asamk/signal/dbus/DbusRegistrationManagerImpl.java

diff --git a/lib/src/main/java/org/asamk/signal/manager/AccountFileUpdaterImpl.java b/lib/src/main/java/org/asamk/signal/manager/AccountFileUpdaterImpl.java
new file mode 100644 (file)
index 0000000..af0008c
--- /dev/null
@@ -0,0 +1,26 @@
+package org.asamk.signal.manager;
+
+import org.asamk.signal.manager.helper.AccountFileUpdater;
+import org.asamk.signal.manager.storage.accounts.AccountsStore;
+import org.whispersystems.signalservice.api.push.ACI;
+
+class AccountFileUpdaterImpl implements AccountFileUpdater {
+
+    private final AccountsStore accountsStore;
+    private final String accountPath;
+
+    public AccountFileUpdaterImpl(final AccountsStore accountsStore, final String accountPath) {
+        this.accountsStore = accountsStore;
+        this.accountPath = accountPath;
+    }
+
+    @Override
+    public void updateAccountIdentifiers(final String newNumber, final ACI newAci) {
+        accountsStore.updateAccount(accountPath, newNumber, newAci);
+    }
+
+    @Override
+    public void removeAccount() {
+        accountsStore.removeAccount(accountPath);
+    }
+}
index 63a26b2b04371d9b0a64d03729c6f54d37136f2e..fa041c55d4898666b01d7b05cfa5064e772f4c38 100644 (file)
@@ -66,6 +66,7 @@ import org.whispersystems.signalservice.api.SignalSessionLock;
 import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
 import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
 import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
+import org.whispersystems.signalservice.api.push.ACI;
 import org.whispersystems.signalservice.api.util.DeviceNameUtil;
 import org.whispersystems.signalservice.api.util.InvalidNumberException;
 import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
@@ -99,6 +100,7 @@ class ManagerImpl implements Manager {
     private final static Logger logger = LoggerFactory.getLogger(ManagerImpl.class);
 
     private SignalAccount account;
+    private AccountFileUpdater accountFileUpdater;
     private final SignalDependencies dependencies;
     private final Context context;
 
@@ -120,6 +122,7 @@ class ManagerImpl implements Manager {
             String userAgent
     ) {
         this.account = account;
+        this.accountFileUpdater = accountFileUpdater;
 
         final var sessionLock = new SignalSessionLock() {
             private final ReentrantLock LEGACY_LOCK = new ReentrantLock();
@@ -140,10 +143,18 @@ class ManagerImpl implements Manager {
         final var attachmentStore = new AttachmentStore(pathConfig.attachmentsPath());
         final var stickerPackStore = new StickerPackStore(pathConfig.stickerPacksPath());
 
-        this.context = new Context(account, (number, aci) -> {
-            accountFileUpdater.updateAccountIdentifiers(number, aci);
-            synchronized (addressChangedListeners) {
-                addressChangedListeners.forEach(Runnable::run);
+        this.context = new Context(account, new AccountFileUpdater() {
+            @Override
+            public void updateAccountIdentifiers(final String number, final ACI aci) {
+                accountFileUpdater.updateAccountIdentifiers(number, aci);
+                synchronized (addressChangedListeners) {
+                    addressChangedListeners.forEach(Runnable::run);
+                }
+            }
+
+            @Override
+            public void removeAccount() {
+                accountFileUpdater.removeAccount();
             }
         }, dependencies, avatarStore, attachmentStore, stickerPackStore);
         this.context.getAccountHelper().setUnregisteredListener(this::close);
index 7d396bb1e2b34dea9b38ef8b5d10cb50b8afbf05..d7c1233783667f999cb7a85ff5742bd7438fb115 100644 (file)
@@ -153,7 +153,7 @@ class ProvisioningManagerImpl implements ProvisioningManager {
                 final var accountPathFinal = accountPath;
                 m = new ManagerImpl(account,
                         pathConfig,
-                        (newNumber, newAci) -> accountsStore.updateAccount(accountPathFinal, newNumber, newAci),
+                        new AccountFileUpdaterImpl(accountsStore, accountPathFinal),
                         serviceEnvironmentConfig,
                         userAgent);
                 account = null;
@@ -220,7 +220,7 @@ class ProvisioningManagerImpl implements ProvisioningManager {
 
             final var m = new ManagerImpl(signalAccount,
                     pathConfig,
-                    (newNumber, newAci) -> accountsStore.updateAccount(accountPath, newNumber, newAci),
+                    new AccountFileUpdaterImpl(accountsStore, accountPath),
                     serviceEnvironmentConfig,
                     userAgent);
             try (m) {
index c1c318d94b218da60888def98ee3b3887805f5d6..507690f413d2ae2f98c354299e0b3589c3855eee 100644 (file)
@@ -14,4 +14,8 @@ public interface RegistrationManager extends Closeable {
     void verifyAccount(
             String verificationCode, String pin
     ) throws IOException, PinLockedException, IncorrectPinException;
+
+    void deleteLocalAccountData() throws IOException;
+
+    boolean isRegistered();
 }
index ebafe24b2e73a6e2325157b6e208cce2e323a1fe..3529154a252c29cdd56e110f83b4da510b664ac2 100644 (file)
@@ -155,6 +155,18 @@ class RegistrationManagerImpl implements RegistrationManager {
         }
     }
 
+    @Override
+    public void deleteLocalAccountData() throws IOException {
+        account.deleteAccountData();
+        accountFileUpdater.removeAccount();
+        account = null;
+    }
+
+    @Override
+    public boolean isRegistered() {
+        return account.isRegistered();
+    }
+
     private boolean attemptReactivateAccount() {
         try {
             final var accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
index 5f0f551c5dfc800afa3df596ff9d9480fd89cebb..3821a30639feed52e031e92f8b78a85ad719f058 100644 (file)
@@ -95,7 +95,7 @@ public class SignalAccountFiles {
 
         final var manager = new ManagerImpl(account,
                 pathConfig,
-                (newNumber, newAci) -> accountsStore.updateAccount(accountPath, newNumber, newAci),
+                new AccountFileUpdaterImpl(accountsStore, accountPath),
                 serviceEnvironmentConfig,
                 userAgent);
 
@@ -155,7 +155,7 @@ public class SignalAccountFiles {
                     serviceEnvironmentConfig,
                     userAgent,
                     newManagerListener,
-                    (newNumber, newAci) -> accountsStore.updateAccount(newAccountPath, newNumber, newAci));
+                    new AccountFileUpdaterImpl(accountsStore, newAccountPath));
         }
 
         var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, trustNewIdentity);
@@ -169,6 +169,6 @@ public class SignalAccountFiles {
                 serviceEnvironmentConfig,
                 userAgent,
                 newManagerListener,
-                (newNumber, newAci) -> accountsStore.updateAccount(accountPath, newNumber, newAci));
+                new AccountFileUpdaterImpl(accountsStore, accountPath));
     }
 }
index fb6e327c62414d9227e386a670073747beb77f3d..a983e4b5299256261de5b241036456f0bbb5161b 100644 (file)
@@ -5,4 +5,6 @@ import org.whispersystems.signalservice.api.push.ACI;
 public interface AccountFileUpdater {
 
     void updateAccountIdentifiers(String number, ACI aci);
+
+    void removeAccount();
 }
index ec8434cc787ca039013d938c84131d380fd4a1f1..f09714cb9039616cfb6625aa79d22a2717c4e902 100644 (file)
@@ -70,9 +70,11 @@ import java.nio.channels.Channels;
 import java.nio.channels.ClosedChannelException;
 import java.nio.channels.FileChannel;
 import java.nio.channels.FileLock;
+import java.nio.file.Files;
 import java.security.SecureRandom;
 import java.sql.SQLException;
 import java.util.Base64;
+import java.util.Comparator;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
@@ -1330,6 +1332,17 @@ public class SignalAccount implements Closeable {
         getIdentityKeyStore().setIdentityTrustLevel(recipientId, publicKey, TrustLevel.TRUSTED_VERIFIED);
     }
 
+    public void deleteAccountData() throws IOException {
+        close();
+        try (final var files = Files.walk(getUserPath(dataPath, accountPath).toPath())
+                .sorted(Comparator.reverseOrder())) {
+            for (final var file = files.iterator(); file.hasNext(); ) {
+                Files.delete(file.next());
+            }
+        }
+        Files.delete(getFileName(dataPath, accountPath).toPath());
+    }
+
     @Override
     public void close() {
         synchronized (fileChannel) {
index 3c81c7d7294faa311a88d0cb89e9c6b0061677d9..ea2f0a1b5e9d70fd5a9043ae2f8233ed4248e785 100644 (file)
@@ -104,6 +104,10 @@ public class AccountsStore {
         return accountPath;
     }
 
+    public void removeAccount(final String accountPath) {
+        updateAccounts(accounts -> accounts.stream().filter(a -> !a.path().equals(accountPath)).toList());
+    }
+
     private String generateNewAccountPath() {
         return new Random().ints(100000, 1000000)
                 .mapToObj(String::valueOf)
index 0562fcca0b15fc63e4e7fc0c9b4c03a5a7e283a8..7bbdbf323da454ecf442f0e935a7708650346cde 100644 (file)
@@ -121,6 +121,16 @@ You will have to be readded to each group.
 
 CAUTION: Only delete your account if you won't use this number again!
 
+=== deleteLocalAccountData
+
+Delete all local data for this account.
+Data should only be deleted if the account is unregistered.
+
+CAUTION: This cannot be undone.
+
+*--ignore-registered*::
+Delete the account data even though the account is still registered on the Signal servers.
+
 === updateAccount
 
 Update the account attributes on the signal server.
index 29b28da09ad5e9cc3cc766a25615d2b4a1ab2952..15ab172482b94387e725802a35442bbcfa2ee107 100644 (file)
@@ -13,6 +13,7 @@ public class Commands {
         addCommand(new AddDeviceCommand());
         addCommand(new BlockCommand());
         addCommand(new DaemonCommand());
+        addCommand(new DeleteLocalAccountDataCommand());
         addCommand(new FinishLinkCommand());
         addCommand(new GetUserStatusCommand());
         addCommand(new JoinGroupCommand());
diff --git a/src/main/java/org/asamk/signal/commands/DeleteLocalAccountDataCommand.java b/src/main/java/org/asamk/signal/commands/DeleteLocalAccountDataCommand.java
new file mode 100644 (file)
index 0000000..51c8582
--- /dev/null
@@ -0,0 +1,68 @@
+package org.asamk.signal.commands;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+
+import net.sourceforge.argparse4j.impl.Arguments;
+import net.sourceforge.argparse4j.inf.Namespace;
+import net.sourceforge.argparse4j.inf.Subparser;
+
+import org.asamk.signal.OutputType;
+import org.asamk.signal.commands.exceptions.CommandException;
+import org.asamk.signal.commands.exceptions.IOErrorException;
+import org.asamk.signal.commands.exceptions.UserErrorException;
+import org.asamk.signal.manager.RegistrationManager;
+import org.asamk.signal.output.JsonWriter;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+public class DeleteLocalAccountDataCommand implements RegistrationCommand, JsonRpcRegistrationCommand<Map<String, Object>> {
+
+    @Override
+    public String getName() {
+        return "deleteLocalAccountData";
+    }
+
+    @Override
+    public void attachToSubparser(final Subparser subparser) {
+        subparser.help(
+                "Delete all local data for this account. Data should only be deleted if the account is unregistered. CAUTION: This cannot be undone.");
+        subparser.addArgument("--ignore-registered")
+                .help("Delete the account data even though the account is still registered on the Signal servers.")
+                .action(Arguments.storeTrue());
+    }
+
+    @Override
+    public void handleCommand(final Namespace ns, final RegistrationManager m) throws CommandException {
+        try {
+            final var ignoreRegistered = Boolean.TRUE.equals(ns.getBoolean("ignore-registered"));
+            if (m.isRegistered() && !ignoreRegistered) {
+                throw new UserErrorException(
+                        "Not deleting account, it is still registered. Use --ignore-registered to delete it anyway.");
+            }
+
+            m.deleteLocalAccountData();
+        } catch (IOException e) {
+            throw new IOErrorException("Deletion error: " + e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public TypeReference<Map<String, Object>> getRequestType() {
+        return new TypeReference<>() {};
+    }
+
+    @Override
+    public List<OutputType> getSupportedOutputTypes() {
+        return List.of(OutputType.PLAIN_TEXT, OutputType.JSON);
+    }
+
+    @Override
+    public void handleCommand(
+            Map<String, Object> request, RegistrationManager m, JsonWriter jsonWriter
+    ) throws CommandException {
+        Namespace commandNamespace = new JsonRpcNamespace(request == null ? Map.of() : request);
+        handleCommand(commandNamespace, m);
+    }
+}
index aa27fc27a600c2b2efa4daaade04feb086b13f2d..4d43b98c28d867b0e0afeb11a9ddc87930071434 100644 (file)
@@ -47,6 +47,16 @@ public class DbusRegistrationManagerImpl implements RegistrationManager {
         }
     }
 
+    @Override
+    public void deleteLocalAccountData() throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isRegistered() {
+        throw new UnsupportedOperationException();
+    }
+
     @Override
     public void close() {
     }