X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/98dee97cc69b49c0c3b75bb8691ae7ccc266e4d2..49c4b762b61a84feda75edb6b1e3e7c47b1467f1:/src/main/java/org/asamk/signal/manager/Manager.java diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index 000455ac..d15d164b 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -43,6 +43,7 @@ import org.signal.libsignal.metadata.ProtocolLegacyMessageException; import org.signal.libsignal.metadata.ProtocolNoSessionException; import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException; import org.signal.libsignal.metadata.SelfSendException; +import org.signal.storageservice.protos.groups.GroupChange; import org.signal.storageservice.protos.groups.local.DecryptedGroup; import org.signal.storageservice.protos.groups.local.DecryptedMember; import org.signal.zkgroup.InvalidInputException; @@ -213,7 +214,9 @@ public class Manager implements Closeable { this.groupHelper = new GroupHelper(this::getRecipientProfileKeyCredential, this::getRecipientProfile, account::getSelfAddress, - groupsV2Operations); + groupsV2Operations, + groupsV2Api, + this::getGroupAuthForToday); } public String getUsername() { @@ -736,52 +739,29 @@ public class Manager implements Closeable { } public Pair> sendQuitGroupMessage(byte[] groupId) throws GroupNotFoundException, IOException, NotAGroupMemberException { - SignalServiceGroup group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.QUIT).withId(groupId).build(); - SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder().asGroupMessage(group); + SignalServiceDataMessage.Builder messageBuilder; final GroupInfo g = getGroupForSending(groupId); if (g instanceof GroupInfoV1) { GroupInfoV1 groupInfoV1 = (GroupInfoV1) g; + SignalServiceGroup group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.QUIT) + .withId(groupId) + .build(); + messageBuilder = SignalServiceDataMessage.newBuilder().asGroupMessage(group); groupInfoV1.removeMember(account.getSelfAddress()); account.getGroupStore().updateGroup(groupInfoV1); } else { - throw new RuntimeException("TODO Not implemented!"); + final GroupInfoV2 groupInfoV2 = (GroupInfoV2) g; + final Pair groupGroupChangePair = groupHelper.leaveGroup(groupInfoV2); + groupInfoV2.setGroup(groupGroupChangePair.first()); + messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2, groupGroupChangePair.second().toByteArray()); + account.getGroupStore().updateGroup(groupInfoV2); } return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfAddress())); } - private GroupInfoV2 createGroupV2( - String name, Collection members, InputStream avatar - ) throws IOException { - byte[] avatarBytes = avatar == null ? null : IOUtils.readFully(avatar); - final GroupsV2Operations.NewGroup newGroup = groupHelper.createGroupV2(name, members, avatarBytes); - final GroupSecretParams groupSecretParams = newGroup.getGroupSecretParams(); - - final GroupsV2AuthorizationString groupAuthForToday; - final DecryptedGroup decryptedGroup; - try { - groupAuthForToday = getGroupAuthForToday(groupSecretParams); - groupsV2Api.putNewGroup(newGroup, groupAuthForToday); - decryptedGroup = groupsV2Api.getGroup(groupSecretParams, groupAuthForToday); - } catch (IOException | VerificationFailedException | InvalidGroupStateException e) { - System.err.println("Failed to create V2 group: " + e.getMessage()); - return null; - } - if (decryptedGroup == null) { - System.err.println("Failed to create V2 group!"); - return null; - } - - final byte[] groupId = groupSecretParams.getPublicParams().getGroupIdentifier().serialize(); - final GroupMasterKey masterKey = groupSecretParams.getMasterKey(); - GroupInfoV2 g = new GroupInfoV2(groupId, masterKey); - g.setGroup(decryptedGroup); - - return g; - } - private Pair> sendUpdateGroupMessage( byte[] groupId, String name, Collection members, String avatarFile ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException { @@ -789,8 +769,7 @@ public class Manager implements Closeable { SignalServiceDataMessage.Builder messageBuilder; if (groupId == null) { // Create new group - InputStream avatar = avatarFile == null ? null : new FileInputStream(avatarFile); - GroupInfoV2 gv2 = createGroupV2(name, members, avatar); + GroupInfoV2 gv2 = groupHelper.createGroupV2(name, members, avatarFile); if (gv2 == null) { GroupInfoV1 gv1 = new GroupInfoV1(KeyUtils.createGroupId()); gv1.addMembers(Collections.singleton(account.getSelfAddress())); @@ -798,18 +777,41 @@ public class Manager implements Closeable { messageBuilder = getGroupUpdateMessageBuilder(gv1); g = gv1; } else { - messageBuilder = getGroupUpdateMessageBuilder(gv2); + messageBuilder = getGroupUpdateMessageBuilder(gv2, null); g = gv2; } } else { GroupInfo group = getGroupForSending(groupId); - if (!(group instanceof GroupInfoV1)) { - throw new RuntimeException("TODO Not implemented!"); + if (group instanceof GroupInfoV2) { + Pair groupGroupChangePair = null; + if (members != null) { + final Set newMembers = new HashSet<>(members); + newMembers.removeAll(group.getMembers()); + if (newMembers.size() > 0) { + groupGroupChangePair = groupHelper.updateGroupV2((GroupInfoV2) group, newMembers); + } + } + if (groupGroupChangePair == null || name != null || avatarFile != null) { + if (groupGroupChangePair != null) { + ((GroupInfoV2) group).setGroup(groupGroupChangePair.first()); + messageBuilder = getGroupUpdateMessageBuilder((GroupInfoV2) group, + groupGroupChangePair.second().toByteArray()); + sendMessage(messageBuilder, group.getMembersWithout(account.getSelfAddress())); + } + + groupGroupChangePair = groupHelper.updateGroupV2((GroupInfoV2) group, name, avatarFile); + } + + ((GroupInfoV2) group).setGroup(groupGroupChangePair.first()); + messageBuilder = getGroupUpdateMessageBuilder((GroupInfoV2) group, + groupGroupChangePair.second().toByteArray()); + g = group; + } else { + GroupInfoV1 gv1 = (GroupInfoV1) group; + updateGroupV1(gv1, name, members, avatarFile); + messageBuilder = getGroupUpdateMessageBuilder(gv1); + g = gv1; } - GroupInfoV1 gv1 = (GroupInfoV1) group; - updateGroupV1(gv1, name, members, avatarFile); - messageBuilder = getGroupUpdateMessageBuilder(gv1); - g = gv1; } account.getGroupStore().updateGroup(g); @@ -899,11 +901,10 @@ public class Manager implements Closeable { .withExpiration(g.getMessageExpirationTime()); } - private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV2 g) { + private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV2 g, byte[] signedGroupChange) { SignalServiceGroupV2.Builder group = SignalServiceGroupV2.newBuilder(g.getMasterKey()) .withRevision(g.getGroup().getRevision()) -// .withSignedGroupChange() // TODO - ; + .withSignedGroupChange(signedGroupChange); return SignalServiceDataMessage.newBuilder() .asGroupMessage(group.build()) .withExpiration(g.getMessageExpirationTime()); @@ -1427,16 +1428,20 @@ public class Manager implements Closeable { private GroupsV2AuthorizationString getGroupAuthForToday( final GroupSecretParams groupSecretParams - ) throws IOException, VerificationFailedException { + ) throws IOException { final int today = currentTimeDays(); // Returns credentials for the next 7 days final HashMap credentials = groupsV2Api.getCredentials(today); // TODO cache credentials until they expire AuthCredentialResponse authCredentialResponse = credentials.get(today); - return groupsV2Api.getGroupsV2AuthorizationString(account.getUuid(), - today, - groupSecretParams, - authCredentialResponse); + try { + return groupsV2Api.getGroupsV2AuthorizationString(account.getUuid(), + today, + groupSecretParams, + authCredentialResponse); + } catch (VerificationFailedException e) { + throw new IOException(e); + } } private List handleSignalServiceDataMessage( @@ -1863,7 +1868,7 @@ public class Manager implements Closeable { ) { List actions = new ArrayList<>(); if (content != null) { - SignalServiceAddress sender; + final SignalServiceAddress sender; if (!envelope.isUnidentifiedSender() && envelope.hasSource()) { sender = envelope.getSourceAddress(); } else { @@ -1890,11 +1895,14 @@ public class Manager implements Closeable { SignalServiceSyncMessage syncMessage = content.getSyncMessage().get(); if (syncMessage.getSent().isPresent()) { SentTranscriptMessage message = syncMessage.getSent().get(); - actions.addAll(handleSignalServiceDataMessage(message.getMessage(), - true, - sender, - message.getDestination().orNull(), - ignoreAttachments)); + final SignalServiceAddress destination = message.getDestination().orNull(); + if (destination != null) { + actions.addAll(handleSignalServiceDataMessage(message.getMessage(), + true, + sender, + destination, + ignoreAttachments)); + } } if (syncMessage.getRequest().isPresent()) { RequestMessage rm = syncMessage.getRequest().get();