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.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,11 +143,19 @@ 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) -> {
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);
this.context.getReceiveHelper().setAuthenticationFailureListener(this::close);

View 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) {

View 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();
}

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

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

View file

@ -5,4 +5,6 @@ import org.whispersystems.signalservice.api.push.ACI;
public interface AccountFileUpdater {
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.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) {

View 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)

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!
=== 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.

View 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());

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