mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-11 18:29:05 +01:00
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:
@@ -11,6 +11,7 @@ import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.KeyPair;
|
||||
import org.briarproject.api.crypto.SecretKey;
|
||||
import org.briarproject.api.data.BdfList;
|
||||
import org.briarproject.api.db.DatabaseComponent;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.db.Transaction;
|
||||
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.LocalAuthor;
|
||||
import org.briarproject.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.api.privategroup.GroupMember;
|
||||
import org.briarproject.api.privategroup.GroupMessage;
|
||||
import org.briarproject.api.privategroup.GroupMessageFactory;
|
||||
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.TestUtils.getRandomBytes;
|
||||
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.sync.ValidationManager.State.DELIVERED;
|
||||
import static org.briarproject.api.sync.ValidationManager.State.INVALID;
|
||||
@@ -69,13 +75,16 @@ import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class PrivateGroupManagerTest extends BriarIntegrationTest {
|
||||
|
||||
private LifecycleManager lifecycleManager0, lifecycleManager1;
|
||||
private SyncSessionFactory sync0, sync1;
|
||||
private PrivateGroupManager groupManager0, groupManager1;
|
||||
private ContactManager contactManager0, contactManager1;
|
||||
private ContactId contactId0, contactId1;
|
||||
private IdentityManager identityManager0, identityManager1;
|
||||
private LocalAuthor author0, author1;
|
||||
private LifecycleManager lifecycleManager0, lifecycleManager1,
|
||||
lifecycleManager2;
|
||||
private SyncSessionFactory sync0, sync1, sync2;
|
||||
private PrivateGroupManager groupManager0, groupManager1, groupManager2;
|
||||
private ContactManager contactManager0, contactManager1, contactManager2;
|
||||
private ContactId contactId01, contactId02, contactId1, contactId2;
|
||||
private IdentityManager identityManager0, identityManager1,
|
||||
identityManager2;
|
||||
private LocalAuthor author0, author1, author2;
|
||||
private DatabaseComponent db0, db1, db2;
|
||||
private PrivateGroup privateGroup0;
|
||||
private GroupId groupId0;
|
||||
|
||||
@@ -101,13 +110,14 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
|
||||
private final File testDir = TestUtils.getTestDirectory();
|
||||
private final SecretKey master = TestUtils.getSecretKey();
|
||||
private final int TIMEOUT = 15000;
|
||||
private final String AUTHOR0 = "Author 0";
|
||||
private final String AUTHOR1 = "Author 1";
|
||||
private final String AUTHOR2 = "Author 2";
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(PrivateGroupManagerTest.class.getName());
|
||||
|
||||
private PrivateGroupManagerTestComponent t0, t1;
|
||||
private PrivateGroupManagerTestComponent t0, t1, t2;
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
@@ -117,26 +127,36 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
|
||||
PrivateGroupManagerTestComponent component =
|
||||
DaggerPrivateGroupManagerTestComponent.builder().build();
|
||||
component.inject(this);
|
||||
injectEagerSingletons(component);
|
||||
|
||||
assertTrue(testDir.mkdirs());
|
||||
File t0Dir = new File(testDir, AUTHOR1);
|
||||
File t0Dir = new File(testDir, AUTHOR0);
|
||||
t0 = DaggerPrivateGroupManagerTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t0Dir)).build();
|
||||
injectEagerSingletons(t0);
|
||||
File t1Dir = new File(testDir, AUTHOR2);
|
||||
File t1Dir = new File(testDir, AUTHOR1);
|
||||
t1 = DaggerPrivateGroupManagerTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t1Dir)).build();
|
||||
injectEagerSingletons(t1);
|
||||
File t2Dir = new File(testDir, AUTHOR2);
|
||||
t2 = DaggerPrivateGroupManagerTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t2Dir)).build();
|
||||
injectEagerSingletons(t2);
|
||||
|
||||
identityManager0 = t0.getIdentityManager();
|
||||
identityManager1 = t1.getIdentityManager();
|
||||
identityManager2 = t2.getIdentityManager();
|
||||
contactManager0 = t0.getContactManager();
|
||||
contactManager1 = t1.getContactManager();
|
||||
contactManager2 = t2.getContactManager();
|
||||
db0 = t0.getDatabaseComponent();
|
||||
db1 = t1.getDatabaseComponent();
|
||||
db2 = t2.getDatabaseComponent();
|
||||
groupManager0 = t0.getPrivateGroupManager();
|
||||
groupManager1 = t1.getPrivateGroupManager();
|
||||
groupManager2 = t2.getPrivateGroupManager();
|
||||
sync0 = t0.getSyncSessionFactory();
|
||||
sync1 = t1.getSyncSessionFactory();
|
||||
sync2 = t2.getSyncSessionFactory();
|
||||
|
||||
// initialize waiters fresh for each test
|
||||
validationWaiter = new Waiter();
|
||||
@@ -192,6 +212,7 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
|
||||
defaultInit();
|
||||
|
||||
// create and add test message with no previousMsgId
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
GroupMessage msg = groupMessageFactory
|
||||
.createGroupMessage(groupId0, clock.currentTimeMillis(), null,
|
||||
author0, "test", null);
|
||||
@@ -323,17 +344,15 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
|
||||
GroupMessage joinMsg0 = groupMessageFactory
|
||||
.createJoinMessage(privateGroup0.getId(), joinTime, author0,
|
||||
joinTime, getRandomBytes(12));
|
||||
groupManager0.addPrivateGroup(privateGroup0, joinMsg0);
|
||||
groupManager0.addPrivateGroup(privateGroup0, joinMsg0, true);
|
||||
assertEquals(joinMsg0.getMessage().getId(),
|
||||
groupManager0.getPreviousMsgId(groupId0));
|
||||
|
||||
// make group visible to 1
|
||||
Transaction txn0 = t0.getDatabaseComponent().startTransaction(false);
|
||||
t0.getDatabaseComponent()
|
||||
.setVisibleToContact(txn0, contactId1, privateGroup0.getId(),
|
||||
true);
|
||||
t0.getDatabaseComponent().commitTransaction(txn0);
|
||||
t0.getDatabaseComponent().endTransaction(txn0);
|
||||
Transaction txn0 = db0.startTransaction(false);
|
||||
db0.setVisibleToContact(txn0, contactId1, privateGroup0.getId(), true);
|
||||
db0.commitTransaction(txn0);
|
||||
db0.endTransaction(txn0);
|
||||
|
||||
// author1 joins privateGroup0 with wrong timestamp
|
||||
joinTime = clock.currentTimeMillis();
|
||||
@@ -348,17 +367,15 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
|
||||
GroupMessage joinMsg1 = groupMessageFactory
|
||||
.createJoinMessage(privateGroup0.getId(), joinTime, author1,
|
||||
inviteTime, creatorSignature);
|
||||
groupManager1.addPrivateGroup(privateGroup0, joinMsg1);
|
||||
groupManager1.addPrivateGroup(privateGroup0, joinMsg1, false);
|
||||
assertEquals(joinMsg1.getMessage().getId(),
|
||||
groupManager1.getPreviousMsgId(groupId0));
|
||||
|
||||
// make group visible to 0
|
||||
Transaction txn1 = t1.getDatabaseComponent().startTransaction(false);
|
||||
t1.getDatabaseComponent()
|
||||
.setVisibleToContact(txn1, contactId0, privateGroup0.getId(),
|
||||
true);
|
||||
t1.getDatabaseComponent().commitTransaction(txn1);
|
||||
t1.getDatabaseComponent().endTransaction(txn1);
|
||||
Transaction txn1 = db1.startTransaction(false);
|
||||
db1.setVisibleToContact(txn1, contactId01, privateGroup0.getId(), true);
|
||||
db1.commitTransaction(txn1);
|
||||
db1.endTransaction(txn1);
|
||||
|
||||
// sync join messages
|
||||
sync0To1();
|
||||
@@ -394,17 +411,15 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
|
||||
GroupMessage joinMsg0 = groupMessageFactory
|
||||
.createJoinMessage(privateGroup0.getId(), joinTime, author0,
|
||||
inviteTime, creatorSignature);
|
||||
groupManager0.addPrivateGroup(privateGroup0, joinMsg0);
|
||||
groupManager0.addPrivateGroup(privateGroup0, joinMsg0, true);
|
||||
assertEquals(joinMsg0.getMessage().getId(),
|
||||
groupManager0.getPreviousMsgId(groupId0));
|
||||
|
||||
// make group visible to 1
|
||||
Transaction txn0 = t0.getDatabaseComponent().startTransaction(false);
|
||||
t0.getDatabaseComponent()
|
||||
.setVisibleToContact(txn0, contactId1, privateGroup0.getId(),
|
||||
true);
|
||||
t0.getDatabaseComponent().commitTransaction(txn0);
|
||||
t0.getDatabaseComponent().endTransaction(txn0);
|
||||
Transaction txn0 = db0.startTransaction(false);
|
||||
db0.setVisibleToContact(txn0, contactId1, privateGroup0.getId(), true);
|
||||
db0.commitTransaction(txn0);
|
||||
db0.endTransaction(txn0);
|
||||
|
||||
// author1 joins privateGroup0 with wrong signature in join message
|
||||
joinTime = clock.currentTimeMillis();
|
||||
@@ -419,17 +434,15 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
|
||||
GroupMessage joinMsg1 = groupMessageFactory
|
||||
.createJoinMessage(privateGroup0.getId(), joinTime, author1,
|
||||
inviteTime, creatorSignature);
|
||||
groupManager1.addPrivateGroup(privateGroup0, joinMsg1);
|
||||
groupManager1.addPrivateGroup(privateGroup0, joinMsg1, false);
|
||||
assertEquals(joinMsg1.getMessage().getId(),
|
||||
groupManager1.getPreviousMsgId(groupId0));
|
||||
|
||||
// make group visible to 0
|
||||
Transaction txn1 = t1.getDatabaseComponent().startTransaction(false);
|
||||
t1.getDatabaseComponent()
|
||||
.setVisibleToContact(txn1, contactId0, privateGroup0.getId(),
|
||||
true);
|
||||
t1.getDatabaseComponent().commitTransaction(txn1);
|
||||
t1.getDatabaseComponent().endTransaction(txn1);
|
||||
Transaction txn1 = db1.startTransaction(false);
|
||||
db1.setVisibleToContact(txn1, contactId01, privateGroup0.getId(), true);
|
||||
db1.commitTransaction(txn1);
|
||||
db1.endTransaction(txn1);
|
||||
|
||||
// sync join messages
|
||||
sync0To1();
|
||||
@@ -445,6 +458,193 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
|
||||
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
|
||||
public void tearDown() throws Exception {
|
||||
stopLifecycles();
|
||||
@@ -483,7 +683,7 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
|
||||
byte[] publicKey0 = keyPair0.getPublic().getEncoded();
|
||||
byte[] privateKey0 = keyPair0.getPrivate().getEncoded();
|
||||
author0 = authorFactory
|
||||
.createLocalAuthor(AUTHOR1, publicKey0, privateKey0);
|
||||
.createLocalAuthor(AUTHOR0, publicKey0, privateKey0);
|
||||
identityManager0.registerLocalAuthor(author0);
|
||||
privateGroup0 =
|
||||
privateGroupFactory.createPrivateGroup("Testgroup", author0);
|
||||
@@ -493,21 +693,34 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
|
||||
byte[] publicKey1 = keyPair1.getPublic().getEncoded();
|
||||
byte[] privateKey1 = keyPair1.getPrivate().getEncoded();
|
||||
author1 = authorFactory
|
||||
.createLocalAuthor(AUTHOR2, publicKey1, privateKey1);
|
||||
.createLocalAuthor(AUTHOR1, publicKey1, privateKey1);
|
||||
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 {
|
||||
// sharer adds invitee as contact
|
||||
contactId1 = contactManager0.addContact(author1,
|
||||
author0.getId(), master, clock.currentTimeMillis(), true,
|
||||
true, true
|
||||
);
|
||||
// invitee adds sharer back
|
||||
contactId0 = contactManager1.addContact(author0,
|
||||
author1.getId(), master, clock.currentTimeMillis(), true,
|
||||
true, true
|
||||
);
|
||||
// creator adds invitee as contact
|
||||
contactId1 = contactManager0
|
||||
.addContact(author1, author0.getId(), master,
|
||||
clock.currentTimeMillis(), true, true, true);
|
||||
// invitee adds creator back
|
||||
contactId01 = contactManager1
|
||||
.addContact(author0, author1.getId(), master,
|
||||
clock.currentTimeMillis(), 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() {
|
||||
@@ -515,6 +728,8 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
|
||||
t0.getEventBus().addListener(listener0);
|
||||
Listener listener1 = new Listener();
|
||||
t1.getEventBus().addListener(listener1);
|
||||
Listener listener2 = new Listener();
|
||||
t2.getEventBus().addListener(listener2);
|
||||
}
|
||||
|
||||
private void addGroup() throws Exception {
|
||||
@@ -522,17 +737,15 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
|
||||
long joinTime = clock.currentTimeMillis();
|
||||
GroupMessage joinMsg0 = groupMessageFactory
|
||||
.createJoinMessage(privateGroup0.getId(), joinTime, author0);
|
||||
groupManager0.addPrivateGroup(privateGroup0, joinMsg0);
|
||||
groupManager0.addPrivateGroup(privateGroup0, joinMsg0, true);
|
||||
assertEquals(joinMsg0.getMessage().getId(),
|
||||
groupManager0.getPreviousMsgId(groupId0));
|
||||
|
||||
// make group visible to 1
|
||||
Transaction txn0 = t0.getDatabaseComponent().startTransaction(false);
|
||||
t0.getDatabaseComponent()
|
||||
.setVisibleToContact(txn0, contactId1, privateGroup0.getId(),
|
||||
true);
|
||||
t0.getDatabaseComponent().commitTransaction(txn0);
|
||||
t0.getDatabaseComponent().endTransaction(txn0);
|
||||
Transaction txn0 = db0.startTransaction(false);
|
||||
db0.setVisibleToContact(txn0, contactId1, privateGroup0.getId(), true);
|
||||
db0.commitTransaction(txn0);
|
||||
db0.endTransaction(txn0);
|
||||
|
||||
// author1 joins privateGroup0
|
||||
joinTime = clock.currentTimeMillis();
|
||||
@@ -547,17 +760,15 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
|
||||
GroupMessage joinMsg1 = groupMessageFactory
|
||||
.createJoinMessage(privateGroup0.getId(), joinTime, author1,
|
||||
inviteTime, creatorSignature);
|
||||
groupManager1.addPrivateGroup(privateGroup0, joinMsg1);
|
||||
assertEquals(joinMsg1.getMessage().getId(),
|
||||
groupManager1.getPreviousMsgId(groupId0));
|
||||
groupManager1.addPrivateGroup(privateGroup0, joinMsg1, false);
|
||||
|
||||
// make group visible to 0
|
||||
Transaction txn1 = t1.getDatabaseComponent().startTransaction(false);
|
||||
t1.getDatabaseComponent()
|
||||
.setVisibleToContact(txn1, contactId0, privateGroup0.getId(),
|
||||
true);
|
||||
t1.getDatabaseComponent().commitTransaction(txn1);
|
||||
t1.getDatabaseComponent().endTransaction(txn1);
|
||||
Transaction txn1 = db1.startTransaction(false);
|
||||
db1.setVisibleToContact(txn1, contactId01, privateGroup0.getId(), true);
|
||||
db1.commitTransaction(txn1);
|
||||
db1.endTransaction(txn1);
|
||||
assertEquals(joinMsg1.getMessage().getId(),
|
||||
groupManager1.getPreviousMsgId(groupId0));
|
||||
|
||||
// sync join messages
|
||||
sync0To1();
|
||||
@@ -567,11 +778,11 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
|
||||
}
|
||||
|
||||
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 {
|
||||
deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0");
|
||||
deliverMessage(sync1, contactId1, sync0, contactId01, "1 to 0");
|
||||
}
|
||||
|
||||
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
|
||||
lifecycleManager0 = t0.getLifecycleManager();
|
||||
lifecycleManager1 = t1.getLifecycleManager();
|
||||
lifecycleManager0.startServices(AUTHOR1);
|
||||
lifecycleManager1.startServices(AUTHOR2);
|
||||
lifecycleManager2 = t2.getLifecycleManager();
|
||||
lifecycleManager0.startServices(AUTHOR0);
|
||||
lifecycleManager1.startServices(AUTHOR1);
|
||||
lifecycleManager2.startServices(AUTHOR2);
|
||||
lifecycleManager0.waitForStartup();
|
||||
lifecycleManager1.waitForStartup();
|
||||
lifecycleManager2.waitForStartup();
|
||||
}
|
||||
|
||||
private void stopLifecycles() throws InterruptedException {
|
||||
// Clean up
|
||||
lifecycleManager0.stopServices();
|
||||
lifecycleManager1.stopServices();
|
||||
lifecycleManager2.stopServices();
|
||||
lifecycleManager0.waitForShutdown();
|
||||
lifecycleManager1.waitForShutdown();
|
||||
lifecycleManager2.waitForShutdown();
|
||||
}
|
||||
|
||||
private void injectEagerSingletons(
|
||||
|
||||
@@ -105,7 +105,7 @@ public class GroupActivity extends
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setSubtitle(getString(R.string.groups_created_by,
|
||||
group.getAuthor().getName()));
|
||||
group.getCreator().getName()));
|
||||
}
|
||||
controller.isCreator(group,
|
||||
new UiResultExceptionHandler<Boolean, DbException>(this) {
|
||||
|
||||
@@ -196,7 +196,7 @@ public class GroupControllerImpl extends
|
||||
try {
|
||||
LocalAuthor author = identityManager.getLocalAuthor();
|
||||
boolean isCreator =
|
||||
author.getId().equals(group.getAuthor().getId());
|
||||
author.getId().equals(group.getCreator().getId());
|
||||
handler.onResult(isCreator);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
|
||||
@@ -117,7 +117,7 @@ public class CreateGroupControllerImpl extends DbControllerImpl
|
||||
public void run() {
|
||||
LOG.info("Adding group to database...");
|
||||
try {
|
||||
groupManager.addPrivateGroup(group, joinMsg);
|
||||
groupManager.addPrivateGroup(group, joinMsg, true);
|
||||
handler.onResult(group.getId());
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
|
||||
@@ -43,7 +43,7 @@ class GroupItem {
|
||||
}
|
||||
|
||||
Author getCreator() {
|
||||
return privateGroup.getAuthor();
|
||||
return privateGroup.getCreator();
|
||||
}
|
||||
|
||||
String getName() {
|
||||
|
||||
@@ -7,6 +7,8 @@ import org.briarproject.api.privategroup.GroupMember;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static org.briarproject.api.privategroup.Visibility.INVISIBLE;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class MemberListItem {
|
||||
@@ -17,7 +19,7 @@ class MemberListItem {
|
||||
|
||||
public MemberListItem(GroupMember groupMember) {
|
||||
this.member = groupMember.getAuthor();
|
||||
this.sharing = groupMember.isShared();
|
||||
this.sharing = groupMember.getVisibility() != INVISIBLE; // TODO #732
|
||||
this.status = groupMember.getStatus();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,12 +12,12 @@ public class GroupMember {
|
||||
|
||||
private final Author author;
|
||||
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.status = status;
|
||||
this.shared = shared;
|
||||
this.visibility = visibility;
|
||||
}
|
||||
|
||||
public Author getAuthor() {
|
||||
@@ -28,8 +28,8 @@ public class GroupMember {
|
||||
return status;
|
||||
}
|
||||
|
||||
public boolean isShared() {
|
||||
return shared;
|
||||
public Visibility getVisibility() {
|
||||
return visibility;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
package org.briarproject.api.privategroup;
|
||||
|
||||
import org.briarproject.api.identity.Author;
|
||||
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;
|
||||
|
||||
@@ -12,10 +8,16 @@ import javax.annotation.concurrent.Immutable;
|
||||
@NotNullByDefault
|
||||
public class JoinMessageHeader extends GroupMessageHeader {
|
||||
|
||||
public JoinMessageHeader(GroupId groupId, MessageId id,
|
||||
@Nullable MessageId parentId, long timestamp, Author author,
|
||||
Author.Status authorStatus, boolean read) {
|
||||
super(groupId, id, parentId, timestamp, author, authorStatus, read);
|
||||
private final Visibility visibility;
|
||||
|
||||
public JoinMessageHeader(GroupMessageHeader h, Visibility visibility) {
|
||||
super(h.getGroupId(), h.getId(), h.getParentId(), h.getTimestamp(),
|
||||
h.getAuthor(), h.getAuthorStatus(), h.isRead());
|
||||
this.visibility = visibility;
|
||||
}
|
||||
|
||||
public Visibility getVisibility() {
|
||||
return visibility;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,20 +12,20 @@ import javax.annotation.concurrent.Immutable;
|
||||
@NotNullByDefault
|
||||
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);
|
||||
this.author = author;
|
||||
this.creator = creator;
|
||||
}
|
||||
|
||||
public Author getAuthor() {
|
||||
return author;
|
||||
public Author getCreator() {
|
||||
return creator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof PrivateGroup && super.equals(o);
|
||||
return o instanceof PrivateGroup && super.equals(o);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package org.briarproject.api.privategroup;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.clients.MessageTracker;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.db.Transaction;
|
||||
import org.briarproject.api.identity.Author;
|
||||
import org.briarproject.api.identity.AuthorId;
|
||||
import org.briarproject.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.api.sync.ClientId;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
@@ -23,19 +25,21 @@ public interface PrivateGroupManager extends MessageTracker {
|
||||
* Adds a new private group and joins it.
|
||||
*
|
||||
* @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)
|
||||
throws DbException;
|
||||
void addPrivateGroup(PrivateGroup group, GroupMessage joinMsg,
|
||||
boolean creator) throws DbException;
|
||||
|
||||
/**
|
||||
* Adds a new private group and joins it.
|
||||
*
|
||||
* @param group The private group to add
|
||||
* @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,
|
||||
GroupMessage joinMsg) throws DbException;
|
||||
GroupMessage joinMsg, boolean creator) throws DbException;
|
||||
|
||||
/**
|
||||
* Removes a dissolved private group.
|
||||
@@ -43,7 +47,7 @@ public interface PrivateGroupManager extends MessageTracker {
|
||||
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;
|
||||
|
||||
@@ -103,6 +107,17 @@ public interface PrivateGroupManager extends MessageTracker {
|
||||
*/
|
||||
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
|
||||
* or groups are removed.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,20 +2,22 @@ package org.briarproject.privategroup;
|
||||
|
||||
import static org.briarproject.clients.BdfConstants.MSG_KEY_READ;
|
||||
|
||||
interface Constants {
|
||||
interface GroupConstants {
|
||||
|
||||
// Database keys
|
||||
String KEY_TYPE = "type";
|
||||
String KEY_TIMESTAMP = "timestamp";
|
||||
String KEY_READ = MSG_KEY_READ;
|
||||
String KEY_PARENT_MSG_ID = "parentMsgId";
|
||||
String KEY_NEW_MEMBER_MSG_ID = "newMemberMsgId";
|
||||
String KEY_PREVIOUS_MSG_ID = "previousMsgId";
|
||||
String KEY_MEMBER_ID = "memberId";
|
||||
String KEY_MEMBER_NAME = "memberName";
|
||||
String KEY_MEMBER_PUBLIC_KEY = "memberPublicKey";
|
||||
|
||||
String KEY_MEMBERS = "members";
|
||||
String KEY_DISSOLVED = "dissolved";
|
||||
String GROUP_KEY_MEMBERS = "members";
|
||||
String GROUP_KEY_OUR_GROUP = "ourGroup";
|
||||
String GROUP_KEY_CREATOR_ID = "creatorId";
|
||||
String GROUP_KEY_DISSOLVED = "dissolved";
|
||||
String GROUP_KEY_VISIBILITY = "visibility";
|
||||
|
||||
}
|
||||
|
||||
@@ -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.POST;
|
||||
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.Constants.KEY_MEMBER_NAME;
|
||||
import static org.briarproject.privategroup.Constants.KEY_MEMBER_PUBLIC_KEY;
|
||||
import static org.briarproject.privategroup.Constants.KEY_PARENT_MSG_ID;
|
||||
import static org.briarproject.privategroup.Constants.KEY_PREVIOUS_MSG_ID;
|
||||
import static org.briarproject.privategroup.Constants.KEY_READ;
|
||||
import static org.briarproject.privategroup.Constants.KEY_TIMESTAMP;
|
||||
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;
|
||||
|
||||
class GroupMessageValidator extends BdfMessageValidator {
|
||||
|
||||
@@ -98,7 +98,7 @@ class GroupMessageValidator extends BdfMessageValidator {
|
||||
PrivateGroup pg = privateGroupFactory.parsePrivateGroup(g);
|
||||
|
||||
// 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);
|
||||
if (invite == null) {
|
||||
if (!member.equals(creator))
|
||||
|
||||
@@ -2,8 +2,7 @@ package org.briarproject.privategroup;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
import org.briarproject.api.contact.Contact;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.clients.ProtocolStateException;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.data.BdfEntry;
|
||||
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.IdentityManager;
|
||||
import org.briarproject.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.api.privategroup.ContactRelationshipRevealedEvent;
|
||||
import org.briarproject.api.privategroup.GroupMember;
|
||||
import org.briarproject.api.privategroup.GroupMessage;
|
||||
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.PrivateGroupFactory;
|
||||
import org.briarproject.api.privategroup.PrivateGroupManager;
|
||||
import org.briarproject.api.privategroup.Visibility;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.Message;
|
||||
@@ -48,20 +49,25 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import javax.inject.Inject;
|
||||
|
||||
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.POST;
|
||||
import static org.briarproject.privategroup.Constants.KEY_DISSOLVED;
|
||||
import static org.briarproject.privategroup.Constants.KEY_MEMBERS;
|
||||
import static org.briarproject.privategroup.Constants.KEY_MEMBER_ID;
|
||||
import static org.briarproject.privategroup.Constants.KEY_MEMBER_NAME;
|
||||
import static org.briarproject.privategroup.Constants.KEY_MEMBER_PUBLIC_KEY;
|
||||
import static org.briarproject.privategroup.Constants.KEY_PARENT_MSG_ID;
|
||||
import static org.briarproject.privategroup.Constants.KEY_PREVIOUS_MSG_ID;
|
||||
import static org.briarproject.privategroup.Constants.KEY_READ;
|
||||
import static org.briarproject.privategroup.Constants.KEY_TIMESTAMP;
|
||||
import static org.briarproject.privategroup.Constants.KEY_TYPE;
|
||||
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.privategroup.GroupConstants.GROUP_KEY_CREATOR_ID;
|
||||
import static org.briarproject.privategroup.GroupConstants.GROUP_KEY_DISSOLVED;
|
||||
import static org.briarproject.privategroup.GroupConstants.GROUP_KEY_MEMBERS;
|
||||
import static org.briarproject.privategroup.GroupConstants.GROUP_KEY_OUR_GROUP;
|
||||
import static org.briarproject.privategroup.GroupConstants.GROUP_KEY_VISIBILITY;
|
||||
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
|
||||
public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
|
||||
@@ -84,11 +90,11 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPrivateGroup(PrivateGroup group, GroupMessage joinMsg)
|
||||
throws DbException {
|
||||
public void addPrivateGroup(PrivateGroup group, GroupMessage joinMsg,
|
||||
boolean creator) throws DbException {
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
addPrivateGroup(txn, group, joinMsg);
|
||||
addPrivateGroup(txn, group, joinMsg, creator);
|
||||
db.commitTransaction(txn);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
@@ -97,12 +103,15 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
|
||||
|
||||
@Override
|
||||
public void addPrivateGroup(Transaction txn, PrivateGroup group,
|
||||
GroupMessage joinMsg) throws DbException {
|
||||
GroupMessage joinMsg, boolean creator) throws DbException {
|
||||
try {
|
||||
db.addGroup(txn, group.getGroup());
|
||||
AuthorId creatorId = group.getCreator().getId();
|
||||
BdfDictionary meta = BdfDictionary.of(
|
||||
new BdfEntry(KEY_MEMBERS, new BdfList()),
|
||||
new BdfEntry(KEY_DISSOLVED, false)
|
||||
new BdfEntry(GROUP_KEY_MEMBERS, new BdfList()),
|
||||
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);
|
||||
joinPrivateGroup(txn, joinMsg);
|
||||
@@ -118,9 +127,10 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
|
||||
addMessageMetadata(meta, m, true);
|
||||
clientHelper.addLocalMessage(txn, m.getMessage(), meta, true);
|
||||
trackOutgoingMessage(txn, m.getMessage());
|
||||
addMember(txn, m.getMessage().getGroupId(), m.getMember());
|
||||
addMember(txn, m.getMessage().getGroupId(), m.getMember(), VISIBLE);
|
||||
setPreviousMsgId(txn, m.getMessage().getGroupId(),
|
||||
m.getMessage().getId());
|
||||
attachJoinMessageAddedEvent(txn, m.getMessage(), meta, true, VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -171,7 +181,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
|
||||
public void markGroupDissolved(Transaction txn, GroupId g)
|
||||
throws DbException {
|
||||
BdfDictionary meta = BdfDictionary.of(
|
||||
new BdfEntry(KEY_DISSOLVED, true)
|
||||
new BdfEntry(GROUP_KEY_DISSOLVED, true)
|
||||
);
|
||||
try {
|
||||
clientHelper.mergeGroupMetadata(txn, g, meta);
|
||||
@@ -283,7 +293,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
|
||||
public boolean isDissolved(GroupId g) throws DbException {
|
||||
try {
|
||||
BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(g);
|
||||
return meta.getBoolean(KEY_DISSOLVED);
|
||||
return meta.getBoolean(GROUP_KEY_DISSOLVED);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
@@ -322,11 +332,22 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
|
||||
for (AuthorId id : authors) {
|
||||
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()) {
|
||||
BdfDictionary meta = entry.getValue();
|
||||
headers.add(getGroupMessageHeader(txn, g, entry.getKey(), meta,
|
||||
statuses));
|
||||
if (meta.getLong(KEY_TYPE) == JOIN.getInt()) {
|
||||
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);
|
||||
return headers;
|
||||
@@ -356,19 +377,17 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
|
||||
}
|
||||
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,
|
||||
status, read);
|
||||
}
|
||||
|
||||
private GroupMessageHeader getGroupMessageHeader(Transaction txn, GroupId g,
|
||||
MessageId id, BdfDictionary meta)
|
||||
throws DbException, FormatException {
|
||||
return getGroupMessageHeader(txn, g, id, meta,
|
||||
Collections.<AuthorId, Status>emptyMap());
|
||||
private JoinMessageHeader getJoinMessageHeader(Transaction txn, GroupId g,
|
||||
MessageId id, BdfDictionary meta, Map<AuthorId, Status> statuses,
|
||||
Visibility v) throws DbException, FormatException {
|
||||
|
||||
GroupMessageHeader header =
|
||||
getGroupMessageHeader(txn, g, id, meta, statuses);
|
||||
return new JoinMessageHeader(header, v);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -376,18 +395,11 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
Collection<GroupMember> members = new ArrayList<GroupMember>();
|
||||
Collection<Author> authors = getMembers(txn, g);
|
||||
for (Author a : authors) {
|
||||
Status status = identityManager.getAuthorStatus(txn, a.getId());
|
||||
boolean shared = false;
|
||||
if (status == VERIFIED || status == UNVERIFIED) {
|
||||
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));
|
||||
Map<Author, Visibility> authors = getMembers(txn, g);
|
||||
for (Entry<Author, Visibility> m : authors.entrySet()) {
|
||||
Status status = identityManager
|
||||
.getAuthorStatus(txn, m.getKey().getId());
|
||||
members.add(new GroupMember(m.getKey(), status, m.getValue()));
|
||||
}
|
||||
db.commitTransaction(txn);
|
||||
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 {
|
||||
try {
|
||||
Collection<Author> members = new ArrayList<Author>();
|
||||
BdfDictionary meta =
|
||||
clientHelper.getGroupMetadataAsDictionary(txn, g);
|
||||
BdfList list = meta.getList(KEY_MEMBERS);
|
||||
for (Object o : list) {
|
||||
BdfDictionary d = (BdfDictionary) o;
|
||||
BdfList list = meta.getList(GROUP_KEY_MEMBERS);
|
||||
Map<Author, Visibility> members =
|
||||
new HashMap<Author, Visibility>(list.size());
|
||||
for (int i = 0 ; i < list.size(); i++) {
|
||||
BdfDictionary d = list.getDictionary(i);
|
||||
Author member = getAuthor(d);
|
||||
members.add(member);
|
||||
Visibility v = getVisibility(d);
|
||||
members.put(member, v);
|
||||
}
|
||||
return members;
|
||||
} catch (FormatException e) {
|
||||
@@ -417,12 +431,35 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
|
||||
@Override
|
||||
public boolean isMember(Transaction txn, GroupId g, Author a)
|
||||
throws DbException {
|
||||
for (Author member : getMembers(txn, g)) {
|
||||
for (Author member : getMembers(txn, g).keySet()) {
|
||||
if (member.equals(a)) return true;
|
||||
}
|
||||
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
|
||||
public void registerPrivateGroupHook(PrivateGroupHook hook) {
|
||||
hooks.add(hook);
|
||||
@@ -432,47 +469,14 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
|
||||
protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
|
||||
BdfDictionary meta) throws DbException, FormatException {
|
||||
|
||||
long timestamp = meta.getLong(KEY_TIMESTAMP);
|
||||
MessageType type =
|
||||
MessageType.valueOf(meta.getLong(KEY_TYPE).intValue());
|
||||
switch (type) {
|
||||
case JOIN:
|
||||
addMember(txn, m.getGroupId(), getAuthor(meta));
|
||||
trackIncomingMessage(txn, m);
|
||||
attachGroupMessageAddedEvent(txn, m, meta, false);
|
||||
handleJoinMessage(txn, m, meta);
|
||||
return true;
|
||||
case POST:
|
||||
// timestamp must be greater than the timestamps of parent post
|
||||
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);
|
||||
handleGroupMessage(txn, m, meta);
|
||||
return true;
|
||||
default:
|
||||
// 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,
|
||||
BdfDictionary meta, boolean local)
|
||||
throws DbException, FormatException {
|
||||
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);
|
||||
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 {
|
||||
|
||||
BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(txn, g);
|
||||
BdfList members = meta.getList(KEY_MEMBERS);
|
||||
BdfList members = meta.getList(GROUP_KEY_MEMBERS);
|
||||
members.add(BdfDictionary.of(
|
||||
new BdfEntry(KEY_MEMBER_ID, a.getId()),
|
||||
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);
|
||||
for (PrivateGroupHook hook : hooks) {
|
||||
@@ -512,4 +585,10 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
|
||||
return new Author(authorId, name, publicKey);
|
||||
}
|
||||
|
||||
private Visibility getVisibility(BdfDictionary meta)
|
||||
throws FormatException {
|
||||
return Visibility
|
||||
.valueOf(meta.getLong(GROUP_KEY_VISIBILITY).intValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ abstract class AbstractProtocolEngine<S extends Session>
|
||||
}
|
||||
Message m = messageEncoder.encodeInviteMessage(
|
||||
session.getContactGroupId(), privateGroup.getId(),
|
||||
timestamp, privateGroup.getName(), privateGroup.getAuthor(),
|
||||
timestamp, privateGroup.getName(), privateGroup.getCreator(),
|
||||
privateGroup.getSalt(), message, signature);
|
||||
sendMessage(txn, m, INVITE, privateGroup.getId(), true);
|
||||
return m;
|
||||
@@ -183,7 +183,8 @@ abstract class AbstractProtocolEngine<S extends Session>
|
||||
GroupMessage joinMessage = groupMessageFactory.createJoinMessage(
|
||||
privateGroup.getId(), timestamp, member, invite.getTimestamp(),
|
||||
invite.getSignature());
|
||||
privateGroupManager.addPrivateGroup(txn, privateGroup, joinMessage);
|
||||
privateGroupManager
|
||||
.addPrivateGroup(txn, privateGroup, joinMessage, false);
|
||||
}
|
||||
|
||||
long getLocalTimestamp(S session) {
|
||||
|
||||
Reference in New Issue
Block a user