Add removeContact command

Closes #335
This commit is contained in:
AsamK 2021-11-26 20:50:54 +01:00
parent 5cd5697aea
commit 7e7e4150e1
14 changed files with 167 additions and 7 deletions

View file

@ -2070,7 +2070,8 @@
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$CallMessage$Opaque", "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$CallMessage$Opaque",
"fields":[ "fields":[
{"name":"bitField0_"}, {"name":"bitField0_"},
{"name":"data_"} {"name":"data_"},
{"name":"urgency_"}
]} ]}
, ,
{ {
@ -2520,6 +2521,15 @@
{"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", "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$Viewed",
"fields":[ "fields":[

View 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( void setContactName(
RecipientIdentifier.Single recipient, String name RecipientIdentifier.Single recipient, String name
) throws NotMasterDeviceException, IOException; ) throws NotMasterDeviceException, IOException;

View file

@ -279,13 +279,17 @@ public class ManagerImpl implements Manager {
* *
* @param numbers The set of phone number in question * @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. * @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 @Override
public Map<String, Pair<String, UUID>> areUsersRegistered(Set<String> numbers) throws IOException { public Map<String, Pair<String, UUID>> areUsersRegistered(Set<String> numbers) throws IOException {
Map<String, String> canonicalizedNumbers = numbers.stream().collect(Collectors.toMap(n -> n, n -> { Map<String, String> canonicalizedNumbers = numbers.stream().collect(Collectors.toMap(n -> n, n -> {
try { 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) { } catch (InvalidNumberException e) {
return ""; 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 @Override
public void setContactName( public void setContactName(
RecipientIdentifier.Single recipient, String name RecipientIdentifier.Single recipient, String name

View file

@ -2,6 +2,8 @@ package org.asamk.signal.manager.api;
import org.asamk.signal.manager.groups.GroupId; import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.storage.recipients.RecipientAddress; 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.PhoneNumberFormatter;
import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.api.util.UuidUtil;
@ -18,9 +20,16 @@ public sealed interface RecipientIdentifier {
static Single fromString(String identifier, String localNumber) throws InvalidNumberException { static Single fromString(String identifier, String localNumber) throws InvalidNumberException {
try { try {
return UuidUtil.isUuid(identifier) if (UuidUtil.isUuid(identifier)) {
? new Uuid(UUID.fromString(identifier)) return new Uuid(UUID.fromString(identifier));
: new Number(PhoneNumberFormatter.formatNumber(identifier, localNumber)); }
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) { } catch (org.whispersystems.signalservice.api.util.InvalidNumberException e) {
throw new InvalidNumberException(e.getMessage(), e); throw new InvalidNumberException(e.getMessage(), e);
} }

View 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) { public static File getFileName(File dataPath, String account) {
return new File(dataPath, account); return new File(dataPath, account);
} }

View file

@ -13,4 +13,6 @@ public interface ContactsStore {
Contact getContact(RecipientId recipientId); Contact getContact(RecipientId recipientId);
List<Pair<RecipientId, Contact>> getContacts(); List<Pair<RecipientId, Contact>> getContacts();
void deleteContact(RecipientId recipientId);
} }

View 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
*/ */

View 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) { private File getMessageCachePath(RecipientId recipientId) {
if (recipientId == null) { if (recipientId == null) {
return messageCachePath; return messageCachePath;

View 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 @Override
public Profile getProfile(final RecipientId recipientId) { public Profile getProfile(final RecipientId recipientId) {
final var recipient = getRecipient(recipientId); final var recipient = getRecipient(recipientId);

View 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); senderKeySharedStore.deleteAllFor(recipientId);
senderKeyRecordStore.deleteAllFor(recipientId); senderKeyRecordStore.deleteAllFor(recipientId);
} }

View file

@ -442,6 +442,15 @@ Specify the new name for this contact.
Set expiration time of messages (seconds). Set expiration time of messages (seconds).
To disable expiration set expiration time to 0. 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
Block the given contacts or groups (no messages will be received). Block the given contacts or groups (no messages will be received).

View file

@ -26,6 +26,7 @@ public class Commands {
addCommand(new QuitGroupCommand()); addCommand(new QuitGroupCommand());
addCommand(new ReceiveCommand()); addCommand(new ReceiveCommand());
addCommand(new RegisterCommand()); addCommand(new RegisterCommand());
addCommand(new RemoveContactCommand());
addCommand(new RemoveDeviceCommand()); addCommand(new RemoveDeviceCommand());
addCommand(new RemoteDeleteCommand()); addCommand(new RemoteDeleteCommand());
addCommand(new RemovePinCommand()); addCommand(new RemovePinCommand());

View file

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

View 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 @Override
public void setContactName( public void setContactName(
final RecipientIdentifier.Single recipient, final String name final RecipientIdentifier.Single recipient, final String name