X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/b810e303ec9d0fcc3ba948b7e65d57f85bffe437..5bbfd3259891e18a11cb878e14a9c17990b13d79:/lib/src/main/java/org/asamk/signal/manager/Manager.java diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index 327e876d..8aa7ed18 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -33,6 +33,7 @@ import org.asamk.signal.manager.groups.NotAGroupMemberException; import org.asamk.signal.manager.helper.GroupV2Helper; import org.asamk.signal.manager.helper.PinHelper; import org.asamk.signal.manager.helper.ProfileHelper; +import org.asamk.signal.manager.helper.SendHelper; import org.asamk.signal.manager.helper.UnidentifiedAccessHelper; import org.asamk.signal.manager.jobs.Context; import org.asamk.signal.manager.jobs.Job; @@ -86,7 +87,6 @@ import org.whispersystems.libsignal.util.Pair; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.InvalidMessageStructureException; import org.whispersystems.signalservice.api.SignalSessionLock; -import org.whispersystems.signalservice.api.crypto.ContentHint; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException; import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString; @@ -111,7 +111,6 @@ import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroup; import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsInputStream; import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsOutputStream; import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage; -import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage; import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage; @@ -120,7 +119,6 @@ import org.whispersystems.signalservice.api.profiles.SignalServiceProfile; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.ConflictException; import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException; -import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; import org.whispersystems.signalservice.api.util.DeviceNameUtil; import org.whispersystems.signalservice.api.util.InvalidNumberException; import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; @@ -179,10 +177,11 @@ public class Manager implements Closeable { private final ExecutorService executor = Executors.newCachedThreadPool(); - private final UnidentifiedAccessHelper unidentifiedAccessHelper; private final ProfileHelper profileHelper; private final GroupV2Helper groupV2Helper; private final PinHelper pinHelper; + private final SendHelper sendHelper; + private final AvatarStore avatarStore; private final AttachmentStore attachmentStore; private final StickerPackStore stickerPackStore; @@ -218,7 +217,7 @@ public class Manager implements Closeable { sessionLock); this.pinHelper = new PinHelper(dependencies.getKeyBackupService()); - this.unidentifiedAccessHelper = new UnidentifiedAccessHelper(account::getProfileKey, + final var unidentifiedAccessHelper = new UnidentifiedAccessHelper(account::getProfileKey, account.getProfileStore()::getProfileKey, this::getRecipientProfile, this::getSenderCertificate); @@ -237,6 +236,14 @@ public class Manager implements Closeable { this.avatarStore = new AvatarStore(pathConfig.getAvatarsPath()); this.attachmentStore = new AttachmentStore(pathConfig.getAttachmentsPath()); this.stickerPackStore = new StickerPackStore(pathConfig.getStickerPacksPath()); + this.sendHelper = new SendHelper(account, + dependencies, + unidentifiedAccessHelper, + this::resolveSignalServiceAddress, + this::resolveRecipient, + this::handleIdentityFailure, + this::getGroup, + this::refreshRegisteredUser); } public String getUsername() { @@ -320,16 +327,32 @@ public class Manager implements Closeable { * 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 areUsersRegistered(Set numbers) throws IOException { + public Map> areUsersRegistered(Set numbers) throws IOException { + Map 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 { @@ -396,10 +419,7 @@ public class Manager implements Closeable { } account.getProfileStore().storeProfile(account.getSelfRecipientId(), newProfile); - try { - sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.LOCAL_PROFILE)); - } catch (UntrustedIdentityException ignored) { - } + sendHelper.sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.LOCAL_PROFILE)); } public void unregister() throws IOException { @@ -472,9 +492,6 @@ public class Manager implements Closeable { account.setRegistrationLockPin(pin.get(), masterKey); } else { - // Remove legacy registration lock - dependencies.getAccountManager().removeRegistrationLockV1(); - // Remove KBS Pin pinHelper.removeRegistrationLockPin(); @@ -656,17 +673,6 @@ public class Manager implements Closeable { return Optional.of(AttachmentUtils.createAttachment(streamDetails, Optional.absent())); } - private GroupInfo getGroupForSending(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException { - var g = getGroup(groupId); - if (g == null) { - throw new GroupNotFoundException(groupId); - } - if (!g.isMember(account.getSelfRecipientId())) { - throw new NotAGroupMemberException(groupId, g.getTitle()); - } - return g; - } - private GroupInfo getGroupForUpdating(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException { var g = getGroup(groupId); if (g == null) { @@ -685,12 +691,12 @@ public class Manager implements Closeable { public Pair> sendGroupMessage( String messageText, List attachments, GroupId groupId ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException { - final var messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText); + final var messageBuilder = createMessageBuilder().withBody(messageText); if (attachments != null) { messageBuilder.withAttachments(AttachmentUtils.getSignalServiceAttachments(attachments)); } - return sendGroupMessage(messageBuilder, groupId); + return sendHelper.sendAsGroupMessage(messageBuilder, groupId); } public Pair> sendGroupMessageReaction( @@ -701,20 +707,9 @@ public class Manager implements Closeable { remove, resolveSignalServiceAddress(targetAuthorRecipientId), targetSentTimestamp); - final var messageBuilder = SignalServiceDataMessage.newBuilder().withReaction(reaction); - - return sendGroupMessage(messageBuilder, groupId); - } + final var messageBuilder = createMessageBuilder().withReaction(reaction); - public Pair> sendGroupMessage( - SignalServiceDataMessage.Builder messageBuilder, GroupId groupId - ) throws IOException, GroupNotFoundException, NotAGroupMemberException { - final var g = getGroupForSending(groupId); - - GroupUtils.setGroupContext(messageBuilder, g); - messageBuilder.withExpiration(g.getMessageExpirationTime()); - - return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfRecipientId())); + return sendHelper.sendAsGroupMessage(messageBuilder, groupId); } public Pair> sendQuitGroupMessage( @@ -725,7 +720,7 @@ public class Manager implements Closeable { return quitGroupV1((GroupInfoV1) group); } - final var newAdmins = getSignalServiceAddresses(groupAdmins); + final var newAdmins = getRecipientIds(groupAdmins); try { return quitGroupV2((GroupInfoV2) group, newAdmins); } catch (ConflictException e) { @@ -740,10 +735,11 @@ public class Manager implements Closeable { .withId(groupInfoV1.getGroupId().serialize()) .build(); - var messageBuilder = SignalServiceDataMessage.newBuilder().asGroupMessage(group); + var messageBuilder = createMessageBuilder().asGroupMessage(group); groupInfoV1.removeMember(account.getSelfRecipientId()); account.getGroupStore().updateGroup(groupInfoV1); - return sendMessage(messageBuilder, groupInfoV1.getMembersWithout(account.getSelfRecipientId())); + return sendHelper.sendGroupMessage(messageBuilder.build(), + groupInfoV1.getMembersIncludingPendingWithout(account.getSelfRecipientId())); } private Pair> quitGroupV2( @@ -763,7 +759,8 @@ public class Manager implements Closeable { groupInfoV2.setGroup(groupGroupChangePair.first(), this::resolveRecipient); var messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2, groupGroupChangePair.second().toByteArray()); account.getGroupStore().updateGroup(groupInfoV2); - return sendMessage(messageBuilder, groupInfoV2.getMembersWithout(account.getSelfRecipientId())); + return sendHelper.sendGroupMessage(messageBuilder.build(), + groupInfoV2.getMembersIncludingPendingWithout(account.getSelfRecipientId())); } public void deleteGroup(GroupId groupId) throws IOException { @@ -774,7 +771,7 @@ public class Manager implements Closeable { public Pair> createGroup( String name, List members, File avatarFile ) throws IOException, AttachmentInvalidException, InvalidNumberException { - return createGroup(name, members == null ? null : getSignalServiceAddresses(members), avatarFile); + return createGroup(name, members == null ? null : getRecipientIds(members), avatarFile); } private Pair> createGroup( @@ -810,7 +807,8 @@ public class Manager implements Closeable { messageBuilder = getGroupUpdateMessageBuilder(gv2, null); account.getGroupStore().updateGroup(gv2); - final var result = sendMessage(messageBuilder, gv2.getMembersIncludingPendingWithout(selfRecipientId)); + final var result = sendHelper.sendGroupMessage(messageBuilder.build(), + gv2.getMembersIncludingPendingWithout(selfRecipientId)); return new Pair<>(gv2.getGroupId(), result.second()); } @@ -827,21 +825,23 @@ public class Manager implements Closeable { GroupPermission addMemberPermission, GroupPermission editDetailsPermission, File avatarFile, - Integer expirationTimer + Integer expirationTimer, + Boolean isAnnouncementGroup ) throws IOException, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException, NotAGroupMemberException { return updateGroup(groupId, name, description, - members == null ? null : getSignalServiceAddresses(members), - removeMembers == null ? null : getSignalServiceAddresses(removeMembers), - admins == null ? null : getSignalServiceAddresses(admins), - removeAdmins == null ? null : getSignalServiceAddresses(removeAdmins), + members == null ? null : getRecipientIds(members), + removeMembers == null ? null : getRecipientIds(removeMembers), + admins == null ? null : getRecipientIds(admins), + removeAdmins == null ? null : getRecipientIds(removeAdmins), resetGroupLink, groupLinkState, addMemberPermission, editDetailsPermission, avatarFile, - expirationTimer); + expirationTimer, + isAnnouncementGroup); } private Pair> updateGroup( @@ -857,7 +857,8 @@ public class Manager implements Closeable { 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); @@ -875,7 +876,8 @@ public class Manager implements Closeable { addMemberPermission, editDetailsPermission, avatarFile, - expirationTimer); + expirationTimer, + isAnnouncementGroup); } catch (ConflictException e) { // Detected conflicting update, refreshing group and trying again group = getGroup(groupId, true); @@ -891,7 +893,8 @@ public class Manager implements Closeable { addMemberPermission, editDetailsPermission, avatarFile, - expirationTimer); + expirationTimer, + isAnnouncementGroup); } } @@ -911,7 +914,8 @@ public class Manager implements Closeable { account.getGroupStore().updateGroup(gv1); - return sendMessage(messageBuilder, gv1.getMembersIncludingPendingWithout(account.getSelfRecipientId())); + return sendHelper.sendGroupMessage(messageBuilder.build(), + gv1.getMembersIncludingPendingWithout(account.getSelfRecipientId())); } private void updateGroupV1Details( @@ -965,7 +969,8 @@ public class Manager implements Closeable { final GroupPermission addMemberPermission, final GroupPermission editDetailsPermission, final File avatarFile, - Integer expirationTimer + final Integer expirationTimer, + final Boolean isAnnouncementGroup ) throws IOException { Pair> result = null; if (group.isPendingMember(account.getSelfRecipientId())) { @@ -1051,6 +1056,11 @@ public class Manager implements Closeable { 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) { @@ -1095,7 +1105,7 @@ public class Manager implements Closeable { final var messageBuilder = getGroupUpdateMessageBuilder(group, groupChange.toByteArray()); account.getGroupStore().updateGroup(group); - return sendMessage(messageBuilder, members); + return sendHelper.sendGroupMessage(messageBuilder.build(), members); } private static int currentTimeDays() { @@ -1125,9 +1135,9 @@ public class Manager implements Closeable { GroupIdV1 groupId, SignalServiceAddress recipient ) throws IOException, NotAGroupMemberException, GroupNotFoundException, AttachmentInvalidException { GroupInfoV1 g; - var group = getGroupForSending(groupId); + var group = getGroupForUpdating(groupId); if (!(group instanceof GroupInfoV1)) { - throw new RuntimeException("Received an invalid group request for a v2 group!"); + throw new IOException("Received an invalid group request for a v2 group!"); } g = (GroupInfoV1) group; @@ -1139,7 +1149,7 @@ public class Manager implements Closeable { var messageBuilder = getGroupUpdateMessageBuilder(g); // Send group message only to the recipient who requested it - return sendMessage(messageBuilder, Set.of(recipientId)); + return sendHelper.sendGroupMessage(messageBuilder.build(), Set.of(recipientId)); } private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV1 g) throws AttachmentInvalidException { @@ -1160,18 +1170,14 @@ public class Manager implements Closeable { throw new AttachmentInvalidException(g.getGroupId().toBase64(), e); } - return SignalServiceDataMessage.newBuilder() - .asGroupMessage(group.build()) - .withExpiration(g.getMessageExpirationTime()); + return createMessageBuilder().asGroupMessage(group.build()).withExpiration(g.getMessageExpirationTime()); } private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV2 g, byte[] signedGroupChange) { var group = SignalServiceGroupV2.newBuilder(g.getMasterKey()) .withRevision(g.getGroup().getRevision()) .withSignedGroupChange(signedGroupChange); - return SignalServiceDataMessage.newBuilder() - .asGroupMessage(group.build()) - .withExpiration(g.getMessageExpirationTime()); + return createMessageBuilder().asGroupMessage(group.build()).withExpiration(g.getMessageExpirationTime()); } Pair> sendGroupInfoRequest( @@ -1179,10 +1185,10 @@ public class Manager implements Closeable { ) throws IOException { var group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.REQUEST_INFO).withId(groupId.serialize()); - var messageBuilder = SignalServiceDataMessage.newBuilder().asGroupMessage(group.build()); + var messageBuilder = createMessageBuilder().asGroupMessage(group.build()); // Send group info request message to the recipient who sent us a message with this groupId - return sendMessage(messageBuilder, Set.of(resolveRecipient(recipient))); + return sendHelper.sendGroupMessage(messageBuilder.build(), Set.of(resolveRecipient(recipient))); } void sendReceipt( @@ -1192,16 +1198,13 @@ public class Manager implements Closeable { List.of(messageId), System.currentTimeMillis()); - dependencies.getMessageSender() - .sendReceipt(remoteAddress, - unidentifiedAccessHelper.getAccessFor(resolveRecipient(remoteAddress)), - receiptMessage); + sendHelper.sendReceiptMessage(receiptMessage, resolveRecipient(remoteAddress)); } public Pair> sendMessage( String messageText, List attachments, List recipients ) throws IOException, AttachmentInvalidException, InvalidNumberException { - final var messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText); + final var messageBuilder = createMessageBuilder().withBody(messageText); if (attachments != null) { var attachmentStreams = AttachmentUtils.getSignalServiceAttachments(attachments); @@ -1218,33 +1221,33 @@ public class Manager implements Closeable { messageBuilder.withAttachments(attachmentPointers); } - return sendMessage(messageBuilder, getSignalServiceAddresses(recipients)); + return sendHelper.sendMessage(messageBuilder, getRecipientIds(recipients)); } public Pair sendSelfMessage( String messageText, List attachments ) throws IOException, AttachmentInvalidException { - final var messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText); + final var messageBuilder = createMessageBuilder().withBody(messageText); if (attachments != null) { messageBuilder.withAttachments(AttachmentUtils.getSignalServiceAttachments(attachments)); } - return sendSelfMessage(messageBuilder); + return sendHelper.sendSelfMessage(messageBuilder); } public Pair> sendRemoteDeleteMessage( long targetSentTimestamp, List recipients ) throws IOException, InvalidNumberException { var delete = new SignalServiceDataMessage.RemoteDelete(targetSentTimestamp); - final var messageBuilder = SignalServiceDataMessage.newBuilder().withRemoteDelete(delete); - return sendMessage(messageBuilder, getSignalServiceAddresses(recipients)); + final var messageBuilder = createMessageBuilder().withRemoteDelete(delete); + return sendHelper.sendMessage(messageBuilder, getRecipientIds(recipients)); } public Pair> sendGroupRemoteDeleteMessage( long targetSentTimestamp, GroupId groupId ) throws IOException, NotAGroupMemberException, GroupNotFoundException { var delete = new SignalServiceDataMessage.RemoteDelete(targetSentTimestamp); - final var messageBuilder = SignalServiceDataMessage.newBuilder().withRemoteDelete(delete); - return sendGroupMessage(messageBuilder, groupId); + final var messageBuilder = createMessageBuilder().withRemoteDelete(delete); + return sendHelper.sendAsGroupMessage(messageBuilder, groupId); } public Pair> sendMessageReaction( @@ -1255,28 +1258,27 @@ public class Manager implements Closeable { remove, resolveSignalServiceAddress(targetAuthorRecipientId), targetSentTimestamp); - final var messageBuilder = SignalServiceDataMessage.newBuilder().withReaction(reaction); - return sendMessage(messageBuilder, getSignalServiceAddresses(recipients)); + final var messageBuilder = createMessageBuilder().withReaction(reaction); + return sendHelper.sendMessage(messageBuilder, getRecipientIds(recipients)); } public Pair> sendEndSessionMessage(List recipients) throws IOException, InvalidNumberException { - var messageBuilder = SignalServiceDataMessage.newBuilder().asEndSessionMessage(); + var messageBuilder = createMessageBuilder().asEndSessionMessage(); - final var signalServiceAddresses = getSignalServiceAddresses(recipients); + final var recipientIds = getRecipientIds(recipients); try { - return sendMessage(messageBuilder, signalServiceAddresses); - } catch (Exception e) { - for (var address : signalServiceAddresses) { - handleEndSession(address); + return sendHelper.sendMessage(messageBuilder, recipientIds); + } finally { + for (var recipientId : recipientIds) { + handleEndSession(recipientId); } - throw e; } } void renewSession(RecipientId recipientId) throws IOException { account.getSessionStore().archiveSessions(recipientId); if (!recipientId.equals(getSelfRecipientId())) { - sendNullMessage(recipientId); + sendHelper.sendNullMessage(recipientId); } } @@ -1326,8 +1328,8 @@ public class Manager implements Closeable { } private void sendExpirationTimerUpdate(RecipientId recipientId) throws IOException { - final var messageBuilder = SignalServiceDataMessage.newBuilder().asExpirationUpdate(); - sendMessage(messageBuilder, Set.of(recipientId)); + final var messageBuilder = createMessageBuilder().asExpirationUpdate(); + sendHelper.sendMessage(messageBuilder, Set.of(recipientId)); } /** @@ -1353,8 +1355,8 @@ public class Manager implements Closeable { } private void sendExpirationTimerUpdate(GroupIdV1 groupId) throws IOException, NotAGroupMemberException, GroupNotFoundException { - final var messageBuilder = SignalServiceDataMessage.newBuilder().asExpirationUpdate(); - sendGroupMessage(messageBuilder, groupId); + final var messageBuilder = createMessageBuilder().asExpirationUpdate(); + sendHelper.sendAsGroupMessage(messageBuilder, groupId); } /** @@ -1401,11 +1403,7 @@ public class Manager implements Closeable { .setType(SignalServiceProtos.SyncMessage.Request.Type.GROUPS) .build(); var message = SignalServiceSyncMessage.forRequest(new RequestMessage(r)); - try { - sendSyncMessage(message); - } catch (UntrustedIdentityException e) { - throw new AssertionError(e); - } + sendHelper.sendSyncMessage(message); } private void requestSyncContacts() throws IOException { @@ -1413,11 +1411,7 @@ public class Manager implements Closeable { .setType(SignalServiceProtos.SyncMessage.Request.Type.CONTACTS) .build(); var message = SignalServiceSyncMessage.forRequest(new RequestMessage(r)); - try { - sendSyncMessage(message); - } catch (UntrustedIdentityException e) { - throw new AssertionError(e); - } + sendHelper.sendSyncMessage(message); } private void requestSyncBlocked() throws IOException { @@ -1425,11 +1419,7 @@ public class Manager implements Closeable { .setType(SignalServiceProtos.SyncMessage.Request.Type.BLOCKED) .build(); var message = SignalServiceSyncMessage.forRequest(new RequestMessage(r)); - try { - sendSyncMessage(message); - } catch (UntrustedIdentityException e) { - throw new AssertionError(e); - } + sendHelper.sendSyncMessage(message); } private void requestSyncConfiguration() throws IOException { @@ -1437,11 +1427,7 @@ public class Manager implements Closeable { .setType(SignalServiceProtos.SyncMessage.Request.Type.CONFIGURATION) .build(); var message = SignalServiceSyncMessage.forRequest(new RequestMessage(r)); - try { - sendSyncMessage(message); - } catch (UntrustedIdentityException e) { - throw new AssertionError(e); - } + sendHelper.sendSyncMessage(message); } private void requestSyncKeys() throws IOException { @@ -1449,11 +1435,7 @@ public class Manager implements Closeable { .setType(SignalServiceProtos.SyncMessage.Request.Type.KEYS) .build(); var message = SignalServiceSyncMessage.forRequest(new RequestMessage(r)); - try { - sendSyncMessage(message); - } catch (UntrustedIdentityException e) { - throw new AssertionError(e); - } + sendHelper.sendSyncMessage(message); } private byte[] getSenderCertificate() { @@ -1472,12 +1454,7 @@ public class Manager implements Closeable { return certificate; } - private void sendSyncMessage(SignalServiceSyncMessage message) throws IOException, UntrustedIdentityException { - var messageSender = dependencies.getMessageSender(); - messageSender.sendSyncMessage(message, unidentifiedAccessHelper.getAccessForSync()); - } - - private Set getSignalServiceAddresses(Collection numbers) throws InvalidNumberException { + private Set getRecipientIds(Collection numbers) throws InvalidNumberException { final var signalServiceAddresses = new HashSet(numbers.size()); final var addressesMissingUuid = new HashSet(); @@ -1540,176 +1517,27 @@ public class Manager implements Closeable { public void sendTypingMessage( TypingAction action, Set recipients ) throws IOException, UntrustedIdentityException, InvalidNumberException { - sendTypingMessageInternal(action, getSignalServiceAddresses(recipients)); - } - - private void sendTypingMessageInternal( - TypingAction action, Set recipientIds - ) throws IOException, UntrustedIdentityException { final var timestamp = System.currentTimeMillis(); var message = new SignalServiceTypingMessage(action.toSignalService(), timestamp, Optional.absent()); - var messageSender = dependencies.getMessageSender(); - for (var recipientId : recipientIds) { - final var address = resolveSignalServiceAddress(recipientId); - messageSender.sendTyping(address, unidentifiedAccessHelper.getAccessFor(recipientId), message); - } + sendHelper.sendTypingMessage(message, getRecipientIds(recipients)); } public void sendGroupTypingMessage( TypingAction action, GroupId groupId ) throws IOException, NotAGroupMemberException, GroupNotFoundException { final var timestamp = System.currentTimeMillis(); - final var g = getGroupForSending(groupId); final var message = new SignalServiceTypingMessage(action.toSignalService(), timestamp, Optional.of(groupId.serialize())); - final var messageSender = dependencies.getMessageSender(); - final var recipientIdList = new ArrayList<>(g.getMembersWithout(account.getSelfRecipientId())); - final var addresses = recipientIdList.stream() - .map(this::resolveSignalServiceAddress) - .collect(Collectors.toList()); - messageSender.sendTyping(addresses, unidentifiedAccessHelper.getAccessFor(recipientIdList), message, null); + sendHelper.sendGroupTypingMessage(message, groupId); } - private Pair> sendMessage( - SignalServiceDataMessage.Builder messageBuilder, Set recipientIds - ) throws IOException { + private SignalServiceDataMessage.Builder createMessageBuilder() { final var timestamp = System.currentTimeMillis(); - messageBuilder.withTimestamp(timestamp); - - SignalServiceDataMessage message = null; - try { - message = messageBuilder.build(); - if (message.getGroupContext().isPresent()) { - try { - var messageSender = dependencies.getMessageSender(); - final var isRecipientUpdate = false; - final var recipientIdList = new ArrayList<>(recipientIds); - final var addresses = recipientIdList.stream() - .map(this::resolveSignalServiceAddress) - .collect(Collectors.toList()); - var result = messageSender.sendDataMessage(addresses, - unidentifiedAccessHelper.getAccessFor(recipientIdList), - isRecipientUpdate, - ContentHint.DEFAULT, - message, - sendResult -> logger.trace("Partial message send result: {}", sendResult.isSuccess()), - () -> false); - - for (var r : result) { - if (r.getIdentityFailure() != null) { - final var recipientId = resolveRecipient(r.getAddress()); - final var newIdentity = account.getIdentityKeyStore() - .saveIdentity(recipientId, r.getIdentityFailure().getIdentityKey(), new Date()); - if (newIdentity) { - account.getSessionStore().archiveSessions(recipientId); - } - } - } - - return new Pair<>(timestamp, result); - } catch (UntrustedIdentityException e) { - return new Pair<>(timestamp, List.of()); - } - } else { - // Send to all individually, so sync messages are sent correctly - messageBuilder.withProfileKey(account.getProfileKey().serialize()); - var results = new ArrayList(recipientIds.size()); - for (var recipientId : recipientIds) { - final var contact = account.getContactStore().getContact(recipientId); - final var expirationTime = contact != null ? contact.getMessageExpirationTime() : 0; - messageBuilder.withExpiration(expirationTime); - message = messageBuilder.build(); - results.add(sendMessage(recipientId, message)); - } - return new Pair<>(timestamp, results); - } - } finally { - if (message != null && message.isEndSession()) { - for (var recipient : recipientIds) { - handleEndSession(recipient); - } - } - } - } - private Pair sendSelfMessage( - SignalServiceDataMessage.Builder messageBuilder - ) throws IOException { - final var timestamp = System.currentTimeMillis(); + var messageBuilder = SignalServiceDataMessage.newBuilder(); messageBuilder.withTimestamp(timestamp); - final var recipientId = account.getSelfRecipientId(); - - final var contact = account.getContactStore().getContact(recipientId); - final var expirationTime = contact != null ? contact.getMessageExpirationTime() : 0; - messageBuilder.withExpiration(expirationTime); - - var message = messageBuilder.build(); - final var result = sendSelfMessage(message); - return new Pair<>(timestamp, result); - } - - private SendMessageResult sendSelfMessage(SignalServiceDataMessage message) throws IOException { - var messageSender = dependencies.getMessageSender(); - - var recipientId = account.getSelfRecipientId(); - - final var unidentifiedAccess = unidentifiedAccessHelper.getAccessFor(recipientId); - var recipient = resolveSignalServiceAddress(recipientId); - var transcript = new SentTranscriptMessage(Optional.of(recipient), - message.getTimestamp(), - message, - message.getExpiresInSeconds(), - Map.of(recipient, unidentifiedAccess.isPresent()), - false); - var syncMessage = SignalServiceSyncMessage.forSentTranscript(transcript); - - try { - return messageSender.sendSyncMessage(syncMessage, unidentifiedAccess); - } catch (UntrustedIdentityException e) { - return SendMessageResult.identityFailure(recipient, e.getIdentityKey()); - } - } - - private SendMessageResult sendMessage( - RecipientId recipientId, SignalServiceDataMessage message - ) throws IOException { - var messageSender = dependencies.getMessageSender(); - - final var address = resolveSignalServiceAddress(recipientId); - try { - try { - return messageSender.sendDataMessage(address, - unidentifiedAccessHelper.getAccessFor(recipientId), - ContentHint.DEFAULT, - message); - } catch (UnregisteredUserException e) { - final var newRecipientId = refreshRegisteredUser(recipientId); - return messageSender.sendDataMessage(resolveSignalServiceAddress(newRecipientId), - unidentifiedAccessHelper.getAccessFor(newRecipientId), - ContentHint.DEFAULT, - message); - } - } catch (UntrustedIdentityException e) { - return SendMessageResult.identityFailure(address, e.getIdentityKey()); - } - } - - private SendMessageResult sendNullMessage(RecipientId recipientId) throws IOException { - var messageSender = dependencies.getMessageSender(); - - final var address = resolveSignalServiceAddress(recipientId); - try { - try { - return messageSender.sendNullMessage(address, unidentifiedAccessHelper.getAccessFor(recipientId)); - } catch (UnregisteredUserException e) { - final var newRecipientId = refreshRegisteredUser(recipientId); - final var newAddress = resolveSignalServiceAddress(newRecipientId); - return messageSender.sendNullMessage(newAddress, unidentifiedAccessHelper.getAccessFor(newRecipientId)); - } - } catch (UntrustedIdentityException e) { - return SendMessageResult.identityFailure(address, e.getIdentityKey()); - } + return messageBuilder; } private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException, ProtocolInvalidMessageException, ProtocolDuplicateMessageException, ProtocolLegacyMessageException, ProtocolInvalidKeyIdException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolNoSessionException, ProtocolInvalidKeyException, SelfSendException, UnsupportedDataMessageException, ProtocolUntrustedIdentityException, InvalidMessageStructureException { @@ -2062,7 +1890,6 @@ public class Manager implements Closeable { // address/uuid in envelope is sent by server resolveRecipientTrusted(envelope.getSourceAddress()); } - final var notAGroupMember = isNotAGroupMember(envelope, content); if (!envelope.isReceipt()) { try { content = decryptMessage(envelope); @@ -2098,10 +1925,13 @@ public class Manager implements Closeable { 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); } @@ -2164,7 +1994,7 @@ public class Manager implements Closeable { return sourceContact != null && sourceContact.isBlocked(); } - private boolean isNotAGroupMember( + private boolean isNotAllowedToSendToGroup( SignalServiceEnvelope envelope, SignalServiceContent content ) { SignalServiceAddress source; @@ -2176,23 +2006,32 @@ public class Manager implements Closeable { 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 handleMessage( @@ -2602,7 +2441,7 @@ public class Manager implements Closeable { .retrieveAttachment(pointer, tmpFile, ServiceConfig.MAX_ATTACHMENT_SIZE); } - void sendGroups() throws IOException, UntrustedIdentityException { + void sendGroups() throws IOException { var groupsFile = IOUtils.createTempFile(); try { @@ -2636,7 +2475,7 @@ public class Manager implements Closeable { .withLength(groupsFile.length()) .build(); - sendSyncMessage(SignalServiceSyncMessage.forGroups(attachmentStream)); + sendHelper.sendSyncMessage(SignalServiceSyncMessage.forGroups(attachmentStream)); } } } finally { @@ -2648,7 +2487,7 @@ public class Manager implements Closeable { } } - public void sendContacts() throws IOException, UntrustedIdentityException { + public void sendContacts() throws IOException { var contactsFile = IOUtils.createTempFile(); try { @@ -2704,7 +2543,8 @@ public class Manager implements Closeable { .withLength(contactsFile.length()) .build(); - sendSyncMessage(SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream, true))); + sendHelper.sendSyncMessage(SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream, + true))); } } } finally { @@ -2716,7 +2556,7 @@ public class Manager implements Closeable { } } - void sendBlockedList() throws IOException, UntrustedIdentityException { + void sendBlockedList() throws IOException { var addresses = new ArrayList(); for (var record : account.getContactStore().getContacts()) { if (record.second().isBlocked()) { @@ -2729,17 +2569,17 @@ public class Manager implements Closeable { groupIds.add(record.getGroupId().serialize()); } } - sendSyncMessage(SignalServiceSyncMessage.forBlocked(new BlockedListMessage(addresses, groupIds))); + sendHelper.sendSyncMessage(SignalServiceSyncMessage.forBlocked(new BlockedListMessage(addresses, groupIds))); } private void sendVerifiedMessage( SignalServiceAddress destination, IdentityKey identityKey, TrustLevel trustLevel - ) throws IOException, UntrustedIdentityException { + ) throws IOException { var verifiedMessage = new VerifiedMessage(destination, identityKey, trustLevel.toVerifiedState(), System.currentTimeMillis()); - sendSyncMessage(SignalServiceSyncMessage.forVerified(verifiedMessage)); + sendHelper.sendSyncMessage(SignalServiceSyncMessage.forVerified(verifiedMessage)); } public List> getContacts() { @@ -2840,21 +2680,44 @@ public class Manager implements Closeable { try { var address = account.getRecipientStore().resolveServiceAddress(recipientId); sendVerifiedMessage(address, identity.getIdentityKey(), trustLevel); - } catch (IOException | UntrustedIdentityException e) { + } catch (IOException e) { logger.warn("Failed to send verification sync message: {}", e.getMessage()); } return true; } - public String computeSafetyNumber( - SignalServiceAddress theirAddress, IdentityKey theirIdentityKey + private void handleIdentityFailure( + final RecipientId recipientId, final SendMessageResult.IdentityFailure identityFailure ) { - return Utils.computeSafetyNumber(ServiceConfig.capabilities.isUuid(), + final var identityKey = identityFailure.getIdentityKey(); + if (identityKey != null) { + final var newIdentity = account.getIdentityKeyStore().saveIdentity(recipientId, identityKey, new Date()); + if (newIdentity) { + account.getSessionStore().archiveSessions(recipientId); + } + } else { + // Retrieve profile to get the current identity key from the server + retrieveEncryptedProfile(recipientId); + } + } + + 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 @@ -2877,14 +2740,16 @@ public class Manager implements Closeable { 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);