mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-27 04:04:43 +01:00
Group invite link epoch support.
This commit is contained in:
committed by
Greyson Parrelli
parent
e006306036
commit
477bb45df7
@@ -5,16 +5,19 @@ import com.google.protobuf.ByteString;
|
||||
import org.junit.Test;
|
||||
import org.signal.storageservice.protos.groups.AccessControl;
|
||||
import org.signal.storageservice.protos.groups.Member;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedApproveMember;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
||||
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.storageservice.protos.groups.local.DecryptedRequestingMember;
|
||||
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;
|
||||
|
||||
@@ -23,12 +26,28 @@ import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.admin
|
||||
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.asAdmin;
|
||||
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.asMember;
|
||||
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.member;
|
||||
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.newProfileKey;
|
||||
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.pendingMember;
|
||||
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.ProtoTestUtils.withProfileKey;
|
||||
import static org.whispersystems.signalservice.api.groupsv2.ProtobufTestUtils.getMaxDeclaredFieldNumber;
|
||||
|
||||
public final class DecryptedGroupUtil_apply_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 applied by {@link DecryptedGroupUtil#apply}.
|
||||
*/
|
||||
@Test
|
||||
public void ensure_DecryptedGroupUtil_knows_about_all_fields_of_DecryptedGroupChange() {
|
||||
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class);
|
||||
|
||||
assertEquals("DecryptedGroupUtil and its tests need updating to account for new fields on " + DecryptedGroupChange.class.getName(),
|
||||
19, maxFieldFound);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void apply_revision() throws NotAbleToApplyGroupV2ChangeException {
|
||||
DecryptedGroup newGroup = DecryptedGroupUtil.apply(DecryptedGroup.newBuilder()
|
||||
@@ -580,4 +599,168 @@ public final class DecryptedGroupUtil_apply_Test {
|
||||
.build(),
|
||||
newGroup);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invite_link_access() throws NotAbleToApplyGroupV2ChangeException {
|
||||
DecryptedGroup newGroup = DecryptedGroupUtil.apply(DecryptedGroup.newBuilder()
|
||||
.setRevision(10)
|
||||
.setAccessControl(AccessControl.newBuilder()
|
||||
.setAttributes(AccessControl.AccessRequired.MEMBER)
|
||||
.setMembers(AccessControl.AccessRequired.MEMBER)
|
||||
.setAddFromInviteLink(AccessControl.AccessRequired.UNSATISFIABLE)
|
||||
.build())
|
||||
.build(),
|
||||
DecryptedGroupChange.newBuilder()
|
||||
.setRevision(11)
|
||||
.setNewInviteLinkAccess(AccessControl.AccessRequired.ADMINISTRATOR)
|
||||
.build());
|
||||
|
||||
assertEquals(DecryptedGroup.newBuilder()
|
||||
.setRevision(11)
|
||||
.setAccessControl(AccessControl.newBuilder()
|
||||
.setAttributes(AccessControl.AccessRequired.MEMBER)
|
||||
.setMembers(AccessControl.AccessRequired.MEMBER)
|
||||
.setAddFromInviteLink(AccessControl.AccessRequired.ADMINISTRATOR)
|
||||
.build())
|
||||
.build(),
|
||||
newGroup);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void apply_new_requesting_member() throws NotAbleToApplyGroupV2ChangeException {
|
||||
DecryptedRequestingMember member1 = requestingMember(UUID.randomUUID());
|
||||
DecryptedRequestingMember member2 = requestingMember(UUID.randomUUID());
|
||||
|
||||
DecryptedGroup newGroup = DecryptedGroupUtil.apply(DecryptedGroup.newBuilder()
|
||||
.setRevision(10)
|
||||
.addRequestingMembers(member1)
|
||||
.build(),
|
||||
DecryptedGroupChange.newBuilder()
|
||||
.setRevision(11)
|
||||
.addNewRequestingMembers(member2)
|
||||
.build());
|
||||
|
||||
assertEquals(DecryptedGroup.newBuilder()
|
||||
.setRevision(11)
|
||||
.addRequestingMembers(member1)
|
||||
.addRequestingMembers(member2)
|
||||
.build(),
|
||||
newGroup);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void apply_remove_requesting_member() throws NotAbleToApplyGroupV2ChangeException {
|
||||
DecryptedRequestingMember member1 = requestingMember(UUID.randomUUID());
|
||||
DecryptedRequestingMember member2 = requestingMember(UUID.randomUUID());
|
||||
|
||||
DecryptedGroup newGroup = DecryptedGroupUtil.apply(DecryptedGroup.newBuilder()
|
||||
.setRevision(13)
|
||||
.addRequestingMembers(member1)
|
||||
.addRequestingMembers(member2)
|
||||
.build(),
|
||||
DecryptedGroupChange.newBuilder()
|
||||
.setRevision(14)
|
||||
.addDeleteRequestingMembers(member1.getUuid())
|
||||
.build());
|
||||
|
||||
assertEquals(DecryptedGroup.newBuilder()
|
||||
.setRevision(14)
|
||||
.addRequestingMembers(member2)
|
||||
.build(),
|
||||
newGroup);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void promote_requesting_member() throws NotAbleToApplyGroupV2ChangeException {
|
||||
UUID uuid1 = UUID.randomUUID();
|
||||
UUID uuid2 = UUID.randomUUID();
|
||||
UUID uuid3 = UUID.randomUUID();
|
||||
ProfileKey profileKey1 = newProfileKey();
|
||||
ProfileKey profileKey2 = newProfileKey();
|
||||
ProfileKey profileKey3 = newProfileKey();
|
||||
DecryptedRequestingMember member1 = requestingMember(uuid1, profileKey1);
|
||||
DecryptedRequestingMember member2 = requestingMember(uuid2, profileKey2);
|
||||
DecryptedRequestingMember member3 = requestingMember(uuid3, profileKey3);
|
||||
|
||||
DecryptedGroup newGroup = DecryptedGroupUtil.apply(DecryptedGroup.newBuilder()
|
||||
.setRevision(13)
|
||||
.addRequestingMembers(member1)
|
||||
.addRequestingMembers(member2)
|
||||
.addRequestingMembers(member3)
|
||||
.build(),
|
||||
DecryptedGroupChange.newBuilder()
|
||||
.setRevision(14)
|
||||
.addPromoteRequestingMembers(DecryptedApproveMember.newBuilder()
|
||||
.setRole(Member.Role.DEFAULT)
|
||||
.setUuid(member1.getUuid()))
|
||||
.addPromoteRequestingMembers(DecryptedApproveMember.newBuilder()
|
||||
.setRole(Member.Role.ADMINISTRATOR)
|
||||
.setUuid(member2.getUuid()))
|
||||
.build());
|
||||
|
||||
assertEquals(DecryptedGroup.newBuilder()
|
||||
.setRevision(14)
|
||||
.addMembers(member(uuid1, profileKey1))
|
||||
.addMembers(admin(uuid2, profileKey2))
|
||||
.addRequestingMembers(member3)
|
||||
.build(),
|
||||
newGroup);
|
||||
}
|
||||
|
||||
@Test(expected = NotAbleToApplyGroupV2ChangeException.class)
|
||||
public void cannot_apply_promote_requesting_member_without_a_role() throws NotAbleToApplyGroupV2ChangeException {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
DecryptedRequestingMember member = requestingMember(uuid);
|
||||
|
||||
DecryptedGroupUtil.apply(DecryptedGroup.newBuilder()
|
||||
.setRevision(13)
|
||||
.addRequestingMembers(member)
|
||||
.build(),
|
||||
DecryptedGroupChange.newBuilder()
|
||||
.setRevision(14)
|
||||
.addPromoteRequestingMembers(DecryptedApproveMember.newBuilder()
|
||||
.setUuid(member.getUuid()))
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invite_link_password() throws NotAbleToApplyGroupV2ChangeException {
|
||||
ByteString password1 = ByteString.copyFrom(Util.getSecretBytes(16));
|
||||
ByteString password2 = ByteString.copyFrom(Util.getSecretBytes(16));
|
||||
|
||||
DecryptedGroup newGroup = DecryptedGroupUtil.apply(DecryptedGroup.newBuilder()
|
||||
.setRevision(10)
|
||||
.setInviteLinkPassword(password1)
|
||||
.build(),
|
||||
DecryptedGroupChange.newBuilder()
|
||||
.setRevision(11)
|
||||
.setNewInviteLinkPassword(password2)
|
||||
.build());
|
||||
|
||||
assertEquals(DecryptedGroup.newBuilder()
|
||||
.setRevision(11)
|
||||
.setInviteLinkPassword(password2)
|
||||
.build(),
|
||||
newGroup);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invite_link_password_not_changed() throws NotAbleToApplyGroupV2ChangeException {
|
||||
ByteString password = ByteString.copyFrom(Util.getSecretBytes(16));
|
||||
|
||||
DecryptedGroup newGroup = DecryptedGroupUtil.apply(DecryptedGroup.newBuilder()
|
||||
.setRevision(10)
|
||||
.setInviteLinkPassword(password)
|
||||
.build(),
|
||||
DecryptedGroupChange.newBuilder()
|
||||
.setRevision(11)
|
||||
.build());
|
||||
|
||||
assertEquals(DecryptedGroup.newBuilder()
|
||||
.setRevision(11)
|
||||
.setInviteLinkPassword(password)
|
||||
.build(),
|
||||
newGroup);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
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.DecryptedApproveMember;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedRequestingMember;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedString;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedTimer;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
@@ -31,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(),
|
||||
DecryptedGroupUtil.MAX_CHANGE_FIELD, maxFieldFound);
|
||||
19, maxFieldFound);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -158,4 +162,54 @@ public final class DecryptedGroupUtil_empty_Test {
|
||||
assertFalse(DecryptedGroupUtil.changeIsEmpty(change));
|
||||
assertFalse(DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(change));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void not_empty_with_modify_add_from_invite_link_access_field_15() {
|
||||
DecryptedGroupChange change = DecryptedGroupChange.newBuilder()
|
||||
.setNewInviteLinkAccess(AccessControl.AccessRequired.ADMINISTRATOR)
|
||||
.build();
|
||||
|
||||
assertFalse(DecryptedGroupUtil.changeIsEmpty(change));
|
||||
assertFalse(DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(change));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void not_empty_with_an_add_requesting_member_field_16() {
|
||||
DecryptedGroupChange change = DecryptedGroupChange.newBuilder()
|
||||
.addNewRequestingMembers(DecryptedRequestingMember.getDefaultInstance())
|
||||
.build();
|
||||
|
||||
assertFalse(DecryptedGroupUtil.changeIsEmpty(change));
|
||||
assertFalse(DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(change));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void not_empty_with_a_delete_requesting_member_field_17() {
|
||||
DecryptedGroupChange change = DecryptedGroupChange.newBuilder()
|
||||
.addDeleteRequestingMembers(ByteString.copyFrom(new byte[16]))
|
||||
.build();
|
||||
|
||||
assertFalse(DecryptedGroupUtil.changeIsEmpty(change));
|
||||
assertFalse(DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(change));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void not_empty_with_a_promote_requesting_member_field_18() {
|
||||
DecryptedGroupChange change = DecryptedGroupChange.newBuilder()
|
||||
.addPromoteRequestingMembers(DecryptedApproveMember.getDefaultInstance())
|
||||
.build();
|
||||
|
||||
assertFalse(DecryptedGroupUtil.changeIsEmpty(change));
|
||||
assertFalse(DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(change));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void not_empty_with_a_new_invite_link_password_19() {
|
||||
DecryptedGroupChange change = DecryptedGroupChange.newBuilder()
|
||||
.setNewInviteLinkPassword(ByteString.copyFrom(new byte[16]))
|
||||
.build();
|
||||
|
||||
assertFalse(DecryptedGroupUtil.changeIsEmpty(change));
|
||||
assertFalse(DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(change));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
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;
|
||||
@@ -8,21 +10,40 @@ 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.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.admin;
|
||||
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.approveAdmin;
|
||||
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.newProfileKey;
|
||||
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.ProtoTestUtils.withProfileKey;
|
||||
import static org.whispersystems.signalservice.api.groupsv2.ProtobufTestUtils.getMaxDeclaredFieldNumber;
|
||||
|
||||
public final class GroupChangeReconstructTest {
|
||||
|
||||
/**
|
||||
* 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 detected by {@link GroupChangeReconstruct#reconstructGroupChange}.
|
||||
*/
|
||||
@Test
|
||||
public void ensure_GroupChangeReconstruct_knows_about_all_fields_of_DecryptedGroup() {
|
||||
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroup.class);
|
||||
|
||||
assertEquals("GroupChangeReconstruct and its tests need updating to account for new fields on " + DecryptedGroup.class.getName(),
|
||||
10, maxFieldFound);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void empty_to_empty() {
|
||||
DecryptedGroup from = DecryptedGroup.newBuilder().build();
|
||||
@@ -219,4 +240,122 @@ public final class GroupChangeReconstructTest {
|
||||
|
||||
assertEquals(DecryptedGroupChange.newBuilder().addModifiedProfileKeys(withProfileKey(admin(uuid),profileKey2)).build(), decryptedGroupChange);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void new_invite_access() {
|
||||
DecryptedGroup from = DecryptedGroup.newBuilder()
|
||||
.setAccessControl(AccessControl.newBuilder()
|
||||
.setAddFromInviteLink(AccessControl.AccessRequired.ADMINISTRATOR))
|
||||
.build();
|
||||
DecryptedGroup to = DecryptedGroup.newBuilder()
|
||||
.setAccessControl(AccessControl.newBuilder()
|
||||
.setAddFromInviteLink(AccessControl.AccessRequired.UNSATISFIABLE))
|
||||
.build();
|
||||
|
||||
DecryptedGroupChange decryptedGroupChange = GroupChangeReconstruct.reconstructGroupChange(from, to);
|
||||
|
||||
assertEquals(DecryptedGroupChange.newBuilder()
|
||||
.setNewInviteLinkAccess(AccessControl.AccessRequired.UNSATISFIABLE)
|
||||
.build(),
|
||||
decryptedGroupChange);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void new_requesting_members() {
|
||||
UUID member1 = UUID.randomUUID();
|
||||
ProfileKey profileKey1 = newProfileKey();
|
||||
DecryptedGroup from = DecryptedGroup.newBuilder()
|
||||
.build();
|
||||
DecryptedGroup to = DecryptedGroup.newBuilder()
|
||||
.addRequestingMembers(requestingMember(member1, profileKey1))
|
||||
.build();
|
||||
|
||||
DecryptedGroupChange decryptedGroupChange = GroupChangeReconstruct.reconstructGroupChange(from, to);
|
||||
|
||||
assertEquals(DecryptedGroupChange.newBuilder()
|
||||
.addNewRequestingMembers(requestingMember(member1, profileKey1))
|
||||
.build(),
|
||||
decryptedGroupChange);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void new_requesting_members_ignores_existing_by_uuid() {
|
||||
UUID member1 = UUID.randomUUID();
|
||||
UUID member2 = UUID.randomUUID();
|
||||
ProfileKey profileKey2 = newProfileKey();
|
||||
DecryptedGroup from = DecryptedGroup.newBuilder()
|
||||
.addRequestingMembers(requestingMember(member1, newProfileKey()))
|
||||
.build();
|
||||
DecryptedGroup to = DecryptedGroup.newBuilder()
|
||||
.addRequestingMembers(requestingMember(member1, newProfileKey()))
|
||||
.addRequestingMembers(requestingMember(member2, profileKey2))
|
||||
.build();
|
||||
|
||||
DecryptedGroupChange decryptedGroupChange = GroupChangeReconstruct.reconstructGroupChange(from, to);
|
||||
|
||||
assertEquals(DecryptedGroupChange.newBuilder()
|
||||
.addNewRequestingMembers(requestingMember(member2, profileKey2))
|
||||
.build(),
|
||||
decryptedGroupChange);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removed_requesting_members() {
|
||||
UUID member1 = UUID.randomUUID();
|
||||
DecryptedGroup from = DecryptedGroup.newBuilder()
|
||||
.addRequestingMembers(requestingMember(member1, newProfileKey()))
|
||||
.build();
|
||||
DecryptedGroup to = DecryptedGroup.newBuilder()
|
||||
.build();
|
||||
|
||||
DecryptedGroupChange decryptedGroupChange = GroupChangeReconstruct.reconstructGroupChange(from, to);
|
||||
|
||||
assertEquals(DecryptedGroupChange.newBuilder()
|
||||
.addDeleteRequestingMembers(UuidUtil.toByteString(member1))
|
||||
.build(),
|
||||
decryptedGroupChange);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void promote_requesting_members() {
|
||||
UUID member1 = UUID.randomUUID();
|
||||
ProfileKey profileKey1 = newProfileKey();
|
||||
UUID member2 = UUID.randomUUID();
|
||||
ProfileKey profileKey2 = newProfileKey();
|
||||
DecryptedGroup from = DecryptedGroup.newBuilder()
|
||||
.addRequestingMembers(requestingMember(member1, profileKey1))
|
||||
.addRequestingMembers(requestingMember(member2, profileKey2))
|
||||
.build();
|
||||
DecryptedGroup to = DecryptedGroup.newBuilder()
|
||||
.addMembers(member(member1, profileKey1))
|
||||
.addMembers(admin(member2, profileKey2))
|
||||
.build();
|
||||
|
||||
DecryptedGroupChange decryptedGroupChange = GroupChangeReconstruct.reconstructGroupChange(from, to);
|
||||
|
||||
assertEquals(DecryptedGroupChange.newBuilder()
|
||||
.addPromoteRequestingMembers(approveMember(member1))
|
||||
.addPromoteRequestingMembers(approveAdmin(member2))
|
||||
.build(),
|
||||
decryptedGroupChange);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void new_invite_link_password() {
|
||||
ByteString password1 = ByteString.copyFrom(Util.getSecretBytes(16));
|
||||
ByteString password2 = ByteString.copyFrom(Util.getSecretBytes(16));
|
||||
DecryptedGroup from = DecryptedGroup.newBuilder()
|
||||
.setInviteLinkPassword(password1)
|
||||
.build();
|
||||
DecryptedGroup to = DecryptedGroup.newBuilder()
|
||||
.setInviteLinkPassword(password2)
|
||||
.build();
|
||||
|
||||
DecryptedGroupChange decryptedGroupChange = GroupChangeReconstruct.reconstructGroupChange(from, to);
|
||||
|
||||
assertEquals(DecryptedGroupChange.newBuilder()
|
||||
.setNewInviteLinkPassword(password2)
|
||||
.build(),
|
||||
decryptedGroupChange);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.whispersystems.signalservice.api.groupsv2.ProtobufTestUtils.getMaxDeclaredFieldNumber;
|
||||
|
||||
public final class GroupChangeUtilTest {
|
||||
public final class GroupChangeUtil_changeIsEmpty_Test {
|
||||
|
||||
/**
|
||||
* Reflects over the generated protobuf class and ensures that no new fields have been added since we wrote this.
|
||||
@@ -20,7 +20,7 @@ public final class GroupChangeUtilTest {
|
||||
int maxFieldFound = getMaxDeclaredFieldNumber(GroupChange.Actions.class);
|
||||
|
||||
assertEquals("GroupChangeUtil and its tests need updating to account for new fields on " + GroupChange.Actions.class.getName(),
|
||||
GroupChangeUtil.CHANGE_ACTION_MAX_FIELD, maxFieldFound);
|
||||
19, maxFieldFound);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -31,7 +31,7 @@ public final class GroupChangeUtilTest {
|
||||
@Test
|
||||
public void not_empty_with_add_member_field_3() {
|
||||
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
|
||||
.addAddMembers(GroupChange.Actions.AddMemberAction.newBuilder().getDefaultInstanceForType())
|
||||
.addAddMembers(GroupChange.Actions.AddMemberAction.getDefaultInstance())
|
||||
.build();
|
||||
|
||||
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
|
||||
@@ -40,7 +40,7 @@ public final class GroupChangeUtilTest {
|
||||
@Test
|
||||
public void not_empty_with_delete_member_field_4() {
|
||||
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
|
||||
.addDeleteMembers(GroupChange.Actions.DeleteMemberAction.newBuilder().getDefaultInstanceForType())
|
||||
.addDeleteMembers(GroupChange.Actions.DeleteMemberAction.getDefaultInstance())
|
||||
.build();
|
||||
|
||||
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
|
||||
@@ -49,7 +49,7 @@ public final class GroupChangeUtilTest {
|
||||
@Test
|
||||
public void not_empty_with_modify_member_roles_field_5() {
|
||||
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
|
||||
.addModifyMemberRoles(GroupChange.Actions.ModifyMemberRoleAction.newBuilder().getDefaultInstanceForType())
|
||||
.addModifyMemberRoles(GroupChange.Actions.ModifyMemberRoleAction.getDefaultInstance())
|
||||
.build();
|
||||
|
||||
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
|
||||
@@ -58,7 +58,7 @@ public final class GroupChangeUtilTest {
|
||||
@Test
|
||||
public void not_empty_with_modify_profile_keys_field_6() {
|
||||
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
|
||||
.addModifyMemberProfileKeys(GroupChange.Actions.ModifyMemberProfileKeyAction.newBuilder().getDefaultInstanceForType())
|
||||
.addModifyMemberProfileKeys(GroupChange.Actions.ModifyMemberProfileKeyAction.getDefaultInstance())
|
||||
.build();
|
||||
|
||||
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
|
||||
@@ -67,7 +67,7 @@ public final class GroupChangeUtilTest {
|
||||
@Test
|
||||
public void not_empty_with_add_pending_members_field_7() {
|
||||
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
|
||||
.addAddPendingMembers(GroupChange.Actions.AddPendingMemberAction.newBuilder().getDefaultInstanceForType())
|
||||
.addAddPendingMembers(GroupChange.Actions.AddPendingMemberAction.getDefaultInstance())
|
||||
.build();
|
||||
|
||||
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
|
||||
@@ -76,7 +76,7 @@ public final class GroupChangeUtilTest {
|
||||
@Test
|
||||
public void not_empty_with_delete_pending_members_field_8() {
|
||||
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
|
||||
.addDeletePendingMembers(GroupChange.Actions.DeletePendingMemberAction.newBuilder().getDefaultInstanceForType())
|
||||
.addDeletePendingMembers(GroupChange.Actions.DeletePendingMemberAction.getDefaultInstance())
|
||||
.build();
|
||||
|
||||
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
|
||||
@@ -85,7 +85,7 @@ public final class GroupChangeUtilTest {
|
||||
@Test
|
||||
public void not_empty_with_promote_delete_pending_members_field_9() {
|
||||
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
|
||||
.addPromotePendingMembers(GroupChange.Actions.PromotePendingMemberAction.newBuilder().getDefaultInstanceForType())
|
||||
.addPromotePendingMembers(GroupChange.Actions.PromotePendingMemberAction.getDefaultInstance())
|
||||
.build();
|
||||
|
||||
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
|
||||
@@ -94,7 +94,7 @@ public final class GroupChangeUtilTest {
|
||||
@Test
|
||||
public void not_empty_with_modify_title_field_10() {
|
||||
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
|
||||
.setModifyTitle(GroupChange.Actions.ModifyTitleAction.newBuilder().getDefaultInstanceForType())
|
||||
.setModifyTitle(GroupChange.Actions.ModifyTitleAction.getDefaultInstance())
|
||||
.build();
|
||||
|
||||
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
|
||||
@@ -103,7 +103,7 @@ public final class GroupChangeUtilTest {
|
||||
@Test
|
||||
public void not_empty_with_modify_avatar_field_11() {
|
||||
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
|
||||
.setModifyAvatar(GroupChange.Actions.ModifyAvatarAction.newBuilder().getDefaultInstanceForType())
|
||||
.setModifyAvatar(GroupChange.Actions.ModifyAvatarAction.getDefaultInstance())
|
||||
.build();
|
||||
|
||||
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
|
||||
@@ -112,7 +112,7 @@ public final class GroupChangeUtilTest {
|
||||
@Test
|
||||
public void not_empty_with_modify_disappearing_message_timer_field_12() {
|
||||
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
|
||||
.setModifyDisappearingMessagesTimer(GroupChange.Actions.ModifyDisappearingMessagesTimerAction.newBuilder().getDefaultInstanceForType())
|
||||
.setModifyDisappearingMessagesTimer(GroupChange.Actions.ModifyDisappearingMessagesTimerAction.getDefaultInstance())
|
||||
.build();
|
||||
|
||||
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
|
||||
@@ -121,7 +121,7 @@ public final class GroupChangeUtilTest {
|
||||
@Test
|
||||
public void not_empty_with_modify_attributes_field_13() {
|
||||
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
|
||||
.setModifyAttributesAccess(GroupChange.Actions.ModifyAttributesAccessControlAction.newBuilder().getDefaultInstanceForType())
|
||||
.setModifyAttributesAccess(GroupChange.Actions.ModifyAttributesAccessControlAction.getDefaultInstance())
|
||||
.build();
|
||||
|
||||
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
|
||||
@@ -130,7 +130,52 @@ public final class GroupChangeUtilTest {
|
||||
@Test
|
||||
public void not_empty_with_modify_member_access_field_14() {
|
||||
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
|
||||
.setModifyMemberAccess(GroupChange.Actions.ModifyMembersAccessControlAction.newBuilder().getDefaultInstanceForType())
|
||||
.setModifyMemberAccess(GroupChange.Actions.ModifyMembersAccessControlAction.getDefaultInstance())
|
||||
.build();
|
||||
|
||||
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void not_empty_with_modify_add_from_invite_link_field_15() {
|
||||
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
|
||||
.setModifyAddFromInviteLinkAccess(GroupChange.Actions.ModifyAddFromInviteLinkAccessControlAction.getDefaultInstance())
|
||||
.build();
|
||||
|
||||
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void not_empty_with_add_requesting_members_field_16() {
|
||||
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
|
||||
.addAddRequestingMembers(GroupChange.Actions.AddRequestingMemberAction.getDefaultInstance())
|
||||
.build();
|
||||
|
||||
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void not_empty_with_delete_requesting_members_field_17() {
|
||||
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
|
||||
.addDeleteRequestingMembers(GroupChange.Actions.DeleteRequestingMemberAction.getDefaultInstance())
|
||||
.build();
|
||||
|
||||
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void not_empty_with_promote_requesting_members_field_18() {
|
||||
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
|
||||
.addPromoteRequestingMembers(GroupChange.Actions.PromoteRequestingMemberAction.getDefaultInstance())
|
||||
.build();
|
||||
|
||||
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void not_empty_with_promote_requesting_members_field_19() {
|
||||
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
|
||||
.setModifyInviteLinkPassword(GroupChange.Actions.ModifyInviteLinkPasswordAction.getDefaultInstance())
|
||||
.build();
|
||||
|
||||
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
|
||||
@@ -13,24 +13,69 @@ 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.encrypt;
|
||||
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.encryptedMember;
|
||||
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.encryptedRequestingMember;
|
||||
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.presentation;
|
||||
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_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}.
|
||||
*/
|
||||
@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}.
|
||||
*/
|
||||
@Test
|
||||
public void ensure_resolveConflict_knows_about_all_fields_of_GroupChange() {
|
||||
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class);
|
||||
|
||||
assertEquals("GroupChangeUtil#resolveConflict and its tests need updating to account for new fields on " + GroupChange.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}.
|
||||
*/
|
||||
@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 empty_actions() {
|
||||
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(DecryptedGroup.newBuilder().build(),
|
||||
@@ -471,4 +516,160 @@ public final class GroupChangeUtil_resolveConflict_Test {
|
||||
|
||||
assertTrue(GroupChangeUtil.changeIsEmpty(resolvedActions));
|
||||
}
|
||||
|
||||
@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();
|
||||
GroupChange.Actions change = GroupChange.Actions.newBuilder()
|
||||
.setModifyAddFromInviteLinkAccess(GroupChange.Actions.ModifyAddFromInviteLinkAccessControlAction.newBuilder().setAddFromInviteLinkAccess(AccessControl.AccessRequired.ADMINISTRATOR))
|
||||
.build();
|
||||
|
||||
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
|
||||
|
||||
assertTrue(GroupChangeUtil.changeIsEmpty(resolvedActions));
|
||||
}
|
||||
|
||||
@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))
|
||||
.addNewRequestingMembers(requestingMember(member3))
|
||||
.build();
|
||||
GroupChange.Actions change = GroupChange.Actions.newBuilder()
|
||||
.addAddRequestingMembers(GroupChange.Actions.AddRequestingMemberAction.newBuilder().setAdded(encryptedRequestingMember(member1, randomProfileKey())))
|
||||
.addAddRequestingMembers(GroupChange.Actions.AddRequestingMemberAction.newBuilder().setAdded(encryptedRequestingMember(member2, profileKey2)))
|
||||
.addAddRequestingMembers(GroupChange.Actions.AddRequestingMemberAction.newBuilder().setAdded(encryptedRequestingMember(member3, randomProfileKey())))
|
||||
.build();
|
||||
|
||||
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
|
||||
|
||||
GroupChange.Actions expected = GroupChange.Actions.newBuilder()
|
||||
.addAddRequestingMembers(GroupChange.Actions.AddRequestingMemberAction.newBuilder().setAdded(encryptedRequestingMember(member2, profileKey2)))
|
||||
.build();
|
||||
assertEquals(expected, resolvedActions);
|
||||
}
|
||||
|
||||
@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();
|
||||
GroupChange.Actions change = GroupChange.Actions.newBuilder()
|
||||
.addAddRequestingMembers(GroupChange.Actions.AddRequestingMemberAction.newBuilder().setAdded(encryptedRequestingMember(member1, profileKey1)))
|
||||
.addAddRequestingMembers(GroupChange.Actions.AddRequestingMemberAction.newBuilder().setAdded(encryptedRequestingMember(member2, profileKey2)))
|
||||
.addAddRequestingMembers(GroupChange.Actions.AddRequestingMemberAction.newBuilder().setAdded(encryptedRequestingMember(member3, profileKey3)))
|
||||
.build();
|
||||
|
||||
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
|
||||
|
||||
GroupChange.Actions expected = GroupChange.Actions.newBuilder()
|
||||
.addPromotePendingMembers(GroupChange.Actions.PromotePendingMemberAction.newBuilder().setPresentation(presentation(member1, profileKey1)))
|
||||
.addAddRequestingMembers(GroupChange.Actions.AddRequestingMemberAction.newBuilder().setAdded(encryptedRequestingMember(member2, profileKey2)))
|
||||
.addPromotePendingMembers(GroupChange.Actions.PromotePendingMemberAction.newBuilder().setPresentation(presentation(member3, profileKey3)))
|
||||
.build();
|
||||
assertEquals(expected, resolvedActions);
|
||||
}
|
||||
|
||||
@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();
|
||||
GroupChange.Actions change = GroupChange.Actions.newBuilder()
|
||||
.addDeleteRequestingMembers(GroupChange.Actions.DeleteRequestingMemberAction.newBuilder().setDeletedUserId(encrypt(member1)))
|
||||
.addDeleteRequestingMembers(GroupChange.Actions.DeleteRequestingMemberAction.newBuilder().setDeletedUserId(encrypt(member2)))
|
||||
.addDeleteRequestingMembers(GroupChange.Actions.DeleteRequestingMemberAction.newBuilder().setDeletedUserId(encrypt(member3)))
|
||||
.build();
|
||||
|
||||
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
|
||||
|
||||
GroupChange.Actions expected = GroupChange.Actions.newBuilder()
|
||||
.addDeleteRequestingMembers(GroupChange.Actions.DeleteRequestingMemberAction.newBuilder().setDeletedUserId(encrypt(member2)))
|
||||
.build();
|
||||
assertEquals(expected, resolvedActions);
|
||||
}
|
||||
|
||||
@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();
|
||||
GroupChange.Actions change = GroupChange.Actions.newBuilder()
|
||||
.addPromoteRequestingMembers(GroupChange.Actions.PromoteRequestingMemberAction.newBuilder().setRole(Member.Role.DEFAULT).setUserId(UuidUtil.toByteString(member1)))
|
||||
.addPromoteRequestingMembers(GroupChange.Actions.PromoteRequestingMemberAction.newBuilder().setRole(Member.Role.DEFAULT).setUserId(UuidUtil.toByteString(member2)))
|
||||
.addPromoteRequestingMembers(GroupChange.Actions.PromoteRequestingMemberAction.newBuilder().setRole(Member.Role.DEFAULT).setUserId(UuidUtil.toByteString(member3)))
|
||||
.build();
|
||||
|
||||
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
|
||||
|
||||
GroupChange.Actions expected = GroupChange.Actions.newBuilder()
|
||||
.addPromoteRequestingMembers(GroupChange.Actions.PromoteRequestingMemberAction.newBuilder().setRole(Member.Role.DEFAULT).setUserId(UuidUtil.toByteString(member2)))
|
||||
.build();
|
||||
assertEquals(expected, resolvedActions);
|
||||
}
|
||||
|
||||
@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();
|
||||
GroupChange.Actions change = GroupChange.Actions.newBuilder()
|
||||
.setModifyInviteLinkPassword(GroupChange.Actions.ModifyInviteLinkPasswordAction.newBuilder().setInviteLinkPassword(password2))
|
||||
.build();
|
||||
|
||||
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
|
||||
|
||||
GroupChange.Actions expected = GroupChange.Actions.newBuilder()
|
||||
.setModifyInviteLinkPassword(GroupChange.Actions.ModifyInviteLinkPasswordAction.newBuilder().setInviteLinkPassword(password2))
|
||||
.build();
|
||||
assertEquals(expected, resolvedActions);
|
||||
}
|
||||
}
|
||||
@@ -8,11 +8,13 @@ import org.junit.Test;
|
||||
import org.signal.storageservice.protos.groups.AccessControl;
|
||||
import org.signal.storageservice.protos.groups.GroupChange;
|
||||
import org.signal.storageservice.protos.groups.Member;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedApproveMember;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
||||
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.storageservice.protos.groups.local.DecryptedRequestingMember;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedString;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedTimer;
|
||||
import org.signal.zkgroup.InvalidInputException;
|
||||
@@ -93,6 +95,23 @@ public final class GroupsV2Operations_decrypt_change_Test {
|
||||
.setUuid(UuidUtil.toByteString(newMember))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void can_decrypt_member_direct_join_field3() {
|
||||
UUID newMember = UUID.randomUUID();
|
||||
ProfileKey profileKey = newProfileKey();
|
||||
GroupCandidate groupCandidate = groupCandidate(newMember, profileKey);
|
||||
|
||||
assertDecryption(groupOperations.createGroupJoinDirect(groupCandidate.getProfileKeyCredential().get())
|
||||
.setRevision(10),
|
||||
DecryptedGroupChange.newBuilder()
|
||||
.setRevision(10)
|
||||
.addNewMembers(DecryptedMember.newBuilder()
|
||||
.setRole(Member.Role.DEFAULT)
|
||||
.setProfileKey(ByteString.copyFrom(profileKey.serialize()))
|
||||
.setJoinedAtRevision(10)
|
||||
.setUuid(UuidUtil.toByteString(newMember))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void can_decrypt_member_additions_direct_to_admin_field3() {
|
||||
UUID self = UUID.randomUUID();
|
||||
@@ -266,20 +285,80 @@ public final class GroupsV2Operations_decrypt_change_Test {
|
||||
|
||||
@Test
|
||||
public void can_pass_through_new_attribute_access_rights_field_13() {
|
||||
assertDecryption(GroupChange.Actions.newBuilder()
|
||||
.setModifyAttributesAccess(GroupChange.Actions.ModifyAttributesAccessControlAction.newBuilder()
|
||||
.setAttributesAccess(AccessControl.AccessRequired.MEMBER)),
|
||||
DecryptedGroupChange.newBuilder()
|
||||
.setNewAttributeAccess(AccessControl.AccessRequired.MEMBER));
|
||||
assertDecryption(groupOperations.createChangeAttributesRights(AccessControl.AccessRequired.MEMBER),
|
||||
DecryptedGroupChange.newBuilder()
|
||||
.setNewAttributeAccess(AccessControl.AccessRequired.MEMBER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void can_pass_through_new_membership_rights_field_14() {
|
||||
assertDecryption(groupOperations.createChangeMembershipRights(AccessControl.AccessRequired.ADMINISTRATOR),
|
||||
DecryptedGroupChange.newBuilder()
|
||||
.setNewMemberAccess(AccessControl.AccessRequired.ADMINISTRATOR));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void can_pass_through_new_add_by_invite_link_rights_field_15() {
|
||||
assertDecryption(groupOperations.createChangeJoinByLinkRights(AccessControl.AccessRequired.ADMINISTRATOR),
|
||||
DecryptedGroupChange.newBuilder()
|
||||
.setNewInviteLinkAccess(AccessControl.AccessRequired.ADMINISTRATOR));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void can_pass_through_new_add_by_invite_link_rights_field_15_unsatisfiable() {
|
||||
assertDecryption(groupOperations.createChangeJoinByLinkRights(AccessControl.AccessRequired.UNSATISFIABLE),
|
||||
DecryptedGroupChange.newBuilder()
|
||||
.setNewInviteLinkAccess(AccessControl.AccessRequired.UNSATISFIABLE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void can_decrypt_member_requests_field16() {
|
||||
UUID newRequestingMember = UUID.randomUUID();
|
||||
ProfileKey profileKey = newProfileKey();
|
||||
GroupCandidate groupCandidate = groupCandidate(newRequestingMember, profileKey);
|
||||
|
||||
assertDecryption(groupOperations.createGroupJoinRequest(groupCandidate.getProfileKeyCredential().get())
|
||||
.setRevision(10),
|
||||
DecryptedGroupChange.newBuilder()
|
||||
.setRevision(10)
|
||||
.addNewRequestingMembers(DecryptedRequestingMember.newBuilder()
|
||||
.setUuid(UuidUtil.toByteString(newRequestingMember))
|
||||
.setProfileKey(ByteString.copyFrom(profileKey.serialize()))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void can_decrypt_member_requests_refusals_field17() {
|
||||
UUID newRequestingMember = UUID.randomUUID();
|
||||
|
||||
assertDecryption(groupOperations.createRefuseGroupJoinRequest(Collections.singleton(newRequestingMember))
|
||||
.setRevision(10),
|
||||
DecryptedGroupChange.newBuilder()
|
||||
.setRevision(10)
|
||||
.addDeleteRequestingMembers(UuidUtil.toByteString(newRequestingMember)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void can_decrypt_promote_requesting_members_field18() {
|
||||
UUID newRequestingMember = UUID.randomUUID();
|
||||
|
||||
assertDecryption(groupOperations.createApproveGroupJoinRequest(Collections.singleton(newRequestingMember))
|
||||
.setRevision(15),
|
||||
DecryptedGroupChange.newBuilder()
|
||||
.setRevision(15)
|
||||
.addPromoteRequestingMembers(DecryptedApproveMember.newBuilder()
|
||||
.setRole(Member.Role.DEFAULT)
|
||||
.setUuid(UuidUtil.toByteString(newRequestingMember))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void can_pass_through_new_invite_link_password_field19() {
|
||||
byte[] newPassword = Util.getSecretBytes(16);
|
||||
|
||||
assertDecryption(GroupChange.Actions.newBuilder()
|
||||
.setModifyMemberAccess(GroupChange.Actions.ModifyMembersAccessControlAction.newBuilder()
|
||||
.setMembersAccess(AccessControl.AccessRequired.ADMINISTRATOR)),
|
||||
.setModifyInviteLinkPassword(GroupChange.Actions.ModifyInviteLinkPasswordAction.newBuilder()
|
||||
.setInviteLinkPassword(ByteString.copyFrom(newPassword))),
|
||||
DecryptedGroupChange.newBuilder()
|
||||
.setNewMemberAccess(AccessControl.AccessRequired.ADMINISTRATOR));
|
||||
.setNewInviteLinkPassword(ByteString.copyFrom(newPassword)));
|
||||
}
|
||||
|
||||
private static ProfileKey newProfileKey() {
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package org.whispersystems.signalservice.api.groupsv2;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.signal.storageservice.protos.groups.AccessControl;
|
||||
import org.signal.storageservice.protos.groups.GroupJoinInfo;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo;
|
||||
import org.signal.zkgroup.InvalidInputException;
|
||||
import org.signal.zkgroup.groups.GroupMasterKey;
|
||||
import org.signal.zkgroup.groups.GroupSecretParams;
|
||||
import org.whispersystems.signalservice.internal.util.Util;
|
||||
import org.whispersystems.signalservice.testutil.ZkGroupLibraryUtil;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.whispersystems.signalservice.api.groupsv2.ProtobufTestUtils.getMaxDeclaredFieldNumber;
|
||||
|
||||
public final class GroupsV2Operations_decrypt_groupJoinInfo_Test {
|
||||
|
||||
private GroupsV2Operations.GroupOperations groupOperations;
|
||||
|
||||
@Before
|
||||
public void setup() throws InvalidInputException {
|
||||
ZkGroupLibraryUtil.assumeZkGroupSupportedOnOS();
|
||||
|
||||
TestZkGroupServer server = new TestZkGroupServer();
|
||||
ClientZkOperations clientZkOperations = new ClientZkOperations(server.getServerPublicParams());
|
||||
GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(new GroupMasterKey(Util.getSecretBytes(32)));
|
||||
|
||||
groupOperations = new GroupsV2Operations(clientZkOperations).forGroup(groupSecretParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 decrypted by {@link GroupsV2Operations.GroupOperations#decryptGroupJoinInfo}.
|
||||
*/
|
||||
@Test
|
||||
public void ensure_GroupOperations_knows_about_all_fields_of_Group() {
|
||||
int maxFieldFound = getMaxDeclaredFieldNumber(GroupJoinInfo.class);
|
||||
|
||||
assertEquals("GroupOperations and its tests need updating to account for new fields on " + GroupJoinInfo.class.getName(),
|
||||
6, maxFieldFound);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decrypt_title_field_2() {
|
||||
GroupJoinInfo groupJoinInfo = GroupJoinInfo.newBuilder()
|
||||
.setTitle(groupOperations.encryptTitle("Title!"))
|
||||
.build();
|
||||
|
||||
DecryptedGroupJoinInfo decryptedGroupJoinInfo = groupOperations.decryptGroupJoinInfo(groupJoinInfo);
|
||||
|
||||
assertEquals("Title!", decryptedGroupJoinInfo.getTitle());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void avatar_field_passed_through_3() {
|
||||
GroupJoinInfo groupJoinInfo = GroupJoinInfo.newBuilder()
|
||||
.setAvatar("AvatarCdnKey")
|
||||
.build();
|
||||
|
||||
DecryptedGroupJoinInfo decryptedGroupJoinInfo = groupOperations.decryptGroupJoinInfo(groupJoinInfo);
|
||||
|
||||
assertEquals("AvatarCdnKey", decryptedGroupJoinInfo.getAvatar());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void member_count_passed_through_4() {
|
||||
GroupJoinInfo groupJoinInfo = GroupJoinInfo.newBuilder()
|
||||
.setMemberCount(97)
|
||||
.build();
|
||||
|
||||
DecryptedGroupJoinInfo decryptedGroupJoinInfo = groupOperations.decryptGroupJoinInfo(groupJoinInfo);
|
||||
|
||||
assertEquals(97, decryptedGroupJoinInfo.getMemberCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void add_from_invite_link_access_control_passed_though_5_administrator() {
|
||||
GroupJoinInfo groupJoinInfo = GroupJoinInfo.newBuilder()
|
||||
.setAddFromInviteLink(AccessControl.AccessRequired.ADMINISTRATOR)
|
||||
.build();
|
||||
|
||||
DecryptedGroupJoinInfo decryptedGroupJoinInfo = groupOperations.decryptGroupJoinInfo(groupJoinInfo);
|
||||
|
||||
assertEquals(AccessControl.AccessRequired.ADMINISTRATOR, decryptedGroupJoinInfo.getAddFromInviteLink());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void add_from_invite_link_access_control_passed_though_5_any() {
|
||||
GroupJoinInfo groupJoinInfo = GroupJoinInfo.newBuilder()
|
||||
.setAddFromInviteLink(AccessControl.AccessRequired.ANY)
|
||||
.build();
|
||||
|
||||
DecryptedGroupJoinInfo decryptedGroupJoinInfo = groupOperations.decryptGroupJoinInfo(groupJoinInfo);
|
||||
|
||||
assertEquals(AccessControl.AccessRequired.ANY, decryptedGroupJoinInfo.getAddFromInviteLink());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void revision_passed_though_6() {
|
||||
GroupJoinInfo groupJoinInfo = GroupJoinInfo.newBuilder()
|
||||
.setRevision(11)
|
||||
.build();
|
||||
|
||||
DecryptedGroupJoinInfo decryptedGroupJoinInfo = groupOperations.decryptGroupJoinInfo(groupJoinInfo);
|
||||
|
||||
assertEquals(11, decryptedGroupJoinInfo.getRevision());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
package org.whispersystems.signalservice.api.groupsv2;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.signal.storageservice.protos.groups.AccessControl;
|
||||
import org.signal.storageservice.protos.groups.Group;
|
||||
import org.signal.storageservice.protos.groups.GroupChange;
|
||||
import org.signal.storageservice.protos.groups.Member;
|
||||
import org.signal.storageservice.protos.groups.PendingMember;
|
||||
import org.signal.storageservice.protos.groups.RequestingMember;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedApproveMember;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
||||
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.storageservice.protos.groups.local.DecryptedRequestingMember;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedString;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedTimer;
|
||||
import org.signal.zkgroup.InvalidInputException;
|
||||
import org.signal.zkgroup.VerificationFailedException;
|
||||
import org.signal.zkgroup.groups.ClientZkGroupCipher;
|
||||
import org.signal.zkgroup.groups.GroupMasterKey;
|
||||
import org.signal.zkgroup.groups.GroupSecretParams;
|
||||
import org.signal.zkgroup.groups.UuidCiphertext;
|
||||
import org.signal.zkgroup.profiles.ClientZkProfileOperations;
|
||||
import org.signal.zkgroup.profiles.ProfileKey;
|
||||
import org.signal.zkgroup.profiles.ProfileKeyCommitment;
|
||||
import org.signal.zkgroup.profiles.ProfileKeyCredential;
|
||||
import org.signal.zkgroup.profiles.ProfileKeyCredentialPresentation;
|
||||
import org.signal.zkgroup.profiles.ProfileKeyCredentialRequest;
|
||||
import org.signal.zkgroup.profiles.ProfileKeyCredentialRequestContext;
|
||||
import org.signal.zkgroup.profiles.ProfileKeyCredentialResponse;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
import org.whispersystems.signalservice.internal.util.Util;
|
||||
import org.whispersystems.signalservice.testutil.ZkGroupLibraryUtil;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.whispersystems.signalservice.api.groupsv2.ProtobufTestUtils.getMaxDeclaredFieldNumber;
|
||||
|
||||
public final class GroupsV2Operations_decrypt_group_Test {
|
||||
|
||||
private GroupSecretParams groupSecretParams;
|
||||
private GroupsV2Operations.GroupOperations groupOperations;
|
||||
|
||||
@Before
|
||||
public void setup() throws InvalidInputException {
|
||||
ZkGroupLibraryUtil.assumeZkGroupSupportedOnOS();
|
||||
|
||||
TestZkGroupServer server = new TestZkGroupServer();
|
||||
ClientZkOperations clientZkOperations = new ClientZkOperations(server.getServerPublicParams());
|
||||
|
||||
groupSecretParams = GroupSecretParams.deriveFromMasterKey(new GroupMasterKey(Util.getSecretBytes(32)));
|
||||
groupOperations = new GroupsV2Operations(clientZkOperations).forGroup(groupSecretParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 decrypted by {@link GroupsV2Operations.GroupOperations#decryptGroup}.
|
||||
*/
|
||||
@Test
|
||||
public void ensure_GroupOperations_knows_about_all_fields_of_Group() {
|
||||
int maxFieldFound = getMaxDeclaredFieldNumber(Group.class);
|
||||
|
||||
assertEquals("GroupOperations and its tests need updating to account for new fields on " + Group.class.getName(),
|
||||
10, maxFieldFound);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decrypt_title_field_2() throws VerificationFailedException, InvalidGroupStateException {
|
||||
Group group = Group.newBuilder()
|
||||
.setTitle(groupOperations.encryptTitle("Title!"))
|
||||
.build();
|
||||
|
||||
DecryptedGroup decryptedGroup = groupOperations.decryptGroup(group);
|
||||
|
||||
assertEquals("Title!", decryptedGroup.getTitle());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void avatar_field_passed_through_3() throws VerificationFailedException, InvalidGroupStateException {
|
||||
Group group = Group.newBuilder()
|
||||
.setAvatar("AvatarCdnKey")
|
||||
.build();
|
||||
|
||||
DecryptedGroup decryptedGroup = groupOperations.decryptGroup(group);
|
||||
|
||||
assertEquals("AvatarCdnKey", decryptedGroup.getAvatar());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decrypt_message_timer_field_4() throws VerificationFailedException, InvalidGroupStateException {
|
||||
Group group = Group.newBuilder()
|
||||
.setDisappearingMessagesTimer(groupOperations.encryptTimer(123))
|
||||
.build();
|
||||
|
||||
DecryptedGroup decryptedGroup = groupOperations.decryptGroup(group);
|
||||
|
||||
assertEquals(123, decryptedGroup.getDisappearingMessagesTimer().getDuration());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pass_through_access_control_field_5() throws VerificationFailedException, InvalidGroupStateException {
|
||||
AccessControl accessControl = AccessControl.newBuilder()
|
||||
.setMembers(AccessControl.AccessRequired.ADMINISTRATOR)
|
||||
.setAttributes(AccessControl.AccessRequired.MEMBER)
|
||||
.setAddFromInviteLink(AccessControl.AccessRequired.UNSATISFIABLE)
|
||||
.build();
|
||||
Group group = Group.newBuilder()
|
||||
.setAccessControl(accessControl)
|
||||
.build();
|
||||
|
||||
DecryptedGroup decryptedGroup = groupOperations.decryptGroup(group);
|
||||
|
||||
assertEquals(accessControl, decryptedGroup.getAccessControl());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void set_revision_field_6() throws VerificationFailedException, InvalidGroupStateException {
|
||||
Group group = Group.newBuilder()
|
||||
.setRevision(99)
|
||||
.build();
|
||||
|
||||
DecryptedGroup decryptedGroup = groupOperations.decryptGroup(group);
|
||||
|
||||
assertEquals(99, decryptedGroup.getRevision());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decrypt_full_members_field_7() throws VerificationFailedException, InvalidGroupStateException {
|
||||
UUID admin1 = UUID.randomUUID();
|
||||
UUID member1 = UUID.randomUUID();
|
||||
ProfileKey adminProfileKey = newProfileKey();
|
||||
ProfileKey memberProfileKey = newProfileKey();
|
||||
|
||||
Group group = Group.newBuilder()
|
||||
.addMembers(Member.newBuilder()
|
||||
.setRole(Member.Role.ADMINISTRATOR)
|
||||
.setUserId(groupOperations.encryptUuid(admin1))
|
||||
.setJoinedAtRevision(4)
|
||||
.setProfileKey(encryptProfileKey(admin1, adminProfileKey)))
|
||||
.addMembers(Member.newBuilder()
|
||||
.setRole(Member.Role.DEFAULT)
|
||||
.setUserId(groupOperations.encryptUuid(member1))
|
||||
.setJoinedAtRevision(7)
|
||||
.setProfileKey(encryptProfileKey(member1, memberProfileKey)))
|
||||
.build();
|
||||
|
||||
DecryptedGroup decryptedGroup = groupOperations.decryptGroup(group);
|
||||
|
||||
assertEquals(DecryptedGroup.newBuilder()
|
||||
.addMembers(DecryptedMember.newBuilder()
|
||||
.setJoinedAtRevision(4)
|
||||
.setUuid(UuidUtil.toByteString(admin1))
|
||||
.setRole(Member.Role.ADMINISTRATOR)
|
||||
.setProfileKey(ByteString.copyFrom(adminProfileKey.serialize())))
|
||||
.addMembers(DecryptedMember.newBuilder()
|
||||
.setJoinedAtRevision(7)
|
||||
.setRole(Member.Role.DEFAULT)
|
||||
.setUuid(UuidUtil.toByteString(member1))
|
||||
.setProfileKey(ByteString.copyFrom(memberProfileKey.serialize())))
|
||||
.build().getMembersList(),
|
||||
decryptedGroup.getMembersList());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decrypt_pending_members_field_8() throws VerificationFailedException, InvalidGroupStateException {
|
||||
UUID admin1 = UUID.randomUUID();
|
||||
UUID member1 = UUID.randomUUID();
|
||||
UUID member2 = UUID.randomUUID();
|
||||
UUID inviter1 = UUID.randomUUID();
|
||||
UUID inviter2 = UUID.randomUUID();
|
||||
|
||||
Group group = Group.newBuilder()
|
||||
.addPendingMembers(PendingMember.newBuilder()
|
||||
.setAddedByUserId(groupOperations.encryptUuid(inviter1))
|
||||
.setTimestamp(100)
|
||||
.setMember(Member.newBuilder()
|
||||
.setRole(Member.Role.ADMINISTRATOR)
|
||||
.setUserId(groupOperations.encryptUuid(admin1))))
|
||||
.addPendingMembers(PendingMember.newBuilder()
|
||||
.setAddedByUserId(groupOperations.encryptUuid(inviter1))
|
||||
.setTimestamp(200)
|
||||
.setMember(Member.newBuilder()
|
||||
.setRole(Member.Role.DEFAULT)
|
||||
.setUserId(groupOperations.encryptUuid(member1))))
|
||||
.addPendingMembers(PendingMember.newBuilder()
|
||||
.setAddedByUserId(groupOperations.encryptUuid(inviter2))
|
||||
.setTimestamp(1500)
|
||||
.setMember(Member.newBuilder()
|
||||
.setUserId(groupOperations.encryptUuid(member2))))
|
||||
.build();
|
||||
|
||||
DecryptedGroup decryptedGroup = groupOperations.decryptGroup(group);
|
||||
|
||||
assertEquals(DecryptedGroup.newBuilder()
|
||||
.addPendingMembers(DecryptedPendingMember.newBuilder()
|
||||
.setUuid(UuidUtil.toByteString(admin1))
|
||||
.setUuidCipherText(groupOperations.encryptUuid(admin1))
|
||||
.setTimestamp(100)
|
||||
.setAddedByUuid(UuidUtil.toByteString(inviter1))
|
||||
.setRole(Member.Role.ADMINISTRATOR))
|
||||
.addPendingMembers(DecryptedPendingMember.newBuilder()
|
||||
.setUuid(UuidUtil.toByteString(member1))
|
||||
.setUuidCipherText(groupOperations.encryptUuid(member1))
|
||||
.setTimestamp(200)
|
||||
.setAddedByUuid(UuidUtil.toByteString(inviter1))
|
||||
.setRole(Member.Role.DEFAULT))
|
||||
.addPendingMembers(DecryptedPendingMember.newBuilder()
|
||||
.setUuid(UuidUtil.toByteString(member2))
|
||||
.setUuidCipherText(groupOperations.encryptUuid(member2))
|
||||
.setTimestamp(1500)
|
||||
.setAddedByUuid(UuidUtil.toByteString(inviter2))
|
||||
.setRole(Member.Role.DEFAULT))
|
||||
.build().getPendingMembersList(),
|
||||
decryptedGroup.getPendingMembersList());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decrypt_requesting_members_field_9() throws VerificationFailedException, InvalidGroupStateException {
|
||||
UUID admin1 = UUID.randomUUID();
|
||||
UUID member1 = UUID.randomUUID();
|
||||
ProfileKey adminProfileKey = newProfileKey();
|
||||
ProfileKey memberProfileKey = newProfileKey();
|
||||
|
||||
Group group = Group.newBuilder()
|
||||
.addRequestingMembers(RequestingMember.newBuilder()
|
||||
.setUserId(groupOperations.encryptUuid(admin1))
|
||||
.setProfileKey(encryptProfileKey(admin1, adminProfileKey))
|
||||
.setTimestamp(5000))
|
||||
.addRequestingMembers(RequestingMember.newBuilder()
|
||||
.setUserId(groupOperations.encryptUuid(member1))
|
||||
.setProfileKey(encryptProfileKey(member1, memberProfileKey))
|
||||
.setTimestamp(15000))
|
||||
.build();
|
||||
|
||||
DecryptedGroup decryptedGroup = groupOperations.decryptGroup(group);
|
||||
|
||||
assertEquals(DecryptedGroup.newBuilder()
|
||||
.addRequestingMembers(DecryptedRequestingMember.newBuilder()
|
||||
.setUuid(UuidUtil.toByteString(admin1))
|
||||
.setProfileKey(ByteString.copyFrom(adminProfileKey.serialize()))
|
||||
.setTimestamp(5000))
|
||||
.addRequestingMembers(DecryptedRequestingMember.newBuilder()
|
||||
.setUuid(UuidUtil.toByteString(member1))
|
||||
.setProfileKey(ByteString.copyFrom(memberProfileKey.serialize()))
|
||||
.setTimestamp(15000))
|
||||
.build().getRequestingMembersList(),
|
||||
decryptedGroup.getRequestingMembersList());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pass_through_group_link_password_field_10() throws VerificationFailedException, InvalidGroupStateException {
|
||||
ByteString password = ByteString.copyFrom(Util.getSecretBytes(16));
|
||||
Group group = Group.newBuilder()
|
||||
.setInviteLinkPassword(password)
|
||||
.build();
|
||||
|
||||
DecryptedGroup decryptedGroup = groupOperations.decryptGroup(group);
|
||||
|
||||
assertEquals(password, decryptedGroup.getInviteLinkPassword());
|
||||
}
|
||||
|
||||
private ByteString encryptProfileKey(UUID uuid, ProfileKey profileKey) {
|
||||
return ByteString.copyFrom(new ClientZkGroupCipher(groupSecretParams).encryptProfileKey(profileKey, uuid).serialize());
|
||||
}
|
||||
|
||||
private static ProfileKey newProfileKey() {
|
||||
try {
|
||||
return new ProfileKey(Util.getSecretBytes(32));
|
||||
} catch (InvalidInputException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,17 @@ package org.whispersystems.signalservice.api.groupsv2;
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.signal.storageservice.protos.groups.Member;
|
||||
import org.signal.storageservice.protos.groups.RequestingMember;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedApproveMember;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
||||
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.storageservice.protos.groups.local.DecryptedRequestingMember;
|
||||
import org.signal.zkgroup.InvalidInputException;
|
||||
import org.signal.zkgroup.profiles.ProfileKey;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
import org.whispersystems.signalservice.internal.util.Util;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
@@ -70,6 +74,12 @@ final class ProtoTestUtils {
|
||||
.build();
|
||||
}
|
||||
|
||||
static RequestingMember encryptedRequestingMember(UUID uuid, ProfileKey profileKey) {
|
||||
return RequestingMember.newBuilder()
|
||||
.setPresentation(presentation(uuid, profileKey))
|
||||
.build();
|
||||
}
|
||||
|
||||
static DecryptedMember member(UUID uuid) {
|
||||
return DecryptedMember.newBuilder()
|
||||
.setUuid(UuidUtil.toByteString(uuid))
|
||||
@@ -101,6 +111,32 @@ final class ProtoTestUtils {
|
||||
.build();
|
||||
}
|
||||
|
||||
static DecryptedRequestingMember requestingMember(UUID uuid) {
|
||||
return requestingMember(uuid, newProfileKey());
|
||||
}
|
||||
|
||||
static DecryptedRequestingMember requestingMember(UUID uuid, ProfileKey profileKey) {
|
||||
return DecryptedRequestingMember.newBuilder()
|
||||
.setUuid(UuidUtil.toByteString(uuid))
|
||||
.setProfileKey(ByteString.copyFrom(profileKey.serialize()))
|
||||
.build();
|
||||
}
|
||||
|
||||
static DecryptedApproveMember approveMember(UUID uuid) {
|
||||
return approve(uuid, Member.Role.DEFAULT);
|
||||
}
|
||||
|
||||
static DecryptedApproveMember approveAdmin(UUID uuid) {
|
||||
return approve(uuid, Member.Role.ADMINISTRATOR);
|
||||
}
|
||||
|
||||
private static DecryptedApproveMember approve(UUID uuid, Member.Role role) {
|
||||
return DecryptedApproveMember.newBuilder()
|
||||
.setUuid(UuidUtil.toByteString(uuid))
|
||||
.setRole(role)
|
||||
.build();
|
||||
}
|
||||
|
||||
static DecryptedMember member(UUID uuid, ProfileKey profileKey) {
|
||||
return withProfileKey(member(uuid), profileKey);
|
||||
}
|
||||
@@ -135,4 +171,12 @@ final class ProtoTestUtils {
|
||||
.setRole(Member.Role.DEFAULT)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static ProfileKey newProfileKey() {
|
||||
try {
|
||||
return new ProfileKey(Util.getSecretBytes(32));
|
||||
} catch (InvalidInputException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package org.whispersystems.util;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.whispersystems.signalservice.internal.util.Hex;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public final class Base64UrlSafeTest {
|
||||
|
||||
private final byte[] data;
|
||||
private final String encoded;
|
||||
private final String encodedWithoutPadding;
|
||||
|
||||
@Parameterized.Parameters
|
||||
public static Collection<Object[]> data() {
|
||||
return Arrays.asList(new Object[][]{
|
||||
{ "", "", "" },
|
||||
{ "01", "AQ==", "AQ" },
|
||||
{ "0102", "AQI=", "AQI" },
|
||||
{ "010203", "AQID", "AQID" },
|
||||
{ "030405", "AwQF", "AwQF" },
|
||||
{ "03040506", "AwQFBg==", "AwQFBg" },
|
||||
{ "0304050708", "AwQFBwg=", "AwQFBwg" },
|
||||
{ "af4d6cff", "r01s_w==", "r01s_w" },
|
||||
{ "ffefde", "_-_e", "_-_e" },
|
||||
});
|
||||
}
|
||||
|
||||
public Base64UrlSafeTest(String hexData, String encoded, String encodedWithoutPadding) throws IOException {
|
||||
this.data = Hex.fromStringCondensed(hexData);
|
||||
this.encoded = encoded;
|
||||
this.encodedWithoutPadding = encodedWithoutPadding;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encodes_as_expected() {
|
||||
assertEquals(encoded, Base64UrlSafe.encodeBytes(data));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encodes_as_expected_without_padding() {
|
||||
assertEquals(encodedWithoutPadding, Base64UrlSafe.encodeBytesWithoutPadding(data));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodes_as_expected() throws IOException {
|
||||
assertArrayEquals(data, Base64UrlSafe.decode(encoded));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodes_padding_agnostic_as_expected() throws IOException {
|
||||
assertArrayEquals(data, Base64UrlSafe.decodePaddingAgnostic(encoded));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodes_as_expected_without_padding() throws IOException {
|
||||
assertArrayEquals(data, Base64UrlSafe.decodePaddingAgnostic(encodedWithoutPadding));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user