Merge branch '732-reveal-backennd' into 'master'

Add support for revealing contacts to the PrivateGroupManager

This also adds three integration tests and improves some small details here and there in the private group client.

Prerequisite for #732.

See merge request !396
This commit is contained in:
akwizgran
2016-11-10 17:00:31 +00:00
16 changed files with 582 additions and 208 deletions

View File

@@ -11,6 +11,7 @@ import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.KeyPair; import org.briarproject.api.crypto.KeyPair;
import org.briarproject.api.crypto.SecretKey; import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.data.BdfList; import org.briarproject.api.data.BdfList;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction; import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.Event; import org.briarproject.api.event.Event;
@@ -20,6 +21,7 @@ import org.briarproject.api.identity.AuthorFactory;
import org.briarproject.api.identity.IdentityManager; import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.lifecycle.LifecycleManager; import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.privategroup.GroupMember;
import org.briarproject.api.privategroup.GroupMessage; import org.briarproject.api.privategroup.GroupMessage;
import org.briarproject.api.privategroup.GroupMessageFactory; import org.briarproject.api.privategroup.GroupMessageFactory;
import org.briarproject.api.privategroup.GroupMessageHeader; import org.briarproject.api.privategroup.GroupMessageHeader;
@@ -59,6 +61,10 @@ import javax.inject.Inject;
import static org.briarproject.TestPluginsModule.MAX_LATENCY; import static org.briarproject.TestPluginsModule.MAX_LATENCY;
import static org.briarproject.TestUtils.getRandomBytes; import static org.briarproject.TestUtils.getRandomBytes;
import static org.briarproject.api.identity.Author.Status.VERIFIED; import static org.briarproject.api.identity.Author.Status.VERIFIED;
import static org.briarproject.api.privategroup.Visibility.INVISIBLE;
import static org.briarproject.api.privategroup.Visibility.REVEALED_BY_CONTACT;
import static org.briarproject.api.privategroup.Visibility.REVEALED_BY_US;
import static org.briarproject.api.privategroup.Visibility.VISIBLE;
import static org.briarproject.api.privategroup.invitation.GroupInvitationManager.CLIENT_ID; import static org.briarproject.api.privategroup.invitation.GroupInvitationManager.CLIENT_ID;
import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
import static org.briarproject.api.sync.ValidationManager.State.INVALID; import static org.briarproject.api.sync.ValidationManager.State.INVALID;
@@ -69,13 +75,16 @@ import static org.junit.Assert.assertTrue;
public class PrivateGroupManagerTest extends BriarIntegrationTest { public class PrivateGroupManagerTest extends BriarIntegrationTest {
private LifecycleManager lifecycleManager0, lifecycleManager1; private LifecycleManager lifecycleManager0, lifecycleManager1,
private SyncSessionFactory sync0, sync1; lifecycleManager2;
private PrivateGroupManager groupManager0, groupManager1; private SyncSessionFactory sync0, sync1, sync2;
private ContactManager contactManager0, contactManager1; private PrivateGroupManager groupManager0, groupManager1, groupManager2;
private ContactId contactId0, contactId1; private ContactManager contactManager0, contactManager1, contactManager2;
private IdentityManager identityManager0, identityManager1; private ContactId contactId01, contactId02, contactId1, contactId2;
private LocalAuthor author0, author1; private IdentityManager identityManager0, identityManager1,
identityManager2;
private LocalAuthor author0, author1, author2;
private DatabaseComponent db0, db1, db2;
private PrivateGroup privateGroup0; private PrivateGroup privateGroup0;
private GroupId groupId0; private GroupId groupId0;
@@ -101,13 +110,14 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
private final File testDir = TestUtils.getTestDirectory(); private final File testDir = TestUtils.getTestDirectory();
private final SecretKey master = TestUtils.getSecretKey(); private final SecretKey master = TestUtils.getSecretKey();
private final int TIMEOUT = 15000; private final int TIMEOUT = 15000;
private final String AUTHOR0 = "Author 0";
private final String AUTHOR1 = "Author 1"; private final String AUTHOR1 = "Author 1";
private final String AUTHOR2 = "Author 2"; private final String AUTHOR2 = "Author 2";
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(PrivateGroupManagerTest.class.getName()); Logger.getLogger(PrivateGroupManagerTest.class.getName());
private PrivateGroupManagerTestComponent t0, t1; private PrivateGroupManagerTestComponent t0, t1, t2;
@Rule @Rule
public ExpectedException thrown = ExpectedException.none(); public ExpectedException thrown = ExpectedException.none();
@@ -117,26 +127,36 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
PrivateGroupManagerTestComponent component = PrivateGroupManagerTestComponent component =
DaggerPrivateGroupManagerTestComponent.builder().build(); DaggerPrivateGroupManagerTestComponent.builder().build();
component.inject(this); component.inject(this);
injectEagerSingletons(component);
assertTrue(testDir.mkdirs()); assertTrue(testDir.mkdirs());
File t0Dir = new File(testDir, AUTHOR1); File t0Dir = new File(testDir, AUTHOR0);
t0 = DaggerPrivateGroupManagerTestComponent.builder() t0 = DaggerPrivateGroupManagerTestComponent.builder()
.testDatabaseModule(new TestDatabaseModule(t0Dir)).build(); .testDatabaseModule(new TestDatabaseModule(t0Dir)).build();
injectEagerSingletons(t0); injectEagerSingletons(t0);
File t1Dir = new File(testDir, AUTHOR2); File t1Dir = new File(testDir, AUTHOR1);
t1 = DaggerPrivateGroupManagerTestComponent.builder() t1 = DaggerPrivateGroupManagerTestComponent.builder()
.testDatabaseModule(new TestDatabaseModule(t1Dir)).build(); .testDatabaseModule(new TestDatabaseModule(t1Dir)).build();
injectEagerSingletons(t1); injectEagerSingletons(t1);
File t2Dir = new File(testDir, AUTHOR2);
t2 = DaggerPrivateGroupManagerTestComponent.builder()
.testDatabaseModule(new TestDatabaseModule(t2Dir)).build();
injectEagerSingletons(t2);
identityManager0 = t0.getIdentityManager(); identityManager0 = t0.getIdentityManager();
identityManager1 = t1.getIdentityManager(); identityManager1 = t1.getIdentityManager();
identityManager2 = t2.getIdentityManager();
contactManager0 = t0.getContactManager(); contactManager0 = t0.getContactManager();
contactManager1 = t1.getContactManager(); contactManager1 = t1.getContactManager();
contactManager2 = t2.getContactManager();
db0 = t0.getDatabaseComponent();
db1 = t1.getDatabaseComponent();
db2 = t2.getDatabaseComponent();
groupManager0 = t0.getPrivateGroupManager(); groupManager0 = t0.getPrivateGroupManager();
groupManager1 = t1.getPrivateGroupManager(); groupManager1 = t1.getPrivateGroupManager();
groupManager2 = t2.getPrivateGroupManager();
sync0 = t0.getSyncSessionFactory(); sync0 = t0.getSyncSessionFactory();
sync1 = t1.getSyncSessionFactory(); sync1 = t1.getSyncSessionFactory();
sync2 = t2.getSyncSessionFactory();
// initialize waiters fresh for each test // initialize waiters fresh for each test
validationWaiter = new Waiter(); validationWaiter = new Waiter();
@@ -192,6 +212,7 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
defaultInit(); defaultInit();
// create and add test message with no previousMsgId // create and add test message with no previousMsgId
@SuppressWarnings("ConstantConditions")
GroupMessage msg = groupMessageFactory GroupMessage msg = groupMessageFactory
.createGroupMessage(groupId0, clock.currentTimeMillis(), null, .createGroupMessage(groupId0, clock.currentTimeMillis(), null,
author0, "test", null); author0, "test", null);
@@ -323,17 +344,15 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
GroupMessage joinMsg0 = groupMessageFactory GroupMessage joinMsg0 = groupMessageFactory
.createJoinMessage(privateGroup0.getId(), joinTime, author0, .createJoinMessage(privateGroup0.getId(), joinTime, author0,
joinTime, getRandomBytes(12)); joinTime, getRandomBytes(12));
groupManager0.addPrivateGroup(privateGroup0, joinMsg0); groupManager0.addPrivateGroup(privateGroup0, joinMsg0, true);
assertEquals(joinMsg0.getMessage().getId(), assertEquals(joinMsg0.getMessage().getId(),
groupManager0.getPreviousMsgId(groupId0)); groupManager0.getPreviousMsgId(groupId0));
// make group visible to 1 // make group visible to 1
Transaction txn0 = t0.getDatabaseComponent().startTransaction(false); Transaction txn0 = db0.startTransaction(false);
t0.getDatabaseComponent() db0.setVisibleToContact(txn0, contactId1, privateGroup0.getId(), true);
.setVisibleToContact(txn0, contactId1, privateGroup0.getId(), db0.commitTransaction(txn0);
true); db0.endTransaction(txn0);
t0.getDatabaseComponent().commitTransaction(txn0);
t0.getDatabaseComponent().endTransaction(txn0);
// author1 joins privateGroup0 with wrong timestamp // author1 joins privateGroup0 with wrong timestamp
joinTime = clock.currentTimeMillis(); joinTime = clock.currentTimeMillis();
@@ -348,17 +367,15 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
GroupMessage joinMsg1 = groupMessageFactory GroupMessage joinMsg1 = groupMessageFactory
.createJoinMessage(privateGroup0.getId(), joinTime, author1, .createJoinMessage(privateGroup0.getId(), joinTime, author1,
inviteTime, creatorSignature); inviteTime, creatorSignature);
groupManager1.addPrivateGroup(privateGroup0, joinMsg1); groupManager1.addPrivateGroup(privateGroup0, joinMsg1, false);
assertEquals(joinMsg1.getMessage().getId(), assertEquals(joinMsg1.getMessage().getId(),
groupManager1.getPreviousMsgId(groupId0)); groupManager1.getPreviousMsgId(groupId0));
// make group visible to 0 // make group visible to 0
Transaction txn1 = t1.getDatabaseComponent().startTransaction(false); Transaction txn1 = db1.startTransaction(false);
t1.getDatabaseComponent() db1.setVisibleToContact(txn1, contactId01, privateGroup0.getId(), true);
.setVisibleToContact(txn1, contactId0, privateGroup0.getId(), db1.commitTransaction(txn1);
true); db1.endTransaction(txn1);
t1.getDatabaseComponent().commitTransaction(txn1);
t1.getDatabaseComponent().endTransaction(txn1);
// sync join messages // sync join messages
sync0To1(); sync0To1();
@@ -394,17 +411,15 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
GroupMessage joinMsg0 = groupMessageFactory GroupMessage joinMsg0 = groupMessageFactory
.createJoinMessage(privateGroup0.getId(), joinTime, author0, .createJoinMessage(privateGroup0.getId(), joinTime, author0,
inviteTime, creatorSignature); inviteTime, creatorSignature);
groupManager0.addPrivateGroup(privateGroup0, joinMsg0); groupManager0.addPrivateGroup(privateGroup0, joinMsg0, true);
assertEquals(joinMsg0.getMessage().getId(), assertEquals(joinMsg0.getMessage().getId(),
groupManager0.getPreviousMsgId(groupId0)); groupManager0.getPreviousMsgId(groupId0));
// make group visible to 1 // make group visible to 1
Transaction txn0 = t0.getDatabaseComponent().startTransaction(false); Transaction txn0 = db0.startTransaction(false);
t0.getDatabaseComponent() db0.setVisibleToContact(txn0, contactId1, privateGroup0.getId(), true);
.setVisibleToContact(txn0, contactId1, privateGroup0.getId(), db0.commitTransaction(txn0);
true); db0.endTransaction(txn0);
t0.getDatabaseComponent().commitTransaction(txn0);
t0.getDatabaseComponent().endTransaction(txn0);
// author1 joins privateGroup0 with wrong signature in join message // author1 joins privateGroup0 with wrong signature in join message
joinTime = clock.currentTimeMillis(); joinTime = clock.currentTimeMillis();
@@ -419,17 +434,15 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
GroupMessage joinMsg1 = groupMessageFactory GroupMessage joinMsg1 = groupMessageFactory
.createJoinMessage(privateGroup0.getId(), joinTime, author1, .createJoinMessage(privateGroup0.getId(), joinTime, author1,
inviteTime, creatorSignature); inviteTime, creatorSignature);
groupManager1.addPrivateGroup(privateGroup0, joinMsg1); groupManager1.addPrivateGroup(privateGroup0, joinMsg1, false);
assertEquals(joinMsg1.getMessage().getId(), assertEquals(joinMsg1.getMessage().getId(),
groupManager1.getPreviousMsgId(groupId0)); groupManager1.getPreviousMsgId(groupId0));
// make group visible to 0 // make group visible to 0
Transaction txn1 = t1.getDatabaseComponent().startTransaction(false); Transaction txn1 = db1.startTransaction(false);
t1.getDatabaseComponent() db1.setVisibleToContact(txn1, contactId01, privateGroup0.getId(), true);
.setVisibleToContact(txn1, contactId0, privateGroup0.getId(), db1.commitTransaction(txn1);
true); db1.endTransaction(txn1);
t1.getDatabaseComponent().commitTransaction(txn1);
t1.getDatabaseComponent().endTransaction(txn1);
// sync join messages // sync join messages
sync0To1(); sync0To1();
@@ -445,6 +458,193 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
assertEquals(1, groupManager0.getHeaders(groupId0).size()); assertEquals(1, groupManager0.getHeaders(groupId0).size());
} }
@Test
public void testGetMembers() throws Exception {
defaultInit();
Collection<GroupMember> members0 = groupManager0.getMembers(groupId0);
assertEquals(2, members0.size());
for (GroupMember m : members0) {
if (m.getAuthor().equals(author0)) {
assertEquals(VISIBLE, m.getVisibility());
} else {
assertEquals(author1, m.getAuthor());
assertEquals(VISIBLE, m.getVisibility());
}
}
Collection<GroupMember> members1 = groupManager1.getMembers(groupId0);
assertEquals(2, members1.size());
for (GroupMember m : members1) {
if (m.getAuthor().equals(author1)) {
assertEquals(VISIBLE, m.getVisibility());
} else {
assertEquals(author0, m.getAuthor());
assertEquals(VISIBLE, m.getVisibility());
}
}
}
@Test
public void testJoinMessages() throws Exception {
defaultInit();
Collection<GroupMessageHeader> headers0 =
groupManager0.getHeaders(groupId0);
for (GroupMessageHeader h : headers0) {
if (h instanceof JoinMessageHeader) {
JoinMessageHeader j = (JoinMessageHeader) h;
// all relationships of the creator are visible
assertEquals(VISIBLE, j.getVisibility());
}
}
Collection<GroupMessageHeader> headers1 =
groupManager1.getHeaders(groupId0);
for (GroupMessageHeader h : headers1) {
if (h instanceof JoinMessageHeader) {
JoinMessageHeader j = (JoinMessageHeader) h;
if (h.getAuthor().equals(author1))
// we are visible to ourselves
assertEquals(VISIBLE, j.getVisibility());
else
// our relationship to the creator is visible
assertEquals(VISIBLE, j.getVisibility());
}
}
}
@Test
public void testRevealingRelationships() throws Exception {
defaultInit();
// make group visible to 2
Transaction txn0 = db0.startTransaction(false);
db0.setVisibleToContact(txn0, contactId2, privateGroup0.getId(), true);
db0.commitTransaction(txn0);
db0.endTransaction(txn0);
// author2 joins privateGroup0
long joinTime = clock.currentTimeMillis();
long inviteTime = joinTime - 1;
Group invitationGroup = contactGroupFactory
.createContactGroup(CLIENT_ID, author0.getId(),
author2.getId());
BdfList toSign = BdfList.of(0, inviteTime, invitationGroup.getId(),
privateGroup0.getId());
byte[] creatorSignature =
clientHelper.sign(toSign, author0.getPrivateKey());
GroupMessage joinMsg2 = groupMessageFactory
.createJoinMessage(privateGroup0.getId(), joinTime, author2,
inviteTime, creatorSignature);
Transaction txn2 = db2.startTransaction(false);
groupManager2.addPrivateGroup(txn2, privateGroup0, joinMsg2, false);
// make group visible to 0
db2.setVisibleToContact(txn2, contactId01, privateGroup0.getId(), true);
db2.commitTransaction(txn2);
db2.endTransaction(txn2);
// sync join messages
deliverMessage(sync2, contactId2, sync0, contactId02, "2 to 0");
deliveryWaiter.await(TIMEOUT, 1);
deliverMessage(sync0, contactId02, sync2, contactId2, "0 to 2");
deliveryWaiter.await(TIMEOUT, 2);
sync0To1();
deliveryWaiter.await(TIMEOUT, 1);
// check that everybody sees everybody else as joined
Collection<GroupMember> members0 = groupManager0.getMembers(groupId0);
assertEquals(3, members0.size());
Collection<GroupMember> members1 = groupManager1.getMembers(groupId0);
assertEquals(3, members1.size());
Collection<GroupMember> members2 = groupManager2.getMembers(groupId0);
assertEquals(3, members2.size());
// assert that contact relationship is not revealed initially
for (GroupMember m : members1) {
if (m.getAuthor().equals(author2)) {
assertEquals(INVISIBLE, m.getVisibility());
}
}
for (GroupMember m : members2) {
if (m.getAuthor().equals(author1)) {
assertEquals(INVISIBLE, m.getVisibility());
}
}
// reveal contact relationship
Transaction txn1 = db1.startTransaction(false);
groupManager1
.relationshipRevealed(txn1, groupId0, author2.getId(), false);
db1.commitTransaction(txn1);
db1.endTransaction(txn1);
txn2 = db2.startTransaction(false);
groupManager2
.relationshipRevealed(txn2, groupId0, author1.getId(), true);
db2.commitTransaction(txn2);
db2.endTransaction(txn2);
// assert that contact relationship is now revealed properly
members1 = groupManager1.getMembers(groupId0);
for (GroupMember m : members1) {
if (m.getAuthor().equals(author2)) {
assertEquals(REVEALED_BY_US, m.getVisibility());
}
}
members2 = groupManager2.getMembers(groupId0);
for (GroupMember m : members2) {
if (m.getAuthor().equals(author1)) {
assertEquals(REVEALED_BY_CONTACT, m.getVisibility());
}
}
// assert that join messages reflect revealed relationship
Collection<GroupMessageHeader> headers1 =
groupManager1.getHeaders(groupId0);
for (GroupMessageHeader h : headers1) {
if (h instanceof JoinMessageHeader) {
JoinMessageHeader j = (JoinMessageHeader) h;
if (h.getAuthor().equals(author2))
// 1 revealed the relationship to 2
assertEquals(REVEALED_BY_US, j.getVisibility());
else
// 1's other relationship (to 1 and creator) are visible
assertEquals(VISIBLE, j.getVisibility());
}
}
Collection<GroupMessageHeader> headers2 =
groupManager2.getHeaders(groupId0);
for (GroupMessageHeader h : headers2) {
if (h instanceof JoinMessageHeader) {
JoinMessageHeader j = (JoinMessageHeader) h;
if (h.getAuthor().equals(author1))
// 2's relationship was revealed by 1
assertEquals(REVEALED_BY_CONTACT, j.getVisibility());
else
// 2's other relationship (to 2 and creator) are visible
assertEquals(VISIBLE, j.getVisibility());
}
}
}
@Test
public void testDissolveGroup() throws Exception {
defaultInit();
// group is not dissolved initially
assertFalse(groupManager1.isDissolved(groupId0));
// creator dissolves group
Transaction txn1 = db1.startTransaction(false);
groupManager1.markGroupDissolved(txn1, groupId0);
db1.commitTransaction(txn1);
db1.endTransaction(txn1);
// group is dissolved now
assertTrue(groupManager1.isDissolved(groupId0));
}
@After @After
public void tearDown() throws Exception { public void tearDown() throws Exception {
stopLifecycles(); stopLifecycles();
@@ -483,7 +683,7 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
byte[] publicKey0 = keyPair0.getPublic().getEncoded(); byte[] publicKey0 = keyPair0.getPublic().getEncoded();
byte[] privateKey0 = keyPair0.getPrivate().getEncoded(); byte[] privateKey0 = keyPair0.getPrivate().getEncoded();
author0 = authorFactory author0 = authorFactory
.createLocalAuthor(AUTHOR1, publicKey0, privateKey0); .createLocalAuthor(AUTHOR0, publicKey0, privateKey0);
identityManager0.registerLocalAuthor(author0); identityManager0.registerLocalAuthor(author0);
privateGroup0 = privateGroup0 =
privateGroupFactory.createPrivateGroup("Testgroup", author0); privateGroupFactory.createPrivateGroup("Testgroup", author0);
@@ -493,21 +693,34 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
byte[] publicKey1 = keyPair1.getPublic().getEncoded(); byte[] publicKey1 = keyPair1.getPublic().getEncoded();
byte[] privateKey1 = keyPair1.getPrivate().getEncoded(); byte[] privateKey1 = keyPair1.getPrivate().getEncoded();
author1 = authorFactory author1 = authorFactory
.createLocalAuthor(AUTHOR2, publicKey1, privateKey1); .createLocalAuthor(AUTHOR1, publicKey1, privateKey1);
identityManager1.registerLocalAuthor(author1); identityManager1.registerLocalAuthor(author1);
KeyPair keyPair2 = crypto.generateSignatureKeyPair();
byte[] publicKey2 = keyPair2.getPublic().getEncoded();
byte[] privateKey2 = keyPair2.getPrivate().getEncoded();
author2 = authorFactory
.createLocalAuthor(AUTHOR2, publicKey2, privateKey2);
identityManager2.registerLocalAuthor(author2);
} }
private void addDefaultContacts() throws DbException { private void addDefaultContacts() throws DbException {
// sharer adds invitee as contact // creator adds invitee as contact
contactId1 = contactManager0.addContact(author1, contactId1 = contactManager0
author0.getId(), master, clock.currentTimeMillis(), true, .addContact(author1, author0.getId(), master,
true, true clock.currentTimeMillis(), true, true, true);
); // invitee adds creator back
// invitee adds sharer back contactId01 = contactManager1
contactId0 = contactManager1.addContact(author0, .addContact(author0, author1.getId(), master,
author1.getId(), master, clock.currentTimeMillis(), true, clock.currentTimeMillis(), true, true, true);
true, true // creator adds invitee as contact
); contactId2 = contactManager0
.addContact(author2, author0.getId(), master,
clock.currentTimeMillis(), true, true, true);
// invitee adds creator back
contactId02 = contactManager2
.addContact(author0, author2.getId(), master,
clock.currentTimeMillis(), true, true, true);
} }
private void listenToEvents() { private void listenToEvents() {
@@ -515,6 +728,8 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
t0.getEventBus().addListener(listener0); t0.getEventBus().addListener(listener0);
Listener listener1 = new Listener(); Listener listener1 = new Listener();
t1.getEventBus().addListener(listener1); t1.getEventBus().addListener(listener1);
Listener listener2 = new Listener();
t2.getEventBus().addListener(listener2);
} }
private void addGroup() throws Exception { private void addGroup() throws Exception {
@@ -522,17 +737,15 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
long joinTime = clock.currentTimeMillis(); long joinTime = clock.currentTimeMillis();
GroupMessage joinMsg0 = groupMessageFactory GroupMessage joinMsg0 = groupMessageFactory
.createJoinMessage(privateGroup0.getId(), joinTime, author0); .createJoinMessage(privateGroup0.getId(), joinTime, author0);
groupManager0.addPrivateGroup(privateGroup0, joinMsg0); groupManager0.addPrivateGroup(privateGroup0, joinMsg0, true);
assertEquals(joinMsg0.getMessage().getId(), assertEquals(joinMsg0.getMessage().getId(),
groupManager0.getPreviousMsgId(groupId0)); groupManager0.getPreviousMsgId(groupId0));
// make group visible to 1 // make group visible to 1
Transaction txn0 = t0.getDatabaseComponent().startTransaction(false); Transaction txn0 = db0.startTransaction(false);
t0.getDatabaseComponent() db0.setVisibleToContact(txn0, contactId1, privateGroup0.getId(), true);
.setVisibleToContact(txn0, contactId1, privateGroup0.getId(), db0.commitTransaction(txn0);
true); db0.endTransaction(txn0);
t0.getDatabaseComponent().commitTransaction(txn0);
t0.getDatabaseComponent().endTransaction(txn0);
// author1 joins privateGroup0 // author1 joins privateGroup0
joinTime = clock.currentTimeMillis(); joinTime = clock.currentTimeMillis();
@@ -547,17 +760,15 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
GroupMessage joinMsg1 = groupMessageFactory GroupMessage joinMsg1 = groupMessageFactory
.createJoinMessage(privateGroup0.getId(), joinTime, author1, .createJoinMessage(privateGroup0.getId(), joinTime, author1,
inviteTime, creatorSignature); inviteTime, creatorSignature);
groupManager1.addPrivateGroup(privateGroup0, joinMsg1); groupManager1.addPrivateGroup(privateGroup0, joinMsg1, false);
assertEquals(joinMsg1.getMessage().getId(),
groupManager1.getPreviousMsgId(groupId0));
// make group visible to 0 // make group visible to 0
Transaction txn1 = t1.getDatabaseComponent().startTransaction(false); Transaction txn1 = db1.startTransaction(false);
t1.getDatabaseComponent() db1.setVisibleToContact(txn1, contactId01, privateGroup0.getId(), true);
.setVisibleToContact(txn1, contactId0, privateGroup0.getId(), db1.commitTransaction(txn1);
true); db1.endTransaction(txn1);
t1.getDatabaseComponent().commitTransaction(txn1); assertEquals(joinMsg1.getMessage().getId(),
t1.getDatabaseComponent().endTransaction(txn1); groupManager1.getPreviousMsgId(groupId0));
// sync join messages // sync join messages
sync0To1(); sync0To1();
@@ -567,11 +778,11 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
} }
private void sync0To1() throws IOException, TimeoutException { private void sync0To1() throws IOException, TimeoutException {
deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1"); deliverMessage(sync0, contactId01, sync1, contactId1, "0 to 1");
} }
private void sync1To0() throws IOException, TimeoutException { private void sync1To0() throws IOException, TimeoutException {
deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0"); deliverMessage(sync1, contactId1, sync0, contactId01, "1 to 0");
} }
private void deliverMessage(SyncSessionFactory fromSync, ContactId fromId, private void deliverMessage(SyncSessionFactory fromSync, ContactId fromId,
@@ -600,18 +811,23 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
// Start the lifecycle manager and wait for it to finish // Start the lifecycle manager and wait for it to finish
lifecycleManager0 = t0.getLifecycleManager(); lifecycleManager0 = t0.getLifecycleManager();
lifecycleManager1 = t1.getLifecycleManager(); lifecycleManager1 = t1.getLifecycleManager();
lifecycleManager0.startServices(AUTHOR1); lifecycleManager2 = t2.getLifecycleManager();
lifecycleManager1.startServices(AUTHOR2); lifecycleManager0.startServices(AUTHOR0);
lifecycleManager1.startServices(AUTHOR1);
lifecycleManager2.startServices(AUTHOR2);
lifecycleManager0.waitForStartup(); lifecycleManager0.waitForStartup();
lifecycleManager1.waitForStartup(); lifecycleManager1.waitForStartup();
lifecycleManager2.waitForStartup();
} }
private void stopLifecycles() throws InterruptedException { private void stopLifecycles() throws InterruptedException {
// Clean up // Clean up
lifecycleManager0.stopServices(); lifecycleManager0.stopServices();
lifecycleManager1.stopServices(); lifecycleManager1.stopServices();
lifecycleManager2.stopServices();
lifecycleManager0.waitForShutdown(); lifecycleManager0.waitForShutdown();
lifecycleManager1.waitForShutdown(); lifecycleManager1.waitForShutdown();
lifecycleManager2.waitForShutdown();
} }
private void injectEagerSingletons( private void injectEagerSingletons(

View File

@@ -105,7 +105,7 @@ public class GroupActivity extends
ActionBar actionBar = getSupportActionBar(); ActionBar actionBar = getSupportActionBar();
if (actionBar != null) { if (actionBar != null) {
actionBar.setSubtitle(getString(R.string.groups_created_by, actionBar.setSubtitle(getString(R.string.groups_created_by,
group.getAuthor().getName())); group.getCreator().getName()));
} }
controller.isCreator(group, controller.isCreator(group,
new UiResultExceptionHandler<Boolean, DbException>(this) { new UiResultExceptionHandler<Boolean, DbException>(this) {

View File

@@ -196,7 +196,7 @@ public class GroupControllerImpl extends
try { try {
LocalAuthor author = identityManager.getLocalAuthor(); LocalAuthor author = identityManager.getLocalAuthor();
boolean isCreator = boolean isCreator =
author.getId().equals(group.getAuthor().getId()); author.getId().equals(group.getCreator().getId());
handler.onResult(isCreator); handler.onResult(isCreator);
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))

View File

@@ -117,7 +117,7 @@ public class CreateGroupControllerImpl extends DbControllerImpl
public void run() { public void run() {
LOG.info("Adding group to database..."); LOG.info("Adding group to database...");
try { try {
groupManager.addPrivateGroup(group, joinMsg); groupManager.addPrivateGroup(group, joinMsg, true);
handler.onResult(group.getId()); handler.onResult(group.getId());
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))

View File

@@ -43,7 +43,7 @@ class GroupItem {
} }
Author getCreator() { Author getCreator() {
return privateGroup.getAuthor(); return privateGroup.getCreator();
} }
String getName() { String getName() {

View File

@@ -7,6 +7,8 @@ import org.briarproject.api.privategroup.GroupMember;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.api.privategroup.Visibility.INVISIBLE;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class MemberListItem { class MemberListItem {
@@ -17,7 +19,7 @@ class MemberListItem {
public MemberListItem(GroupMember groupMember) { public MemberListItem(GroupMember groupMember) {
this.member = groupMember.getAuthor(); this.member = groupMember.getAuthor();
this.sharing = groupMember.isShared(); this.sharing = groupMember.getVisibility() != INVISIBLE; // TODO #732
this.status = groupMember.getStatus(); this.status = groupMember.getStatus();
} }

View File

@@ -0,0 +1,30 @@
package org.briarproject.api.privategroup;
import org.briarproject.api.event.Event;
import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.api.sync.GroupId;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class ContactRelationshipRevealedEvent extends Event {
private final GroupId groupId;
private final Visibility visibility;
public ContactRelationshipRevealedEvent(GroupId groupId,
Visibility visibility) {
this.groupId = groupId;
this.visibility = visibility;
}
public GroupId getGroupId() {
return groupId;
}
public Visibility getVisibility() {
return visibility;
}
}

View File

@@ -12,12 +12,12 @@ public class GroupMember {
private final Author author; private final Author author;
private final Status status; private final Status status;
private final boolean shared; private final Visibility visibility;
public GroupMember(Author author, Status status, boolean shared) { public GroupMember(Author author, Status status, Visibility visibility) {
this.author = author; this.author = author;
this.status = status; this.status = status;
this.shared = shared; this.visibility = visibility;
} }
public Author getAuthor() { public Author getAuthor() {
@@ -28,8 +28,8 @@ public class GroupMember {
return status; return status;
} }
public boolean isShared() { public Visibility getVisibility() {
return shared; return visibility;
} }
} }

View File

@@ -1,10 +1,6 @@
package org.briarproject.api.privategroup; package org.briarproject.api.privategroup;
import org.briarproject.api.identity.Author;
import org.briarproject.api.nullsafety.NotNullByDefault; import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import org.jetbrains.annotations.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
@@ -12,10 +8,16 @@ import javax.annotation.concurrent.Immutable;
@NotNullByDefault @NotNullByDefault
public class JoinMessageHeader extends GroupMessageHeader { public class JoinMessageHeader extends GroupMessageHeader {
public JoinMessageHeader(GroupId groupId, MessageId id, private final Visibility visibility;
@Nullable MessageId parentId, long timestamp, Author author,
Author.Status authorStatus, boolean read) { public JoinMessageHeader(GroupMessageHeader h, Visibility visibility) {
super(groupId, id, parentId, timestamp, author, authorStatus, read); super(h.getGroupId(), h.getId(), h.getParentId(), h.getTimestamp(),
h.getAuthor(), h.getAuthorStatus(), h.isRead());
this.visibility = visibility;
}
public Visibility getVisibility() {
return visibility;
} }
} }

View File

@@ -12,20 +12,20 @@ import javax.annotation.concurrent.Immutable;
@NotNullByDefault @NotNullByDefault
public class PrivateGroup extends NamedGroup implements Shareable { public class PrivateGroup extends NamedGroup implements Shareable {
private final Author author; private final Author creator;
public PrivateGroup(Group group, String name, Author author, byte[] salt) { public PrivateGroup(Group group, String name, Author creator, byte[] salt) {
super(group, name, salt); super(group, name, salt);
this.author = author; this.creator = creator;
} }
public Author getAuthor() { public Author getCreator() {
return author; return creator;
} }
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
return o instanceof PrivateGroup && super.equals(o); return o instanceof PrivateGroup && super.equals(o);
} }
} }

View File

@@ -1,9 +1,11 @@
package org.briarproject.api.privategroup; package org.briarproject.api.privategroup;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.MessageTracker; import org.briarproject.api.clients.MessageTracker;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction; import org.briarproject.api.db.Transaction;
import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.nullsafety.NotNullByDefault; import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
@@ -23,19 +25,21 @@ public interface PrivateGroupManager extends MessageTracker {
* Adds a new private group and joins it. * Adds a new private group and joins it.
* *
* @param group The private group to add * @param group The private group to add
* @param joinMsg The new member's join message * @param joinMsg The creators's join message
* @param creator True if the group is added by its creator
*/ */
void addPrivateGroup(PrivateGroup group, GroupMessage joinMsg) void addPrivateGroup(PrivateGroup group, GroupMessage joinMsg,
throws DbException; boolean creator) throws DbException;
/** /**
* Adds a new private group and joins it. * Adds a new private group and joins it.
* *
* @param group The private group to add * @param group The private group to add
* @param joinMsg The new member's join message * @param joinMsg The new member's join message
* @param creator True if the group is added by its creator
*/ */
void addPrivateGroup(Transaction txn, PrivateGroup group, void addPrivateGroup(Transaction txn, PrivateGroup group,
GroupMessage joinMsg) throws DbException; GroupMessage joinMsg, boolean creator) throws DbException;
/** /**
* Removes a dissolved private group. * Removes a dissolved private group.
@@ -43,7 +47,7 @@ public interface PrivateGroupManager extends MessageTracker {
void removePrivateGroup(GroupId g) throws DbException; void removePrivateGroup(GroupId g) throws DbException;
/** /**
* Gets the MessageId of your previous message sent to the group * Gets the MessageId of the user's previous message sent to the group
*/ */
MessageId getPreviousMsgId(GroupId g) throws DbException; MessageId getPreviousMsgId(GroupId g) throws DbException;
@@ -103,6 +107,17 @@ public interface PrivateGroupManager extends MessageTracker {
*/ */
boolean isMember(Transaction txn, GroupId g, Author a) throws DbException; boolean isMember(Transaction txn, GroupId g, Author a) throws DbException;
/**
* This method needs to be called when a contact relationship
* has been revealed between the user and the Author with AuthorId a
* in the Group identified by the GroupId g.
*
* @param byContact true if the remote contact has revealed
* the relationship first. Otherwise false.
*/
void relationshipRevealed(Transaction txn, GroupId g, AuthorId a,
boolean byContact) throws FormatException, DbException;
/** /**
* Registers a hook to be called when members are added * Registers a hook to be called when members are added
* or groups are removed. * or groups are removed.

View File

@@ -0,0 +1,27 @@
package org.briarproject.api.privategroup;
import org.briarproject.api.FormatException;
public enum Visibility {
INVISIBLE(0),
VISIBLE(1),
REVEALED_BY_US(2),
REVEALED_BY_CONTACT(3);
int value;
Visibility(int value) {
this.value = value;
}
public static Visibility valueOf(int value) throws FormatException {
for (Visibility v : values()) if (v.value == value) return v;
throw new FormatException();
}
public int getInt() {
return value;
}
}

View File

@@ -2,20 +2,22 @@ package org.briarproject.privategroup;
import static org.briarproject.clients.BdfConstants.MSG_KEY_READ; import static org.briarproject.clients.BdfConstants.MSG_KEY_READ;
interface Constants { interface GroupConstants {
// Database keys // Database keys
String KEY_TYPE = "type"; String KEY_TYPE = "type";
String KEY_TIMESTAMP = "timestamp"; String KEY_TIMESTAMP = "timestamp";
String KEY_READ = MSG_KEY_READ; String KEY_READ = MSG_KEY_READ;
String KEY_PARENT_MSG_ID = "parentMsgId"; String KEY_PARENT_MSG_ID = "parentMsgId";
String KEY_NEW_MEMBER_MSG_ID = "newMemberMsgId";
String KEY_PREVIOUS_MSG_ID = "previousMsgId"; String KEY_PREVIOUS_MSG_ID = "previousMsgId";
String KEY_MEMBER_ID = "memberId"; String KEY_MEMBER_ID = "memberId";
String KEY_MEMBER_NAME = "memberName"; String KEY_MEMBER_NAME = "memberName";
String KEY_MEMBER_PUBLIC_KEY = "memberPublicKey"; String KEY_MEMBER_PUBLIC_KEY = "memberPublicKey";
String KEY_MEMBERS = "members"; String GROUP_KEY_MEMBERS = "members";
String KEY_DISSOLVED = "dissolved"; String GROUP_KEY_OUR_GROUP = "ourGroup";
String GROUP_KEY_CREATOR_ID = "creatorId";
String GROUP_KEY_DISSOLVED = "dissolved";
String GROUP_KEY_VISIBILITY = "visibility";
} }

View File

@@ -29,14 +29,14 @@ import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH
import static org.briarproject.api.privategroup.MessageType.JOIN; import static org.briarproject.api.privategroup.MessageType.JOIN;
import static org.briarproject.api.privategroup.MessageType.POST; import static org.briarproject.api.privategroup.MessageType.POST;
import static org.briarproject.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_BODY_LENGTH; import static org.briarproject.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_BODY_LENGTH;
import static org.briarproject.privategroup.Constants.KEY_MEMBER_ID; import static org.briarproject.privategroup.GroupConstants.KEY_MEMBER_ID;
import static org.briarproject.privategroup.Constants.KEY_MEMBER_NAME; import static org.briarproject.privategroup.GroupConstants.KEY_MEMBER_NAME;
import static org.briarproject.privategroup.Constants.KEY_MEMBER_PUBLIC_KEY; import static org.briarproject.privategroup.GroupConstants.KEY_MEMBER_PUBLIC_KEY;
import static org.briarproject.privategroup.Constants.KEY_PARENT_MSG_ID; import static org.briarproject.privategroup.GroupConstants.KEY_PARENT_MSG_ID;
import static org.briarproject.privategroup.Constants.KEY_PREVIOUS_MSG_ID; import static org.briarproject.privategroup.GroupConstants.KEY_PREVIOUS_MSG_ID;
import static org.briarproject.privategroup.Constants.KEY_READ; import static org.briarproject.privategroup.GroupConstants.KEY_READ;
import static org.briarproject.privategroup.Constants.KEY_TIMESTAMP; import static org.briarproject.privategroup.GroupConstants.KEY_TIMESTAMP;
import static org.briarproject.privategroup.Constants.KEY_TYPE; import static org.briarproject.privategroup.GroupConstants.KEY_TYPE;
class GroupMessageValidator extends BdfMessageValidator { class GroupMessageValidator extends BdfMessageValidator {
@@ -98,7 +98,7 @@ class GroupMessageValidator extends BdfMessageValidator {
PrivateGroup pg = privateGroupFactory.parsePrivateGroup(g); PrivateGroup pg = privateGroupFactory.parsePrivateGroup(g);
// invite is null if the member is the creator of the private group // invite is null if the member is the creator of the private group
Author creator = pg.getAuthor(); Author creator = pg.getCreator();
BdfList invite = body.getOptionalList(3); BdfList invite = body.getOptionalList(3);
if (invite == null) { if (invite == null) {
if (!member.equals(creator)) if (!member.equals(creator))

View File

@@ -2,8 +2,7 @@ package org.briarproject.privategroup;
import org.briarproject.api.FormatException; import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.contact.Contact; import org.briarproject.api.clients.ProtocolStateException;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfEntry; import org.briarproject.api.data.BdfEntry;
import org.briarproject.api.data.BdfList; import org.briarproject.api.data.BdfList;
@@ -19,6 +18,7 @@ import org.briarproject.api.identity.Author.Status;
import org.briarproject.api.identity.AuthorId; import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.identity.IdentityManager; import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.nullsafety.NotNullByDefault; import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.api.privategroup.ContactRelationshipRevealedEvent;
import org.briarproject.api.privategroup.GroupMember; import org.briarproject.api.privategroup.GroupMember;
import org.briarproject.api.privategroup.GroupMessage; import org.briarproject.api.privategroup.GroupMessage;
import org.briarproject.api.privategroup.GroupMessageHeader; import org.briarproject.api.privategroup.GroupMessageHeader;
@@ -27,6 +27,7 @@ import org.briarproject.api.privategroup.MessageType;
import org.briarproject.api.privategroup.PrivateGroup; import org.briarproject.api.privategroup.PrivateGroup;
import org.briarproject.api.privategroup.PrivateGroupFactory; import org.briarproject.api.privategroup.PrivateGroupFactory;
import org.briarproject.api.privategroup.PrivateGroupManager; import org.briarproject.api.privategroup.PrivateGroupManager;
import org.briarproject.api.privategroup.Visibility;
import org.briarproject.api.sync.Group; import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.Message; import org.briarproject.api.sync.Message;
@@ -48,20 +49,25 @@ import java.util.concurrent.CopyOnWriteArrayList;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.api.identity.Author.Status.OURSELVES; import static org.briarproject.api.identity.Author.Status.OURSELVES;
import static org.briarproject.api.identity.Author.Status.UNVERIFIED;
import static org.briarproject.api.identity.Author.Status.VERIFIED;
import static org.briarproject.api.privategroup.MessageType.JOIN; import static org.briarproject.api.privategroup.MessageType.JOIN;
import static org.briarproject.api.privategroup.MessageType.POST; import static org.briarproject.api.privategroup.MessageType.POST;
import static org.briarproject.privategroup.Constants.KEY_DISSOLVED; import static org.briarproject.api.privategroup.Visibility.INVISIBLE;
import static org.briarproject.privategroup.Constants.KEY_MEMBERS; import static org.briarproject.api.privategroup.Visibility.REVEALED_BY_CONTACT;
import static org.briarproject.privategroup.Constants.KEY_MEMBER_ID; import static org.briarproject.api.privategroup.Visibility.REVEALED_BY_US;
import static org.briarproject.privategroup.Constants.KEY_MEMBER_NAME; import static org.briarproject.api.privategroup.Visibility.VISIBLE;
import static org.briarproject.privategroup.Constants.KEY_MEMBER_PUBLIC_KEY; import static org.briarproject.privategroup.GroupConstants.GROUP_KEY_CREATOR_ID;
import static org.briarproject.privategroup.Constants.KEY_PARENT_MSG_ID; import static org.briarproject.privategroup.GroupConstants.GROUP_KEY_DISSOLVED;
import static org.briarproject.privategroup.Constants.KEY_PREVIOUS_MSG_ID; import static org.briarproject.privategroup.GroupConstants.GROUP_KEY_MEMBERS;
import static org.briarproject.privategroup.Constants.KEY_READ; import static org.briarproject.privategroup.GroupConstants.GROUP_KEY_OUR_GROUP;
import static org.briarproject.privategroup.Constants.KEY_TIMESTAMP; import static org.briarproject.privategroup.GroupConstants.GROUP_KEY_VISIBILITY;
import static org.briarproject.privategroup.Constants.KEY_TYPE; import static org.briarproject.privategroup.GroupConstants.KEY_MEMBER_ID;
import static org.briarproject.privategroup.GroupConstants.KEY_MEMBER_NAME;
import static org.briarproject.privategroup.GroupConstants.KEY_MEMBER_PUBLIC_KEY;
import static org.briarproject.privategroup.GroupConstants.KEY_PARENT_MSG_ID;
import static org.briarproject.privategroup.GroupConstants.KEY_PREVIOUS_MSG_ID;
import static org.briarproject.privategroup.GroupConstants.KEY_READ;
import static org.briarproject.privategroup.GroupConstants.KEY_TIMESTAMP;
import static org.briarproject.privategroup.GroupConstants.KEY_TYPE;
@NotNullByDefault @NotNullByDefault
public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
@@ -84,11 +90,11 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
} }
@Override @Override
public void addPrivateGroup(PrivateGroup group, GroupMessage joinMsg) public void addPrivateGroup(PrivateGroup group, GroupMessage joinMsg,
throws DbException { boolean creator) throws DbException {
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
addPrivateGroup(txn, group, joinMsg); addPrivateGroup(txn, group, joinMsg, creator);
db.commitTransaction(txn); db.commitTransaction(txn);
} finally { } finally {
db.endTransaction(txn); db.endTransaction(txn);
@@ -97,12 +103,15 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
@Override @Override
public void addPrivateGroup(Transaction txn, PrivateGroup group, public void addPrivateGroup(Transaction txn, PrivateGroup group,
GroupMessage joinMsg) throws DbException { GroupMessage joinMsg, boolean creator) throws DbException {
try { try {
db.addGroup(txn, group.getGroup()); db.addGroup(txn, group.getGroup());
AuthorId creatorId = group.getCreator().getId();
BdfDictionary meta = BdfDictionary.of( BdfDictionary meta = BdfDictionary.of(
new BdfEntry(KEY_MEMBERS, new BdfList()), new BdfEntry(GROUP_KEY_MEMBERS, new BdfList()),
new BdfEntry(KEY_DISSOLVED, false) new BdfEntry(GROUP_KEY_CREATOR_ID, creatorId),
new BdfEntry(GROUP_KEY_OUR_GROUP, creator),
new BdfEntry(GROUP_KEY_DISSOLVED, false)
); );
clientHelper.mergeGroupMetadata(txn, group.getId(), meta); clientHelper.mergeGroupMetadata(txn, group.getId(), meta);
joinPrivateGroup(txn, joinMsg); joinPrivateGroup(txn, joinMsg);
@@ -118,9 +127,10 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
addMessageMetadata(meta, m, true); addMessageMetadata(meta, m, true);
clientHelper.addLocalMessage(txn, m.getMessage(), meta, true); clientHelper.addLocalMessage(txn, m.getMessage(), meta, true);
trackOutgoingMessage(txn, m.getMessage()); trackOutgoingMessage(txn, m.getMessage());
addMember(txn, m.getMessage().getGroupId(), m.getMember()); addMember(txn, m.getMessage().getGroupId(), m.getMember(), VISIBLE);
setPreviousMsgId(txn, m.getMessage().getGroupId(), setPreviousMsgId(txn, m.getMessage().getGroupId(),
m.getMessage().getId()); m.getMessage().getId());
attachJoinMessageAddedEvent(txn, m.getMessage(), meta, true, VISIBLE);
} }
@Override @Override
@@ -171,7 +181,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
public void markGroupDissolved(Transaction txn, GroupId g) public void markGroupDissolved(Transaction txn, GroupId g)
throws DbException { throws DbException {
BdfDictionary meta = BdfDictionary.of( BdfDictionary meta = BdfDictionary.of(
new BdfEntry(KEY_DISSOLVED, true) new BdfEntry(GROUP_KEY_DISSOLVED, true)
); );
try { try {
clientHelper.mergeGroupMetadata(txn, g, meta); clientHelper.mergeGroupMetadata(txn, g, meta);
@@ -283,7 +293,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
public boolean isDissolved(GroupId g) throws DbException { public boolean isDissolved(GroupId g) throws DbException {
try { try {
BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(g); BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(g);
return meta.getBoolean(KEY_DISSOLVED); return meta.getBoolean(GROUP_KEY_DISSOLVED);
} catch (FormatException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);
} }
@@ -322,11 +332,22 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
for (AuthorId id : authors) { for (AuthorId id : authors) {
statuses.put(id, identityManager.getAuthorStatus(txn, id)); statuses.put(id, identityManager.getAuthorStatus(txn, id));
} }
// Parse the metadata // get current visibilities for join messages
Map<Author, Visibility> visibilities = getMembers(txn, g);
// parse the metadata
for (Entry<MessageId, BdfDictionary> entry : metadata.entrySet()) { for (Entry<MessageId, BdfDictionary> entry : metadata.entrySet()) {
BdfDictionary meta = entry.getValue(); BdfDictionary meta = entry.getValue();
headers.add(getGroupMessageHeader(txn, g, entry.getKey(), meta, if (meta.getLong(KEY_TYPE) == JOIN.getInt()) {
statuses)); Author member = getAuthor(meta);
Visibility v = visibilities.get(member);
headers.add(
getJoinMessageHeader(txn, g, entry.getKey(), meta,
statuses, v));
} else {
headers.add(
getGroupMessageHeader(txn, g, entry.getKey(), meta,
statuses));
}
} }
db.commitTransaction(txn); db.commitTransaction(txn);
return headers; return headers;
@@ -356,19 +377,17 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
} }
boolean read = meta.getBoolean(KEY_READ); boolean read = meta.getBoolean(KEY_READ);
if (meta.getLong(KEY_TYPE) == JOIN.getInt()) {
return new JoinMessageHeader(g, id, parentId, timestamp, author,
status, read);
}
return new GroupMessageHeader(g, id, parentId, timestamp, author, return new GroupMessageHeader(g, id, parentId, timestamp, author,
status, read); status, read);
} }
private GroupMessageHeader getGroupMessageHeader(Transaction txn, GroupId g, private JoinMessageHeader getJoinMessageHeader(Transaction txn, GroupId g,
MessageId id, BdfDictionary meta) MessageId id, BdfDictionary meta, Map<AuthorId, Status> statuses,
throws DbException, FormatException { Visibility v) throws DbException, FormatException {
return getGroupMessageHeader(txn, g, id, meta,
Collections.<AuthorId, Status>emptyMap()); GroupMessageHeader header =
getGroupMessageHeader(txn, g, id, meta, statuses);
return new JoinMessageHeader(header, v);
} }
@Override @Override
@@ -376,18 +395,11 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
Transaction txn = db.startTransaction(true); Transaction txn = db.startTransaction(true);
try { try {
Collection<GroupMember> members = new ArrayList<GroupMember>(); Collection<GroupMember> members = new ArrayList<GroupMember>();
Collection<Author> authors = getMembers(txn, g); Map<Author, Visibility> authors = getMembers(txn, g);
for (Author a : authors) { for (Entry<Author, Visibility> m : authors.entrySet()) {
Status status = identityManager.getAuthorStatus(txn, a.getId()); Status status = identityManager
boolean shared = false; .getAuthorStatus(txn, m.getKey().getId());
if (status == VERIFIED || status == UNVERIFIED) { members.add(new GroupMember(m.getKey(), status, m.getValue()));
Collection<Contact> contacts =
db.getContactsByAuthorId(txn, a.getId());
if (contacts.size() != 1) throw new DbException();
ContactId c = contacts.iterator().next().getId();
shared = db.isVisibleToContact(txn, c, g);
}
members.add(new GroupMember(a, status, shared));
} }
db.commitTransaction(txn); db.commitTransaction(txn);
return members; return members;
@@ -396,17 +408,19 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
} }
} }
private Collection<Author> getMembers(Transaction txn, GroupId g) private Map<Author, Visibility> getMembers(Transaction txn, GroupId g)
throws DbException { throws DbException {
try { try {
Collection<Author> members = new ArrayList<Author>();
BdfDictionary meta = BdfDictionary meta =
clientHelper.getGroupMetadataAsDictionary(txn, g); clientHelper.getGroupMetadataAsDictionary(txn, g);
BdfList list = meta.getList(KEY_MEMBERS); BdfList list = meta.getList(GROUP_KEY_MEMBERS);
for (Object o : list) { Map<Author, Visibility> members =
BdfDictionary d = (BdfDictionary) o; new HashMap<Author, Visibility>(list.size());
for (int i = 0 ; i < list.size(); i++) {
BdfDictionary d = list.getDictionary(i);
Author member = getAuthor(d); Author member = getAuthor(d);
members.add(member); Visibility v = getVisibility(d);
members.put(member, v);
} }
return members; return members;
} catch (FormatException e) { } catch (FormatException e) {
@@ -417,12 +431,35 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
@Override @Override
public boolean isMember(Transaction txn, GroupId g, Author a) public boolean isMember(Transaction txn, GroupId g, Author a)
throws DbException { throws DbException {
for (Author member : getMembers(txn, g)) { for (Author member : getMembers(txn, g).keySet()) {
if (member.equals(a)) return true; if (member.equals(a)) return true;
} }
return false; return false;
} }
@Override
public void relationshipRevealed(Transaction txn, GroupId g, AuthorId a,
boolean byContact) throws FormatException, DbException {
BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(txn, g);
BdfList members = meta.getList(GROUP_KEY_MEMBERS);
Visibility v = INVISIBLE;
boolean foundMember = false;
for (int i = 0 ; i < members.size(); i++) {
BdfDictionary d = members.getDictionary(i);
AuthorId memberId = new AuthorId(d.getRaw(KEY_MEMBER_ID));
if (a.equals(memberId)) {
foundMember = true;
Visibility vOld = getVisibility(d);
if (vOld != INVISIBLE) throw new ProtocolStateException();
v = byContact ? REVEALED_BY_CONTACT : REVEALED_BY_US;
d.put(GROUP_KEY_VISIBILITY, v.getInt());
}
}
if (!foundMember) throw new ProtocolStateException();
clientHelper.mergeGroupMetadata(txn, g, meta);
txn.attach(new ContactRelationshipRevealedEvent(g, v));
}
@Override @Override
public void registerPrivateGroupHook(PrivateGroupHook hook) { public void registerPrivateGroupHook(PrivateGroupHook hook) {
hooks.add(hook); hooks.add(hook);
@@ -432,47 +469,14 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
protected boolean incomingMessage(Transaction txn, Message m, BdfList body, protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
BdfDictionary meta) throws DbException, FormatException { BdfDictionary meta) throws DbException, FormatException {
long timestamp = meta.getLong(KEY_TIMESTAMP);
MessageType type = MessageType type =
MessageType.valueOf(meta.getLong(KEY_TYPE).intValue()); MessageType.valueOf(meta.getLong(KEY_TYPE).intValue());
switch (type) { switch (type) {
case JOIN: case JOIN:
addMember(txn, m.getGroupId(), getAuthor(meta)); handleJoinMessage(txn, m, meta);
trackIncomingMessage(txn, m);
attachGroupMessageAddedEvent(txn, m, meta, false);
return true; return true;
case POST: case POST:
// timestamp must be greater than the timestamps of parent post handleGroupMessage(txn, m, meta);
byte[] parentIdBytes = meta.getOptionalRaw(KEY_PARENT_MSG_ID);
if (parentIdBytes != null) {
MessageId parentId = new MessageId(parentIdBytes);
BdfDictionary parentMeta = clientHelper
.getMessageMetadataAsDictionary(txn, parentId);
if (timestamp <= parentMeta.getLong(KEY_TIMESTAMP))
throw new FormatException();
MessageType parentType = MessageType
.valueOf(parentMeta.getLong(KEY_TYPE).intValue());
if (parentType != POST)
throw new FormatException();
}
// and the member's previous message
byte[] previousMsgIdBytes = meta.getRaw(KEY_PREVIOUS_MSG_ID);
MessageId previousMsgId = new MessageId(previousMsgIdBytes);
BdfDictionary previousMeta = clientHelper
.getMessageMetadataAsDictionary(txn, previousMsgId);
if (timestamp <= previousMeta.getLong(KEY_TIMESTAMP))
throw new FormatException();
// previous message must be from same member
if (!Arrays.equals(meta.getRaw(KEY_MEMBER_ID),
previousMeta.getRaw(KEY_MEMBER_ID)))
throw new FormatException();
// previous message must be a POST or JOIN
MessageType previousType = MessageType
.valueOf(previousMeta.getLong(KEY_TYPE).intValue());
if (previousType != JOIN && previousType != POST)
throw new FormatException();
trackIncomingMessage(txn, m);
attachGroupMessageAddedEvent(txn, m, meta, false);
return true; return true;
default: default:
// the validator should only let valid types pass // the validator should only let valid types pass
@@ -480,24 +484,93 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
} }
} }
private void handleJoinMessage(Transaction txn, Message m,
BdfDictionary meta) throws FormatException, DbException {
// find out if contact relationship is visible and then add new member
Author member = getAuthor(meta);
BdfDictionary groupMeta = clientHelper
.getGroupMetadataAsDictionary(txn, m.getGroupId());
boolean ourGroup = groupMeta.getBoolean(GROUP_KEY_OUR_GROUP);
Visibility v = VISIBLE;
if (!ourGroup) {
AuthorId creatorId = new AuthorId(
groupMeta.getRaw(GROUP_KEY_CREATOR_ID));
if (!creatorId.equals(member.getId()))
v = INVISIBLE;
}
addMember(txn, m.getGroupId(), member, v);
// track message and broadcast event
trackIncomingMessage(txn, m);
attachJoinMessageAddedEvent(txn, m, meta, false, v);
}
private void handleGroupMessage(Transaction txn, Message m,
BdfDictionary meta) throws FormatException, DbException {
// timestamp must be greater than the timestamps of parent post
long timestamp = meta.getLong(KEY_TIMESTAMP);
byte[] parentIdBytes = meta.getOptionalRaw(KEY_PARENT_MSG_ID);
if (parentIdBytes != null) {
MessageId parentId = new MessageId(parentIdBytes);
BdfDictionary parentMeta = clientHelper
.getMessageMetadataAsDictionary(txn, parentId);
if (timestamp <= parentMeta.getLong(KEY_TIMESTAMP))
throw new FormatException();
MessageType parentType = MessageType
.valueOf(parentMeta.getLong(KEY_TYPE).intValue());
if (parentType != POST)
throw new FormatException();
}
// and the member's previous message
byte[] previousMsgIdBytes = meta.getRaw(KEY_PREVIOUS_MSG_ID);
MessageId previousMsgId = new MessageId(previousMsgIdBytes);
BdfDictionary previousMeta = clientHelper
.getMessageMetadataAsDictionary(txn, previousMsgId);
if (timestamp <= previousMeta.getLong(KEY_TIMESTAMP))
throw new FormatException();
// previous message must be from same member
if (!Arrays.equals(meta.getRaw(KEY_MEMBER_ID),
previousMeta.getRaw(KEY_MEMBER_ID)))
throw new FormatException();
// previous message must be a POST or JOIN
MessageType previousType = MessageType
.valueOf(previousMeta.getLong(KEY_TYPE).intValue());
if (previousType != JOIN && previousType != POST)
throw new FormatException();
// track message and broadcast event
trackIncomingMessage(txn, m);
attachGroupMessageAddedEvent(txn, m, meta, false);
}
private void attachGroupMessageAddedEvent(Transaction txn, Message m, private void attachGroupMessageAddedEvent(Transaction txn, Message m,
BdfDictionary meta, boolean local) BdfDictionary meta, boolean local)
throws DbException, FormatException { throws DbException, FormatException {
GroupMessageHeader h = GroupMessageHeader h =
getGroupMessageHeader(txn, m.getGroupId(), m.getId(), meta); getGroupMessageHeader(txn, m.getGroupId(), m.getId(), meta,
Collections.<AuthorId, Status>emptyMap());
Event e = new GroupMessageAddedEvent(m.getGroupId(), h, local); Event e = new GroupMessageAddedEvent(m.getGroupId(), h, local);
txn.attach(e); txn.attach(e);
} }
private void addMember(Transaction txn, GroupId g, Author a) private void attachJoinMessageAddedEvent(Transaction txn, Message m,
BdfDictionary meta, boolean local, Visibility v)
throws DbException, FormatException {
JoinMessageHeader h =
getJoinMessageHeader(txn, m.getGroupId(), m.getId(), meta,
Collections.<AuthorId, Status>emptyMap(), v);
Event e = new GroupMessageAddedEvent(m.getGroupId(), h, local);
txn.attach(e);
}
private void addMember(Transaction txn, GroupId g, Author a, Visibility v)
throws DbException, FormatException { throws DbException, FormatException {
BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(txn, g); BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(txn, g);
BdfList members = meta.getList(KEY_MEMBERS); BdfList members = meta.getList(GROUP_KEY_MEMBERS);
members.add(BdfDictionary.of( members.add(BdfDictionary.of(
new BdfEntry(KEY_MEMBER_ID, a.getId()), new BdfEntry(KEY_MEMBER_ID, a.getId()),
new BdfEntry(KEY_MEMBER_NAME, a.getName()), new BdfEntry(KEY_MEMBER_NAME, a.getName()),
new BdfEntry(KEY_MEMBER_PUBLIC_KEY, a.getPublicKey()) new BdfEntry(KEY_MEMBER_PUBLIC_KEY, a.getPublicKey()),
new BdfEntry(GROUP_KEY_VISIBILITY, v.getInt())
)); ));
clientHelper.mergeGroupMetadata(txn, g, meta); clientHelper.mergeGroupMetadata(txn, g, meta);
for (PrivateGroupHook hook : hooks) { for (PrivateGroupHook hook : hooks) {
@@ -512,4 +585,10 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
return new Author(authorId, name, publicKey); return new Author(authorId, name, publicKey);
} }
private Visibility getVisibility(BdfDictionary meta)
throws FormatException {
return Visibility
.valueOf(meta.getLong(GROUP_KEY_VISIBILITY).intValue());
}
} }

View File

@@ -105,7 +105,7 @@ abstract class AbstractProtocolEngine<S extends Session>
} }
Message m = messageEncoder.encodeInviteMessage( Message m = messageEncoder.encodeInviteMessage(
session.getContactGroupId(), privateGroup.getId(), session.getContactGroupId(), privateGroup.getId(),
timestamp, privateGroup.getName(), privateGroup.getAuthor(), timestamp, privateGroup.getName(), privateGroup.getCreator(),
privateGroup.getSalt(), message, signature); privateGroup.getSalt(), message, signature);
sendMessage(txn, m, INVITE, privateGroup.getId(), true); sendMessage(txn, m, INVITE, privateGroup.getId(), true);
return m; return m;
@@ -183,7 +183,8 @@ abstract class AbstractProtocolEngine<S extends Session>
GroupMessage joinMessage = groupMessageFactory.createJoinMessage( GroupMessage joinMessage = groupMessageFactory.createJoinMessage(
privateGroup.getId(), timestamp, member, invite.getTimestamp(), privateGroup.getId(), timestamp, member, invite.getTimestamp(),
invite.getSignature()); invite.getSignature());
privateGroupManager.addPrivateGroup(txn, privateGroup, joinMessage); privateGroupManager
.addPrivateGroup(txn, privateGroup, joinMessage, false);
} }
long getLocalTimestamp(S session) { long getLocalTimestamp(S session) {