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",
"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":[

View 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;

View 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

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

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

View file

@ -13,4 +13,6 @@ public interface ContactsStore {
Contact getContact(RecipientId recipientId);
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
*/

View 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;

View 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);

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

View 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).

View 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());

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