Enforce limit for total number of blocked requests.

This commit is contained in:
Cody Henthorne
2022-03-22 11:08:11 -04:00
committed by Greyson Parrelli
parent b3d9a85fa2
commit 6890973ce8
14 changed files with 236 additions and 43 deletions

View File

@@ -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);
}

View File

@@ -108,9 +108,10 @@ public final class GroupChangeReconstruct {
builder.addNewPendingMembers(invitedMember);
}
Set<ByteString> consistentMemberUuids = intersect(fromStateMemberUuids, toStateMemberUuids);
Set<DecryptedMember> changedMembers = intersectByUUID(subtract(toState.getMembersList(), fromState.getMembersList()), consistentMemberUuids);
Map<ByteString, DecryptedMember> membersUuidMap = uuidMap(fromState.getMembersList());
Set<ByteString> consistentMemberUuids = intersect(fromStateMemberUuids, toStateMemberUuids);
Set<DecryptedMember> changedMembers = intersectByUUID(subtract(toState.getMembersList(), fromState.getMembersList()), consistentMemberUuids);
Map<ByteString, DecryptedMember> membersUuidMap = uuidMap(fromState.getMembersList());
Map<ByteString, DecryptedBannedMember> 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<ByteString, DecryptedBannedMember> bannedUuidMap(List<DecryptedBannedMember> membersList) {
Map<ByteString, DecryptedBannedMember> map = new LinkedHashMap<>(membersList.size());
for (DecryptedBannedMember member : membersList) {
map.put(member.getUuid(), member);
}
return map;
}
private static Set<DecryptedMember> intersectByUUID(Collection<DecryptedMember> members, Set<ByteString> uuids) {
Set<DecryptedMember> result = new LinkedHashSet<>(members.size());
for (DecryptedMember member : members) {

View File

@@ -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<UUID> requestsToRemove, boolean alsoBan) {
GroupChange.Actions.Builder actions = alsoBan ? createBanUuidsChange(requestsToRemove, false)
public GroupChange.Actions.Builder createRefuseGroupJoinRequest(Set<UUID> requestsToRemove, boolean alsoBan, List<DecryptedBannedMember> 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<UUID> membersToRemove, boolean alsoBan) {
GroupChange.Actions.Builder actions = alsoBan ? createBanUuidsChange(membersToRemove, false)
public GroupChange.Actions.Builder createRemoveMembersChange(final Set<UUID> membersToRemove, boolean alsoBan, List<DecryptedBannedMember> 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<UUID> 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<UUID> banUuids, boolean rejectJoinRequest) {
GroupChange.Actions.Builder builder = rejectJoinRequest ? createRefuseGroupJoinRequest(banUuids, false)
public GroupChange.Actions.Builder createBanUuidsChange(Set<UUID> banUuids, boolean rejectJoinRequest, List<DecryptedBannedMember> 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<ByteString> 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

View File

@@ -34,7 +34,8 @@ message DecryptedRequestingMember {
}
message DecryptedBannedMember {
bytes uuid = 1;
bytes uuid = 1;
uint64 timestamp = 2;
}
message DecryptedPendingMemberRemoval {

View File

@@ -46,7 +46,8 @@ message RequestingMember {
}
message BannedMember {
bytes userId = 1;
bytes userId = 1;
uint64 timestamp = 2;
}
message AccessControl {

View File

@@ -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<DecryptedBannedMember> 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<DecryptedBannedMember> 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<UUID> toBan = new ArrayList<>();
toBan.add(UUID.randomUUID());
toBan.add(UUID.randomUUID());
List<DecryptedBannedMember> alreadyBanned = new ArrayList<>(10);
for (int i = 0; i < 10; i++) {
alreadyBanned.add(bannedMember(UUID.randomUUID()).toBuilder().setTimestamp(100 + i).build());
}
List<ByteString> 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))));
}
}

View File

@@ -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)

View File

@@ -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);
}
/**

View File

@@ -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);
}
/**