]> nmode's Git Repositories - signal-cli/blobdiff - src/main/java/org/asamk/signal/manager/helper/GroupHelper.java
Implement join group via invitation link
[signal-cli] / src / main / java / org / asamk / signal / manager / helper / GroupHelper.java
index 1f7e69e3552b9d2ae7e55d589e95d4b359f9883f..d66831bba0775fed65217319d97377500b2045f1 100644 (file)
@@ -2,12 +2,15 @@ package org.asamk.signal.manager.helper;
 
 import com.google.protobuf.InvalidProtocolBufferException;
 
+import org.asamk.signal.manager.GroupLinkPassword;
 import org.asamk.signal.storage.groups.GroupInfoV2;
 import org.asamk.signal.util.IOUtils;
+import org.signal.storageservice.protos.groups.AccessControl;
 import org.signal.storageservice.protos.groups.GroupChange;
 import org.signal.storageservice.protos.groups.Member;
 import org.signal.storageservice.protos.groups.local.DecryptedGroup;
 import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
+import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo;
 import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
 import org.signal.zkgroup.InvalidInputException;
 import org.signal.zkgroup.VerificationFailedException;
@@ -19,6 +22,7 @@ import org.whispersystems.libsignal.util.Pair;
 import org.whispersystems.libsignal.util.guava.Optional;
 import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
 import org.whispersystems.signalservice.api.groupsv2.GroupCandidate;
+import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException;
 import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
 import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString;
 import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
@@ -66,6 +70,27 @@ public class GroupHelper {
         this.groupAuthorizationProvider = groupAuthorizationProvider;
     }
 
+    public DecryptedGroup getDecryptedGroup(final GroupSecretParams groupSecretParams) {
+        try {
+            final GroupsV2AuthorizationString groupsV2AuthorizationString = groupAuthorizationProvider.getAuthorizationForToday(
+                    groupSecretParams);
+            return groupsV2Api.getGroup(groupSecretParams, groupsV2AuthorizationString);
+        } catch (IOException | VerificationFailedException | InvalidGroupStateException e) {
+            System.err.println("Failed to retrieve Group V2 info, ignoring ...");
+            return null;
+        }
+    }
+
+    public DecryptedGroupJoinInfo getDecryptedGroupJoinInfo(
+            GroupMasterKey groupMasterKey, GroupLinkPassword password
+    ) throws IOException, GroupLinkNotActiveException {
+        GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey);
+
+        return groupsV2Api.getGroupJoinInfo(groupSecretParams,
+                Optional.fromNullable(password).transform(GroupLinkPassword::serialize),
+                groupAuthorizationProvider.getAuthorizationForToday(groupSecretParams));
+    }
+
     public GroupInfoV2 createGroupV2(
             String name, Collection<SignalServiceAddress> members, String avatarFile
     ) throws IOException {
@@ -223,6 +248,32 @@ public class GroupHelper {
         }
     }
 
+    public GroupChange joinGroup(
+            GroupMasterKey groupMasterKey,
+            GroupLinkPassword groupLinkPassword,
+            DecryptedGroupJoinInfo decryptedGroupJoinInfo
+    ) throws IOException {
+        final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey);
+        final GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(groupSecretParams);
+
+        final SignalServiceAddress selfAddress = this.selfAddressProvider.getSelfAddress();
+        final ProfileKeyCredential profileKeyCredential = profileKeyCredentialProvider.getProfileKeyCredential(
+                selfAddress);
+        if (profileKeyCredential == null) {
+            throw new IOException("Cannot join a V2 group as self does not have a versioned profile");
+        }
+
+        boolean requestToJoin = decryptedGroupJoinInfo.getAddFromInviteLink()
+                == AccessControl.AccessRequired.ADMINISTRATOR;
+        GroupChange.Actions.Builder change = requestToJoin
+                ? groupOperations.createGroupJoinRequest(profileKeyCredential)
+                : groupOperations.createGroupJoinDirect(profileKeyCredential);
+
+        change.setSourceUuid(UuidUtil.toByteString(selfAddress.getUuid().get()));
+
+        return commitChange(groupSecretParams, decryptedGroupJoinInfo.getRevision(), change, groupLinkPassword);
+    }
+
     public Pair<DecryptedGroup, GroupChange> acceptInvite(GroupInfoV2 groupInfoV2) throws IOException {
         final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey());
         final GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(groupSecretParams);
@@ -284,13 +335,27 @@ public class GroupHelper {
             throw new IOException(e);
         }
 
-        GroupChange signedGroupChange = groupsV2Api.patchGroup(change.build(),
+        GroupChange signedGroupChange = groupsV2Api.patchGroup(changeActions,
                 groupAuthorizationProvider.getAuthorizationForToday(groupSecretParams),
                 Optional.absent());
 
         return new Pair<>(decryptedGroupState, signedGroupChange);
     }
 
+    private GroupChange commitChange(
+            GroupSecretParams groupSecretParams,
+            int currentRevision,
+            GroupChange.Actions.Builder change,
+            GroupLinkPassword password
+    ) throws IOException {
+        final int nextRevision = currentRevision + 1;
+        final GroupChange.Actions changeActions = change.setRevision(nextRevision).build();
+
+        return groupsV2Api.patchGroup(changeActions,
+                groupAuthorizationProvider.getAuthorizationForToday(groupSecretParams),
+                Optional.fromNullable(password).transform(GroupLinkPassword::serialize));
+    }
+
     public DecryptedGroup getUpdatedDecryptedGroup(
             DecryptedGroup group, byte[] signedGroupChange, GroupMasterKey groupMasterKey
     ) {