Join group via invite link.

This commit is contained in:
Alan Evans
2020-08-26 12:51:25 -03:00
committed by GitHub
parent b58376920f
commit 860f06ec9e
45 changed files with 2488 additions and 271 deletions

View File

@@ -0,0 +1,39 @@
package org.whispersystems.signalservice.api.groupsv2;
public interface ChangeSetModifier {
void removeAddMembers(int i);
void moveAddToPromote(int i);
void removeDeleteMembers(int i);
void removeModifyMemberRoles(int i);
void removeModifyMemberProfileKeys(int i);
void removeAddPendingMembers(int i);
void removeDeletePendingMembers(int i);
void removePromotePendingMembers(int i);
void clearModifyTitle();
void clearModifyAvatar();
void clearModifyDisappearingMessagesTimer();
void clearModifyAttributesAccess();
void clearModifyMemberAccess();
void clearModifyAddFromInviteLinkAccess();
void removeAddRequestingMembers(int i);
void moveAddRequestingMembersToPromote(int i);
void removeDeleteRequestingMembers(int i);
void removePromoteRequestingMembers(int i);
}

View File

@@ -0,0 +1,133 @@
package org.whispersystems.signalservice.api.groupsv2;
import com.google.protobuf.ByteString;
import org.signal.storageservice.protos.groups.Member;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedMember;
import org.signal.storageservice.protos.groups.local.DecryptedRequestingMember;
import java.util.ArrayList;
import java.util.List;
final class DecryptedGroupChangeActionsBuilderChangeSetModifier implements ChangeSetModifier {
private final DecryptedGroupChange.Builder result;
public DecryptedGroupChangeActionsBuilderChangeSetModifier(DecryptedGroupChange.Builder result) {
this.result = result;
}
@Override
public void removeAddMembers(int i) {
result.removeNewMembers(i);
}
@Override
public void moveAddToPromote(int i) {
DecryptedMember addMemberAction = result.getNewMembersList().get(i);
result.removeNewMembers(i);
result.addPromotePendingMembers(addMemberAction);
}
@Override
public void removeDeleteMembers(int i) {
List<ByteString> newList = removeIndexFromByteStringList(result.getDeleteMembersList(), i);
result.clearDeleteMembers()
.addAllDeleteMembers(newList);
}
@Override
public void removeModifyMemberRoles(int i) {
result.removeModifyMemberRoles(i);
}
@Override
public void removeModifyMemberProfileKeys(int i) {
result.removeModifiedProfileKeys(i);
}
@Override
public void removeAddPendingMembers(int i) {
result.removeNewPendingMembers(i);
}
@Override
public void removeDeletePendingMembers(int i) {
result.removeDeletePendingMembers(i);
}
@Override
public void removePromotePendingMembers(int i) {
result.removePromotePendingMembers(i);
}
@Override
public void clearModifyTitle() {
result.clearNewTitle();
}
@Override
public void clearModifyAvatar() {
result.clearNewAvatar();
}
@Override
public void clearModifyDisappearingMessagesTimer() {
result.clearNewTimer();
}
@Override
public void clearModifyAttributesAccess() {
result.clearNewAttributeAccess();
}
@Override
public void clearModifyMemberAccess() {
result.clearNewMemberAccess();
}
@Override
public void clearModifyAddFromInviteLinkAccess() {
result.clearNewInviteLinkAccess();
}
@Override
public void removeAddRequestingMembers(int i) {
result.removeNewRequestingMembers(i);
}
@Override
public void moveAddRequestingMembersToPromote(int i) {
DecryptedRequestingMember addMemberAction = result.getNewRequestingMembersList().get(i);
result.removeNewRequestingMembers(i);
DecryptedMember build = DecryptedMember.newBuilder()
.setUuid(addMemberAction.getUuid())
.setProfileKey(addMemberAction.getProfileKey())
.setRole(Member.Role.DEFAULT).build();
result.addPromotePendingMembers(0, build);
}
@Override
public void removeDeleteRequestingMembers(int i) {
List<ByteString> newList = removeIndexFromByteStringList(result.getDeleteRequestingMembersList(), i);
result.clearDeleteRequestingMembers()
.addAllDeleteRequestingMembers(newList);
}
@Override
public void removePromoteRequestingMembers(int i) {
result.removePromoteRequestingMembers(i);
}
private static List<ByteString> removeIndexFromByteStringList(List<ByteString> byteStrings, int i) {
List<ByteString> modifiedList = new ArrayList<>(byteStrings);
modifiedList.remove(i);
return modifiedList;
}
}

View File

