mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 10:30:38 +00:00
Implement join group via invitation link
This commit is contained in:
parent
9912da9546
commit
445e8592c4
9 changed files with 239 additions and 56 deletions
|
@ -4,6 +4,7 @@
|
||||||
### Added
|
### Added
|
||||||
- Accept group invitation with `updateGroup -g GROUP_ID`
|
- Accept group invitation with `updateGroup -g GROUP_ID`
|
||||||
- Decline group invitation with `quitGroup -g GROUP_ID`
|
- Decline group invitation with `quitGroup -g GROUP_ID`
|
||||||
|
- Join group via invitation link `joinGroup --uri https://signal.group/#...`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Include group ids for v2 groups in json output
|
- Include group ids for v2 groups in json output
|
||||||
|
|
|
@ -178,6 +178,13 @@ Don’t download attachments of received messages.
|
||||||
*--json*::
|
*--json*::
|
||||||
Output received messages in json format, one object per line.
|
Output received messages in json format, one object per line.
|
||||||
|
|
||||||
|
=== joinGroup
|
||||||
|
|
||||||
|
Join a group via an invitation link.
|
||||||
|
|
||||||
|
*--uri*::
|
||||||
|
The invitation link URI (starts with `https://signal.group/#`)
|
||||||
|
|
||||||
=== updateGroup
|
=== updateGroup
|
||||||
|
|
||||||
Create or update a group.
|
Create or update a group.
|
||||||
|
|
|
@ -16,6 +16,7 @@ public class Commands {
|
||||||
addCommand("listDevices", new ListDevicesCommand());
|
addCommand("listDevices", new ListDevicesCommand());
|
||||||
addCommand("listGroups", new ListGroupsCommand());
|
addCommand("listGroups", new ListGroupsCommand());
|
||||||
addCommand("listIdentities", new ListIdentitiesCommand());
|
addCommand("listIdentities", new ListIdentitiesCommand());
|
||||||
|
addCommand("joinGroup", new JoinGroupCommand());
|
||||||
addCommand("quitGroup", new QuitGroupCommand());
|
addCommand("quitGroup", new QuitGroupCommand());
|
||||||
addCommand("receive", new ReceiveCommand());
|
addCommand("receive", new ReceiveCommand());
|
||||||
addCommand("register", new RegisterCommand());
|
addCommand("register", new RegisterCommand());
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
package org.asamk.signal.commands;
|
||||||
|
|
||||||
|
import net.sourceforge.argparse4j.inf.Namespace;
|
||||||
|
import net.sourceforge.argparse4j.inf.Subparser;
|
||||||
|
|
||||||
|
import org.asamk.Signal;
|
||||||
|
import org.asamk.signal.manager.GroupInviteLinkUrl;
|
||||||
|
import org.asamk.signal.manager.Manager;
|
||||||
|
import org.freedesktop.dbus.exceptions.DBusExecutionException;
|
||||||
|
import org.whispersystems.libsignal.util.Pair;
|
||||||
|
import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException;
|
||||||
|
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||||
|
import org.whispersystems.signalservice.internal.push.exceptions.GroupPatchNotAcceptedException;
|
||||||
|
import org.whispersystems.util.Base64;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.asamk.signal.util.ErrorUtils.handleAssertionError;
|
||||||
|
import static org.asamk.signal.util.ErrorUtils.handleIOException;
|
||||||
|
import static org.asamk.signal.util.ErrorUtils.handleTimestampAndSendMessageResults;
|
||||||
|
|
||||||
|
public class JoinGroupCommand implements LocalCommand {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void attachToSubparser(final Subparser subparser) {
|
||||||
|
subparser.addArgument("--uri").required(true).help("Specify the uri with the group invitation link.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int handleCommand(final Namespace ns, final Manager m) {
|
||||||
|
if (!m.isRegistered()) {
|
||||||
|
System.err.println("User is not registered.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
final GroupInviteLinkUrl linkUrl;
|
||||||
|
String uri = ns.getString("uri");
|
||||||
|
try {
|
||||||
|
linkUrl = GroupInviteLinkUrl.fromUri(uri);
|
||||||
|
} catch (GroupInviteLinkUrl.InvalidGroupLinkException e) {
|
||||||
|
System.err.println("Group link is invalid: " + e.getMessage());
|
||||||
|
return 2;
|
||||||
|
} catch (GroupInviteLinkUrl.UnknownGroupLinkVersionException e) {
|
||||||
|
System.err.println("Group link was created with an incompatible version: " + e.getMessage());
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (linkUrl == null) {
|
||||||
|
System.err.println("Link is not a signal group invitation link");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final Pair<byte[], List<SendMessageResult>> results = m.joinGroup(linkUrl);
|
||||||
|
byte[] newGroupId = results.first();
|
||||||
|
if (!m.getGroup(newGroupId).isMember(m.getSelfAddress())) {
|
||||||
|
System.out.println("Requested to join group \"" + Base64.encodeBytes(newGroupId) + "\"");
|
||||||
|
} else {
|
||||||
|
System.out.println("Joined group \"" + Base64.encodeBytes(newGroupId) + "\"");
|
||||||
|
}
|
||||||
|
return handleTimestampAndSendMessageResults(0, results.second());
|
||||||
|
} catch (AssertionError e) {
|
||||||
|
handleAssertionError(e);
|
||||||
|
return 1;
|
||||||
|
} catch (GroupPatchNotAcceptedException e) {
|
||||||
|
System.err.println("Failed to join group, maybe already a member");
|
||||||
|
return 1;
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
handleIOException(e);
|
||||||
|
return 1;
|
||||||
|
} catch (Signal.Error.AttachmentInvalid e) {
|
||||||
|
System.err.println("Failed to add avatar attachment for group\": " + e.getMessage());
|
||||||
|
return 1;
|
||||||
|
} catch (DBusExecutionException e) {
|
||||||
|
System.err.println("Failed to send message: " + e.getMessage());
|
||||||
|
return 1;
|
||||||
|
} catch (GroupLinkNotActiveException e) {
|
||||||
|
System.err.println("Group link is not valid: " + e.getMessage());
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -74,8 +74,7 @@ public class SendReactionCommand implements LocalCommand {
|
||||||
targetTimestamp,
|
targetTimestamp,
|
||||||
ns.getList("recipient"));
|
ns.getList("recipient"));
|
||||||
}
|
}
|
||||||
handleTimestampAndSendMessageResults(results.first(), results.second());
|
return handleTimestampAndSendMessageResults(results.first(), results.second());
|
||||||
return 0;
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
handleIOException(e);
|
handleIOException(e);
|
||||||
return 3;
|
return 3;
|
||||||
|
|
|
@ -35,9 +35,9 @@ public class GroupUtils {
|
||||||
return groupSecretParams.getPublicParams().getGroupIdentifier().serialize();
|
return groupSecretParams.getPublicParams().getGroupIdentifier().serialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GroupMasterKey deriveV2MigrationMasterKey(byte[] groupId) {
|
public static GroupMasterKey deriveV2MigrationMasterKey(byte[] groupIdV1) {
|
||||||
try {
|
try {
|
||||||
return new GroupMasterKey(new HKDFv3().deriveSecrets(groupId,
|
return new GroupMasterKey(new HKDFv3().deriveSecrets(groupIdV1,
|
||||||
"GV2 Migration".getBytes(),
|
"GV2 Migration".getBytes(),
|
||||||
GroupMasterKey.SIZE));
|
GroupMasterKey.SIZE));
|
||||||
} catch (InvalidInputException e) {
|
} catch (InvalidInputException e) {
|
||||||
|
|
|
@ -45,6 +45,7 @@ import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
|
||||||
import org.signal.libsignal.metadata.SelfSendException;
|
import org.signal.libsignal.metadata.SelfSendException;
|
||||||
import org.signal.storageservice.protos.groups.GroupChange;
|
import org.signal.storageservice.protos.groups.GroupChange;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||||
|
import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
||||||
import org.signal.zkgroup.InvalidInputException;
|
import org.signal.zkgroup.InvalidInputException;
|
||||||
import org.signal.zkgroup.VerificationFailedException;
|
import org.signal.zkgroup.VerificationFailedException;
|
||||||
|
@ -78,10 +79,10 @@ import org.whispersystems.signalservice.api.crypto.SignalServiceCipher;
|
||||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
|
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
|
||||||
|
import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
|
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString;
|
import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.InvalidGroupStateException;
|
|
||||||
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
|
||||||
|
@ -849,6 +850,34 @@ public class Manager implements Closeable {
|
||||||
return new Pair<>(g.groupId, result.second());
|
return new Pair<>(g.groupId, result.second());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Pair<byte[], List<SendMessageResult>> joinGroup(
|
||||||
|
GroupInviteLinkUrl inviteLinkUrl
|
||||||
|
) throws IOException, GroupLinkNotActiveException {
|
||||||
|
return sendJoinGroupMessage(inviteLinkUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Pair<byte[], List<SendMessageResult>> sendJoinGroupMessage(
|
||||||
|
GroupInviteLinkUrl inviteLinkUrl
|
||||||
|
) throws IOException, GroupLinkNotActiveException {
|
||||||
|
final DecryptedGroupJoinInfo groupJoinInfo = groupHelper.getDecryptedGroupJoinInfo(inviteLinkUrl.getGroupMasterKey(),
|
||||||
|
inviteLinkUrl.getPassword());
|
||||||
|
final GroupChange groupChange = groupHelper.joinGroup(inviteLinkUrl.getGroupMasterKey(),
|
||||||
|
inviteLinkUrl.getPassword(),
|
||||||
|
groupJoinInfo);
|
||||||
|
final GroupInfoV2 group = getOrMigrateGroup(inviteLinkUrl.getGroupMasterKey(),
|
||||||
|
groupJoinInfo.getRevision() + 1,
|
||||||
|
groupChange.toByteArray());
|
||||||
|
|
||||||
|
if (group.getGroup() == null) {
|
||||||
|
// Only requested member, can't send update to group members
|
||||||
|
return new Pair<>(group.groupId, List.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
final Pair<Long, List<SendMessageResult>> result = sendUpdateGroupMessage(group, group.getGroup(), groupChange);
|
||||||
|
|
||||||
|
return new Pair<>(group.groupId, result.second());
|
||||||
|
}
|
||||||
|
|
||||||
private Pair<Long, List<SendMessageResult>> sendUpdateGroupMessage(
|
private Pair<Long, List<SendMessageResult>> sendUpdateGroupMessage(
|
||||||
GroupInfoV2 group, DecryptedGroup newDecryptedGroup, GroupChange groupChange
|
GroupInfoV2 group, DecryptedGroup newDecryptedGroup, GroupChange groupChange
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
|
@ -1584,48 +1613,12 @@ public class Manager implements Closeable {
|
||||||
final SignalServiceGroupV2 groupContext = message.getGroupContext().get().getGroupV2().get();
|
final SignalServiceGroupV2 groupContext = message.getGroupContext().get().getGroupV2().get();
|
||||||
final GroupMasterKey groupMasterKey = groupContext.getMasterKey();
|
final GroupMasterKey groupMasterKey = groupContext.getMasterKey();
|
||||||
|
|
||||||
final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey);
|
getOrMigrateGroup(groupMasterKey,
|
||||||
|
groupContext.getRevision(),
|
||||||
byte[] groupId = groupSecretParams.getPublicParams().getGroupIdentifier().serialize();
|
groupContext.hasSignedGroupChange() ? groupContext.getSignedGroupChange() : null);
|
||||||
GroupInfo groupInfo = account.getGroupStore().getGroupByV2Id(groupId);
|
|
||||||
if (groupInfo instanceof GroupInfoV1) {
|
|
||||||
// Received a v2 group message for a v2 group, we need to locally migrate the group
|
|
||||||
account.getGroupStore().deleteGroup(groupInfo.groupId);
|
|
||||||
GroupInfoV2 groupInfoV2 = new GroupInfoV2(groupId, groupMasterKey);
|
|
||||||
groupInfoV2.setGroup(getDecryptedGroup(groupSecretParams));
|
|
||||||
account.getGroupStore().updateGroup(groupInfoV2);
|
|
||||||
System.err.println("Locally migrated group "
|
|
||||||
+ Base64.encodeBytes(groupInfo.groupId)
|
|
||||||
+ " to group v2, id: "
|
|
||||||
+ Base64.encodeBytes(groupInfoV2.groupId)
|
|
||||||
+ " !!!");
|
|
||||||
} else if (groupInfo == null || groupInfo instanceof GroupInfoV2) {
|
|
||||||
GroupInfoV2 groupInfoV2 = groupInfo == null
|
|
||||||
? new GroupInfoV2(groupId, groupMasterKey)
|
|
||||||
: (GroupInfoV2) groupInfo;
|
|
||||||
|
|
||||||
if (groupInfoV2.getGroup() == null
|
|
||||||
|| groupInfoV2.getGroup().getRevision() < groupContext.getRevision()) {
|
|
||||||
DecryptedGroup group = null;
|
|
||||||
if (groupContext.hasSignedGroupChange()
|
|
||||||
&& groupInfoV2.getGroup() != null
|
|
||||||
&& groupInfoV2.getGroup().getRevision() + 1 == groupContext.getRevision()) {
|
|
||||||
group = groupHelper.getUpdatedDecryptedGroup(groupInfoV2.getGroup(),
|
|
||||||
groupContext.getSignedGroupChange(),
|
|
||||||
groupMasterKey);
|
|
||||||
if (group != null) {
|
|
||||||
storeProfileKeysFromMembers(group);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (group == null) {
|
|
||||||
group = getDecryptedGroup(groupSecretParams);
|
|
||||||
}
|
|
||||||
groupInfoV2.setGroup(group);
|
|
||||||
account.getGroupStore().updateGroup(groupInfoV2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final SignalServiceAddress conversationPartnerAddress = isSync ? destination : source;
|
final SignalServiceAddress conversationPartnerAddress = isSync ? destination : source;
|
||||||
if (message.isEndSession()) {
|
if (message.isEndSession()) {
|
||||||
handleEndSession(conversationPartnerAddress);
|
handleEndSession(conversationPartnerAddress);
|
||||||
|
@ -1708,16 +1701,47 @@ public class Manager implements Closeable {
|
||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
private DecryptedGroup getDecryptedGroup(final GroupSecretParams groupSecretParams) {
|
private GroupInfoV2 getOrMigrateGroup(
|
||||||
try {
|
final GroupMasterKey groupMasterKey, final int revision, final byte[] signedGroupChange
|
||||||
final GroupsV2AuthorizationString groupsV2AuthorizationString = getGroupAuthForToday(groupSecretParams);
|
) {
|
||||||
DecryptedGroup group = groupsV2Api.getGroup(groupSecretParams, groupsV2AuthorizationString);
|
final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey);
|
||||||
storeProfileKeysFromMembers(group);
|
|
||||||
return group;
|
byte[] groupId = groupSecretParams.getPublicParams().getGroupIdentifier().serialize();
|
||||||
} catch (IOException | VerificationFailedException | InvalidGroupStateException e) {
|
GroupInfo groupInfo = account.getGroupStore().getGroupByV2Id(groupId);
|
||||||
System.err.println("Failed to retrieve Group V2 info, ignoring ...");
|
final GroupInfoV2 groupInfoV2;
|
||||||
return null;
|
if (groupInfo instanceof GroupInfoV1) {
|
||||||
|
// Received a v2 group message for a v1 group, we need to locally migrate the group
|
||||||
|
account.getGroupStore().deleteGroup(groupInfo.groupId);
|
||||||
|
groupInfoV2 = new GroupInfoV2(groupId, groupMasterKey);
|
||||||
|
System.err.println("Locally migrated group "
|
||||||
|
+ Base64.encodeBytes(groupInfo.groupId)
|
||||||
|
+ " to group v2, id: "
|
||||||
|
+ Base64.encodeBytes(groupInfoV2.groupId)
|
||||||
|
+ " !!!");
|
||||||
|
} else if (groupInfo instanceof GroupInfoV2) {
|
||||||
|
groupInfoV2 = (GroupInfoV2) groupInfo;
|
||||||
|
} else {
|
||||||
|
groupInfoV2 = new GroupInfoV2(groupId, groupMasterKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (groupInfoV2.getGroup() == null || groupInfoV2.getGroup().getRevision() < revision) {
|
||||||
|
DecryptedGroup group = null;
|
||||||
|
if (signedGroupChange != null
|
||||||
|
&& groupInfoV2.getGroup() != null
|
||||||
|
&& groupInfoV2.getGroup().getRevision() + 1 == revision) {
|
||||||
|
group = groupHelper.getUpdatedDecryptedGroup(groupInfoV2.getGroup(), signedGroupChange, groupMasterKey);
|
||||||
|
}
|
||||||
|
if (group == null) {
|
||||||
|
group = groupHelper.getDecryptedGroup(groupSecretParams);
|
||||||
|
}
|
||||||
|
if (group != null) {
|
||||||
|
storeProfileKeysFromMembers(group);
|
||||||
|
}
|
||||||
|
groupInfoV2.setGroup(group);
|
||||||
|
account.getGroupStore().updateGroup(groupInfoV2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupInfoV2;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void storeProfileKeysFromMembers(final DecryptedGroup group) {
|
private void storeProfileKeysFromMembers(final DecryptedGroup group) {
|
||||||
|
|
|
@ -2,12 +2,15 @@ package org.asamk.signal.manager.helper;
|
||||||
|
|
||||||
import com.google.protobuf.InvalidProtocolBufferException;
|
import com.google.protobuf.InvalidProtocolBufferException;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.GroupLinkPassword;
|
||||||
import org.asamk.signal.storage.groups.GroupInfoV2;
|
import org.asamk.signal.storage.groups.GroupInfoV2;
|
||||||
import org.asamk.signal.util.IOUtils;
|
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.GroupChange;
|
||||||
import org.signal.storageservice.protos.groups.Member;
|
import org.signal.storageservice.protos.groups.Member;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
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.storageservice.protos.groups.local.DecryptedPendingMember;
|
||||||
import org.signal.zkgroup.InvalidInputException;
|
import org.signal.zkgroup.InvalidInputException;
|
||||||
import org.signal.zkgroup.VerificationFailedException;
|
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.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
|
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.GroupCandidate;
|
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.GroupsV2Api;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString;
|
import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
||||||
|
@ -66,6 +70,27 @@ public class GroupHelper {
|
||||||
this.groupAuthorizationProvider = groupAuthorizationProvider;
|
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(
|
public GroupInfoV2 createGroupV2(
|
||||||
String name, Collection<SignalServiceAddress> members, String avatarFile
|
String name, Collection<SignalServiceAddress> members, String avatarFile
|
||||||
) throws IOException {
|
) 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 {
|
public Pair<DecryptedGroup, GroupChange> acceptInvite(GroupInfoV2 groupInfoV2) throws IOException {
|
||||||
final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey());
|
final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey());
|
||||||
final GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(groupSecretParams);
|
final GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(groupSecretParams);
|
||||||
|
@ -284,13 +335,27 @@ public class GroupHelper {
|
||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
GroupChange signedGroupChange = groupsV2Api.patchGroup(change.build(),
|
GroupChange signedGroupChange = groupsV2Api.patchGroup(changeActions,
|
||||||
groupAuthorizationProvider.getAuthorizationForToday(groupSecretParams),
|
groupAuthorizationProvider.getAuthorizationForToday(groupSecretParams),
|
||||||
Optional.absent());
|
Optional.absent());
|
||||||
|
|
||||||
return new Pair<>(decryptedGroupState, signedGroupChange);
|
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(
|
public DecryptedGroup getUpdatedDecryptedGroup(
|
||||||
DecryptedGroup group, byte[] signedGroupChange, GroupMasterKey groupMasterKey
|
DecryptedGroup group, byte[] signedGroupChange, GroupMasterKey groupMasterKey
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -22,7 +22,9 @@ public class ErrorUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int handleTimestampAndSendMessageResults(long timestamp, List<SendMessageResult> results) {
|
public static int handleTimestampAndSendMessageResults(long timestamp, List<SendMessageResult> results) {
|
||||||
System.out.println(timestamp);
|
if (timestamp != 0) {
|
||||||
|
System.out.println(timestamp);
|
||||||
|
}
|
||||||
List<String> errors = getErrorMessagesFromSendMessageResults(results);
|
List<String> errors = getErrorMessagesFromSendMessageResults(results);
|
||||||
return handleSendMessageResultErrors(errors);
|
return handleSendMessageResultErrors(errors);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue