Add support for Group V2 description field.

This commit is contained in:
Cody Henthorne
2021-05-07 13:43:31 -04:00
committed by Greyson Parrelli
parent b3aec58e69
commit 8c9df8d3be
56 changed files with 892 additions and 117 deletions

View File

@@ -36,4 +36,6 @@ public interface ChangeSetModifier {
void removeDeleteRequestingMembers(int i);
void removePromoteRequestingMembers(int i);
void clearModifyDescription();
}

View File

@@ -123,6 +123,11 @@ final class DecryptedGroupChangeActionsBuilderChangeSetModifier implements Chang
result.removePromoteRequestingMembers(i);
}
@Override
public void clearModifyDescription() {
result.clearNewDescription();
}
private static List<ByteString> removeIndexFromByteStringList(List<ByteString> byteStrings, int i) {
List<ByteString> modifiedList = new ArrayList<>(byteStrings);

View File

@@ -274,6 +274,8 @@ public final class DecryptedGroupUtil {
applyModifyTitleAction(builder, change);
applyModifyDescriptionAction(builder, change);
applyModifyAvatarAction(builder, change);
applyModifyDisappearingMessagesTimerAction(builder, change);
@@ -403,6 +405,12 @@ public final class DecryptedGroupUtil {
}
}
protected static void applyModifyDescriptionAction(DecryptedGroup.Builder builder, DecryptedGroupChange change) {
if (change.hasNewDescription()) {
builder.setDescription(change.getNewDescription().getValue());
}
}
protected static void applyModifyAvatarAction(DecryptedGroup.Builder builder, DecryptedGroupChange change) {
if (change.hasNewAvatar()) {
builder.setAvatar(change.getNewAvatar().getValue());
@@ -578,7 +586,8 @@ public final class DecryptedGroupUtil {
change.getNewRequestingMembersCount() == 0 && // field 16
change.getDeleteRequestingMembersCount() == 0 && // field 17
change.getPromoteRequestingMembersCount() == 0 && // field 18
change.getNewInviteLinkPassword().size() == 0; // field 19
change.getNewInviteLinkPassword().size() == 0 && // field 19
!change.hasNewDescription(); // field 20
}
static boolean isSet(AccessControl.AccessRequired newAttributeAccess) {

View File

@@ -102,4 +102,9 @@ final class GroupChangeActionsBuilderChangeSetModifier implements ChangeSetModif
public void removePromoteRequestingMembers(int i) {
result.removePromoteRequestingMembers(i);
}
@Override
public void clearModifyDescription() {
result.clearModifyDescription();
}
}

View File

@@ -32,6 +32,10 @@ public final class GroupChangeReconstruct {
builder.setNewTitle(DecryptedString.newBuilder().setValue(toState.getTitle()));
}
if (!fromState.getDescription().equals(toState.getDescription())) {
builder.setNewDescription(DecryptedString.newBuilder().setValue(toState.getDescription()));
}
if (!fromState.getAvatar().equals(toState.getAvatar())) {
builder.setNewAvatar(DecryptedString.newBuilder().setValue(toState.getAvatar()));
}

View File

@@ -40,7 +40,8 @@ public final class GroupChangeUtil {
change.getAddRequestingMembersCount() == 0 && // field 16
change.getDeleteRequestingMembersCount() == 0 && // field 17
change.getPromoteRequestingMembersCount() == 0 && // field 18
!change.hasModifyInviteLinkPassword(); // field 19
!change.hasModifyInviteLinkPassword() && // field 19
!change.hasModifyDescription(); // field 20
}
/**
@@ -133,6 +134,7 @@ public final class GroupChangeUtil {
resolveField16AddRequestingMembers (conflictingChange, changeSetModifier, fullMembersByUuid, pendingMembersByUuid);
resolveField17DeleteMembers (conflictingChange, changeSetModifier, requestingMembersByUuid);
resolveField18PromoteRequestingMembers (conflictingChange, changeSetModifier, requestingMembersByUuid);
resolveField20ModifyDescription (groupState, conflictingChange, changeSetModifier);
}
private static void resolveField3AddMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap<ByteString, DecryptedMember> fullMembersByUuid, HashMap<ByteString, DecryptedPendingMember> pendingMembersByUuid) {
@@ -302,4 +304,10 @@ public final class GroupChangeUtil {
}
}
}
private static void resolveField20ModifyDescription(DecryptedGroup groupState, DecryptedGroupChange conflictingChange, ChangeSetModifier result) {
if (conflictingChange.hasNewDescription() && conflictingChange.getNewDescription().getValue().equals(groupState.getDescription())) {
result.clearModifyDescription();
}
}
}

View File

@@ -59,7 +59,7 @@ public final class GroupsV2Operations {
public static final UUID UNKNOWN_UUID = UuidUtil.UNKNOWN_UUID;
/** Highest change epoch this class knows now to decrypt */
public static final int HIGHEST_KNOWN_EPOCH = 1;
public static final int HIGHEST_KNOWN_EPOCH = 2;
private final ServerPublicParams serverPublicParams;
private final ClientZkProfileOperations clientZkProfileOperations;
@@ -149,6 +149,10 @@ public final class GroupsV2Operations {
.setTitle(encryptTitle(title)));
}
public GroupChange.Actions.ModifyDescriptionAction.Builder createModifyGroupDescription(final String description) {
return GroupChange.Actions.ModifyDescriptionAction.newBuilder().setDescription(encryptDescription(description));
}
public GroupChange.Actions.Builder createModifyGroupMembershipChange(Set<GroupCandidate> membersToAdd, UUID selfUuid) {
final GroupOperations groupOperations = forGroup(groupSecretParams);
@@ -381,6 +385,7 @@ public final class GroupsV2Operations {
return DecryptedGroup.newBuilder()
.setTitle(decryptTitle(group.getTitle()))
.setDescription(decryptDescription(group.getDescription()))
.setAvatar(group.getAvatar())
.setAccessControl(group.getAccessControl())
.setRevision(group.getRevision())
@@ -565,6 +570,11 @@ public final class GroupsV2Operations {
builder.setNewInviteLinkPassword(actions.getModifyInviteLinkPassword().getInviteLinkPassword());
}
// Field 20
if (actions.hasModifyDescription()) {
builder.setNewDescription(DecryptedString.newBuilder().setValue(decryptDescription(actions.getModifyDescription().getDescription())));
}
return builder.build();
}
@@ -576,6 +586,7 @@ public final class GroupsV2Operations {
.setAddFromInviteLink(joinInfo.getAddFromInviteLink())
.setRevision(joinInfo.getRevision())
.setPendingAdminApproval(joinInfo.getPendingAdminApproval())
.setDescription(decryptDescription(joinInfo.getDescription()))
.build();
}
@@ -708,6 +719,20 @@ public final class GroupsV2Operations {
return decryptBlob(cipherText).getTitle().trim();
}
ByteString encryptDescription(String description) {
try {
GroupAttributeBlob blob = GroupAttributeBlob.newBuilder().setDescription(description).build();
return ByteString.copyFrom(clientZkGroupCipher.encryptBlob(blob.toByteArray()));
} catch (VerificationFailedException e) {
throw new AssertionError(e);
}
}
private String decryptDescription(ByteString cipherText) {
return decryptBlob(cipherText).getDescription().trim();
}
private int decryptDisappearingMessagesTimer(ByteString encryptedTimerMessage) {
return decryptBlob(encryptedTimerMessage).getDisappearingMessagesDuration();
}

View File

@@ -60,6 +60,7 @@ message DecryptedGroup {
repeated DecryptedPendingMember pendingMembers = 8;
repeated DecryptedRequestingMember requestingMembers = 9;
bytes inviteLinkPassword = 10;
string description = 11;
}
// Decrypted version of message GroupChange.Actions
@@ -84,6 +85,7 @@ message DecryptedGroupChange {
repeated bytes deleteRequestingMembers = 17;
repeated DecryptedApproveMember promoteRequestingMembers = 18;
bytes newInviteLinkPassword = 19;
DecryptedString newDescription = 20;
}
message DecryptedString {
@@ -101,4 +103,5 @@ message DecryptedGroupJoinInfo {
AccessControl.AccessRequired addFromInviteLink = 5;
uint32 revision = 6;
bool pendingAdminApproval = 7;
string description = 8;
}

View File

@@ -70,6 +70,7 @@ message Group {
repeated PendingMember pendingMembers = 8;
repeated RequestingMember requestingMembers = 9;
bytes inviteLinkPassword = 10;
bytes description = 11;
}
message GroupChange {
@@ -123,6 +124,10 @@ message GroupChange {
bytes title = 1;
}
message ModifyDescriptionAction {
bytes description = 1;
}
message ModifyAvatarAction {
string avatar = 1;
}
@@ -166,6 +171,7 @@ message GroupChange {
repeated DeleteRequestingMemberAction deleteRequestingMembers = 17;
repeated PromoteRequestingMemberAction promoteRequestingMembers = 18;
ModifyInviteLinkPasswordAction modifyInviteLinkPassword = 19;
ModifyDescriptionAction modifyDescription = 20;
}
bytes actions = 1;
@@ -187,6 +193,7 @@ message GroupAttributeBlob {
string title = 1;
bytes avatar = 2;
uint32 disappearingMessagesDuration = 3;
string description = 4;
}
}
@@ -209,6 +216,7 @@ message GroupJoinInfo {
AccessControl.AccessRequired addFromInviteLink = 5;
uint32 revision = 6;
bool pendingAdminApproval = 7;
bytes description = 8;
}
message GroupExternalCredential {

View File

@@ -45,7 +45,7 @@ public final class DecryptedGroupUtil_apply_Test {
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class);
assertEquals("DecryptedGroupUtil and its tests need updating to account for new fields on " + DecryptedGroupChange.class.getName(),
19, maxFieldFound);
20, maxFieldFound);
}
@Test
@@ -576,6 +576,24 @@ public final class DecryptedGroupUtil_apply_Test {
newGroup);
}
@Test
public void description() throws NotAbleToApplyGroupV2ChangeException {
DecryptedGroup newGroup = DecryptedGroupUtil.apply(DecryptedGroup.newBuilder()
.setRevision(10)
.setDescription("Old description")
.build(),
DecryptedGroupChange.newBuilder()
.setRevision(11)
.setNewDescription(DecryptedString.newBuilder().setValue("New Description").build())
.build());
assertEquals(DecryptedGroup.newBuilder()
.setRevision(11)
.setDescription("New Description")
.build(),
newGroup);
}
@Test
public void avatar() throws NotAbleToApplyGroupV2ChangeException {
DecryptedGroup newGroup = DecryptedGroupUtil.apply(DecryptedGroup.newBuilder()

View File

@@ -35,7 +35,7 @@ public final class DecryptedGroupUtil_empty_Test {
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class);
assertEquals("DecryptedGroupUtil and its tests need updating to account for new fields on " + DecryptedGroupChange.class.getName(),
19, maxFieldFound);
20, maxFieldFound);
}
@Test
@@ -203,7 +203,7 @@ public final class DecryptedGroupUtil_empty_Test {
assertFalse(DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(change));
}
@Test
@Test
public void not_empty_with_a_new_invite_link_password_19() {
DecryptedGroupChange change = DecryptedGroupChange.newBuilder()
.setNewInviteLinkPassword(ByteString.copyFrom(new byte[16]))
@@ -212,4 +212,14 @@ public final class DecryptedGroupUtil_empty_Test {
assertFalse(DecryptedGroupUtil.changeIsEmpty(change));
assertFalse(DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(change));
}
@Test
public void not_empty_with_modify_description_field_20() {
DecryptedGroupChange change = DecryptedGroupChange.newBuilder()
.setNewDescription(DecryptedString.newBuilder().setValue("New description"))
.build();
assertFalse(DecryptedGroupUtil.changeIsEmpty(change));
assertFalse(DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(change));
}
}

View File

@@ -41,7 +41,7 @@ public final class GroupChangeReconstructTest {
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroup.class);
assertEquals("GroupChangeReconstruct and its tests need updating to account for new fields on " + DecryptedGroup.class.getName(),
10, maxFieldFound);
11, maxFieldFound);
}
@Test
@@ -74,6 +74,16 @@ public final class GroupChangeReconstructTest {
assertEquals(DecryptedGroupChange.newBuilder().setNewTitle(DecryptedString.newBuilder().setValue("B")).build(), decryptedGroupChange);
}
@Test
public void description_change() {
DecryptedGroup from = DecryptedGroup.newBuilder().setDescription("A").build();
DecryptedGroup to = DecryptedGroup.newBuilder().setDescription("B").build();
DecryptedGroupChange decryptedGroupChange = GroupChangeReconstruct.reconstructGroupChange(from, to);
assertEquals(DecryptedGroupChange.newBuilder().setNewDescription(DecryptedString.newBuilder().setValue("B")).build(), decryptedGroupChange);
}
@Test
public void avatar_change() {
DecryptedGroup from = DecryptedGroup.newBuilder().setAvatar("A").build();

View File

@@ -20,7 +20,7 @@ public final class GroupChangeUtil_changeIsEmpty_Test {
int maxFieldFound = getMaxDeclaredFieldNumber(GroupChange.Actions.class);
assertEquals("GroupChangeUtil and its tests need updating to account for new fields on " + GroupChange.Actions.class.getName(),
19, maxFieldFound);
20, maxFieldFound);
}
@Test
@@ -180,4 +180,13 @@ public final class GroupChangeUtil_changeIsEmpty_Test {
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
}
@Test
public void not_empty_with_modify_description_field_20() {
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
.setModifyDescription(GroupChange.Actions.ModifyDescriptionAction.getDefaultInstance())
.build();
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
}
}