@@ -186,6 +186,23 @@ public final class DecryptedGroupUtil {
return -1;
}
public static Optional<DecryptedRequestingMember> findRequestingByUuid(Collection<DecryptedRequestingMember> members, UUID uuid) {
ByteString uuidBytes = UuidUtil.toByteString(uuid);
for (DecryptedRequestingMember member : members) {
if (uuidBytes.equals(member.getUuid())) {
return Optional.of(member);
}
}
return Optional.absent();
}
public static boolean isPendingOrRequesting(DecryptedGroup group, UUID uuid) {
return findPendingByUuid(group.getPendingMembersList(), uuid).isPresent() ||
findRequestingByUuid(group.getRequestingMembersList(), uuid).isPresent();
}
/**
* Removes the uuid from the full members of a group.
* <p>

View File

@@ -0,0 +1,105 @@
package org.whispersystems.signalservice.api.groupsv2;
import org.signal.storageservice.protos.groups.GroupChange;
final class GroupChangeActionsBuilderChangeSetModifier implements ChangeSetModifier {
private final GroupChange.Actions.Builder result;
public GroupChangeActionsBuilderChangeSetModifier(GroupChange.Actions.Builder result) {
this.result = result;
}
@Override
public void removeAddMembers(int i) {
result.removeAddMembers(i);
}
@Override
public void moveAddToPromote(int i) {
GroupChange.Actions.AddMemberAction addMemberAction = result.getAddMembersList().get(i);
result.removeAddMembers(i);
result.addPromotePendingMembers(GroupChange.Actions.PromotePendingMemberAction.newBuilder().setPresentation(addMemberAction.getAdded().getPresentation()));
}
@Override
public void removeDeleteMembers(int i) {
result.removeDeleteMembers(i);
}
@Override
public void removeModifyMemberRoles(int i) {
result.removeModifyMemberRoles(i);
}
@Override
public void removeModifyMemberProfileKeys(int i) {
result.removeModifyMemberProfileKeys(i);
}
@Override
public void removeAddPendingMembers(int i) {
result.removeAddPendingMembers(i);
}
@Override
public void removeDeletePendingMembers(int i) {
result.removeDeletePendingMembers(i);
}
@Override
public void removePromotePendingMembers(int i) {
result.removePromotePendingMembers(i);
}
@Override
public void clearModifyTitle() {
result.clearModifyTitle();
}
@Override
public void clearModifyAvatar() {
result.clearModifyAvatar();
}
@Override
public void clearModifyDisappearingMessagesTimer() {
result.clearModifyDisappearingMessagesTimer();
}
@Override
public void clearModifyAttributesAccess() {
result.clearModifyAttributesAccess();
}
@Override
public void clearModifyMemberAccess() {
result.clearModifyMemberAccess();
}
@Override
public void clearModifyAddFromInviteLinkAccess() {
result.clearModifyAddFromInviteLinkAccess();
}
@Override
public void removeAddRequestingMembers(int i) {
result.removeAddRequestingMembers(i);
}
@Override
public void moveAddRequestingMembersToPromote(int i) {
GroupChange.Actions.AddRequestingMemberAction addMemberAction = result.getAddRequestingMembersList().get(i);
result.removeAddRequestingMembers(i);
result.addPromotePendingMembers(0, GroupChange.Actions.PromotePendingMemberAction.newBuilder().setPresentation(addMemberAction.getAdded().getPresentation()));
}
@Override
public void removeDeleteRequestingMembers(int i) {
result.removeDeleteRequestingMembers(i);
}
@Override
public void removePromoteRequestingMembers(int i) {
result.removePromoteRequestingMembers(i);
}
}

View File

@@ -64,7 +64,43 @@ public final class GroupChangeUtil {
DecryptedGroupChange conflictingChange,
GroupChange.Actions encryptedChange)
{
GroupChange.Actions.Builder result = GroupChange.Actions.newBuilder(encryptedChange);
GroupChange.Actions.Builder result = GroupChange.Actions.newBuilder(encryptedChange);
resolveConflict(groupState, conflictingChange, new GroupChangeActionsBuilderChangeSetModifier(result));
return result;
}
/**
* Given the latest group state and a conflicting change, decides which changes to carry forward
* and returns a new group change which could be empty.
* <p>
* Titles, avatars, and other settings are carried forward if they are different. Last writer wins.
* <p>
* Membership additions and removals also respect last writer wins and are removed if they have
* already been applied. e.g. you add someone but they are already added.
* <p>
* Membership additions will be altered to {@link DecryptedGroupChange} promotes if someone has
* invited them since.
*
* @param groupState Latest group state in plaintext.
* @param conflictingChange The potentially conflicting change in plaintext.
* @return A new change builder.
*/
public static DecryptedGroupChange.Builder resolveConflict(DecryptedGroup groupState,
DecryptedGroupChange conflictingChange)
{
DecryptedGroupChange.Builder result = DecryptedGroupChange.newBuilder(conflictingChange);
resolveConflict(groupState, conflictingChange, new DecryptedGroupChangeActionsBuilderChangeSetModifier(result));
return result;
}
private static void resolveConflict(DecryptedGroup groupState,
DecryptedGroupChange conflictingChange,
ChangeSetModifier changeSetModifier)
{
HashMap<ByteString, DecryptedMember> fullMembersByUuid = new HashMap<>(groupState.getMembersCount());
HashMap<ByteString, DecryptedPendingMember> pendingMembersByUuid = new HashMap<>(groupState.getPendingMembersCount());
HashMap<ByteString, DecryptedRequestingMember> requestingMembersByUuid = new HashMap<>(groupState.getMembersCount());
@@ -81,27 +117,25 @@ public final class GroupChangeUtil {
requestingMembersByUuid.put(member.getUuid(), member);
}
resolveField3AddMembers (conflictingChange, result, fullMembersByUuid, pendingMembersByUuid);
resolveField4DeleteMembers (conflictingChange, result, fullMembersByUuid);
resolveField5ModifyMemberRoles (conflictingChange, result, fullMembersByUuid);
resolveField6ModifyProfileKeys (conflictingChange, result, fullMembersByUuid);
resolveField7AddPendingMembers (conflictingChange, result, fullMembersByUuid, pendingMembersByUuid);
resolveField8DeletePendingMembers (conflictingChange, result, pendingMembersByUuid);
resolveField9PromotePendingMembers (conflictingChange, result, pendingMembersByUuid);
resolveField10ModifyTitle (groupState, conflictingChange, result);
resolveField11ModifyAvatar (groupState, conflictingChange, result);
resolveField12modifyDisappearingMessagesTimer(groupState, conflictingChange, result);
resolveField13modifyAttributesAccess (groupState, conflictingChange, result);
resolveField14modifyAttributesAccess (groupState, conflictingChange, result);
resolveField15modifyAddFromInviteLinkAccess (groupState, conflictingChange, result);
resolveField16AddRequestingMembers (conflictingChange, result, fullMembersByUuid, pendingMembersByUuid);
resolveField17DeleteMembers (conflictingChange, result, requestingMembersByUuid);
resolveField18PromoteRequestingMembers (conflictingChange, result, requestingMembersByUuid);
return result;
resolveField3AddMembers (conflictingChange, changeSetModifier, fullMembersByUuid, pendingMembersByUuid);
resolveField4DeleteMembers (conflictingChange, changeSetModifier, fullMembersByUuid);
resolveField5ModifyMemberRoles (conflictingChange, changeSetModifier, fullMembersByUuid);
resolveField6ModifyProfileKeys (conflictingChange, changeSetModifier, fullMembersByUuid);
resolveField7AddPendingMembers (conflictingChange, changeSetModifier, fullMembersByUuid, pendingMembersByUuid);
resolveField8DeletePendingMembers (conflictingChange, changeSetModifier, pendingMembersByUuid);
resolveField9PromotePendingMembers (conflictingChange, changeSetModifier, pendingMembersByUuid);
resolveField10ModifyTitle (groupState, conflictingChange, changeSetModifier);
resolveField11ModifyAvatar (groupState, conflictingChange, changeSetModifier);
resolveField12modifyDisappearingMessagesTimer(groupState, conflictingChange, changeSetModifier);
resolveField13modifyAttributesAccess (groupState, conflictingChange, changeSetModifier);
resolveField14modifyAttributesAccess (groupState, conflictingChange, changeSetModifier);
resolveField15modifyAddFromInviteLinkAccess (groupState, conflictingChange, changeSetModifier);
resolveField16AddRequestingMembers (conflictingChange, changeSetModifier, fullMembersByUuid, pendingMembersByUuid);
resolveField17DeleteMembers (conflictingChange, changeSetModifier, requestingMembersByUuid);
resolveField18PromoteRequestingMembers (conflictingChange, changeSetModifier, requestingMembersByUuid);
}
private static void resolveField3AddMembers(DecryptedGroupChange conflictingChange, GroupChange.Actions.Builder result, HashMap<ByteString, DecryptedMember> fullMembersByUuid, HashMap<ByteString, DecryptedPendingMember> pendingMembersByUuid) {
private static void resolveField3AddMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap<ByteString, DecryptedMember> fullMembersByUuid, HashMap<ByteString, DecryptedPendingMember> pendingMembersByUuid) {
List<DecryptedMember> newMembersList = conflictingChange.getNewMembersList();
for (int i = newMembersList.size() - 1; i >= 0; i--) {
@@ -110,14 +144,12 @@ public final class GroupChangeUtil {
if (fullMembersByUuid.containsKey(member.getUuid())) {
result.removeAddMembers(i);
} else if (pendingMembersByUuid.containsKey(member.getUuid())) {
GroupChange.Actions.AddMemberAction addMemberAction = result.getAddMembersList().get(i);
result.removeAddMembers(i);
result.addPromotePendingMembers(GroupChange.Actions.PromotePendingMemberAction.newBuilder().setPresentation(addMemberAction.getAdded().getPresentation()));
result.moveAddToPromote(i);
}
}
}
private static void resolveField4DeleteMembers(DecryptedGroupChange conflictingChange, GroupChange.Actions.Builder result, HashMap<ByteString, DecryptedMember> fullMembersByUuid) {
private static void resolveField4DeleteMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap<ByteString, DecryptedMember> fullMembersByUuid) {
List<ByteString> deletedMembersList = conflictingChange.getDeleteMembersList();
for (int i = deletedMembersList.size() - 1; i >= 0; i--) {
@@ -129,7 +161,7 @@ public final class GroupChangeUtil {
}
}
private static void resolveField5ModifyMemberRoles(DecryptedGroupChange conflictingChange, GroupChange.Actions.Builder result, HashMap<ByteString, DecryptedMember> fullMembersByUuid) {
private static void resolveField5ModifyMemberRoles(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap<ByteString, DecryptedMember> fullMembersByUuid) {
List<DecryptedModifyMemberRole> modifyRolesList = conflictingChange.getModifyMemberRolesList();
for (int i = modifyRolesList.size() - 1; i >= 0; i--) {
@@ -142,7 +174,7 @@ public final class GroupChangeUtil {
}
}
private static void resolveField6ModifyProfileKeys(DecryptedGroupChange conflictingChange, GroupChange.Actions.Builder result, HashMap<ByteString, DecryptedMember> fullMembersByUuid) {
private static void resolveField6ModifyProfileKeys(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap<ByteString, DecryptedMember> fullMembersByUuid) {
List<DecryptedMember> modifyProfileKeysList = conflictingChange.getModifiedProfileKeysList();
for (int i = modifyProfileKeysList.size() - 1; i >= 0; i--) {
@@ -155,7 +187,7 @@ public final class GroupChangeUtil {
}
}
private static void resolveField7AddPendingMembers(DecryptedGroupChange conflictingChange, GroupChange.Actions.Builder result, HashMap<ByteString, DecryptedMember> fullMembersByUuid, HashMap<ByteString, DecryptedPendingMember> pendingMembersByUuid) {
private static void resolveField7AddPendingMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap<ByteString, DecryptedMember> fullMembersByUuid, HashMap<ByteString, DecryptedPendingMember> pendingMembersByUuid) {
List<DecryptedPendingMember> newPendingMembersList = conflictingChange.getNewPendingMembersList();
for (int i = newPendingMembersList.size() - 1; i >= 0; i--) {
@@ -167,7 +199,7 @@ public final class GroupChangeUtil {
}
}
private static void resolveField8DeletePendingMembers(DecryptedGroupChange conflictingChange, GroupChange.Actions.Builder result, HashMap<ByteString, DecryptedPendingMember> pendingMembersByUuid) {
private static void resolveField8DeletePendingMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap<ByteString, DecryptedPendingMember> pendingMembersByUuid) {
List<DecryptedPendingMemberRemoval> deletePendingMembersList = conflictingChange.getDeletePendingMembersList();
for (int i = deletePendingMembersList.size() - 1; i >= 0; i--) {
@@ -179,7 +211,7 @@ public final class GroupChangeUtil {
}
}
private static void resolveField9PromotePendingMembers(DecryptedGroupChange conflictingChange, GroupChange.Actions.Builder result, HashMap<ByteString, DecryptedPendingMember> pendingMembersByUuid) {
private static void resolveField9PromotePendingMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap<ByteString, DecryptedPendingMember> pendingMembersByUuid) {
List<DecryptedMember> promotePendingMembersList = conflictingChange.getPromotePendingMembersList();
for (int i = promotePendingMembersList.size() - 1; i >= 0; i--) {
@@ -191,43 +223,43 @@ public final class GroupChangeUtil {
}
}
private static void resolveField10ModifyTitle(DecryptedGroup groupState, DecryptedGroupChange conflictingChange, GroupChange.Actions.Builder result) {
private static void resolveField10ModifyTitle(DecryptedGroup groupState, DecryptedGroupChange conflictingChange, ChangeSetModifier result) {
if (conflictingChange.hasNewTitle() && conflictingChange.getNewTitle().getValue().equals(groupState.getTitle())) {
result.clearModifyTitle();
}
}
private static void resolveField11ModifyAvatar(DecryptedGroup groupState, DecryptedGroupChange conflictingChange, GroupChange.Actions.Builder result) {
private static void resolveField11ModifyAvatar(DecryptedGroup groupState, DecryptedGroupChange conflictingChange, ChangeSetModifier result) {
if (conflictingChange.hasNewAvatar() && conflictingChange.getNewAvatar().getValue().equals(groupState.getAvatar())) {
result.clearModifyAvatar();
}
}
private static void resolveField12modifyDisappearingMessagesTimer(DecryptedGroup groupState, DecryptedGroupChange conflictingChange, GroupChange.Actions.Builder result) {
private static void resolveField12modifyDisappearingMessagesTimer(DecryptedGroup groupState, DecryptedGroupChange conflictingChange, ChangeSetModifier result) {
if (conflictingChange.hasNewTimer() && conflictingChange.getNewTimer().getDuration() == groupState.getDisappearingMessagesTimer().getDuration()) {
result.clearModifyDisappearingMessagesTimer();
}
}
private static void resolveField13modifyAttributesAccess(DecryptedGroup groupState, DecryptedGroupChange conflictingChange, GroupChange.Actions.Builder result) {
private static void resolveField13modifyAttributesAccess(DecryptedGroup groupState, DecryptedGroupChange conflictingChange, ChangeSetModifier result) {
if (conflictingChange.getNewAttributeAccess() == groupState.getAccessControl().getAttributes()) {
result.clearModifyAttributesAccess();
}
}
private static void resolveField14modifyAttributesAccess(DecryptedGroup groupState, DecryptedGroupChange conflictingChange, GroupChange.Actions.Builder result) {
private static void resolveField14modifyAttributesAccess(DecryptedGroup groupState, DecryptedGroupChange conflictingChange, ChangeSetModifier result) {
if (conflictingChange.getNewMemberAccess() == groupState.getAccessControl().getMembers()) {
result.clearModifyMemberAccess();
}
}
private static void resolveField15modifyAddFromInviteLinkAccess(DecryptedGroup groupState, DecryptedGroupChange conflictingChange, GroupChange.Actions.Builder result) {
private static void resolveField15modifyAddFromInviteLinkAccess(DecryptedGroup groupState, DecryptedGroupChange conflictingChange, ChangeSetModifier result) {
if (conflictingChange.getNewInviteLinkAccess() == groupState.getAccessControl().getAddFromInviteLink()) {
result.clearModifyAddFromInviteLinkAccess();
}
}
private static void resolveField16AddRequestingMembers(DecryptedGroupChange conflictingChange, GroupChange.Actions.Builder result, HashMap<ByteString, DecryptedMember> fullMembersByUuid, HashMap<ByteString, DecryptedPendingMember> pendingMembersByUuid) {
private static void resolveField16AddRequestingMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap<ByteString, DecryptedMember> fullMembersByUuid, HashMap<ByteString, DecryptedPendingMember> pendingMembersByUuid) {
List<DecryptedRequestingMember> newMembersList = conflictingChange.getNewRequestingMembersList();
for (int i = newMembersList.size() - 1; i >= 0; i--) {
@@ -236,15 +268,13 @@ public final class GroupChangeUtil {
if (fullMembersByUuid.containsKey(member.getUuid())) {
result.removeAddRequestingMembers(i);
} else if (pendingMembersByUuid.containsKey(member.getUuid())) {
GroupChange.Actions.AddRequestingMemberAction addMemberAction = result.getAddRequestingMembersList().get(i);
result.removeAddRequestingMembers(i);
result.addPromotePendingMembers(0, GroupChange.Actions.PromotePendingMemberAction.newBuilder().setPresentation(addMemberAction.getAdded().getPresentation()));
result.moveAddRequestingMembersToPromote(i);
}
}
}
private static void resolveField17DeleteMembers(DecryptedGroupChange conflictingChange,
GroupChange.Actions.Builder result,
ChangeSetModifier result,
HashMap<ByteString, DecryptedRequestingMember> requestingMembers)
{
List<ByteString> deletedMembersList = conflictingChange.getDeleteRequestingMembersList();
@@ -259,7 +289,7 @@ public final class GroupChangeUtil {
}
private static void resolveField18PromoteRequestingMembers(DecryptedGroupChange conflictingChange,
GroupChange.Actions.Builder result,
ChangeSetModifier result,
HashMap<ByteString, DecryptedRequestingMember> requestingMembersByUuid)
{
List<DecryptedApproveMember> promoteRequestingMembersList = conflictingChange.getPromoteRequestingMembersList();

View File

@@ -7,6 +7,10 @@ package org.whispersystems.signalservice.api.groupsv2;
* - the master key does not match a group on the server
*/
public final class GroupLinkNotActiveException extends Exception {
GroupLinkNotActiveException() {
public GroupLinkNotActiveException() {
}
public GroupLinkNotActiveException(Throwable t) {
super(t);
}
}

View File

@@ -114,7 +114,7 @@ public final class GroupsV2Api {
}
public DecryptedGroupJoinInfo getGroupJoinInfo(GroupSecretParams groupSecretParams,
byte[] password,
Optional<byte[]> password,
GroupsV2AuthorizationString authorization)
throws IOException, GroupLinkNotActiveException
{
@@ -148,10 +148,11 @@ public final class GroupsV2Api {
}
public GroupChange patchGroup(GroupChange.Actions groupChange,
GroupsV2AuthorizationString authorization)
GroupsV2AuthorizationString authorization,
Optional<byte[]> groupLinkPassword)
throws IOException
{
return socket.patchGroupsV2Group(groupChange, authorization.toString());
return socket.patchGroupsV2Group(groupChange, authorization.toString(), groupLinkPassword);
}
private static HashMap<Integer, AuthCredentialResponse> parseCredentialResponse(CredentialResponse credentialResponse)

View File

@@ -188,8 +188,7 @@ public final class GroupsV2Operations {
actions.addAddMembers(GroupChange.Actions.AddMemberAction
.newBuilder()
.setAdded(groupOperations.member(profileKeyCredential, Member.Role.DEFAULT))
.setJoinFromInviteLink(true));
.setAdded(groupOperations.member(profileKeyCredential, Member.Role.DEFAULT)));
return actions;
}
@@ -574,6 +573,7 @@ public final class GroupsV2Operations {
.setMemberCount(joinInfo.getMemberCount())
.setAddFromInviteLink(joinInfo.getAddFromInviteLink())
.setRevision(joinInfo.getRevision())
.setPendingAdminApproval(joinInfo.getPendingAdminApproval())
.build();
}

View File

@@ -1957,11 +1957,14 @@ public class PushServiceSocket {
return AvatarUploadAttributes.parseFrom(readBodyBytes(response));
}
public GroupChange patchGroupsV2Group(GroupChange.Actions groupChange, String authorization)
public GroupChange patchGroupsV2Group(GroupChange.Actions groupChange, String authorization, Optional<byte[]> groupLinkPassword)
throws NonSuccessfulResponseCodeException, PushNetworkException, InvalidProtocolBufferException
{
String path = groupLinkPassword.transform(p -> String.format(GROUPSV2_GROUP_PASSWORD, Base64UrlSafe.encodeBytesWithoutPadding(p)))
.or(GROUPSV2_GROUP);
ResponseBody response = makeStorageRequest(authorization,
GROUPSV2_GROUP,
path,
"PATCH",
protobufRequestBody(groupChange),
GROUPS_V2_PATCH_RESPONSE_HANDLER);
@@ -1981,14 +1984,15 @@ public class PushServiceSocket {
return GroupChanges.parseFrom(readBodyBytes(response));
}
public GroupJoinInfo getGroupJoinInfo(byte[] groupLinkPassword, GroupsV2AuthorizationString authorization)
public GroupJoinInfo getGroupJoinInfo(Optional<byte[]> groupLinkPassword, GroupsV2AuthorizationString authorization)
throws NonSuccessfulResponseCodeException, PushNetworkException, InvalidProtocolBufferException
{
ResponseBody response = makeStorageRequest(authorization.toString(),
String.format(GROUPSV2_GROUP_JOIN, Base64UrlSafe.encodeBytesWithoutPadding(groupLinkPassword)),
"GET",
null,
GROUPS_V2_GET_JOIN_INFO_HANDLER);
String passwordParam = groupLinkPassword.transform(Base64UrlSafe::encodeBytesWithoutPadding).or("");
ResponseBody response = makeStorageRequest(authorization.toString(),
String.format(GROUPSV2_GROUP_JOIN, passwordParam),
"GET",
null,
GROUPS_V2_GET_JOIN_INFO_HANDLER);
return GroupJoinInfo.parseFrom(readBodyBytes(response));
}

View File

@@ -95,9 +95,10 @@ message DecryptedTimer {
}
message DecryptedGroupJoinInfo {
string title = 2;
string avatar = 3;
uint32 memberCount = 4;
AccessControl.AccessRequired addFromInviteLink = 5;
uint32 revision = 6;
string title = 2;
string avatar = 3;
uint32 memberCount = 4;
AccessControl.AccessRequired addFromInviteLink = 5;
uint32 revision = 6;
bool pendingAdminApproval = 7;
}

View File

@@ -202,10 +202,11 @@ message GroupInviteLink {
}
message GroupJoinInfo {
bytes publicKey = 1;
bytes title = 2;
string avatar = 3;
uint32 memberCount = 4;
AccessControl.AccessRequired addFromInviteLink = 5;
uint32 revision = 6;
bytes publicKey = 1;
bytes title = 2;
string avatar = 3;
uint32 memberCount = 4;
AccessControl.AccessRequired addFromInviteLink = 5;
uint32 revision = 6;
bool pendingAdminApproval = 7;
}

View File

@@ -39,7 +39,7 @@ public final class GroupChangeUtil_resolveConflict_Test {
/**
* Reflects over the generated protobuf class and ensures that no new fields have been added since we wrote this.
* <p>
* If we didn't, newly added fields would not be resolved by {@link GroupChangeUtil#resolveConflict}.
* If we didn't, newly added fields would not be resolved by {@link GroupChangeUtil#resolveConflict(DecryptedGroup, DecryptedGroupChange, GroupChange.Actions)}.
*/
@Test
public void ensure_resolveConflict_knows_about_all_fields_of_DecryptedGroupChange() {
@@ -52,7 +52,7 @@ public final class GroupChangeUtil_resolveConflict_Test {
/**
* Reflects over the generated protobuf class and ensures that no new fields have been added since we wrote this.
* <p>
* If we didn't, newly added fields would not be resolved by {@link GroupChangeUtil#resolveConflict}.
* If we didn't, newly added fields would not be resolved by {@link GroupChangeUtil#resolveConflict(DecryptedGroup, DecryptedGroupChange, GroupChange.Actions)}.
*/
@Test
public void ensure_resolveConflict_knows_about_all_fields_of_GroupChange() {
@@ -65,7 +65,7 @@ public final class GroupChangeUtil_resolveConflict_Test {
/**
* Reflects over the generated protobuf class and ensures that no new fields have been added since we wrote this.
* <p>
* If we didn't, newly added fields would not be resolved by {@link GroupChangeUtil#resolveConflict}.
* If we didn't, newly added fields would not be resolved by {@link GroupChangeUtil#resolveConflict(DecryptedGroup, DecryptedGroupChange, GroupChange.Actions)}.
*/
@Test
public void ensure_resolveConflict_knows_about_all_fields_of_DecryptedGroup() {

View File

@@ -0,0 +1,546 @@
package org.whispersystems.signalservice.api.groupsv2;
import com.google.protobuf.ByteString;
import org.junit.Test;
import org.signal.storageservice.protos.groups.AccessControl;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedString;
import org.signal.storageservice.protos.groups.local.DecryptedTimer;
import org.signal.zkgroup.profiles.ProfileKey;
import org.whispersystems.signalservice.api.util.UuidUtil;
import org.whispersystems.signalservice.internal.util.Util;
import java.util.UUID;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.admin;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.approveMember;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.demoteAdmin;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.member;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.pendingMember;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.pendingMemberRemoval;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.promoteAdmin;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.randomProfileKey;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.requestingMember;
import static org.whispersystems.signalservice.api.groupsv2.ProtobufTestUtils.getMaxDeclaredFieldNumber;
public final class GroupChangeUtil_resolveConflict_decryptedOnly_Test {
/**
* Reflects over the generated protobuf class and ensures that no new fields have been added since we wrote this.
* <p>
* If we didn't, newly added fields would not be resolved by {@link GroupChangeUtil#resolveConflict(DecryptedGroup, DecryptedGroupChange)}.
*/
@Test
public void ensure_resolveConflict_knows_about_all_fields_of_DecryptedGroupChange() {
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class);
assertEquals("GroupChangeUtil#resolveConflict and its tests need updating to account for new fields on " + DecryptedGroupChange.class.getName(),
19, maxFieldFound);
}
/**
* Reflects over the generated protobuf class and ensures that no new fields have been added since we wrote this.
* <p>
* If we didn't, newly added fields would not be resolved by {@link GroupChangeUtil#resolveConflict(DecryptedGroup, DecryptedGroupChange)}.
*/
@Test
public void ensure_resolveConflict_knows_about_all_fields_of_DecryptedGroup() {
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroup.class);
assertEquals("GroupChangeUtil#resolveConflict and its tests need updating to account for new fields on " + DecryptedGroup.class.getName(),
10, maxFieldFound);
}
@Test
public void field_3__changes_to_add_existing_members_are_excluded() {
UUID member1 = UUID.randomUUID();
UUID member2 = UUID.randomUUID();
UUID member3 = UUID.randomUUID();
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addMembers(member(member1))
.addMembers(member(member3))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addNewMembers(member(member1))
.addNewMembers(member(member2))
.addNewMembers(member(member3))
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
DecryptedGroupChange expected = DecryptedGroupChange.newBuilder()
.addNewMembers(member(member2))
.build();
assertEquals(expected, resolvedChanges);
}
@Test
public void field_4__changes_to_remove_missing_members_are_excluded() {
UUID member1 = UUID.randomUUID();
UUID member2 = UUID.randomUUID();
UUID member3 = UUID.randomUUID();
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addMembers(member(member2))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addDeleteMembers(UuidUtil.toByteString(member1))
.addDeleteMembers(UuidUtil.toByteString(member2))
.addDeleteMembers(UuidUtil.toByteString(member3))
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
DecryptedGroupChange expected = DecryptedGroupChange.newBuilder()
.addDeleteMembers(UuidUtil.toByteString(member2))
.build();
assertEquals(expected, resolvedChanges);
}
@Test
public void field_5__role_change_is_preserved() {
UUID member1 = UUID.randomUUID();
UUID member2 = UUID.randomUUID();
UUID member3 = UUID.randomUUID();
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addMembers(admin(member1))
.addMembers(member(member2))
.addMembers(member(member3))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addModifyMemberRoles(demoteAdmin(member1))
.addModifyMemberRoles(promoteAdmin(member2))
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
assertEquals(decryptedChange, resolvedChanges);
}
@Test
public void field_5__unnecessary_role_changes_removed() {
UUID member1 = UUID.randomUUID();
UUID member2 = UUID.randomUUID();
UUID member3 = UUID.randomUUID();
UUID memberNotInGroup = UUID.randomUUID();
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addMembers(admin(member1))
.addMembers(member(member2))
.addMembers(member(member3))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addModifyMemberRoles(promoteAdmin(member1))
.addModifyMemberRoles(promoteAdmin(member2))
.addModifyMemberRoles(demoteAdmin(member3))
.addModifyMemberRoles(promoteAdmin(memberNotInGroup))
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
DecryptedGroupChange expected = DecryptedGroupChange.newBuilder()
.addModifyMemberRoles(promoteAdmin(member2))
.build();
assertEquals(expected, resolvedChanges);
}
@Test
public void field_6__profile_key_changes() {
UUID member1 = UUID.randomUUID();
UUID member2 = UUID.randomUUID();
UUID member3 = UUID.randomUUID();
UUID memberNotInGroup = UUID.randomUUID();
ProfileKey profileKey1 = randomProfileKey();
ProfileKey profileKey2 = randomProfileKey();
ProfileKey profileKey3 = randomProfileKey();
ProfileKey profileKey4 = randomProfileKey();
ProfileKey profileKey2b = randomProfileKey();
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addMembers(member(member1, profileKey1))
.addMembers(member(member2, profileKey2))
.addMembers(member(member3, profileKey3))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addModifiedProfileKeys(member(member1, profileKey1))
.addModifiedProfileKeys(member(member2, profileKey2b))
.addModifiedProfileKeys(member(member3, profileKey3))
.addModifiedProfileKeys(member(memberNotInGroup, profileKey4))
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
DecryptedGroupChange expected = DecryptedGroupChange.newBuilder()
.addModifiedProfileKeys(member(member2, profileKey2b))
.build();
assertEquals(expected, resolvedChanges);
}
@Test
public void field_7__add_pending_members() {
UUID member1 = UUID.randomUUID();
UUID member2 = UUID.randomUUID();
UUID member3 = UUID.randomUUID();
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addMembers(member(member1))
.addPendingMembers(pendingMember(member3))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addNewPendingMembers(pendingMember(member1))
.addNewPendingMembers(pendingMember(member2))
.addNewPendingMembers(pendingMember(member3))
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
DecryptedGroupChange expected = DecryptedGroupChange.newBuilder()
.addNewPendingMembers(pendingMember(member2))
.build();
assertEquals(expected, resolvedChanges);
}
@Test
public void field_8__delete_pending_members() {
UUID member1 = UUID.randomUUID();
UUID member2 = UUID.randomUUID();
UUID member3 = UUID.randomUUID();
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addMembers(member(member1))
.addPendingMembers(pendingMember(member2))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addDeletePendingMembers(pendingMemberRemoval(member1))
.addDeletePendingMembers(pendingMemberRemoval(member2))
.addDeletePendingMembers(pendingMemberRemoval(member3))
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
DecryptedGroupChange expected = DecryptedGroupChange.newBuilder()
.addDeletePendingMembers(pendingMemberRemoval(member2))
.build();
assertEquals(expected, resolvedChanges);
}
@Test
public void field_9__promote_pending_members() {
UUID member1 = UUID.randomUUID();
UUID member2 = UUID.randomUUID();
UUID member3 = UUID.randomUUID();
ProfileKey profileKey2 = randomProfileKey();
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addMembers(member(member1))
.addPendingMembers(pendingMember(member2))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addPromotePendingMembers(member(member1))
.addPromotePendingMembers(member(member2, profileKey2))
.addPromotePendingMembers(member(member3))
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
DecryptedGroupChange expected = DecryptedGroupChange.newBuilder()
.addPromotePendingMembers(member(member2, profileKey2))
.build();
assertEquals(expected, resolvedChanges);
}
@Test
public void field_3_to_9__add_of_pending_member_converted_to_a_promote() {
UUID member1 = UUID.randomUUID();
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addPendingMembers(pendingMember(member1))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addNewMembers(member(member1))
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
DecryptedGroupChange expected = DecryptedGroupChange.newBuilder()
.addPromotePendingMembers(member(member1))
.build();
assertEquals(expected, resolvedChanges);
}
@Test
public void field_10__title_change_is_preserved() {
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.setTitle("Existing title")
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.setNewTitle(DecryptedString.newBuilder().setValue("New title").build())
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
assertEquals(decryptedChange, resolvedChanges);
}
@Test
public void field_10__no_title_change_is_removed() {
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.setTitle("Existing title")
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.setNewTitle(DecryptedString.newBuilder().setValue("Existing title").build())
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
assertTrue(DecryptedGroupUtil.changeIsEmpty(resolvedChanges));
}
@Test
public void field_11__avatar_change_is_preserved() {
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.setAvatar("Existing avatar")
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.setNewAvatar(DecryptedString.newBuilder().setValue("New avatar").build())
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
assertEquals(decryptedChange, resolvedChanges);
}
@Test
public void field_11__no_avatar_change_is_removed() {
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.setAvatar("Existing avatar")
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.setNewAvatar(DecryptedString.newBuilder().setValue("Existing avatar").build())
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
assertTrue(DecryptedGroupUtil.changeIsEmpty(resolvedChanges));
}
@Test
public void field_12__timer_change_is_preserved() {
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.setDisappearingMessagesTimer(DecryptedTimer.newBuilder().setDuration(123))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.setNewTimer(DecryptedTimer.newBuilder().setDuration(456))
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
assertEquals(decryptedChange, resolvedChanges);
}
@Test
public void field_12__no_timer_change_is_removed() {
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.setDisappearingMessagesTimer(DecryptedTimer.newBuilder().setDuration(123))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.setNewTimer(DecryptedTimer.newBuilder().setDuration(123))
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
assertTrue(DecryptedGroupUtil.changeIsEmpty(resolvedChanges));
}
@Test
public void field_13__attribute_access_change_is_preserved() {
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.setAccessControl(AccessControl.newBuilder().setAttributes(AccessControl.AccessRequired.ADMINISTRATOR))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.setNewAttributeAccess(AccessControl.AccessRequired.MEMBER)
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
assertEquals(decryptedChange, resolvedChanges);
}
@Test
public void field_13__no_attribute_access_change_is_removed() {
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.setAccessControl(AccessControl.newBuilder().setAttributes(AccessControl.AccessRequired.ADMINISTRATOR))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.setNewAttributeAccess(AccessControl.AccessRequired.ADMINISTRATOR)
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
assertTrue(DecryptedGroupUtil.changeIsEmpty(resolvedChanges));
}
@Test
public void field_14__membership_access_change_is_preserved() {
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.setAccessControl(AccessControl.newBuilder().setMembers(AccessControl.AccessRequired.ADMINISTRATOR))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.setNewMemberAccess(AccessControl.AccessRequired.MEMBER)
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
assertEquals(decryptedChange, resolvedChanges);
}
@Test
public void field_14__no_membership_access_change_is_removed() {
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.setAccessControl(AccessControl.newBuilder().setMembers(AccessControl.AccessRequired.ADMINISTRATOR))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.setNewMemberAccess(AccessControl.AccessRequired.ADMINISTRATOR)
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
assertTrue(DecryptedGroupUtil.changeIsEmpty(resolvedChanges));
}
@Test
public void field_15__no_membership_access_change_is_removed() {
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.setAccessControl(AccessControl.newBuilder().setAddFromInviteLink(AccessControl.AccessRequired.ADMINISTRATOR))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.setNewInviteLinkAccess(AccessControl.AccessRequired.ADMINISTRATOR)
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
assertTrue(DecryptedGroupUtil.changeIsEmpty(resolvedChanges));
}
@Test
public void field_16__changes_to_add_requesting_members_when_full_members_are_removed() {
UUID member1 = UUID.randomUUID();
UUID member2 = UUID.randomUUID();
UUID member3 = UUID.randomUUID();
ProfileKey profileKey2 = randomProfileKey();
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addMembers(member(member1))
.addMembers(member(member3))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addNewRequestingMembers(requestingMember(member1))
.addNewRequestingMembers(requestingMember(member2, profileKey2))
.addNewRequestingMembers(requestingMember(member3))
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
DecryptedGroupChange expected = DecryptedGroupChange.newBuilder()
.addNewRequestingMembers(requestingMember(member2, profileKey2))
.build();
assertEquals(expected, resolvedChanges);
}
@Test
public void field_16__changes_to_add_requesting_members_when_pending_are_promoted() {
UUID member1 = UUID.randomUUID();
UUID member2 = UUID.randomUUID();
UUID member3 = UUID.randomUUID();
ProfileKey profileKey1 = randomProfileKey();
ProfileKey profileKey2 = randomProfileKey();
ProfileKey profileKey3 = randomProfileKey();
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addPendingMembers(pendingMember(member1))
.addPendingMembers(pendingMember(member3))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addNewRequestingMembers(requestingMember(member1, profileKey1))
.addNewRequestingMembers(requestingMember(member2, profileKey2))
.addNewRequestingMembers(requestingMember(member3, profileKey3))
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
DecryptedGroupChange expected = DecryptedGroupChange.newBuilder()
.addPromotePendingMembers(member(member1, profileKey1))
.addNewRequestingMembers(requestingMember(member2, profileKey2))
.addPromotePendingMembers(member(member3, profileKey3))
.build();
assertEquals(expected, resolvedChanges);
}
@Test
public void field_17__changes_to_remove_missing_requesting_members_are_excluded() {
UUID member1 = UUID.randomUUID();
UUID member2 = UUID.randomUUID();
UUID member3 = UUID.randomUUID();
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addRequestingMembers(requestingMember(member2))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addDeleteRequestingMembers(UuidUtil.toByteString(member1))
.addDeleteRequestingMembers(UuidUtil.toByteString(member2))
.addDeleteRequestingMembers(UuidUtil.toByteString(member3))
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
DecryptedGroupChange expected = DecryptedGroupChange.newBuilder()
.addDeleteRequestingMembers(UuidUtil.toByteString(member2))
.build();
assertEquals(expected, resolvedChanges);
}
@Test
public void field_18__promote_requesting_members() {
UUID member1 = UUID.randomUUID();
UUID member2 = UUID.randomUUID();
UUID member3 = UUID.randomUUID();
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addMembers(member(member1))
.addRequestingMembers(requestingMember(member2))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addPromoteRequestingMembers(approveMember(member1))
.addPromoteRequestingMembers(approveMember(member2))
.addPromoteRequestingMembers(approveMember(member3))
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
DecryptedGroupChange expected = DecryptedGroupChange.newBuilder()
.addPromoteRequestingMembers(approveMember(member2))
.build();
assertEquals(expected, resolvedChanges);
}
@Test
public void field_19__password_change_is_kept() {
ByteString password1 = ByteString.copyFrom(Util.getSecretBytes(16));
ByteString password2 = ByteString.copyFrom(Util.getSecretBytes(16));
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.setInviteLinkPassword(password1)
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.setNewInviteLinkPassword(password2)
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
assertEquals(decryptedChange, resolvedChanges);
}
}

View File

@@ -12,6 +12,8 @@ import org.whispersystems.signalservice.internal.util.Util;
import org.whispersystems.signalservice.testutil.ZkGroupLibraryUtil;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.whispersystems.signalservice.api.groupsv2.ProtobufTestUtils.getMaxDeclaredFieldNumber;
public final class GroupsV2Operations_decrypt_groupJoinInfo_Test {
@@ -39,7 +41,7 @@ public final class GroupsV2Operations_decrypt_groupJoinInfo_Test {
int maxFieldFound = getMaxDeclaredFieldNumber(GroupJoinInfo.class);
assertEquals("GroupOperations and its tests need updating to account for new fields on " + GroupJoinInfo.class.getName(),
6, maxFieldFound);
7, maxFieldFound);
}
@Test
@@ -107,4 +109,26 @@ public final class GroupsV2Operations_decrypt_groupJoinInfo_Test {
assertEquals(11, decryptedGroupJoinInfo.getRevision());
}
@Test
public void pending_approval_passed_though_7_true() {
GroupJoinInfo groupJoinInfo = GroupJoinInfo.newBuilder()
.setPendingAdminApproval(true)
.build();
DecryptedGroupJoinInfo decryptedGroupJoinInfo = groupOperations.decryptGroupJoinInfo(groupJoinInfo);
assertTrue(decryptedGroupJoinInfo.getPendingAdminApproval());
}
@Test
public void pending_approval_passed_though_7_false() {
GroupJoinInfo groupJoinInfo = GroupJoinInfo.newBuilder()
.setPendingAdminApproval(false)
.build();
DecryptedGroupJoinInfo decryptedGroupJoinInfo = groupOperations.decryptGroupJoinInfo(groupJoinInfo);
assertFalse(decryptedGroupJoinInfo.getPendingAdminApproval());
}
}