diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java index a1ba7e845..3ec81ab78 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java @@ -105,7 +105,7 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr @Override public @NonNull GroupsV2Operations provideGroupsV2Operations() { - return new GroupsV2Operations(provideClientZkOperations()); + return new GroupsV2Operations(provideClientZkOperations(), FeatureFlags.groupLimits().getHardLimit()); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java index 89a784683..7cfe01c6a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java @@ -283,19 +283,16 @@ public final class GroupManager { @NonNull RecipientId recipientId) throws GroupChangeBusyException, IOException, GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException { - GroupDatabase.GroupRecord groupRecord = SignalDatabase.groups().requireGroup(groupId); - Recipient recipient = Recipient.resolved(recipientId); + GroupDatabase.V2GroupProperties groupProperties = SignalDatabase.groups().requireGroup(groupId).requireV2GroupProperties(); + Recipient recipient = Recipient.resolved(recipientId); - if (groupRecord.requireV2GroupProperties().getBannedMembers().contains(recipient.requireServiceId().uuid())) { + if (groupProperties.getBannedMembers().contains(recipient.requireServiceId().uuid())) { Log.i(TAG, "Attempt to ban already banned recipient: " + recipientId); return; } - ByteString uuid = UuidUtil.toByteString(recipient.requireServiceId().uuid()); - boolean rejectJoinRequest = groupRecord.requireV2GroupProperties().getDecryptedGroup().getRequestingMembersList().stream().anyMatch(m -> m.getUuid().equals(uuid)); - try (GroupManagerV2.GroupEditor editor = new GroupManagerV2(context).edit(groupId.requireV2())) { - editor.ban(Collections.singleton(recipient.requireServiceId().uuid()), rejectJoinRequest); + editor.ban(recipient.requireServiceId().uuid()); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java index 90cb35b55..5826b8e17 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java @@ -309,6 +309,7 @@ final class GroupManagerV2 { final class GroupEditor extends LockOwner { private final GroupId.V2 groupId; + private final GroupDatabase.V2GroupProperties v2GroupProperties; private final GroupMasterKey groupMasterKey; private final GroupSecretParams groupSecretParams; private final GroupsV2Operations.GroupOperations groupOperations; @@ -316,10 +317,10 @@ final class GroupManagerV2 { GroupEditor(@NonNull GroupId.V2 groupId, @NonNull Closeable lock) { super(lock); - GroupDatabase.GroupRecord groupRecord = groupDatabase.requireGroup(groupId); - GroupDatabase.V2GroupProperties v2GroupProperties = groupRecord.requireV2GroupProperties(); + GroupDatabase.GroupRecord groupRecord = groupDatabase.requireGroup(groupId); this.groupId = groupId; + this.v2GroupProperties = groupRecord.requireV2GroupProperties(); this.groupMasterKey = v2GroupProperties.getGroupMasterKey(); this.groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey); this.groupOperations = groupsV2Operations.forGroup(groupSecretParams); @@ -428,7 +429,7 @@ final class GroupManagerV2 { .map(r -> Recipient.resolved(r).requireServiceId().uuid()) .collect(Collectors.toSet()); - return commitChangeWithConflictResolution(groupOperations.createRefuseGroupJoinRequest(uuids, true)); + return commitChangeWithConflictResolution(groupOperations.createRefuseGroupJoinRequest(uuids, true, v2GroupProperties.getDecryptedGroup().getBannedMembersList())); } @WorkerThread @@ -463,7 +464,11 @@ final class GroupManagerV2 { @NonNull GroupManager.GroupActionResult ejectMember(@NonNull ServiceId serviceId, boolean allowWhenBlocked, boolean ban) throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException { - return commitChangeWithConflictResolution(groupOperations.createRemoveMembersChange(Collections.singleton(serviceId.uuid()), ban), allowWhenBlocked); + return commitChangeWithConflictResolution(groupOperations.createRemoveMembersChange(Collections.singleton(serviceId.uuid()), + ban, + ban ? v2GroupProperties.getDecryptedGroup().getBannedMembersList() + : Collections.emptyList()), + allowWhenBlocked); } @WorkerThread @@ -530,11 +535,18 @@ final class GroupManagerV2 { return commitChangeWithConflictResolution(groupOperations.createAcceptInviteChange(groupCandidate.getProfileKeyCredential().get())); } - public GroupManager.GroupActionResult ban(Set uuids, boolean rejectJoinRequest) throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException { - return commitChangeWithConflictResolution(groupOperations.createBanUuidsChange(uuids, rejectJoinRequest)); + public GroupManager.GroupActionResult ban(UUID uuid) + throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException + { + ByteString uuidByteString = UuidUtil.toByteString(uuid); + boolean rejectJoinRequest = v2GroupProperties.getDecryptedGroup().getRequestingMembersList().stream().anyMatch(m -> m.getUuid().equals(uuidByteString)); + + return commitChangeWithConflictResolution(groupOperations.createBanUuidsChange(Collections.singleton(uuid), rejectJoinRequest, v2GroupProperties.getDecryptedGroup().getBannedMembersList())); } - public GroupManager.GroupActionResult unban(Set uuids) throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException { + public GroupManager.GroupActionResult unban(Set uuids) + throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException + { return commitChangeWithConflictResolution(groupOperations.createUnbanUuidsChange(uuids)); } @@ -1102,7 +1114,7 @@ final class GroupManagerV2 { GroupChange signedGroupChange; try { - signedGroupChange = commitCancelChangeWithConflictResolution(groupOperations.createRefuseGroupJoinRequest(uuids, false)); + signedGroupChange = commitCancelChangeWithConflictResolution(groupOperations.createRefuseGroupJoinRequest(uuids, false, Collections.emptyList())); } catch (GroupLinkNotActiveException e) { Log.d(TAG, "Unexpected unable to leave group due to group link off"); throw new GroupChangeFailedException(e); diff --git a/app/src/main/java/org/thoughtcrime/securesms/push/AccountManagerFactory.java b/app/src/main/java/org/thoughtcrime/securesms/push/AccountManagerFactory.java index cc0fa270c..6184fc8d5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/push/AccountManagerFactory.java +++ b/app/src/main/java/org/thoughtcrime/securesms/push/AccountManagerFactory.java @@ -43,7 +43,8 @@ public class AccountManagerFactory { deviceId, password, BuildConfig.SIGNAL_AGENT, - FeatureFlags.okHttpAutomaticRetry()); + FeatureFlags.okHttpAutomaticRetry(), + FeatureFlags.groupLimits().getHardLimit()); } /** @@ -65,7 +66,14 @@ public class AccountManagerFactory { } return new SignalServiceAccountManager(new SignalServiceNetworkAccess(context).getConfiguration(number), - null, null, number, deviceId, password, BuildConfig.SIGNAL_AGENT, FeatureFlags.okHttpAutomaticRetry()); + null, + null, + number, + deviceId, + password, + BuildConfig.SIGNAL_AGENT, + FeatureFlags.okHttpAutomaticRetry(), + FeatureFlags.groupLimits().getHardLimit()); } } diff --git a/app/src/test/java/org/thoughtcrime/securesms/groups/GroupManagerV2Test_edit.kt b/app/src/test/java/org/thoughtcrime/securesms/groups/GroupManagerV2Test_edit.kt index 762152b0a..1b099b75e 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/groups/GroupManagerV2Test_edit.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/groups/GroupManagerV2Test_edit.kt @@ -85,7 +85,7 @@ class GroupManagerV2Test_edit { groupDatabase = mock(GroupDatabase::class.java) groupsV2API = mock(GroupsV2Api::class.java) - groupsV2Operations = GroupsV2Operations(clientZkOperations) + groupsV2Operations = GroupsV2Operations(clientZkOperations, 1000) groupsV2Authorization = mock(GroupsV2Authorization::class.java) groupsV2StateProcessor = mock(GroupsV2StateProcessor::class.java) groupCandidateHelper = mock(GroupCandidateHelper::class.java) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java index 5d430f5be..5c696973e 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java @@ -146,12 +146,13 @@ public class SignalServiceAccountManager { int deviceId, String password, String signalAgent, - boolean automaticNetworkRetry) + boolean automaticNetworkRetry, + int maxGroupSize) { this(configuration, new StaticCredentialsProvider(aci, pni, e164, deviceId, password), signalAgent, - new GroupsV2Operations(ClientZkOperations.create(configuration)), + new GroupsV2Operations(ClientZkOperations.create(configuration), maxGroupSize), automaticNetworkRetry); } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupChangeReconstruct.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupChangeReconstruct.java index d968baf68..f50ad0e87 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupChangeReconstruct.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupChangeReconstruct.java @@ -108,9 +108,10 @@ public final class GroupChangeReconstruct { builder.addNewPendingMembers(invitedMember); } - Set consistentMemberUuids = intersect(fromStateMemberUuids, toStateMemberUuids); - Set changedMembers = intersectByUUID(subtract(toState.getMembersList(), fromState.getMembersList()), consistentMemberUuids); - Map membersUuidMap = uuidMap(fromState.getMembersList()); + Set consistentMemberUuids = intersect(fromStateMemberUuids, toStateMemberUuids); + Set changedMembers = intersectByUUID(subtract(toState.getMembersList(), fromState.getMembersList()), consistentMemberUuids); + Map membersUuidMap = uuidMap(fromState.getMembersList()); + Map bannedMembersUuidMap = bannedUuidMap(toState.getBannedMembersList()); for (DecryptedMember newState : changedMembers) { DecryptedMember oldState = membersUuidMap.get(newState.getUuid()); @@ -152,7 +153,13 @@ public final class GroupChangeReconstruct { } for (ByteString uuid : newBannedMemberUuids) { - builder.addNewBannedMembers(DecryptedBannedMember.newBuilder().setUuid(uuid).build()); + DecryptedBannedMember.Builder newBannedBuilder = DecryptedBannedMember.newBuilder().setUuid(uuid); + DecryptedBannedMember bannedMember = bannedMembersUuidMap.get(uuid); + if (bannedMember != null) { + newBannedBuilder.setTimestamp(bannedMember.getTimestamp()); + } + + builder.addNewBannedMembers(newBannedBuilder); } return builder.build(); @@ -166,6 +173,14 @@ public final class GroupChangeReconstruct { return map; } + private static Map bannedUuidMap(List membersList) { + Map map = new LinkedHashMap<>(membersList.size()); + for (DecryptedBannedMember member : membersList) { + map.put(member.getUuid(), member); + } + return map; + } + private static Set intersectByUUID(Collection members, Set uuids) { Set result = new LinkedHashSet<>(members.size()); for (DecryptedMember member : members) { diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations.java index 7799b65a5..b1b96e3bb 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations.java @@ -44,6 +44,7 @@ import org.whispersystems.signalservice.api.util.UuidUtil; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Locale; import java.util.Optional; @@ -67,12 +68,14 @@ public final class GroupsV2Operations { private final ServerPublicParams serverPublicParams; private final ClientZkProfileOperations clientZkProfileOperations; private final ClientZkAuthOperations clientZkAuthOperations; + private final int maxGroupSize; private final SecureRandom random; - public GroupsV2Operations(ClientZkOperations clientZkOperations) { + public GroupsV2Operations(ClientZkOperations clientZkOperations, int maxGroupSize) { this.serverPublicParams = clientZkOperations.getServerPublicParams(); this.clientZkProfileOperations = clientZkOperations.getProfileOperations(); this.clientZkAuthOperations = clientZkOperations.getAuthOperations(); + this.maxGroupSize = maxGroupSize; this.random = new SecureRandom(); } @@ -209,8 +212,8 @@ public final class GroupsV2Operations { return actions; } - public GroupChange.Actions.Builder createRefuseGroupJoinRequest(Set requestsToRemove, boolean alsoBan) { - GroupChange.Actions.Builder actions = alsoBan ? createBanUuidsChange(requestsToRemove, false) + public GroupChange.Actions.Builder createRefuseGroupJoinRequest(Set requestsToRemove, boolean alsoBan, List bannedMembers) { + GroupChange.Actions.Builder actions = alsoBan ? createBanUuidsChange(requestsToRemove, false, bannedMembers) : GroupChange.Actions.newBuilder(); for (UUID uuid : requestsToRemove) { @@ -235,8 +238,8 @@ public final class GroupsV2Operations { return actions; } - public GroupChange.Actions.Builder createRemoveMembersChange(final Set membersToRemove, boolean alsoBan) { - GroupChange.Actions.Builder actions = alsoBan ? createBanUuidsChange(membersToRemove, false) + public GroupChange.Actions.Builder createRemoveMembersChange(final Set membersToRemove, boolean alsoBan, List bannedMembers) { + GroupChange.Actions.Builder actions = alsoBan ? createBanUuidsChange(membersToRemove, false, bannedMembers) : GroupChange.Actions.newBuilder(); for (UUID remove: membersToRemove) { @@ -249,7 +252,7 @@ public final class GroupsV2Operations { } public GroupChange.Actions.Builder createLeaveAndPromoteMembersToAdmin(UUID self, List membersToMakeAdmin) { - GroupChange.Actions.Builder actions = createRemoveMembersChange(Collections.singleton(self), false); + GroupChange.Actions.Builder actions = createRemoveMembersChange(Collections.singleton(self), false, Collections.emptyList()); for (UUID member : membersToMakeAdmin) { actions.addModifyMemberRoles(GroupChange.Actions.ModifyMemberRoleAction @@ -350,10 +353,23 @@ public final class GroupsV2Operations { .setAnnouncementsOnly(isAnnouncementGroup)); } - public GroupChange.Actions.Builder createBanUuidsChange(Set banUuids, boolean rejectJoinRequest) { - GroupChange.Actions.Builder builder = rejectJoinRequest ? createRefuseGroupJoinRequest(banUuids, false) + public GroupChange.Actions.Builder createBanUuidsChange(Set banUuids, boolean rejectJoinRequest, List bannedMembersList) { + GroupChange.Actions.Builder builder = rejectJoinRequest ? createRefuseGroupJoinRequest(banUuids, false, Collections.emptyList()) : GroupChange.Actions.newBuilder(); + int spacesToFree = bannedMembersList.size() + banUuids.size() - maxGroupSize; + if (spacesToFree > 0) { + List unban = bannedMembersList.stream() + .sorted(Comparator.comparingLong(DecryptedBannedMember::getTimestamp)) + .limit(spacesToFree) + .map(DecryptedBannedMember::getUuid) + .collect(Collectors.toList()); + + for (ByteString uuid : unban) { + builder.addDeleteBannedMembers(GroupChange.Actions.DeleteBannedMemberAction.newBuilder().setDeletedUserId(encryptUuid(UuidUtil.fromByteString(uuid)))); + } + } + for (UUID uuid : banUuids) { builder.addAddBannedMembers(GroupChange.Actions.AddBannedMemberAction.newBuilder().setAdded(BannedMember.newBuilder().setUserId(encryptUuid(uuid)).build())); } @@ -458,7 +474,7 @@ public final class GroupsV2Operations { } for (BannedMember member : group.getBannedMembersList()) { - decryptedBannedMembers.add(DecryptedBannedMember.newBuilder().setUuid(decryptUuidToByteString(member.getUserId())).build()); + decryptedBannedMembers.add(DecryptedBannedMember.newBuilder().setUuid(decryptUuidToByteString(member.getUserId())).setTimestamp(member.getTimestamp()).build()); } return DecryptedGroup.newBuilder() @@ -662,7 +678,7 @@ public final class GroupsV2Operations { // Field 22 for (GroupChange.Actions.AddBannedMemberAction action : actions.getAddBannedMembersList()) { - builder.addNewBannedMembers(DecryptedBannedMember.newBuilder().setUuid(decryptUuidToByteString(action.getAdded().getUserId())).build()); + builder.addNewBannedMembers(DecryptedBannedMember.newBuilder().setUuid(decryptUuidToByteString(action.getAdded().getUserId())).setTimestamp(action.getAdded().getTimestamp()).build()); } // Field 23 diff --git a/libsignal/service/src/main/proto/DecryptedGroups.proto b/libsignal/service/src/main/proto/DecryptedGroups.proto index 8a60c6b50..0c10c5c79 100644 --- a/libsignal/service/src/main/proto/DecryptedGroups.proto +++ b/libsignal/service/src/main/proto/DecryptedGroups.proto @@ -34,7 +34,8 @@ message DecryptedRequestingMember { } message DecryptedBannedMember { - bytes uuid = 1; + bytes uuid = 1; + uint64 timestamp = 2; } message DecryptedPendingMemberRemoval { diff --git a/libsignal/service/src/main/proto/Groups.proto b/libsignal/service/src/main/proto/Groups.proto index b4caa5311..7233a892f 100644 --- a/libsignal/service/src/main/proto/Groups.proto +++ b/libsignal/service/src/main/proto/Groups.proto @@ -46,7 +46,8 @@ message RequestingMember { } message BannedMember { - bytes userId = 1; + bytes userId = 1; + uint64 timestamp = 2; } message AccessControl { diff --git a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_ban_Test.java b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_ban_Test.java new file mode 100644 index 000000000..317add1a2 --- /dev/null +++ b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_ban_Test.java @@ -0,0 +1,142 @@ +package org.whispersystems.signalservice.api.groupsv2; + +import com.google.protobuf.ByteString; + +import org.junit.Before; +import org.junit.Test; +import org.signal.storageservice.protos.groups.BannedMember; +import org.signal.storageservice.protos.groups.GroupChange; +import org.signal.storageservice.protos.groups.GroupChange.Actions.AddBannedMemberAction; +import org.signal.storageservice.protos.groups.GroupChange.Actions.DeleteBannedMemberAction; +import org.signal.storageservice.protos.groups.local.DecryptedBannedMember; +import org.signal.zkgroup.InvalidInputException; +import org.signal.zkgroup.groups.GroupMasterKey; +import org.signal.zkgroup.groups.GroupSecretParams; +import org.whispersystems.signalservice.api.util.UuidUtil; +import org.whispersystems.signalservice.internal.util.Util; +import org.whispersystems.signalservice.testutil.LibSignalLibraryUtil; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static org.hamcrest.CoreMatchers.hasItems; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.bannedMember; + +public final class GroupsV2Operations_ban_Test { + + private GroupsV2Operations.GroupOperations groupOperations; + + @Before + public void setup() throws InvalidInputException { + LibSignalLibraryUtil.assumeLibSignalSupportedOnOS(); + + TestZkGroupServer server = new TestZkGroupServer(); + GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(new GroupMasterKey(Util.getSecretBytes(32))); + ClientZkOperations clientZkOperations = new ClientZkOperations(server.getServerPublicParams()); + + groupOperations = new GroupsV2Operations(clientZkOperations, 10).forGroup(groupSecretParams); + } + + @Test + public void addBanToEmptyList() { + UUID ban = UUID.randomUUID(); + + GroupChange.Actions.Builder banUuidsChange = groupOperations.createBanUuidsChange(Collections.singleton(ban), + false, + Collections.emptyList()); + + assertThat(banUuidsChange.getAddBannedMembersCount(), is(1)); + assertThat(banUuidsChange.getAddBannedMembers(0).getAdded().getUserId(), is(groupOperations.encryptUuid(ban))); + } + + @Test + public void addBanToPartialFullList() { + UUID toBan = UUID.randomUUID(); + List alreadyBanned = new ArrayList<>(5); + + for (int i = 0; i < 5; i++) { + alreadyBanned.add(bannedMember(UUID.randomUUID())); + } + + GroupChange.Actions.Builder banUuidsChange = groupOperations.createBanUuidsChange(Collections.singleton(toBan), + false, + alreadyBanned); + + assertThat(banUuidsChange.getAddBannedMembersCount(), is(1)); + assertThat(banUuidsChange.getAddBannedMembers(0).getAdded().getUserId(), is(groupOperations.encryptUuid(toBan))); + } + + @Test + public void addBanToFullList() { + UUID toBan = UUID.randomUUID(); + List alreadyBanned = new ArrayList<>(10); + DecryptedBannedMember oldest = null; + + for (int i = 0; i < 10; i++) { + DecryptedBannedMember member = bannedMember(UUID.randomUUID()).toBuilder().setTimestamp(100 + i).build(); + if (oldest == null) { + oldest = member; + } + alreadyBanned.add(member); + } + + Collections.shuffle(alreadyBanned); + + GroupChange.Actions.Builder banUuidsChange = groupOperations.createBanUuidsChange(Collections.singleton(toBan), + false, + alreadyBanned); + + assertThat(banUuidsChange.getDeleteBannedMembersCount(), is(1)); + assertThat(banUuidsChange.getDeleteBannedMembers(0).getDeletedUserId(), is(groupOperations.encryptUuid(UuidUtil.fromByteString(oldest.getUuid())))); + + + assertThat(banUuidsChange.getAddBannedMembersCount(), is(1)); + assertThat(banUuidsChange.getAddBannedMembers(0).getAdded().getUserId(), is(groupOperations.encryptUuid(toBan))); + } + + @Test + public void addMultipleBanToFullList() { + List toBan = new ArrayList<>(); + toBan.add(UUID.randomUUID()); + toBan.add(UUID.randomUUID()); + + List alreadyBanned = new ArrayList<>(10); + for (int i = 0; i < 10; i++) { + alreadyBanned.add(bannedMember(UUID.randomUUID()).toBuilder().setTimestamp(100 + i).build()); + } + + List oldest = new ArrayList<>(2); + oldest.add(groupOperations.encryptUuid(UuidUtil.fromByteString(alreadyBanned.get(0).getUuid()))); + oldest.add(groupOperations.encryptUuid(UuidUtil.fromByteString(alreadyBanned.get(1).getUuid()))); + + Collections.shuffle(alreadyBanned); + + GroupChange.Actions.Builder banUuidsChange = groupOperations.createBanUuidsChange(new HashSet<>(toBan), + false, + alreadyBanned); + + assertThat(banUuidsChange.getDeleteBannedMembersCount(), is(2)); + assertThat(banUuidsChange.getDeleteBannedMembersList() + .stream() + .map(DeleteBannedMemberAction::getDeletedUserId) + .collect(Collectors.toList()), + hasItems(oldest.get(0), oldest.get(1))); + + + assertThat(banUuidsChange.getAddBannedMembersCount(), is(2)); + assertThat(banUuidsChange.getAddBannedMembersList() + .stream() + .map(AddBannedMemberAction::getAdded) + .map(BannedMember::getUserId) + .collect(Collectors.toList()), + hasItems(groupOperations.encryptUuid(toBan.get(0)), groupOperations.encryptUuid(toBan.get(1)))); + } +} diff --git a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_decrypt_change_Test.java b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_decrypt_change_Test.java index 09a5a709e..78916668e 100644 --- a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_decrypt_change_Test.java +++ b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_decrypt_change_Test.java @@ -58,7 +58,7 @@ public final class GroupsV2Operations_decrypt_change_Test { server = new TestZkGroupServer(); groupSecretParams = GroupSecretParams.deriveFromMasterKey(new GroupMasterKey(Util.getSecretBytes(32))); clientZkOperations = new ClientZkOperations(server.getServerPublicParams()); - groupOperations = new GroupsV2Operations(clientZkOperations).forGroup(groupSecretParams); + groupOperations = new GroupsV2Operations(clientZkOperations, 1000).forGroup(groupSecretParams); } @Test @@ -157,7 +157,7 @@ public final class GroupsV2Operations_decrypt_change_Test { public void can_decrypt_member_removals_field4() { UUID oldMember = UUID.randomUUID(); - assertDecryption(groupOperations.createRemoveMembersChange(Collections.singleton(oldMember), false) + assertDecryption(groupOperations.createRemoveMembersChange(Collections.singleton(oldMember), false, Collections.emptyList()) .setRevision(10), DecryptedGroupChange.newBuilder() .setRevision(10) @@ -341,7 +341,7 @@ public final class GroupsV2Operations_decrypt_change_Test { public void can_decrypt_member_requests_refusals_field17() { UUID newRequestingMember = UUID.randomUUID(); - assertDecryption(groupOperations.createRefuseGroupJoinRequest(Collections.singleton(newRequestingMember), true) + assertDecryption(groupOperations.createRefuseGroupJoinRequest(Collections.singleton(newRequestingMember), true, Collections.emptyList()) .setRevision(10), DecryptedGroupChange.newBuilder() .setRevision(10) @@ -393,7 +393,7 @@ public final class GroupsV2Operations_decrypt_change_Test { public void can_decrypt_member_bans_field22() { UUID ban = UUID.randomUUID(); - assertDecryption(groupOperations.createBanUuidsChange(Collections.singleton(ban), false) + assertDecryption(groupOperations.createBanUuidsChange(Collections.singleton(ban), false, Collections.emptyList()) .setRevision(13), DecryptedGroupChange.newBuilder() .setRevision(13) diff --git a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_decrypt_groupJoinInfo_Test.java b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_decrypt_groupJoinInfo_Test.java index 73e579412..447009b10 100644 --- a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_decrypt_groupJoinInfo_Test.java +++ b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_decrypt_groupJoinInfo_Test.java @@ -28,7 +28,7 @@ public final class GroupsV2Operations_decrypt_groupJoinInfo_Test { ClientZkOperations clientZkOperations = new ClientZkOperations(server.getServerPublicParams()); GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(new GroupMasterKey(Util.getSecretBytes(32))); - groupOperations = new GroupsV2Operations(clientZkOperations).forGroup(groupSecretParams); + groupOperations = new GroupsV2Operations(clientZkOperations, 1000).forGroup(groupSecretParams); } /** diff --git a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_decrypt_group_Test.java b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_decrypt_group_Test.java index 47bba54f8..a66612d37 100644 --- a/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_decrypt_group_Test.java +++ b/libsignal/service/src/test/java/org/whispersystems/signalservice/api/groupsv2/GroupsV2Operations_decrypt_group_Test.java @@ -44,7 +44,7 @@ public final class GroupsV2Operations_decrypt_group_Test { ClientZkOperations clientZkOperations = new ClientZkOperations(server.getServerPublicParams()); groupSecretParams = GroupSecretParams.deriveFromMasterKey(new GroupMasterKey(Util.getSecretBytes(32))); - groupOperations = new GroupsV2Operations(clientZkOperations).forGroup(groupSecretParams); + groupOperations = new GroupsV2Operations(clientZkOperations, 1000).forGroup(groupSecretParams); } /**