From: AsamK Date: Mon, 16 May 2022 10:20:23 +0000 (+0200) Subject: Add command to delete local account data X-Git-Tag: v0.10.6~16 X-Git-Url: https://git.nmode.ca/signal-cli/commitdiff_plain/22add1cbee357e988ca9c85d9d161fc783fd5fa3 Add command to delete local account data Fixes #912 --- 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 index 00000000..af0008c3 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/AccountFileUpdaterImpl.java @@ -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); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java index 63a26b2b..fa041c55 100644 --- a/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java @@ -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); diff --git a/lib/src/main/java/org/asamk/signal/manager/ProvisioningManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/ProvisioningManagerImpl.java index 7d396bb1..d7c12337 100644 --- a/lib/src/main/java/org/asamk/signal/manager/ProvisioningManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/ProvisioningManagerImpl.java @@ -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) { diff --git a/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java b/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java index c1c318d9..507690f4 100644 --- a/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java +++ b/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java @@ -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(); } diff --git a/lib/src/main/java/org/asamk/signal/manager/RegistrationManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/RegistrationManagerImpl.java index ebafe24b..3529154a 100644 --- a/lib/src/main/java/org/asamk/signal/manager/RegistrationManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/RegistrationManagerImpl.java @@ -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(), diff --git a/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java b/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java index 5f0f551c..3821a306 100644 --- a/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java +++ b/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java @@ -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)); } } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/AccountFileUpdater.java b/lib/src/main/java/org/asamk/signal/manager/helper/AccountFileUpdater.java index fb6e327c..a983e4b5 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/AccountFileUpdater.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/AccountFileUpdater.java @@ -5,4 +5,6 @@ import org.whispersystems.signalservice.api.push.ACI; public interface AccountFileUpdater { void updateAccountIdentifiers(String number, ACI aci); + + void removeAccount(); } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java b/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java index ec8434cc..f09714cb 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java @@ -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) { diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStore.java index 3c81c7d7..ea2f0a1b 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStore.java @@ -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) diff --git a/man/signal-cli.1.adoc b/man/signal-cli.1.adoc index 0562fcca..7bbdbf32 100644 --- a/man/signal-cli.1.adoc +++ b/man/signal-cli.1.adoc @@ -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. diff --git a/src/main/java/org/asamk/signal/commands/Commands.java b/src/main/java/org/asamk/signal/commands/Commands.java index 29b28da0..15ab1724 100644 --- a/src/main/java/org/asamk/signal/commands/Commands.java +++ b/src/main/java/org/asamk/signal/commands/Commands.java @@ -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 index 00000000..51c8582d --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/DeleteLocalAccountDataCommand.java @@ -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> { + + @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> getRequestType() { + return new TypeReference<>() {}; + } + + @Override + public List getSupportedOutputTypes() { + return List.of(OutputType.PLAIN_TEXT, OutputType.JSON); + } + + @Override + public void handleCommand( + Map request, RegistrationManager m, JsonWriter jsonWriter + ) throws CommandException { + Namespace commandNamespace = new JsonRpcNamespace(request == null ? Map.of() : request); + handleCommand(commandNamespace, m); + } +} diff --git a/src/main/java/org/asamk/signal/dbus/DbusRegistrationManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusRegistrationManagerImpl.java index aa27fc27..4d43b98c 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusRegistrationManagerImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusRegistrationManagerImpl.java @@ -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() { }