]> 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":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$CallMessage$Opaque",
   "fields":[
     {"name":"bitField0_"}, 
-    {"name":"data_"}
+    {"name":"data_"}, 
+    {"name":"urgency_"}
   ]}
 ,
 {
   ]}
 ,
 {
     {"name":"type_"}
   ]}
 ,
     {"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":[
 {
   "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;
 
 
     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;
     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.
      *
      * @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 {
      */
     @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 "";
             }
             } 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
     @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.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;
 
 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 {
 
         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);
             }
             } 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);
     }
 
         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);
     }
     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();
     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
      */
     /**
      * @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);
     }
 
         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;
     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());
     }
 
                 .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);
     @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();
     }
 
         senderKeyRecordStore.deleteAll();
     }
 
-    public void rotateSenderKeys(RecipientId recipientId) {
+    public void deleteAll(RecipientId recipientId) {
         senderKeySharedStore.deleteAllFor(recipientId);
         senderKeyRecordStore.deleteAllFor(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.
 
 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).
 === 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 QuitGroupCommand());
         addCommand(new ReceiveCommand());
         addCommand(new RegisterCommand());
+        addCommand(new RemoveContactCommand());
         addCommand(new RemoveDeviceCommand());
         addCommand(new RemoteDeleteCommand());
         addCommand(new RemovePinCommand());
         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());
     }
 
         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
     @Override
     public void setContactName(
             final RecipientIdentifier.Single recipient, final String name