View File

@@ -46,7 +46,7 @@ public final class GroupChangeUtil_resolveConflict_Test {
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class);
assertEquals("GroupChangeUtil#resolveConflict and its tests need updating to account for new fields on " + DecryptedGroupChange.class.getName(),
19, maxFieldFound);
20, maxFieldFound);
}
/**
@@ -59,7 +59,7 @@ public final class GroupChangeUtil_resolveConflict_Test {
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class);
assertEquals("GroupChangeUtil#resolveConflict and its tests need updating to account for new fields on " + GroupChange.class.getName(),
19, maxFieldFound);
20, maxFieldFound);
}
/**
@@ -72,7 +72,7 @@ public final class GroupChangeUtil_resolveConflict_Test {
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroup.class);
assertEquals("GroupChangeUtil#resolveConflict and its tests need updating to account for new fields on " + DecryptedGroup.class.getName(),
10, maxFieldFound);
11, maxFieldFound);
}
@@ -672,4 +672,38 @@ public final class GroupChangeUtil_resolveConflict_Test {
.build();
assertEquals(expected, resolvedActions);
}
@Test
public void field_20__description_change_is_preserved() {
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.setDescription("Existing title")
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.setNewDescription(DecryptedString.newBuilder().setValue("New title").build())
.build();
GroupChange.Actions change = GroupChange.Actions.newBuilder()
.setModifyDescription(GroupChange.Actions.ModifyDescriptionAction.newBuilder().setDescription(ByteString.copyFrom("New title encrypted".getBytes())))
.build();
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
assertEquals(change, resolvedActions);
}
@Test
public void field_20__no_description_change_is_removed() {
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.setDescription("Existing title")
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.setNewDescription(DecryptedString.newBuilder().setValue("Existing title").build())
.build();
GroupChange.Actions change = GroupChange.Actions.newBuilder()
.setModifyDescription(GroupChange.Actions.ModifyDescriptionAction.newBuilder().setDescription(ByteString.copyFrom("Existing title encrypted".getBytes())))
.build();
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
assertTrue(GroupChangeUtil.changeIsEmpty(resolvedActions));
}
}

View File

@@ -39,7 +39,7 @@ public final class GroupChangeUtil_resolveConflict_decryptedOnly_Test {
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class);
assertEquals("GroupChangeUtil#resolveConflict and its tests need updating to account for new fields on " + DecryptedGroupChange.class.getName(),
19, maxFieldFound);
20, maxFieldFound);
}
/**
@@ -52,7 +52,7 @@ public final class GroupChangeUtil_resolveConflict_decryptedOnly_Test {
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroup.class);
assertEquals("GroupChangeUtil#resolveConflict and its tests need updating to account for new fields on " + DecryptedGroup.class.getName(),
10, maxFieldFound);
11, maxFieldFound);
}
@@ -543,4 +543,32 @@ public final class GroupChangeUtil_resolveConflict_decryptedOnly_Test {
assertEquals(decryptedChange, resolvedChanges);
}
@Test
public void field_20__description_change_is_preserved() {
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.setDescription("Existing description")
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.setNewDescription(DecryptedString.newBuilder().setValue("New description").build())
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
assertEquals(decryptedChange, resolvedChanges);
}
@Test
public void field_20__no_description_change_is_removed() {
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.setDescription("Existing description")
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.setNewDescription(DecryptedString.newBuilder().setValue("Existing description").build())
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
assertTrue(DecryptedGroupUtil.changeIsEmpty(resolvedChanges));
}
}

View File

@@ -41,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(),
7, maxFieldFound);
8, maxFieldFound);
}
@Test
@@ -131,4 +131,15 @@ public final class GroupsV2Operations_decrypt_groupJoinInfo_Test {
assertFalse(decryptedGroupJoinInfo.getPendingAdminApproval());
}
@Test
public void decrypt_description_field_8() {
GroupJoinInfo groupJoinInfo = GroupJoinInfo.newBuilder()
.setDescription(groupOperations.encryptDescription("Description!"))
.build();
DecryptedGroupJoinInfo decryptedGroupJoinInfo = groupOperations.decryptGroupJoinInfo(groupJoinInfo);
assertEquals("Description!", decryptedGroupJoinInfo.getDescription());
}
}

View File

@@ -74,7 +74,7 @@ public final class GroupsV2Operations_decrypt_group_Test {
int maxFieldFound = getMaxDeclaredFieldNumber(Group.class);
assertEquals("GroupOperations and its tests need updating to account for new fields on " + Group.class.getName(),
10, maxFieldFound);
11, maxFieldFound);
}
@Test
@@ -272,6 +272,17 @@ public final class GroupsV2Operations_decrypt_group_Test {
assertEquals(password, decryptedGroup.getInviteLinkPassword());
}
@Test
public void decrypt_description_field_11() throws VerificationFailedException, InvalidGroupStateException {
Group group = Group.newBuilder()
.setDescription(groupOperations.encryptDescription("Description!"))
.build();
DecryptedGroup decryptedGroup = groupOperations.decryptGroup(group);
assertEquals("Description!", decryptedGroup.getDescription());
}
private ByteString encryptProfileKey(UUID uuid, ProfileKey profileKey) {
return ByteString.copyFrom(new ClientZkGroupCipher(groupSecretParams).encryptProfileKey(profileKey, uuid).serialize());
}