Skip delete actions where the removed member/pending member is not in the group.
This commit is contained in:
parent
b5656aa5dd
commit
fbfa3abffd
2 changed files with 187 additions and 43 deletions
|
@ -11,6 +11,7 @@ import org.signal.storageservice.protos.groups.local.DecryptedModifyMemberRole;
|
|||
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedPendingMemberRemoval;
|
||||
import org.signal.zkgroup.util.UUIDUtil;
|
||||
import org.whispersystems.libsignal.logging.Log;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
|
||||
|
@ -24,6 +25,8 @@ import java.util.UUID;
|
|||
|
||||
public final class DecryptedGroupUtil {
|
||||
|
||||
private static final String TAG = DecryptedGroupUtil.class.getSimpleName();
|
||||
|
||||
static final int MAX_CHANGE_FIELD = 14;
|
||||
|
||||
public static ArrayList<UUID> toUuidList(Collection<DecryptedMember> membersList) {
|
||||
|
@ -194,21 +197,57 @@ public final class DecryptedGroupUtil {
|
|||
public static DecryptedGroup applyWithoutRevisionCheck(DecryptedGroup group, DecryptedGroupChange change)
|
||||
throws NotAbleToApplyGroupV2ChangeException
|
||||
{
|
||||
DecryptedGroup.Builder builder = DecryptedGroup.newBuilder(group);
|
||||
DecryptedGroup.Builder builder = DecryptedGroup.newBuilder(group)
|
||||
.setRevision(change.getRevision());
|
||||
|
||||
builder.addAllMembers(change.getNewMembersList());
|
||||
applyAddMemberAction(builder, change.getNewMembersList());
|
||||
|
||||
for (ByteString removedMember : change.getDeleteMembersList()) {
|
||||
applyDeleteMemberActions(builder, change.getDeleteMembersList());
|
||||
|
||||
applyModifyMemberRoleActions(builder, change.getModifyMemberRolesList());
|
||||
|
||||
applyModifyMemberProfileKeyActions(builder, change.getModifiedProfileKeysList());
|
||||
|
||||
applyAddPendingMemberActions(builder, change.getNewPendingMembersList());
|
||||
|
||||
applyDeletePendingMemberActions(builder, change.getDeletePendingMembersList());
|
||||
|
||||
applyPromotePendingMemberActions(builder, change.getPromotePendingMembersList());
|
||||
|
||||
applyModifyTitleAction(builder, change);
|
||||
|
||||
applyModifyAvatarAction(builder, change);
|
||||
|
||||
applyModifyDisappearingMessagesTimerAction(builder, change);
|
||||
|
||||
applyModifyAttributesAccessControlAction(builder, change);
|
||||
|
||||
applyModifyMembersAccessControlAction(builder, change);
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static void applyAddMemberAction(DecryptedGroup.Builder builder, List<DecryptedMember> newMembersList) {
|
||||
builder.addAllMembers(newMembersList);
|
||||
|
||||
removePendingMembersNowInGroup(builder);
|
||||
}
|
||||
|
||||
protected static void applyDeleteMemberActions(DecryptedGroup.Builder builder, List<ByteString> deleteMembersList) {
|
||||
for (ByteString removedMember : deleteMembersList) {
|
||||
int index = indexOfUuid(builder.getMembersList(), removedMember);
|
||||
|
||||
if (index == -1) {
|
||||
throw new NotAbleToApplyGroupV2ChangeException();
|
||||
Log.w(TAG, "Deleted member on change not found in group");
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.removeMembers(index);
|
||||
}
|
||||
}
|
||||
|
||||
for (DecryptedModifyMemberRole modifyMemberRole : change.getModifyMemberRolesList()) {
|
||||
private static void applyModifyMemberRoleActions(DecryptedGroup.Builder builder, List<DecryptedModifyMemberRole> modifyMemberRolesList) throws NotAbleToApplyGroupV2ChangeException {
|
||||
for (DecryptedModifyMemberRole modifyMemberRole : modifyMemberRolesList) {
|
||||
int index = indexOfUuid(builder.getMembersList(), modifyMemberRole.getUuid());
|
||||
|
||||
if (index == -1) {
|
||||
|
@ -221,28 +260,50 @@ public final class DecryptedGroupUtil {
|
|||
|
||||
builder.setMembers(index, DecryptedMember.newBuilder(builder.getMembers(index)).setRole(modifyMemberRole.getRole()).build());
|
||||
}
|
||||
}
|
||||
|
||||
for (DecryptedMember modifyProfileKey : change.getModifiedProfileKeysList()) {
|
||||
private static void applyModifyMemberProfileKeyActions(DecryptedGroup.Builder builder, List<DecryptedMember> modifiedProfileKeysList) throws NotAbleToApplyGroupV2ChangeException {
|
||||
for (DecryptedMember modifyProfileKey : modifiedProfileKeysList) {
|
||||
int index = indexOfUuid(builder.getMembersList(), modifyProfileKey.getUuid());
|
||||
|
||||
if (index == -1) {
|
||||
throw new NotAbleToApplyGroupV2ChangeException();
|
||||
}
|
||||
|
||||
builder.setMembers(index, DecryptedMember.newBuilder(builder.getMembers(index)).setProfileKey(modifyProfileKey.getProfileKey()).build());
|
||||
builder.setMembers(index, withNewProfileKey(builder.getMembers(index), modifyProfileKey.getProfileKey()));
|
||||
}
|
||||
}
|
||||
|
||||
for (DecryptedPendingMemberRemoval removedMember : change.getDeletePendingMembersList()) {
|
||||
private static void applyAddPendingMemberActions(DecryptedGroup.Builder builder, List<DecryptedPendingMember> newPendingMembersList) throws NotAbleToApplyGroupV2ChangeException {
|
||||
Set<ByteString> fullMemberSet = getMemberUuidSet(builder.getMembersList());
|
||||
Set<ByteString> pendingMemberCipherTexts = getPendingMemberCipherTextSet(builder.getPendingMembersList());
|
||||
|
||||
for (DecryptedPendingMember pendingMember : newPendingMembersList) {
|
||||
if (fullMemberSet.contains(pendingMember.getUuid())) {
|
||||
throw new NotAbleToApplyGroupV2ChangeException();
|
||||
}
|
||||
|
||||
if (!pendingMemberCipherTexts.contains(pendingMember.getUuidCipherText())) {
|
||||
builder.addPendingMembers(pendingMember);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static void applyDeletePendingMemberActions(DecryptedGroup.Builder builder, List<DecryptedPendingMemberRemoval> deletePendingMembersList) {
|
||||
for (DecryptedPendingMemberRemoval removedMember : deletePendingMembersList) {
|
||||
int index = findPendingIndexByUuidCipherText(builder.getPendingMembersList(), removedMember.getUuidCipherText());
|
||||
|
||||
if (index == -1) {
|
||||
throw new NotAbleToApplyGroupV2ChangeException();
|
||||
Log.w(TAG, "Deleted pending member on change not found in group");
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.removePendingMembers(index);
|
||||
}
|
||||
}
|
||||
|
||||
for (DecryptedMember newMember : change.getPromotePendingMembersList()) {
|
||||
protected static void applyPromotePendingMemberActions(DecryptedGroup.Builder builder, List<DecryptedMember> promotePendingMembersList) throws NotAbleToApplyGroupV2ChangeException {
|
||||
for (DecryptedMember newMember : promotePendingMembersList) {
|
||||
int index = findPendingIndexByUuid(builder.getPendingMembersList(), newMember.getUuid());
|
||||
|
||||
if (index == -1) {
|
||||
|
@ -252,39 +313,69 @@ public final class DecryptedGroupUtil {
|
|||
builder.removePendingMembers(index);
|
||||
builder.addMembers(newMember);
|
||||
}
|
||||
}
|
||||
|
||||
builder.addAllPendingMembers(change.getNewPendingMembersList());
|
||||
|
||||
protected static void applyModifyTitleAction(DecryptedGroup.Builder builder, DecryptedGroupChange change) {
|
||||
if (change.hasNewTitle()) {
|
||||
builder.setTitle(change.getNewTitle().getValue());
|
||||
}
|
||||
}
|
||||
|
||||
protected static void applyModifyAvatarAction(DecryptedGroup.Builder builder, DecryptedGroupChange change) {
|
||||
if (change.hasNewAvatar()) {
|
||||
builder.setAvatar(change.getNewAvatar().getValue());
|
||||
}
|
||||
}
|
||||
|
||||
protected static void applyModifyDisappearingMessagesTimerAction(DecryptedGroup.Builder builder, DecryptedGroupChange change) {
|
||||
if (change.hasNewTimer()) {
|
||||
builder.setDisappearingMessagesTimer(change.getNewTimer());
|
||||
}
|
||||
}
|
||||
|
||||
protected static void applyModifyAttributesAccessControlAction(DecryptedGroup.Builder builder, DecryptedGroupChange change) {
|
||||
if (change.getNewAttributeAccess() != AccessControl.AccessRequired.UNKNOWN) {
|
||||
builder.setAccessControl(AccessControl.newBuilder(builder.getAccessControl())
|
||||
.setAttributesValue(change.getNewAttributeAccessValue())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
protected static void applyModifyMembersAccessControlAction(DecryptedGroup.Builder builder, DecryptedGroupChange change) {
|
||||
if (change.getNewMemberAccess() != AccessControl.AccessRequired.UNKNOWN) {
|
||||
builder.setAccessControl(AccessControl.newBuilder(builder.getAccessControl())
|
||||
.setMembersValue(change.getNewMemberAccessValue())
|
||||
.build());
|
||||
}
|
||||
|
||||
removePendingMembersNowInGroup(builder);
|
||||
|
||||
return builder.setRevision(change.getRevision()).build();
|
||||
}
|
||||
|
||||
static void removePendingMembersNowInGroup(DecryptedGroup.Builder builder) {
|
||||
private static DecryptedMember withNewProfileKey(DecryptedMember member, ByteString profileKey) {
|
||||
return DecryptedMember.newBuilder(member)
|
||||
.setProfileKey(profileKey)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static Set<ByteString> getMemberUuidSet(List<DecryptedMember> membersList) {
|
||||
Set<ByteString> memberUuids = new HashSet<>(membersList.size());
|
||||
|
||||
for (DecryptedMember members : membersList) {
|
||||
memberUuids.add(members.getUuid());
|
||||
}
|
||||
|
||||
return memberUuids;
|
||||
}
|
||||
|
||||
private static Set<ByteString> getPendingMemberCipherTextSet(List<DecryptedPendingMember> pendingMemberList) {
|
||||
Set<ByteString> pendingMemberCipherTexts = new HashSet<>(pendingMemberList.size());
|
||||
|
||||
for (DecryptedPendingMember pendingMember : pendingMemberList) {
|
||||
pendingMemberCipherTexts.add(pendingMember.getUuidCipherText());
|
||||
}
|
||||
|
||||
return pendingMemberCipherTexts;
|
||||
}
|
||||
|
||||
private static void removePendingMembersNowInGroup(DecryptedGroup.Builder builder) {
|
||||
Set<ByteString> allMembers = membersToUuidByteStringSet(builder.getMembersList());
|
||||
|
||||
for (int i = builder.getPendingMembersCount() - 1; i >= 0; i--) {
|
||||
|
|
|
@ -107,12 +107,12 @@ public final class DecryptedGroupUtil_apply_Test {
|
|||
newGroup);
|
||||
}
|
||||
|
||||
@Test(expected = NotAbleToApplyGroupV2ChangeException.class)
|
||||
@Test
|
||||
public void apply_remove_members_not_found() throws NotAbleToApplyGroupV2ChangeException {
|
||||
DecryptedMember member1 = member(UUID.randomUUID());
|
||||
DecryptedMember member2 = member(UUID.randomUUID());
|
||||
|
||||
DecryptedGroupUtil.apply(DecryptedGroup.newBuilder()
|
||||
DecryptedGroup newGroup = DecryptedGroupUtil.apply(DecryptedGroup.newBuilder()
|
||||
.setRevision(13)
|
||||
.addMembers(member1)
|
||||
.build(),
|
||||
|
@ -120,6 +120,12 @@ public final class DecryptedGroupUtil_apply_Test {
|
|||
.setRevision(14)
|
||||
.addDeleteMembers(member2.getUuid())
|
||||
.build());
|
||||
|
||||
assertEquals(DecryptedGroup.newBuilder()
|
||||
.addMembers(member1)
|
||||
.setRevision(14)
|
||||
.build(),
|
||||
newGroup);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -279,6 +285,47 @@ public final class DecryptedGroupUtil_apply_Test {
|
|||
newGroup);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void apply_new_pending_member_already_pending() throws NotAbleToApplyGroupV2ChangeException {
|
||||
DecryptedMember member1 = member(UUID.randomUUID());
|
||||
DecryptedPendingMember pending = pendingMember(UUID.randomUUID());
|
||||
|
||||
DecryptedGroup newGroup = DecryptedGroupUtil.apply(DecryptedGroup.newBuilder()
|
||||
.setRevision(10)
|
||||
.addMembers(member1)
|
||||
.addPendingMembers(pending)
|
||||
.build(),
|
||||
DecryptedGroupChange.newBuilder()
|
||||
.setRevision(11)
|
||||
.addNewPendingMembers(pending)
|
||||
.build());
|
||||
|
||||
assertEquals(DecryptedGroup.newBuilder()
|
||||
.setRevision(11)
|
||||
.addMembers(member1)
|
||||
.addPendingMembers(pending)
|
||||
.build(),
|
||||
newGroup);
|
||||
}
|
||||
|
||||
@Test(expected = NotAbleToApplyGroupV2ChangeException.class)
|
||||
public void apply_new_pending_member_already_in_group() throws NotAbleToApplyGroupV2ChangeException {
|
||||
DecryptedMember member1 = member(UUID.randomUUID());
|
||||
UUID uuid2 = UUID.randomUUID();
|
||||
DecryptedMember member2 = member(uuid2);
|
||||
DecryptedPendingMember pending2 = pendingMember(uuid2);
|
||||
|
||||
DecryptedGroupUtil.apply(DecryptedGroup.newBuilder()
|
||||
.setRevision(10)
|
||||
.addMembers(member1)
|
||||
.addMembers(member2)
|
||||
.build(),
|
||||
DecryptedGroupChange.newBuilder()
|
||||
.setRevision(11)
|
||||
.addNewPendingMembers(pending2)
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void remove_pending_member() throws NotAbleToApplyGroupV2ChangeException {
|
||||
DecryptedMember member1 = member(UUID.randomUUID());
|
||||
|
@ -304,12 +351,12 @@ public final class DecryptedGroupUtil_apply_Test {
|
|||
newGroup);
|
||||
}
|
||||
|
||||
@Test(expected = NotAbleToApplyGroupV2ChangeException.class)
|
||||
@Test
|
||||
public void cannot_remove_pending_member_if_not_in_group() throws NotAbleToApplyGroupV2ChangeException {
|
||||
DecryptedMember member1 = member(UUID.randomUUID());
|
||||
UUID pendingUuid = UUID.randomUUID();
|
||||
|
||||
DecryptedGroupUtil.apply(DecryptedGroup.newBuilder()
|
||||
DecryptedGroup newGroup = DecryptedGroupUtil.apply(DecryptedGroup.newBuilder()
|
||||
.setRevision(10)
|
||||
.addMembers(member1)
|
||||
.build(),
|
||||
|
@ -319,6 +366,12 @@ public final class DecryptedGroupUtil_apply_Test {
|
|||
.setUuidCipherText(ProtoTestUtils.encrypt(pendingUuid))
|
||||
.build())
|
||||
.build());
|
||||
|
||||
assertEquals(DecryptedGroup.newBuilder()
|
||||
.setRevision(11)
|
||||
.addMembers(member1)
|
||||
.build(),
|
||||
newGroup);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Add table
Reference in a new issue