Add command to delete local account data

Fixes #912
This commit is contained in:
AsamK 2022-05-16 12:20:23 +02:00
parent 55dde93811
commit 22add1cbee
13 changed files with 170 additions and 9 deletions

View file

@ -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);
}
}

View file

@ -66,6 +66,7 @@ import org.whispersystems.signalservice.api.SignalSessionLock;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage; import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage; 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.DeviceNameUtil;
import org.whispersystems.signalservice.api.util.InvalidNumberException; import org.whispersystems.signalservice.api.util.InvalidNumberException;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; 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 final static Logger logger = LoggerFactory.getLogger(ManagerImpl.class);
private SignalAccount account; private SignalAccount account;
private AccountFileUpdater accountFileUpdater;
private final SignalDependencies dependencies; private final SignalDependencies dependencies;
private final Context context; private final Context context;
@ -120,6 +122,7 @@ class ManagerImpl implements Manager {
String userAgent String userAgent
) { ) {
this.account = account; this.account = account;
this.accountFileUpdater = accountFileUpdater;
final var sessionLock = new SignalSessionLock() { final var sessionLock = new SignalSessionLock() {
private final ReentrantLock LEGACY_LOCK = new ReentrantLock(); private final ReentrantLock LEGACY_LOCK = new ReentrantLock();
@ -140,10 +143,18 @@ class ManagerImpl implements Manager {
final var attachmentStore = new AttachmentStore(pathConfig.attachmentsPath()); final var attachmentStore = new AttachmentStore(pathConfig.attachmentsPath());
final var stickerPackStore = new StickerPackStore(pathConfig.stickerPacksPath()); final var stickerPackStore = new StickerPackStore(pathConfig.stickerPacksPath());
this.context = new Context(account, (number, aci) -> { this.context = new Context(account, new AccountFileUpdater() {
accountFileUpdater.updateAccountIdentifiers(number, aci); @Override
synchronized (addressChangedListeners) { public void updateAccountIdentifiers(final String number, final ACI aci) {
addressChangedListeners.forEach(Runnable::run); accountFileUpdater.updateAccountIdentifiers(number, aci);
synchronized (addressChangedListeners) {
addressChangedListeners.forEach(Runnable::run);
}
}
@Override
public void removeAccount() {
accountFileUpdater.removeAccount();
} }
}, dependencies, avatarStore, attachmentStore, stickerPackStore); }, dependencies, avatarStore, attachmentStore, stickerPackStore);
this.context.getAccountHelper().setUnregisteredListener(this::close); this.context.getAccountHelper().setUnregisteredListener(this::close);

View file

@ -153,7 +153,7 @@ class ProvisioningManagerImpl implements ProvisioningManager {
final var accountPathFinal = accountPath; final var accountPathFinal = accountPath;
m = new ManagerImpl(account, m = new ManagerImpl(account,
pathConfig, pathConfig,
(newNumber, newAci) -> accountsStore.updateAccount(accountPathFinal, newNumber, newAci), new AccountFileUpdaterImpl(accountsStore, accountPathFinal),
serviceEnvironmentConfig, serviceEnvironmentConfig,
userAgent); userAgent);
account = null; account = null;
@ -220,7 +220,7 @@ class ProvisioningManagerImpl implements ProvisioningManager {
final var m = new ManagerImpl(signalAccount, final var m = new ManagerImpl(signalAccount,
pathConfig, pathConfig,
(newNumber, newAci) -> accountsStore.updateAccount(accountPath, newNumber, newAci), new AccountFileUpdaterImpl(accountsStore, accountPath),
serviceEnvironmentConfig, serviceEnvironmentConfig,
userAgent); userAgent);
try (m) { try (m) {

View file

@ -14,4 +14,8 @@ public interface RegistrationManager extends Closeable {
void verifyAccount( void verifyAccount(
String verificationCode, String pin String verificationCode, String pin
) throws IOException, PinLockedException, IncorrectPinException; ) throws IOException, PinLockedException, IncorrectPinException;
void deleteLocalAccountData() throws IOException;
boolean isRegistered();
} }

View 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() { private boolean attemptReactivateAccount() {
try { try {
final var accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(), final var accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),

View file

@ -95,7 +95,7 @@ public class SignalAccountFiles {
final var manager = new ManagerImpl(account, final var manager = new ManagerImpl(account,
pathConfig, pathConfig,
(newNumber, newAci) -> accountsStore.updateAccount(accountPath, newNumber, newAci), new AccountFileUpdaterImpl(accountsStore, accountPath),
serviceEnvironmentConfig, serviceEnvironmentConfig,
userAgent); userAgent);
@ -155,7 +155,7 @@ public class SignalAccountFiles {
serviceEnvironmentConfig, serviceEnvironmentConfig,
userAgent, userAgent,
newManagerListener, newManagerListener,
(newNumber, newAci) -> accountsStore.updateAccount(newAccountPath, newNumber, newAci)); new AccountFileUpdaterImpl(accountsStore, newAccountPath));
} }
var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, trustNewIdentity); var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, trustNewIdentity);
@ -169,6 +169,6 @@ public class SignalAccountFiles {
serviceEnvironmentConfig, serviceEnvironmentConfig,
userAgent, userAgent,
newManagerListener, newManagerListener,
(newNumber, newAci) -> accountsStore.updateAccount(accountPath, newNumber, newAci)); new AccountFileUpdaterImpl(accountsStore, accountPath));
} }
} }

View file

@ -5,4 +5,6 @@ import org.whispersystems.signalservice.api.push.ACI;
public interface AccountFileUpdater { public interface AccountFileUpdater {
void updateAccountIdentifiers(String number, ACI aci); void updateAccountIdentifiers(String number, ACI aci);
void removeAccount();
} }

View file

@ -70,9 +70,11 @@ import java.nio.channels.Channels;
import java.nio.channels.ClosedChannelException; import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.channels.FileLock; import java.nio.channels.FileLock;
import java.nio.file.Files;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Base64; import java.util.Base64;
import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -1330,6 +1332,17 @@ public class SignalAccount implements Closeable {
getIdentityKeyStore().setIdentityTrustLevel(recipientId, publicKey, TrustLevel.TRUSTED_VERIFIED); 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 @Override
public void close() { public void close() {
synchronized (fileChannel) { synchronized (fileChannel) {

View file

@ -104,6 +104,10 @@ public class AccountsStore {
return accountPath; return accountPath;
} }
public void removeAccount(final String accountPath) {
updateAccounts(accounts -> accounts.stream().filter(a -> !a.path().equals(accountPath)).toList());
}
private String generateNewAccountPath() { private String generateNewAccountPath() {
return new Random().ints(100000, 1000000) return new Random().ints(100000, 1000000)
.mapToObj(String::valueOf) .mapToObj(String::valueOf)

View 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! 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 === updateAccount
Update the account attributes on the signal server. Update the account attributes on the signal server.

View file

@ -13,6 +13,7 @@ public class Commands {
addCommand(new AddDeviceCommand()); addCommand(new AddDeviceCommand());
addCommand(new BlockCommand()); addCommand(new BlockCommand());
addCommand(new DaemonCommand()); addCommand(new DaemonCommand());
addCommand(new DeleteLocalAccountDataCommand());
addCommand(new FinishLinkCommand()); addCommand(new FinishLinkCommand());
addCommand(new GetUserStatusCommand()); addCommand(new GetUserStatusCommand());
addCommand(new JoinGroupCommand()); addCommand(new JoinGroupCommand());

View file

@ -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);
}
}

View 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 @Override
public void close() { public void close() {
} }