]> nmode's Git Repositories - signal-cli/commitdiff
Add removeContact command
authorAsamK <asamk@gmx.de>
Fri, 26 Nov 2021 19:50:54 +0000 (20:50 +0100)
committerAsamK <asamk@gmx.de>
Fri, 26 Nov 2021 19:50:54 +0000 (20:50 +0100)
Closes #335

14 files changed:
graalvm-config-dir/reflect-config.json
lib/src/main/java/org/asamk/signal/manager/Manager.java
lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java
lib/src/main/java/org/asamk/signal/manager/api/RecipientIdentifier.java
lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java
lib/src/main/java/org/asamk/signal/manager/storage/contacts/ContactsStore.java
lib/src/main/java/org/asamk/signal/manager/storage/identities/IdentityKeyStore.java
lib/src/main/java/org/asamk/signal/manager/storage/messageCache/MessageCache.java
lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java
lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeyStore.java
man/signal-cli.1.adoc
src/main/java/org/asamk/signal/commands/Commands.java
src/main/java/org/asamk/signal/commands/RemoveContactCommand.java [new file with mode: 0644]
src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java

index f2b69267df5463c89977c0ccd47de84521c532ed..707b0982093b6ca04828b0f6d546c0ded6199e60 100644 (file)
   "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$CallMessage$Opaque",
   "fields":[
     {"name":"bitField0_"}, 
-    {"name":"data_"}
+    {"name":"data_"}, 
+    {"name":"urgency_"}
   ]}
 ,
 {
     {"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":[
index 61f6e1d6ca316c67d1547ef28dc69dd82f9fa0c0..c9f2bad185a9ac9015ba24f8c8506ea38842a6d7 100644 (file)
@@ -174,6 +174,10 @@ public interface Manager extends Closeable {
 
     SendMessageResults sendEndSessionMessage(Set<RecipientIdentifier.Single> 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;
index 84a866d00df9458fc7bd9dbe65a92d3a5f67ce87..269edb3b440d2b384445e798d5d72190e38ee986 100644 (file)
@@ -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<String, Pair<String, UUID>> areUsersRegistered(Set<String> numbers) throws IOException {
         Map<String, String> 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
index 671cafcd20e8a602b03c31ef6bc87819ac86101c..5d6c7ae9712e7ebe7c30e14eb0cf40ad3966782b 100644 (file)
@@ -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);
             }
index d7f48dcaf141edf150a13a4f56bd3ca99a495b1f..b361f10d286931ef8734892d47009c2e2b750035 100644 (file)
@@ -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);
     }
index e0ccea9efec9acbbae4bbb9c4d9f2ffb31648a6a..2435c4e95151e0edd3e53a4558f546b5967765c6 100644 (file)
@@ -13,4 +13,6 @@ public interface ContactsStore {
     Contact getContact(RecipientId recipientId);
 
     List<Pair<RecipientId, Contact>> getContacts();
+
+    void deleteContact(RecipientId recipientId);
 }
index fdea8b58d210c97fd8040696a0f5f542c65767c1..9e89861ac82de89f8856f0cbca5736806818b82b 100644 (file)
@@ -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
      */
index 0ba0a8d5a338368177ef8b4c3331e7a0765ee51d..dde9e1ccb3624ec97dcc7cb1856e278123b5a908 100644 (file)
@@ -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;
index 82db8946cb5362e4d2496c7550f37fc38ed8b199..16a9f4cbbd6049d92a06b0f66d162c00665d41f8 100644 (file)
@@ -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);
index ab02d755a9e56fdb869f1116eab3ad4643f492c1..ba2a032217823a9b7c8b5867f0c650d8b5a2e230 100644 (file)
@@ -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);
     }
index ff49843cc8f47f17a2abaadf558424e895fabe1e..c04651121e94729f363101d2b173854f70342156 100644 (file)
@@ -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).
index a3c4a863eb927ef075528b8fa2edea619cc82ec5..d13d50acf8f5a1cdf36b8aa135512ac4a1dff8ff 100644 (file)
@@ -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 (file)
index 0000000..c6e9a4c
--- /dev/null
@@ -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);
+        }
+    }
+}
index d2b1bec754189cb2d1ed919caa060f1810320157..adef6f965c27854a91a050ae8afcdafc76d558b5 100644 (file)
@@ -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