From: AsamK Date: Fri, 26 Nov 2021 19:50:54 +0000 (+0100) Subject: Add removeContact command X-Git-Tag: v0.10.0~36 X-Git-Url: https://git.nmode.ca/signal-cli/commitdiff_plain/7e7e4150e19a65c646bb3f4ec33b5ec87224f8cf?ds=sidebyside Add removeContact command Closes #335 --- diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index f2b69267..707b0982 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -2070,7 +2070,8 @@ "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$CallMessage$Opaque", "fields":[ {"name":"bitField0_"}, - {"name":"data_"} + {"name":"data_"}, + {"name":"urgency_"} ]} , { @@ -2520,6 +2521,15 @@ {"name":"type_"} ]} , +{ + "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$ViewOnceOpen", + "fields":[ + {"name":"bitField0_"}, + {"name":"senderE164_"}, + {"name":"senderUuid_"}, + {"name":"timestamp_"} + ]} +, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$Viewed", "fields":[ diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index 61f6e1d6..c9f2bad1 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -174,6 +174,10 @@ public interface Manager extends Closeable { SendMessageResults sendEndSessionMessage(Set recipients) throws IOException; + void deleteRecipient(RecipientIdentifier.Single recipient) throws IOException; + + void deleteContact(RecipientIdentifier.Single recipient) throws IOException; + void setContactName( RecipientIdentifier.Single recipient, String name ) throws NotMasterDeviceException, IOException; 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 84a866d0..269edb3b 100644 --- a/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java @@ -279,13 +279,17 @@ public class ManagerImpl implements Manager { * * @param numbers The set of phone number in question * @return A map of numbers to canonicalized number and uuid. If a number is not registered the uuid is null. - * @throws IOException if its unable to get the contacts to check if they're registered + * @throws IOException if it's unable to get the contacts to check if they're registered */ @Override public Map> areUsersRegistered(Set numbers) throws IOException { Map canonicalizedNumbers = numbers.stream().collect(Collectors.toMap(n -> n, n -> { try { - return PhoneNumberFormatter.formatNumber(n, account.getAccount()); + final var canonicalizedNumber = PhoneNumberFormatter.formatNumber(n, account.getAccount()); + if (!canonicalizedNumber.equals(n)) { + logger.debug("Normalized number {} to {}.", n, canonicalizedNumber); + } + return canonicalizedNumber; } catch (InvalidNumberException e) { return ""; } @@ -774,6 +778,16 @@ public class ManagerImpl implements Manager { } } + @Override + public void deleteRecipient(final RecipientIdentifier.Single recipient) throws IOException { + account.removeRecipient(resolveRecipient(recipient)); + } + + @Override + public void deleteContact(final RecipientIdentifier.Single recipient) throws IOException { + account.getContactStore().deleteContact(resolveRecipient(recipient)); + } + @Override public void setContactName( RecipientIdentifier.Single recipient, String name diff --git a/lib/src/main/java/org/asamk/signal/manager/api/RecipientIdentifier.java b/lib/src/main/java/org/asamk/signal/manager/api/RecipientIdentifier.java index 671cafcd..5d6c7ae9 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/RecipientIdentifier.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/RecipientIdentifier.java @@ -2,6 +2,8 @@ package org.asamk.signal.manager.api; import org.asamk.signal.manager.groups.GroupId; import org.asamk.signal.manager.storage.recipients.RecipientAddress; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; import org.whispersystems.signalservice.api.util.UuidUtil; @@ -18,9 +20,16 @@ public sealed interface RecipientIdentifier { static Single fromString(String identifier, String localNumber) throws InvalidNumberException { try { - return UuidUtil.isUuid(identifier) - ? new Uuid(UUID.fromString(identifier)) - : new Number(PhoneNumberFormatter.formatNumber(identifier, localNumber)); + if (UuidUtil.isUuid(identifier)) { + return new Uuid(UUID.fromString(identifier)); + } + + final var normalizedNumber = PhoneNumberFormatter.formatNumber(identifier, localNumber); + if (!normalizedNumber.equals(identifier)) { + final Logger logger = LoggerFactory.getLogger(RecipientIdentifier.class); + logger.debug("Normalized number {} to {}.", identifier, normalizedNumber); + } + return new Number(normalizedNumber); } catch (org.whispersystems.signalservice.api.util.InvalidNumberException e) { throw new InvalidNumberException(e.getMessage(), e); } 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 d7f48dca..b361f10d 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 @@ -324,6 +324,14 @@ public class SignalAccount implements Closeable { senderKeyStore.mergeRecipients(recipientId, toBeMergedRecipientId); } + public void removeRecipient(final RecipientId recipientId) { + sessionStore.deleteAllSessions(recipientId); + identityKeyStore.deleteIdentity(recipientId); + messageCache.deleteMessages(recipientId); + senderKeyStore.deleteAll(recipientId); + recipientStore.deleteRecipientData(recipientId); + } + public static File getFileName(File dataPath, String account) { return new File(dataPath, account); } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/contacts/ContactsStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/contacts/ContactsStore.java index e0ccea9e..2435c4e9 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/contacts/ContactsStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/contacts/ContactsStore.java @@ -13,4 +13,6 @@ public interface ContactsStore { Contact getContact(RecipientId recipientId); List> getContacts(); + + void deleteContact(RecipientId recipientId); } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/identities/IdentityKeyStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/identities/IdentityKeyStore.java index fdea8b58..9e89861a 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/identities/IdentityKeyStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/identities/IdentityKeyStore.java @@ -172,6 +172,12 @@ public class IdentityKeyStore implements org.whispersystems.libsignal.state.Iden } } + public void deleteIdentity(final RecipientId recipientId) { + synchronized (cachedIdentities) { + deleteIdentityLocked(recipientId); + } + } + /** * @param identifier can be either a serialized uuid or a e164 phone number */ diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/messageCache/MessageCache.java b/lib/src/main/java/org/asamk/signal/manager/storage/messageCache/MessageCache.java index 0ba0a8d5..dde9e1cc 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/messageCache/MessageCache.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/messageCache/MessageCache.java @@ -71,6 +71,25 @@ public class MessageCache { return new CachedMessage(cacheFile); } + public void deleteMessages(final RecipientId recipientId) { + final var recipientMessageCachePath = getMessageCachePath(recipientId); + if (!recipientMessageCachePath.exists()) { + return; + } + + for (var file : Objects.requireNonNull(recipientMessageCachePath.listFiles())) { + if (!file.isFile()) { + continue; + } + + try { + Files.delete(file.toPath()); + } catch (IOException e) { + logger.warn("Failed to delete cache file “{}”, ignoring: {}", file, e.getMessage()); + } + } + } + private File getMessageCachePath(RecipientId recipientId) { if (recipientId == null) { return messageCachePath; diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java index 82db8946..16a9f4cb 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java @@ -220,6 +220,25 @@ public class RecipientStore implements RecipientResolver, ContactsStore, Profile .collect(Collectors.toList()); } + @Override + public void deleteContact(final RecipientId recipientId) { + synchronized (recipients) { + final var recipient = recipients.get(recipientId); + storeRecipientLocked(recipientId, Recipient.newBuilder(recipient).withContact(null).build()); + } + } + + public void deleteRecipientData(final RecipientId recipientId) { + synchronized (recipients) { + final var recipient = recipients.get(recipientId); + storeRecipientLocked(recipientId, + Recipient.newBuilder() + .withRecipientId(recipientId) + .withAddress(new RecipientAddress(recipient.getAddress().getUuid().orElse(null))) + .build()); + } + } + @Override public Profile getProfile(final RecipientId recipientId) { final var recipient = getRecipient(recipientId); diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeyStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeyStore.java index ab02d755..ba2a0322 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeyStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeyStore.java @@ -63,7 +63,7 @@ public class SenderKeyStore implements SignalServiceSenderKeyStore { senderKeyRecordStore.deleteAll(); } - public void rotateSenderKeys(RecipientId recipientId) { + public void deleteAll(RecipientId recipientId) { senderKeySharedStore.deleteAllFor(recipientId); senderKeyRecordStore.deleteAllFor(recipientId); } diff --git a/man/signal-cli.1.adoc b/man/signal-cli.1.adoc index ff49843c..c0465112 100644 --- a/man/signal-cli.1.adoc +++ b/man/signal-cli.1.adoc @@ -442,6 +442,15 @@ Specify the new name for this contact. Set expiration time of messages (seconds). To disable expiration set expiration time to 0. +=== removeContact +Remove the info of a given contact + +NUMBER:: +Specify the contact phone number. + +*--forget*:: +Delete all data associated with this contact, including identity keys and sessions. + === block Block the given contacts or groups (no messages will be received). diff --git a/src/main/java/org/asamk/signal/commands/Commands.java b/src/main/java/org/asamk/signal/commands/Commands.java index a3c4a863..d13d50ac 100644 --- a/src/main/java/org/asamk/signal/commands/Commands.java +++ b/src/main/java/org/asamk/signal/commands/Commands.java @@ -26,6 +26,7 @@ public class Commands { addCommand(new QuitGroupCommand()); addCommand(new ReceiveCommand()); addCommand(new RegisterCommand()); + addCommand(new RemoveContactCommand()); addCommand(new RemoveDeviceCommand()); addCommand(new RemoteDeleteCommand()); addCommand(new RemovePinCommand()); diff --git a/src/main/java/org/asamk/signal/commands/RemoveContactCommand.java b/src/main/java/org/asamk/signal/commands/RemoveContactCommand.java new file mode 100644 index 00000000..c6e9a4c6 --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/RemoveContactCommand.java @@ -0,0 +1,49 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.impl.Arguments; +import net.sourceforge.argparse4j.inf.Namespace; +import net.sourceforge.argparse4j.inf.Subparser; + +import org.asamk.signal.commands.exceptions.CommandException; +import org.asamk.signal.commands.exceptions.IOErrorException; +import org.asamk.signal.manager.Manager; +import org.asamk.signal.output.OutputWriter; +import org.asamk.signal.util.CommandUtil; + +import java.io.IOException; + +public class RemoveContactCommand implements JsonRpcLocalCommand { + + @Override + public String getName() { + return "removeContact"; + } + + @Override + public void attachToSubparser(final Subparser subparser) { + subparser.help("Remove the details of a given contact"); + subparser.addArgument("recipient").help("Contact number"); + subparser.addArgument("--forget") + .action(Arguments.storeTrue()) + .help("Delete all data associated with this contact, including identity keys and sessions."); + } + + @Override + public void handleCommand( + final Namespace ns, final Manager m, final OutputWriter outputWriter + ) throws CommandException { + var recipientString = ns.getString("recipient"); + var recipient = CommandUtil.getSingleRecipientIdentifier(recipientString, m.getSelfNumber()); + + var forget = Boolean.TRUE == ns.getBoolean("forget"); + try { + if (forget) { + m.deleteRecipient(recipient); + } else { + m.deleteContact(recipient); + } + } catch (IOException e) { + throw new IOErrorException("Remove contact error: " + e.getMessage(), e); + } + } +} diff --git a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java index d2b1bec7..adef6f96 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java @@ -381,6 +381,16 @@ public class DbusManagerImpl implements Manager { return new SendMessageResults(0, Map.of()); } + @Override + public void deleteRecipient(final RecipientIdentifier.Single recipient) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void deleteContact(final RecipientIdentifier.Single recipient) throws IOException { + throw new UnsupportedOperationException(); + } + @Override public void setContactName( final RecipientIdentifier.Single recipient, final String name