mirror of
https://github.com/oxen-io/session-android.git
synced 2025-08-25 23:37:27 +00:00
Join group via invite link.
This commit is contained in:
@@ -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);
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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>
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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();
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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();
|
||||
}
|
||||
|
||||
|
@@ -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));
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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() {
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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());
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user