--- /dev/null
+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);
+ }
+}
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;
private final static Logger logger = LoggerFactory.getLogger(ManagerImpl.class);
private SignalAccount account;
+ private AccountFileUpdater accountFileUpdater;
private final SignalDependencies dependencies;
private final Context context;
String userAgent
) {
this.account = account;
+ this.accountFileUpdater = accountFileUpdater;
final var sessionLock = new SignalSessionLock() {
private final ReentrantLock LEGACY_LOCK = new ReentrantLock();
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);
final var accountPathFinal = accountPath;
m = new ManagerImpl(account,
pathConfig,
- (newNumber, newAci) -> accountsStore.updateAccount(accountPathFinal, newNumber, newAci),
+ new AccountFileUpdaterImpl(accountsStore, accountPathFinal),
serviceEnvironmentConfig,
userAgent);
account = null;
final var m = new ManagerImpl(signalAccount,
pathConfig,
- (newNumber, newAci) -> accountsStore.updateAccount(accountPath, newNumber, newAci),
+ new AccountFileUpdaterImpl(accountsStore, accountPath),
serviceEnvironmentConfig,
userAgent);
try (m) {
void verifyAccount(
String verificationCode, String pin
) throws IOException, PinLockedException, IncorrectPinException;
+
+ void deleteLocalAccountData() throws IOException;
+
+ boolean isRegistered();
}
}
}
+ @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(),
final var manager = new ManagerImpl(account,
pathConfig,
- (newNumber, newAci) -> accountsStore.updateAccount(accountPath, newNumber, newAci),
+ new AccountFileUpdaterImpl(accountsStore, accountPath),
serviceEnvironmentConfig,
userAgent);
serviceEnvironmentConfig,
userAgent,
newManagerListener,
- (newNumber, newAci) -> accountsStore.updateAccount(newAccountPath, newNumber, newAci));
+ new AccountFileUpdaterImpl(accountsStore, newAccountPath));
}
var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, trustNewIdentity);
serviceEnvironmentConfig,
userAgent,
newManagerListener,
- (newNumber, newAci) -> accountsStore.updateAccount(accountPath, newNumber, newAci));
+ new AccountFileUpdaterImpl(accountsStore, accountPath));
}
}
public interface AccountFileUpdater {
void updateAccountIdentifiers(String number, ACI aci);
+
+ void removeAccount();
}
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;
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) {
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)
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.
addCommand(new AddDeviceCommand());
addCommand(new BlockCommand());
addCommand(new DaemonCommand());
+ addCommand(new DeleteLocalAccountDataCommand());
addCommand(new FinishLinkCommand());
addCommand(new GetUserStatusCommand());
addCommand(new JoinGroupCommand());
--- /dev/null
+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);
+ }
+}
}
}
+ @Override
+ public void deleteLocalAccountData() throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isRegistered() {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public void close() {
}