* This is used for checking a set of phone numbers for registration on Signal
*
* @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
+ * @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
*/
- public Map<String, Boolean> 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 -> {
+ try {
+ return canonicalizePhoneNumber(n);
+ } catch (InvalidNumberException e) {
+ return "";
+ }
+ }));
+
// Note "contactDetails" has no optionals. It only gives us info on users who are registered
- var contactDetails = getRegisteredUsers(numbers);
+ var contactDetails = getRegisteredUsers(canonicalizedNumbers.values()
+ .stream()
+ .filter(s -> !s.isEmpty())
+ .collect(Collectors.toSet()));
- var registeredUsers = contactDetails.keySet();
+ // Store numbers as recipients so we have the number/uuid association
+ contactDetails.forEach((number, uuid) -> resolveRecipientTrusted(new SignalServiceAddress(uuid, number)));
- return numbers.stream().collect(Collectors.toMap(x -> x, registeredUsers::contains));
+ return numbers.stream().collect(Collectors.toMap(n -> n, n -> {
+ final var number = canonicalizedNumbers.get(n);
+ final var uuid = contactDetails.get(number);
+ return new Pair<>(number.isEmpty() ? null : number, uuid);
+ }));
}
public void updateAccountAttributes() throws IOException {
GroupPermission addMemberPermission,
GroupPermission editDetailsPermission,
File avatarFile,
- Integer expirationTimer
+ Integer expirationTimer,
+ Boolean isAnnouncementGroup
) throws IOException, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException, NotAGroupMemberException {
return updateGroup(groupId,
name,
addMemberPermission,
editDetailsPermission,
avatarFile,
- expirationTimer);
+ expirationTimer,
+ isAnnouncementGroup);
}
private Pair<Long, List<SendMessageResult>> updateGroup(
final GroupPermission addMemberPermission,
final GroupPermission editDetailsPermission,
final File avatarFile,
- final Integer expirationTimer
+ final Integer expirationTimer,
+ final Boolean isAnnouncementGroup
) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException {
var group = getGroupForUpdating(groupId);
addMemberPermission,
editDetailsPermission,
avatarFile,
- expirationTimer);
+ expirationTimer,
+ isAnnouncementGroup);
} catch (ConflictException e) {
// Detected conflicting update, refreshing group and trying again
group = getGroup(groupId, true);
addMemberPermission,
editDetailsPermission,
avatarFile,
- expirationTimer);
+ expirationTimer,
+ isAnnouncementGroup);
}
}
final GroupPermission addMemberPermission,
final GroupPermission editDetailsPermission,
final File avatarFile,
- Integer expirationTimer
+ final Integer expirationTimer,
+ final Boolean isAnnouncementGroup
) throws IOException {
Pair<Long, List<SendMessageResult>> result = null;
if (group.isPendingMember(account.getSelfRecipientId())) {
result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
}
+ if (isAnnouncementGroup != null) {
+ var groupGroupChangePair = groupV2Helper.setIsAnnouncementGroup(group, isAnnouncementGroup);
+ result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
+ }
+
if (name != null || description != null || avatarFile != null) {
var groupGroupChangePair = groupV2Helper.updateGroup(group, name, description, avatarFile);
if (avatarFile != null) {
// address/uuid in envelope is sent by server
resolveRecipientTrusted(envelope.getSourceAddress());
}
- final var notAGroupMember = isNotAGroupMember(envelope, content);
if (!envelope.isReceipt()) {
try {
content = decryptMessage(envelope);
queuedActions.addAll(actions);
}
}
+ final var notAllowedToSendToGroup = isNotAllowedToSendToGroup(envelope, content);
if (isMessageBlocked(envelope, content)) {
logger.info("Ignoring a message from blocked user/group: {}", envelope.getTimestamp());
- } else if (notAGroupMember) {
- logger.info("Ignoring a message from a non group member: {}", envelope.getTimestamp());
+ } else if (notAllowedToSendToGroup) {
+ logger.info("Ignoring a group message from an unauthorized sender (no member or admin): {} {}",
+ (envelope.hasSource() ? envelope.getSourceAddress() : content.getSender()).getIdentifier(),
+ envelope.getTimestamp());
} else {
handler.handleMessage(envelope, content, exception);
}
return sourceContact != null && sourceContact.isBlocked();
}
- private boolean isNotAGroupMember(
+ private boolean isNotAllowedToSendToGroup(
SignalServiceEnvelope envelope, SignalServiceContent content
) {
SignalServiceAddress source;
return false;
}
- if (content != null && content.getDataMessage().isPresent()) {
- var message = content.getDataMessage().get();
- if (message.getGroupContext().isPresent()) {
- if (message.getGroupContext().get().getGroupV1().isPresent()) {
- var groupInfo = message.getGroupContext().get().getGroupV1().get();
- if (groupInfo.getType() == SignalServiceGroup.Type.QUIT) {
- return false;
- }
- }
- var groupId = GroupUtils.getGroupId(message.getGroupContext().get());
- var group = getGroup(groupId);
- if (group != null && !group.isMember(resolveRecipient(source))) {
- return true;
- }
+ if (content == null || !content.getDataMessage().isPresent()) {
+ return false;
+ }
+
+ var message = content.getDataMessage().get();
+ if (!message.getGroupContext().isPresent()) {
+ return false;
+ }
+
+ if (message.getGroupContext().get().getGroupV1().isPresent()) {
+ var groupInfo = message.getGroupContext().get().getGroupV1().get();
+ if (groupInfo.getType() == SignalServiceGroup.Type.QUIT) {
+ return false;
}
}
- return false;
+
+ var groupId = GroupUtils.getGroupId(message.getGroupContext().get());
+ var group = getGroup(groupId);
+ if (group == null) {
+ return false;
+ }
+
+ final var recipientId = resolveRecipient(source);
+ return !group.isMember(recipientId) || (
+ group.isAnnouncementGroup() && !group.isAdmin(recipientId)
+ );
}
private List<HandleAction> handleMessage(
}
}
- public String computeSafetyNumber(
- SignalServiceAddress theirAddress, IdentityKey theirIdentityKey
- ) {
- return Utils.computeSafetyNumber(ServiceConfig.capabilities.isUuid(),
+ public String computeSafetyNumber(SignalServiceAddress theirAddress, IdentityKey theirIdentityKey) {
+ final var fingerprint = Utils.computeSafetyNumber(capabilities.isUuid(),
+ account.getSelfAddress(),
+ getIdentityKeyPair().getPublicKey(),
+ theirAddress,
+ theirIdentityKey);
+ return fingerprint == null ? null : fingerprint.getDisplayableFingerprint().getDisplayText();
+ }
+
+ public byte[] computeSafetyNumberForScanning(SignalServiceAddress theirAddress, IdentityKey theirIdentityKey) {
+ final var fingerprint = Utils.computeSafetyNumber(capabilities.isUuid(),
account.getSelfAddress(),
getIdentityKeyPair().getPublicKey(),
theirAddress,
theirIdentityKey);
+ return fingerprint == null ? null : fingerprint.getScannableFingerprint().getSerialized();
}
@Deprecated
return account.getRecipientStore().resolveServiceAddress(recipientId);
}
- public RecipientId canonicalizeAndResolveRecipient(String identifier) throws InvalidNumberException {
- var canonicalizedNumber = UuidUtil.isUuid(identifier)
- ? identifier
- : PhoneNumberFormatter.formatNumber(identifier, account.getUsername());
+ private RecipientId canonicalizeAndResolveRecipient(String identifier) throws InvalidNumberException {
+ var canonicalizedNumber = UuidUtil.isUuid(identifier) ? identifier : canonicalizePhoneNumber(identifier);
return resolveRecipient(canonicalizedNumber);
}
+ private String canonicalizePhoneNumber(final String number) throws InvalidNumberException {
+ return PhoneNumberFormatter.formatNumber(number, account.getUsername());
+ }
+
private RecipientId resolveRecipient(final String identifier) {
var address = Utils.getSignalServiceAddressFromIdentifier(identifier);