implement uploadStickerPack for Dbus; fix isRegistered error

Note: isRegistered (and related methods) for Dbus can throw an
InvalidNumberException when the phone number is incorrectly formatted.
Previously this led to uncaught exceptions. They are now handled.
The problem is in SignalServiceAccountManager.java in the
package org.whispersystems.signalservice.api, which ignores the
first character of a proposed phone number and checks that the
rest is a legitimate int64.

updated documentation
This commit is contained in:
John Freed 2021-08-18 10:37:58 +02:00
parent 663f6f6e73
commit c39b5450ff
5 changed files with 111 additions and 26 deletions

View file

@ -322,10 +322,16 @@ public class Manager implements Closeable {
* @param numbers The set of phone number in question
* @return A map of numbers to booleans. True if registered, false otherwise. Should never be null
* @throws IOException if its unable to get the contacts to check if they're registered
* @throws InvalidNumberException if phone number is incorrectly formatted
*/
public Map<String, Boolean> areUsersRegistered(Set<String> numbers) throws IOException {
public Map<String, Boolean> areUsersRegistered(Set<String> numbers) throws IOException, InvalidNumberException {
// Note "contactDetails" has no optionals. It only gives us info on users who are registered
var contactDetails = getRegisteredUsers(numbers);
Map<String, UUID> contactDetails = null;
try {
contactDetails = getRegisteredUsers(numbers);
} catch (InvalidNumberException e) {
throw new InvalidNumberException(e.getMessage());
}
var registeredUsers = contactDetails.keySet();
@ -924,6 +930,7 @@ public class Manager implements Closeable {
.map(this::resolveSignalServiceAddress)
.collect(Collectors.toList());
final var newE164Members = new HashSet<String>();
Map<String, UUID> registeredUsers = null;
for (var member : newMemberAddresses) {
if (!member.getNumber().isPresent()) {
continue;
@ -931,7 +938,11 @@ public class Manager implements Closeable {
newE164Members.add(member.getNumber().get());
}
final var registeredUsers = getRegisteredUsers(newE164Members);
try {
registeredUsers = getRegisteredUsers(newE164Members);
} catch (InvalidNumberException e) {
throw new IOException("Invalid number detected: " + e.getMessage());
}
if (registeredUsers.size() != newE164Members.size()) {
// Some of the new members are not registered on Signal
newE164Members.removeAll(registeredUsers.keySet());
@ -1513,25 +1524,35 @@ public class Manager implements Closeable {
return signalServiceAddresses.stream().map(this::resolveRecipient).collect(Collectors.toSet());
}
private RecipientId refreshRegisteredUser(RecipientId recipientId) throws IOException {
private RecipientId refreshRegisteredUser(RecipientId recipientId) throws IOException, InvalidNumberException {
final var address = resolveSignalServiceAddress(recipientId);
if (!address.getNumber().isPresent()) {
return recipientId;
}
final var number = address.getNumber().get();
final var uuidMap = getRegisteredUsers(Set.of(number));
Map<String, UUID> uuidMap;
try {
uuidMap = getRegisteredUsers(Set.of(number));
} catch (InvalidNumberException e) {
logger.warn("Improperly formatted number: {}", e.getMessage());
uuidMap = Map.of();
}
return resolveRecipientTrusted(new SignalServiceAddress(uuidMap.getOrDefault(number, null), number));
}
private Map<String, UUID> getRegisteredUsers(final Set<String> numbers) throws IOException {
private Map<String, UUID> getRegisteredUsers(final Set<String> numbers) throws IOException, InvalidNumberException {
Map<String, UUID> registeredUsers = null;
try {
return dependencies.getAccountManager()
registeredUsers = dependencies.getAccountManager()
.getRegisteredUsers(ServiceConfig.getIasKeyStore(),
numbers,
serviceEnvironmentConfig.getCdsMrenclave());
} catch (Quote.InvalidQuoteFormatException | UnauthenticatedQuoteException | SignatureException | UnauthenticatedResponseException | InvalidKeyException e) {
throw new IOException(e);
throw new IOException(e.getMessage());
} catch (NumberFormatException e) {
throw new InvalidNumberException(e.getMessage());
}
return registeredUsers;
}
public void sendTypingMessage(
@ -1681,7 +1702,13 @@ public class Manager implements Closeable {
ContentHint.DEFAULT,
message);
} catch (UnregisteredUserException e) {
final var newRecipientId = refreshRegisteredUser(recipientId);
RecipientId newRecipientId = null;
try {
newRecipientId = refreshRegisteredUser(recipientId);
} catch (InvalidNumberException ine) {
logger.warn("Invalid number");
return SendMessageResult.unregisteredFailure(resolveSignalServiceAddress(recipientId));
}
return messageSender.sendDataMessage(resolveSignalServiceAddress(newRecipientId),
unidentifiedAccessHelper.getAccessFor(newRecipientId),
ContentHint.DEFAULT,
@ -1700,7 +1727,13 @@ public class Manager implements Closeable {
try {
return messageSender.sendNullMessage(address, unidentifiedAccessHelper.getAccessFor(recipientId));
} catch (UnregisteredUserException e) {
final var newRecipientId = refreshRegisteredUser(recipientId);
RecipientId newRecipientId = null;
try {
newRecipientId = refreshRegisteredUser(recipientId);
} catch (InvalidNumberException ine) {
logger.warn("Invalid number");
return SendMessageResult.unregisteredFailure(resolveSignalServiceAddress(recipientId));
}
final var newAddress = resolveSignalServiceAddress(newRecipientId);
return messageSender.sendNullMessage(newAddress, unidentifiedAccessHelper.getAccessFor(newRecipientId));
}

View file

@ -113,7 +113,7 @@ isMember(base64GroupId<s>) -> active<b>::
* base64GroupId : String representing the internal group identifier in Base64 format
* active : Boolean representing whether you are a member of the group
Note that this method does not raise an Exception for a non-existing/unknown group but will simply return false
Exceptions: None; returns false for a non-existing/unknown group
sendEndSessionMessage(recipients<as>) -> <>::
* recipients : String array of phone numbers
@ -195,13 +195,13 @@ Depending on the type of the recipient(s) field this deletes a message with one
Exceptions: Failure, InvalidNumber
sendContacts() -> <>
sendContacts() -> <>::
Sends a synchronization message with the local contacts list to all linked devices.
Exceptions: Failure, UntrustedIdentity
sendSyncRequest() -> <>
sendSyncRequest() -> <>::
Sends a synchronization request to the primary device (for group, contacts, ...). Only works if a secondary device is running the daemon.
@ -213,7 +213,7 @@ trust(number<s>, safetyNumber<s>) -> <>::
Exceptions: Failure, InvalidNumber;
sendTyping(typingAction<b>, base64GroupId<s>, recipients<as>) -> <>
sendTyping(typingAction<b>, base64GroupId<s>, recipients<as>) -> <>::
* typingAction : true = start typing, false = stop typing
* base64GroupId : String representing the internal group identifier in Base64 format
* recipients : List of phone numbers
@ -226,14 +226,20 @@ getContactName(number<s>) -> name<s>::
* number : Phone number
* name : Contact's name in local storage (from the primary device for a linked account, or the one set with setContactName); if not set, contact's profile name is used
Exception: InvalidNumber
setContactName(number<s>,name<>) -> <>::
* number : Phone number
* name : Name to be set in contacts (in local storage with signal-cli)
Exception: InvalidNumber
setExpirationTimer(number<s>,expiration<i>) -> <>::
* number : Phone number
* expiration : Expiration time for messages sent to this number (in seconds). Set to 0 to disable.
Exception: InvalidNumber
getGroupIds() -> groupList<aay>::
getGroupIds(dummy<s>) -> groupList<as>::
dummy : any string (ignored by method; forces output to be identical with getBase64GroupIds)
@ -242,18 +248,22 @@ base64GroupList : Array of strings representing the internal group identifiers i
All groups known are returned, regardless of their active or blocked status. To query that use isMember() and isGroupBlocked()
Exceptions: None
getBase64GroupIds() -> groupList<as>::
groupList : Array of strings representing the internal group identifiers in Base64 format
All groups known are returned, regardless of their active or blocked status. To query that use isMember() and isGroupBlocked()
Exceptions: None
getGroupName(groupId<ay>) -> groupName<s>::
getGroupName(base64GroupId<s>) -> groupName<s>::
groupName : The display name of the group
groupId : Byte array representing the internal group identifier
base64GroupId : String representing the internal group identifier in Base64 format
Exceptions: None, if the group name is not found an empty string is returned
Exceptions: None; if the group name is not found an empty string is returned
getGroupMembers(groupId<ay>) -> members<as>::
getGroupMembers(base64GroupId<s>) -> members<as>::
@ -261,24 +271,28 @@ members : String array with the phone numbers of all active members of a g
groupId : Byte array representing the internal group identifier
base64GroupId : String representing the internal group identifier in Base64 format
Exceptions: None, if the group name is not found an empty array is returned
Exceptions: None; if the group name is not found an empty array is returned
listNumbers() -> numbers<as>::
numbers : String array of all known numbers
This is a concatenated list of all defined contacts as well of profiles known (e.g. peer group members or sender of received messages)
Exceptions: None
getContactNumber(name<s>) -> numbers<as>::
* numbers : Array of phone numbers
* name : Contact or profile name ("firstname lastname")
Searches contacts and known profiles for a given name and returns the list of all known numbers. May result in e.g. two entries if a contact and profile name is set.
Exception: Failure
isContactBlocked(number<s>) -> state<b>::
* number : Phone number
* state : true=blocked, false=not blocked
Exceptions: None, for unknown numbers false is returned
Exception: InvalidNumber for an incorrectly formatted phone number. For unknown numbers, false is returned, but no exception is raised.
isGroupBlocked(groupId<ay>) -> state<b>::
isGroupBlocked(base64GroupId<s>) -> state<b>::
@ -286,11 +300,13 @@ isGroupBlocked(base64GroupId<s>) -> state<b>::
* base64GroupId : String representing the internal group identifier in Base64 format
* state : true=blocked, false=not blocked
Exceptions: None, for unknown groups false is returned
Exceptions: None; for unknown groups false is returned
version() -> version<s>::
* version : Version string of signal-cli
Exceptions: None
isRegistered(number<s>) -> result<b>::
isRegistered(numbers<as>) -> results<ab>::
* number : Phone number
@ -298,6 +314,8 @@ isRegistered(numbers<as>) -> results<ab>::
* result : true=number is registered, false=number is not registered
* results : Boolean array of results
Exception: InvalidNumber for an incorrectly formatted phone number. For unknown numbers, false is returned, but no exception is raised.
listIdentity(number<s>) -> results<a(ssss)>::
* number : Phone number
* results : Array of elements, each consisting of four strings: trust_level, date_added, fingerprint, safety_number
@ -309,7 +327,7 @@ listIdentity(number<s>) -> results<a(ssss)>::
getObjectPath() -> objectPath<s>::
* objectPath : The DBus object path associated with this connection
updateAccount() -> <>
updateAccount() -> <>::
Updates the account attributes on the Signal server.
@ -351,17 +369,22 @@ removePin() -> <>::
Removes registration PIN protection.
verify(number<s>, verificationCode<s>) -> <>
verify(number<s>, verificationCode<s>) -> <>::
* number : Phone number
* verificationCode : Code received from Signal after successful registration request
Command fails if PIN was set after previous registration; use verifyWithPin instead.
verifyWithPin(number<s>, verificationCode<s>, pin<s>) -> <>
verifyWithPin(number<s>, verificationCode<s>, pin<s>) -> <>::
* number : Phone number
* verificationCode : Code received from Signal after successful registration request
* pin : PIN you set with setPin command after verifying previous registration
uploadStickerPack(stickerPackPath<s>) -> <>::
* stickerPackPath : Path to the sticker pack
Exception: Failure
== Signals
SyncMessageReceived (timestamp<x>, sender<s>, destination<s>, groupId<ay>, message<s>, attachments<as>)::

View file

@ -133,7 +133,7 @@ public interface Signal extends DBusInterface {
void setGroupBlocked(String base64GroupId, boolean blocked) throws Error.GroupNotFound;
List<String> getGroupMembers(String dummy);
List<String> getGroupMembers(String base64GroupId);
long sendGroupMessage(
String message, List<String> attachments, String base64GroupId
@ -147,9 +147,9 @@ public interface Signal extends DBusInterface {
String base64GroupId, String name, List<String> members, String avatar
) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.GroupNotFound;
boolean isRegistered(String number);
boolean isRegistered(String number) throws Error.Failure, Error.InvalidNumber;
List<Boolean> isRegistered(List<String> numbers);
List<Boolean> isRegistered(List<String> numbers) throws Error.Failure, Error.InvalidNumber;
void updateProfile(
String givenName, String familyName, String about, String aboutEmoji, String avatarPath, boolean removeAvatar
@ -209,6 +209,8 @@ public interface Signal extends DBusInterface {
void joinGroup(final String groupLink) throws Error.Failure;
void uploadStickerPack(String stickerPackPath) throws Error.Failure;
class MessageReceived extends DBusSignal {
private final long timestamp;

View file

@ -11,6 +11,7 @@ import org.asamk.signal.commands.exceptions.IOErrorException;
import org.asamk.signal.manager.Manager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.util.InvalidNumberException;
import java.io.IOException;
import java.util.HashSet;
@ -34,12 +35,14 @@ public class GetUserStatusCommand implements JsonRpcLocalCommand {
@Override
public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
// Get a map of registration statuses
Map<String, Boolean> registered;
Map<String, Boolean> registered = null;
try {
registered = m.areUsersRegistered(new HashSet<>(ns.getList("number")));
} catch (IOException e) {
logger.debug("Failed to check registered users", e);
throw new IOErrorException("Unable to check if users are registered");
} catch (InvalidNumberException e) {
logger.debug("Invalid number format", e);
}
// Output

View file

@ -19,6 +19,7 @@ import org.asamk.signal.manager.AttachmentInvalidException;
import org.asamk.signal.manager.AvatarStore;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.NotMasterDeviceException;
import org.asamk.signal.manager.StickerPackInvalidException;
import org.asamk.signal.manager.api.Device;
import org.asamk.signal.manager.api.TypingAction;
import org.asamk.signal.manager.groups.GroupId;
@ -753,6 +754,9 @@ public class DbusSignalImpl implements Signal {
@Override
public boolean isRegistered(String number) {
if (number.isEmpty()) {
return false;
}
try {
Map<String, Boolean> registered;
List<String> numbers = new ArrayList<String>();
@ -761,14 +765,19 @@ public class DbusSignalImpl implements Signal {
return registered.get(number);
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
} catch (InvalidNumberException e) {
throw new Error.InvalidNumber(e.getMessage());
}
}
@Override
public List<Boolean> isRegistered(List<String> numbers) {
List<Boolean> results = new ArrayList<Boolean> ();
if (numbers.isEmpty()) {
return results;
}
try {
Map<String, Boolean> registered;
List<Boolean> results = new ArrayList<Boolean> ();
registered = m.areUsersRegistered(new HashSet<String>(numbers));
for (String number : numbers) {
results.add(registered.get(number));
@ -776,6 +785,8 @@ public class DbusSignalImpl implements Signal {
return results;
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
} catch (InvalidNumberException e) {
throw new Error.InvalidNumber(e.getMessage());
}
}
@ -1014,4 +1025,17 @@ public class DbusSignalImpl implements Signal {
return group.isMember(m.getSelfRecipientId());
}
}
@Override
public void uploadStickerPack(String stickerPackPath) {
File path = new File(stickerPackPath);
try {
var url = m.uploadStickerPack(path);
} catch (IOException e) {
throw new Error.Failure("Upload error (maybe image size is too large):" + e.getMessage());
} catch (StickerPackInvalidException e) {
throw new Error.Failure("Invalid sticker pack: " + e.getMessage());
}
}
}