From d3dbcfd62d87ae2ebd9f68388093178e2a0cb23c Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 15 Jan 2021 15:43:58 -0300 Subject: [PATCH] Recreate plausible private group sharing sessions when re-adding contact from group --- .../api/privategroup/PrivateGroupManager.java | 4 +- .../privategroup/PrivateGroupManagerImpl.java | 5 +- .../GroupInvitationManagerImpl.java | 54 +++++++- .../GroupInvitationIntegrationTest.java | 15 +- .../GroupInvitationManagerImplTest.java | 128 +++++++++++++++--- 5 files changed, 170 insertions(+), 36 deletions(-) diff --git a/briar-api/src/main/java/org/briarproject/briar/api/privategroup/PrivateGroupManager.java b/briar-api/src/main/java/org/briarproject/briar/api/privategroup/PrivateGroupManager.java index da07b091a..ba1fb730c 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/privategroup/PrivateGroupManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/privategroup/PrivateGroupManager.java @@ -110,9 +110,9 @@ public interface PrivateGroupManager { throws DbException; /** - * Returns true if the private group with the given ID was created by us. + * Returns true if the given private group was created by us. */ - boolean isOurPrivateGroup(Transaction txn, GroupId g) throws DbException; + boolean isOurPrivateGroup(Transaction txn, PrivateGroup g) throws DbException; /** * Returns the text of the private group message with the given ID. diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java index 9eea9f11b..bdff685df 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java @@ -287,11 +287,10 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook } @Override - public boolean isOurPrivateGroup(Transaction txn, GroupId g) + public boolean isOurPrivateGroup(Transaction txn, PrivateGroup g) throws DbException { - PrivateGroup group = getPrivateGroup(txn, g); LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); - return localAuthor.getId().equals(group.getCreator().getId()); + return localAuthor.getId().equals(g.getCreator().getId()); } @Override diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java index 2c564852f..7460b0a24 100644 --- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImpl.java @@ -126,20 +126,60 @@ class GroupInvitationManagerImpl extends ConversationClientImpl db.setGroupVisibility(txn, c.getId(), g.getId(), client); // Attach the contact ID to the group clientHelper.setContactId(txn, g.getId(), c.getId()); - // If the contact belongs to any private groups (we didn't create), - // create a peer session - for (Group pg : db.getGroups(txn, PrivateGroupManager.CLIENT_ID, + // If the contact belongs to any private groups, create a peer session + // or sessions in LEFT state for creator/invitee. + for (Group group : db.getGroups(txn, PrivateGroupManager.CLIENT_ID, PrivateGroupManager.MAJOR_VERSION)) { - if (privateGroupManager.isMember(txn, pg.getId(), c.getAuthor())) { - if (!privateGroupManager.isOurPrivateGroup(txn, pg.getId())) { - addingMember(txn, pg.getId(), c); - } + if (privateGroupManager + .isMember(txn, group.getId(), c.getAuthor())) { + PrivateGroup pg = + privateGroupManager.getPrivateGroup(txn, group.getId()); + recreateSession(txn, c, pg, g.getId()); } } } + private void recreateSession(Transaction txn, Contact c, + PrivateGroup pg, GroupId contactGroupId) throws DbException { + boolean isOur = privateGroupManager.isOurPrivateGroup(txn, pg); + boolean isTheirs = + c.getAuthor().getId().equals(pg.getCreator().getId()); + if (isOur || isTheirs) { + // we are creator or invitee, create a left session for each role + MessageId storageId = createStorageId(txn, contactGroupId); + Session session; + if (isOur) { + session = new CreatorSession(contactGroupId, pg.getId(), null, + null, 0, 0, CreatorState.LEFT); + } else { + session = new InviteeSession(contactGroupId, pg.getId(), null, + null, 0, 0, InviteeState.LEFT); + } + try { + storeSession(txn, storageId, session); + } catch (FormatException e) { + throw new DbException(e); + } + } else { + // we are neither creator nor invitee, create peer session + addingMember(txn, pg.getId(), c); + } + } + @Override public void removingContact(Transaction txn, Contact c) throws DbException { + // mark private groups created by that contact as dissolved + for (Group g : db.getGroups(txn, PrivateGroupManager.CLIENT_ID, + PrivateGroupManager.MAJOR_VERSION)) { + if (privateGroupManager.isMember(txn, g.getId(), c.getAuthor())) { + PrivateGroup pg = + privateGroupManager.getPrivateGroup(txn, g.getId()); + // check if contact to be removed is creator of the group + if (c.getAuthor().getId().equals(pg.getCreator().getId())) { + privateGroupManager.markGroupDissolved(txn, g.getId()); + } + } + } // Remove the contact group (all messages will be removed with it) db.removeGroup(txn, getContactGroup(c)); } diff --git a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationIntegrationTest.java index c0261f0a4..daca3e771 100644 --- a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationIntegrationTest.java @@ -722,15 +722,26 @@ public class GroupInvitationIntegrationTest sync0To1(2, true); // + one invitation protocol join message sync1To0(1, true); + // inviting again is not possible assertFalse(groupInvitationManager0 .isInvitationAllowed(contact1From0, privateGroup.getId())); - // re-add contacts + // contacts remove each other removeAllContacts(); + + // group gets dissolved for invitee automatically, but not creator + assertFalse(groupManager0.isDissolved(privateGroup.getId())); + assertTrue(groupManager1.isDissolved(privateGroup.getId())); + + // contacts re-add each other addDefaultContacts(); - assertTrue(groupInvitationManager0 + // creator can still not invite again + assertFalse(groupInvitationManager0 .isInvitationAllowed(contact1From0, privateGroup.getId())); + + // finally invitee can remove group without issues + groupManager1.removePrivateGroup(privateGroup.getId()); } private Collection getMessages1From0() diff --git a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImplTest.java b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImplTest.java index 9eb742cd8..bf3887153 100644 --- a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImplTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationManagerImplTest.java @@ -45,6 +45,8 @@ import java.util.Map; import javax.annotation.Nullable; import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static junit.framework.TestCase.fail; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.test.TestUtils.getAuthor; @@ -105,7 +107,9 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { private final ContactId contactId = contact.getId(); private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION); private final Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); - private final Group privateGroup = getGroup(CLIENT_ID, MAJOR_VERSION); + private final Group group = getGroup(CLIENT_ID, MAJOR_VERSION); + private final PrivateGroup privateGroup = new PrivateGroup(group, + getRandomString(5), getAuthor(), getRandomBytes(32)); private final BdfDictionary meta = BdfDictionary.of(new BdfEntry("m", "e")); private final Message message = getMessage(contactGroup.getId()); private final BdfList body = BdfList.of("body"); @@ -159,9 +163,9 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { will(returnValue(false)); oneOf(db).addGroup(txn, localGroup); oneOf(db).getContacts(txn); - will(returnValue(Collections.singletonList(contact))); + will(returnValue(singletonList(contact))); }}); - expectAddingContact(contact); + expectAddingContact(contact, emptyList()); groupInvitationManager.onDatabaseOpened(txn); } @@ -177,7 +181,8 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { groupInvitationManager.onDatabaseOpened(txn); } - private void expectAddingContact(Contact c) throws Exception { + private void expectAddingContact(Contact c, Collection groups) + throws Exception { context.checking(new Expectations() {{ oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, MAJOR_VERSION, c); @@ -192,15 +197,8 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { .setContactId(txn, contactGroup.getId(), contactId); oneOf(db).getGroups(txn, PrivateGroupManager.CLIENT_ID, PrivateGroupManager.MAJOR_VERSION); - will(returnValue(Collections.singletonList(privateGroup))); - oneOf(privateGroupManager).isMember(txn, privateGroup.getId(), - c.getAuthor()); - will(returnValue(true)); - oneOf(privateGroupManager) - .isOurPrivateGroup(txn, privateGroup.getId()); - will(returnValue(false)); + will(returnValue(groups)); }}); - expectAddingMember(privateGroup.getId(), c); } private void expectAddingMember(GroupId g, Contact c) throws Exception { @@ -254,13 +252,99 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { @Test public void testAddingContact() throws Exception { - expectAddingContact(contact); + expectAddingContact(contact, singletonList(group)); + + context.checking(new Expectations() {{ + oneOf(privateGroupManager) + .isMember(txn, privateGroup.getId(), contact.getAuthor()); + will(returnValue(true)); + oneOf(privateGroupManager) + .getPrivateGroup(txn, privateGroup.getId()); + will(returnValue(privateGroup)); + oneOf(privateGroupManager).isOurPrivateGroup(txn, privateGroup); + will(returnValue(false)); + }}); + // creates PEER session + expectAddingMember(privateGroup.getId(), contact); + groupInvitationManager.addingContact(txn, contact); } @Test - public void testRemovingContact() throws Exception { + public void testAddingContactWhoCreatedGroup() throws Exception { + PrivateGroup privateGroup = new PrivateGroup(group, + getRandomString(5), contact.getAuthor(), getRandomBytes(32)); + + expectAddingContact(contact, singletonList(group)); + context.checking(new Expectations() {{ + oneOf(privateGroupManager) + .isMember(txn, privateGroup.getId(), contact.getAuthor()); + will(returnValue(true)); + oneOf(privateGroupManager) + .getPrivateGroup(txn, privateGroup.getId()); + will(returnValue(privateGroup)); + oneOf(privateGroupManager).isOurPrivateGroup(txn, privateGroup); + will(returnValue(false)); + }}); + expectCreateStorageId(); + context.checking(new Expectations() {{ + oneOf(sessionEncoder) + .encodeSession(with(any(InviteeSession.class))); + will(returnValue(meta)); + oneOf(clientHelper) + .mergeMessageMetadata(txn, storageMessage.getId(), meta); + }}); + + groupInvitationManager.addingContact(txn, contact); + } + + @Test + public void testRemovingContactWithoutCommonGroups() throws Exception { + context.checking(new Expectations() {{ + oneOf(db).getGroups(txn, PrivateGroupManager.CLIENT_ID, + PrivateGroupManager.MAJOR_VERSION); + will(returnValue(emptyList())); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + oneOf(db).removeGroup(txn, contactGroup); + }}); + groupInvitationManager.removingContact(txn, contact); + } + + @Test + public void testRemovingContactWithCommonGroups() throws Exception { + context.checking(new Expectations() {{ + oneOf(db).getGroups(txn, PrivateGroupManager.CLIENT_ID, + PrivateGroupManager.MAJOR_VERSION); + will(returnValue(singletonList(group))); + oneOf(privateGroupManager).isMember(txn, group.getId(), author); + will(returnValue(true)); + oneOf(privateGroupManager).getPrivateGroup(txn, group.getId()); + will(returnValue(privateGroup)); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + oneOf(db).removeGroup(txn, contactGroup); + }}); + groupInvitationManager.removingContact(txn, contact); + } + + @Test + public void testRemovingContactWhoIsCreatorOfCommonGroup() + throws Exception { + PrivateGroup privateGroup = new PrivateGroup(group, + getRandomString(5), contact.getAuthor(), getRandomBytes(32)); + context.checking(new Expectations() {{ + oneOf(db).getGroups(txn, PrivateGroupManager.CLIENT_ID, + PrivateGroupManager.MAJOR_VERSION); + will(returnValue(singletonList(group))); + oneOf(privateGroupManager).isMember(txn, group.getId(), author); + will(returnValue(true)); + oneOf(privateGroupManager).getPrivateGroup(txn, group.getId()); + will(returnValue(privateGroup)); + oneOf(privateGroupManager).markGroupDissolved(txn, group.getId()); oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, MAJOR_VERSION, contact); will(returnValue(contactGroup)); @@ -350,8 +434,8 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { BdfDictionary bdfSession) throws Exception { expectParseMessageMetadata(); expectGetSession(oneResult, sessionId, contactGroup.getId()); - Session session = - expectHandleMessage(role, messageMetadata, bdfSession, type); + Session session = expectHandleMessage(role, messageMetadata, + bdfSession, type); expectStoreSession(session, storageMessage.getId()); } @@ -564,7 +648,7 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { @Test public void testAcceptInvitationWithGroupId() throws Exception { - PrivateGroup pg = new PrivateGroup(privateGroup, + PrivateGroup pg = new PrivateGroup(group, getRandomString(MAX_GROUP_NAME_LENGTH), author, getRandomBytes(GROUP_SALT_LENGTH)); @@ -574,7 +658,7 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { @Test public void testDeclineInvitationWithGroupId() throws Exception { - PrivateGroup pg = new PrivateGroup(privateGroup, + PrivateGroup pg = new PrivateGroup(group, getRandomString(MAX_GROUP_NAME_LENGTH), author, getRandomBytes(GROUP_SALT_LENGTH)); @@ -665,7 +749,7 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { privateGroup.getId(), time1, "name", author, new byte[0], null, new byte[0], NO_AUTO_DELETE_TIMER); PrivateGroup pg = - new PrivateGroup(privateGroup, invite.getGroupName(), + new PrivateGroup(group, invite.getGroupName(), invite.getCreator(), invite.getSalt()); context.checking(new Expectations() {{ @@ -733,7 +817,7 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { new InviteMessage(message2.getId(), contactGroup.getId(), privateGroup.getId(), time2, groupName, author, salt, null, getRandomBytes(5), NO_AUTO_DELETE_TIMER); - PrivateGroup pg = new PrivateGroup(privateGroup, groupName, + PrivateGroup pg = new PrivateGroup(group, groupName, author, salt); context.checking(new Expectations() {{ @@ -742,7 +826,7 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { oneOf(db).startTransaction(true); will(returnValue(txn)); oneOf(db).getContacts(txn); - will(returnValue(Collections.singletonList(contact))); + will(returnValue(singletonList(contact))); oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, MAJOR_VERSION, contact); will(returnValue(contactGroup)); @@ -834,7 +918,7 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase { expectAddingMember(privateGroup.getId(), contact); context.checking(new Expectations() {{ oneOf(db).getContactsByAuthorId(txn, author.getId()); - will(returnValue(Collections.singletonList(contact))); + will(returnValue(singletonList(contact))); }}); groupInvitationManager.addingMember(txn, privateGroup.getId(), author); }