Add support for revealing contacts to the PrivateGroupManager

This also adds two integration tests and improves some small details
This commit is contained in:
Torsten Grote
2016-11-08 22:26:56 -02:00
parent b20c107010
commit ec8982438a
13 changed files with 402 additions and 156 deletions

View File

@@ -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_YOU;
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();
@@ -328,12 +348,10 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
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();
@@ -353,12 +371,10 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
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();
@@ -399,12 +415,10 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
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();
@@ -424,12 +438,10 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
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 +457,119 @@ 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 : members0) {
if (m.getAuthor().equals(author1)) {
assertEquals(VISIBLE, m.getVisibility());
} else {
assertEquals(author0, m.getAuthor());
assertEquals(VISIBLE, m.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);
// 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_YOU, m.getVisibility());
}
}
members2 = groupManager2.getMembers(groupId0);
for (GroupMember m : members2) {
if (m.getAuthor().equals(author1)) {
assertEquals(REVEALED_BY_CONTACT, m.getVisibility());
}
}
}
@After
public void tearDown() throws Exception {
stopLifecycles();
@@ -483,7 +608,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 +618,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 +653,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 {
@@ -527,12 +667,10 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
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 +685,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));
Transaction txn1 = db1.startTransaction(false);
groupManager1.addPrivateGroup(txn1, privateGroup0, joinMsg1);
// 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);
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 +703,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 +736,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(

View File

@@ -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) {

View File

@@ -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))

View File

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

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -12,20 +12,22 @@ 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 &&
creator.equals(((PrivateGroup) o).getCreator()) &&
super.equals(o);
}
}

View File

@@ -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;
@@ -20,16 +22,16 @@ public interface PrivateGroupManager extends MessageTracker {
ClientId CLIENT_ID = new ClientId("org.briarproject.briar.privategroup");
/**
* Adds a new private group and joins it.
* Adds a new private group and joins it as the creator.
*
* @param group The private group to add
* @param joinMsg The new member's join message
* @param joinMsg The creators's join message
*/
void addPrivateGroup(PrivateGroup group, GroupMessage joinMsg)
throws DbException;
/**
* Adds a new private group and joins it.
* Adds a new private group and joins it as a member.
*
* @param group The private group to add
* @param joinMsg The new member's join message
@@ -103,6 +105,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 you 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.

View File

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

View File

@@ -9,13 +9,15 @@ interface Constants {
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";
}

View File

@@ -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))

View File

@@ -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;
@@ -27,6 +26,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,12 +48,17 @@ 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.api.privategroup.Visibility.INVISIBLE;
import static org.briarproject.api.privategroup.Visibility.REVEALED_BY_CONTACT;
import static org.briarproject.api.privategroup.Visibility.REVEALED_BY_YOU;
import static org.briarproject.api.privategroup.Visibility.VISIBLE;
import static org.briarproject.privategroup.Constants.GROUP_KEY_CREATOR_ID;
import static org.briarproject.privategroup.Constants.GROUP_KEY_DISSOLVED;
import static org.briarproject.privategroup.Constants.GROUP_KEY_MEMBERS;
import static org.briarproject.privategroup.Constants.GROUP_KEY_OUR_GROUP;
import static org.briarproject.privategroup.Constants.GROUP_KEY_VISIBILITY;
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;
@@ -88,7 +93,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
throws DbException {
Transaction txn = db.startTransaction(false);
try {
addPrivateGroup(txn, group, joinMsg);
addPrivateGroup(txn, group, joinMsg, true);
db.commitTransaction(txn);
} finally {
db.endTransaction(txn);
@@ -98,11 +103,19 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
@Override
public void addPrivateGroup(Transaction txn, PrivateGroup group,
GroupMessage joinMsg) throws DbException {
addPrivateGroup(txn, group, joinMsg, false);
}
private void addPrivateGroup(Transaction txn, PrivateGroup group,
GroupMessage joinMsg, boolean creator) throws DbException {
try {
db.addGroup(txn, group.getGroup());
AuthorId creatorId = joinMsg.getMember().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,7 +131,7 @@ 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());
}
@@ -171,7 +184,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 +296,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);
}
@@ -376,18 +389,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 +402,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);
BdfList list = meta.getList(GROUP_KEY_MEMBERS);
Map<Author, Visibility> members =
new HashMap<Author, Visibility>(list.size());
for (Object o : list) {
BdfDictionary d = (BdfDictionary) o;
Author member = getAuthor(d);
members.add(member);
Visibility v = getVisibility(d);
members.put(member, v);
}
return members;
} catch (FormatException e) {
@@ -417,12 +425,34 @@ 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);
boolean foundMember = false;
for (Object o : members) {
BdfDictionary d = (BdfDictionary) o;
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();
Visibility vNew =
byContact ? REVEALED_BY_CONTACT : REVEALED_BY_YOU;
d.put(GROUP_KEY_VISIBILITY, vNew.getInt());
}
}
if (!foundMember) throw new ProtocolStateException();
clientHelper.mergeGroupMetadata(txn, g, meta);
}
@Override
public void registerPrivateGroupHook(PrivateGroupHook hook) {
hooks.add(hook);
@@ -432,47 +462,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,6 +477,63 @@ 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);
attachGroupMessageAddedEvent(txn, m, meta, false);
}
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 {
@@ -489,15 +543,16 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
txn.attach(e);
}
private void addMember(Transaction txn, GroupId g, Author a)
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 +567,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());
}
}

View File

@@ -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;