Merge branch '494-implement-backend-for-reblogging-and-blog-comments' into 'master'

Add support for comments and reblogging to Blog Client

Comments and reblogs need to depend on the post they refer to.
Since message dependencies are limited to one group,
the post and also the comments need to be wrapped
when commented on or reblogged to another blog (and group).

For this reason, in addition to comments, two new wrapping message types
are introduced. They retain all data of the original messages and allow
for reconstruction and signature verification.

This MR breaks backwards compatibility with old blog posts.
It removes the content type, title and parent ID from the post.

Furthermore, it includes one commit that replaces the `Message` in `MessageSharedEvent` with a `MessageId`.

Closes #494

See merge request !285
This commit is contained in:
akwizgran
2016-08-30 23:09:31 +00:00
33 changed files with 915 additions and 345 deletions

View File

@@ -3,6 +3,7 @@ package org.briarproject;
import net.jodah.concurrentunit.Waiter; import net.jodah.concurrentunit.Waiter;
import org.briarproject.api.blogs.Blog; import org.briarproject.api.blogs.Blog;
import org.briarproject.api.blogs.BlogCommentHeader;
import org.briarproject.api.blogs.BlogFactory; import org.briarproject.api.blogs.BlogFactory;
import org.briarproject.api.blogs.BlogManager; import org.briarproject.api.blogs.BlogManager;
import org.briarproject.api.blogs.BlogPost; import org.briarproject.api.blogs.BlogPost;
@@ -31,6 +32,7 @@ import org.briarproject.lifecycle.LifecycleModule;
import org.briarproject.properties.PropertiesModule; import org.briarproject.properties.PropertiesModule;
import org.briarproject.sync.SyncModule; import org.briarproject.sync.SyncModule;
import org.briarproject.transport.TransportModule; import org.briarproject.transport.TransportModule;
import org.briarproject.util.StringUtils;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
@@ -50,6 +52,10 @@ import javax.inject.Inject;
import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertFalse;
import static org.briarproject.TestPluginsModule.MAX_LATENCY; import static org.briarproject.TestPluginsModule.MAX_LATENCY;
import static org.briarproject.api.blogs.MessageType.COMMENT;
import static org.briarproject.api.blogs.MessageType.POST;
import static org.briarproject.api.blogs.MessageType.WRAPPED_COMMENT;
import static org.briarproject.api.blogs.MessageType.WRAPPED_POST;
import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
import static org.briarproject.api.sync.ValidationManager.State.INVALID; import static org.briarproject.api.sync.ValidationManager.State.INVALID;
import static org.briarproject.api.sync.ValidationManager.State.PENDING; import static org.briarproject.api.sync.ValidationManager.State.PENDING;
@@ -89,7 +95,6 @@ public class BlogManagerTest {
private final int TIMEOUT = 15000; private final int TIMEOUT = 15000;
private final String AUTHOR1 = "Author 1"; private final String AUTHOR1 = "Author 1";
private final String AUTHOR2 = "Author 2"; private final String AUTHOR2 = "Author 2";
private final String CONTENT_TYPE = "text/plain";
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(ForumSharingIntegrationTest.class.getName()); Logger.getLogger(ForumSharingIntegrationTest.class.getName());
@@ -172,15 +177,15 @@ public class BlogManagerTest {
defaultInit(); defaultInit();
// check that blog0 has no posts // check that blog0 has no posts
final byte[] body = TestUtils.getRandomBytes(42); final String body = TestUtils.getRandomString(42);
Collection<BlogPostHeader> headers0 = Collection<BlogPostHeader> headers0 =
blogManager0.getPostHeaders(blog0.getId()); blogManager0.getPostHeaders(blog0.getId());
assertEquals(0, headers0.size()); assertEquals(0, headers0.size());
// add a post to blog0 // add a post to blog0
BlogPost p = blogPostFactory BlogPost p = blogPostFactory
.createBlogPost(blog0.getId(), null, clock.currentTimeMillis(), .createBlogPost(blog0.getId(), clock.currentTimeMillis(), null,
null, author0, CONTENT_TYPE, body); author0, body);
blogManager0.addLocalPost(p); blogManager0.addLocalPost(p);
// check that post is now in blog0 // check that post is now in blog0
@@ -188,7 +193,7 @@ public class BlogManagerTest {
assertEquals(1, headers0.size()); assertEquals(1, headers0.size());
// check that body is there // check that body is there
assertArrayEquals(body, assertArrayEquals(StringUtils.toUtf8(body),
blogManager0.getPostBody(p.getMessage().getId())); blogManager0.getPostBody(p.getMessage().getId()));
// make sure that blog0 at author1 doesn't have the post yet // make sure that blog0 at author1 doesn't have the post yet
@@ -203,9 +208,10 @@ public class BlogManagerTest {
// make sure post arrived // make sure post arrived
headers1 = blogManager1.getPostHeaders(blog0.getId()); headers1 = blogManager1.getPostHeaders(blog0.getId());
assertEquals(1, headers1.size()); assertEquals(1, headers1.size());
assertEquals(POST, headers1.iterator().next().getType());
// check that body is there // check that body is there
assertArrayEquals(body, assertArrayEquals(StringUtils.toUtf8(body),
blogManager1.getPostBody(p.getMessage().getId())); blogManager1.getPostBody(p.getMessage().getId()));
stopLifecycles(); stopLifecycles();
@@ -217,10 +223,10 @@ public class BlogManagerTest {
defaultInit(); defaultInit();
// add a post to blog1 // add a post to blog1
final byte[] body = TestUtils.getRandomBytes(42); final String body = TestUtils.getRandomString(42);
BlogPost p = blogPostFactory BlogPost p = blogPostFactory
.createBlogPost(blog1.getId(), null, clock.currentTimeMillis(), .createBlogPost(blog1.getId(), clock.currentTimeMillis(), null,
null, author0, CONTENT_TYPE, body); author0, body);
blogManager0.addLocalPost(p); blogManager0.addLocalPost(p);
// check that post is now in blog1 // check that post is now in blog1
@@ -287,6 +293,241 @@ public class BlogManagerTest {
stopLifecycles(); stopLifecycles();
} }
@Test
public void testBlogComment() throws Exception {
startLifecycles();
defaultInit();
// add a post to blog0
final String body = TestUtils.getRandomString(42);
BlogPost p = blogPostFactory
.createBlogPost(blog0.getId(), clock.currentTimeMillis(), null,
author0, body);
blogManager0.addLocalPost(p);
// sync the post over
sync0To1();
deliveryWaiter.await(TIMEOUT, 1);
// make sure post arrived
Collection<BlogPostHeader> headers1 =
blogManager1.getPostHeaders(blog0.getId());
assertEquals(1, headers1.size());
assertEquals(POST, headers1.iterator().next().getType());
// 1 adds a comment to that blog post
String comment = "This is a comment on a blog post!";
blogManager1
.addLocalComment(author1, blog1.getId(), comment,
headers1.iterator().next());
// sync comment over
sync1To0();
deliveryWaiter.await(TIMEOUT, 2);
// make sure comment and wrapped post arrived
Collection<BlogPostHeader> headers0 =
blogManager0.getPostHeaders(blog1.getId());
assertEquals(1, headers0.size());
assertEquals(COMMENT, headers0.iterator().next().getType());
BlogCommentHeader h = (BlogCommentHeader) headers0.iterator().next();
assertEquals(author0, h.getParent().getAuthor());
// ensure that body can be retrieved from wrapped post
assertArrayEquals(StringUtils.toUtf8(body),
blogManager0.getPostBody(h.getParentId()));
// 1 has only their own comment in their blog
headers1 = blogManager1.getPostHeaders(blog1.getId());
assertEquals(1, headers1.size());
stopLifecycles();
}
@Test
public void testBlogCommentOnOwnPost() throws Exception {
startLifecycles();
defaultInit();
// add a post to blog0
final String body = TestUtils.getRandomString(42);
BlogPost p = blogPostFactory
.createBlogPost(blog0.getId(), clock.currentTimeMillis(), null,
author0, body);
blogManager0.addLocalPost(p);
// get header of own post
Collection<BlogPostHeader> headers0 =
blogManager0.getPostHeaders(blog0.getId());
assertEquals(1, headers0.size());
BlogPostHeader header = headers0.iterator().next();
// add a comment on own post
String comment = "This is a comment on my own blog post!";
blogManager0
.addLocalComment(author0, blog0.getId(), comment, header);
// sync the post and comment over
sync0To1();
deliveryWaiter.await(TIMEOUT, 2);
// make sure post arrived
Collection<BlogPostHeader> headers1 =
blogManager1.getPostHeaders(blog0.getId());
assertEquals(2, headers1.size());
stopLifecycles();
}
@Test
public void testCommentOnComment() throws Exception {
startLifecycles();
defaultInit();
// add a post to blog0
final String body = TestUtils.getRandomString(42);
BlogPost p = blogPostFactory
.createBlogPost(blog0.getId(), clock.currentTimeMillis(), null,
author0, body);
blogManager0.addLocalPost(p);
// sync the post over
sync0To1();
deliveryWaiter.await(TIMEOUT, 1);
// make sure post arrived
Collection<BlogPostHeader> headers1 =
blogManager1.getPostHeaders(blog0.getId());
assertEquals(1, headers1.size());
assertEquals(POST, headers1.iterator().next().getType());
// 1 reblogs that blog post
blogManager1
.addLocalComment(author1, blog1.getId(), null,
headers1.iterator().next());
// sync comment over
sync1To0();
deliveryWaiter.await(TIMEOUT, 2);
// make sure comment and wrapped post arrived
Collection<BlogPostHeader> headers0 =
blogManager0.getPostHeaders(blog1.getId());
assertEquals(1, headers0.size());
// get header of comment
BlogPostHeader cHeader = headers0.iterator().next();
assertEquals(COMMENT, cHeader.getType());
// comment on the comment
String comment = "This is a comment on a reblogged post.";
blogManager0
.addLocalComment(author0, blog0.getId(), comment, cHeader);
// sync comment over
sync0To1();
deliveryWaiter.await(TIMEOUT, 3);
// check that comment arrived
headers1 =
blogManager1.getPostHeaders(blog0.getId());
assertEquals(2, headers1.size());
// get header of comment
cHeader = null;
for (BlogPostHeader h : headers1) {
if (h.getType() == COMMENT) {
cHeader = h;
}
}
assertTrue(cHeader != null);
// another comment on the comment
String comment2 = "This is a comment on a comment.";
blogManager1.addLocalComment(author1, blog1.getId(), comment2, cHeader);
// sync comment over
sync1To0();
deliveryWaiter.await(TIMEOUT, 4);
// make sure new comment arrived
headers0 =
blogManager0.getPostHeaders(blog1.getId());
assertEquals(2, headers0.size());
boolean satisfied = false;
for (BlogPostHeader h : headers0) {
assertEquals(COMMENT, h.getType());
BlogCommentHeader c = (BlogCommentHeader) h;
if (c.getComment() != null && c.getComment().equals(comment2)) {
assertEquals(author0, c.getParent().getAuthor());
assertEquals(WRAPPED_COMMENT, c.getParent().getType());
assertEquals(comment,
((BlogCommentHeader) c.getParent()).getComment());
assertEquals(WRAPPED_COMMENT,
((BlogCommentHeader) c.getParent()).getParent()
.getType());
assertEquals(WRAPPED_POST,
((BlogCommentHeader) ((BlogCommentHeader) c
.getParent()).getParent()).getParent()
.getType());
satisfied = true;
}
}
assertTrue(satisfied);
stopLifecycles();
}
@Test
public void testCommentOnOwnComment() throws Exception {
startLifecycles();
defaultInit();
// add a post to blog0
final String body = TestUtils.getRandomString(42);
BlogPost p = blogPostFactory
.createBlogPost(blog0.getId(), clock.currentTimeMillis(), null,
author0, body);
blogManager0.addLocalPost(p);
// sync the post over
sync0To1();
deliveryWaiter.await(TIMEOUT, 1);
// make sure post arrived
Collection<BlogPostHeader> headers1 =
blogManager1.getPostHeaders(blog0.getId());
assertEquals(1, headers1.size());
assertEquals(POST, headers1.iterator().next().getType());
// 1 reblogs that blog post with a comment
String comment = "This is a comment on a post.";
blogManager1
.addLocalComment(author1, blog1.getId(), comment,
headers1.iterator().next());
// get comment from own blog
headers1 = blogManager1.getPostHeaders(blog1.getId());
assertEquals(1, headers1.size());
assertEquals(COMMENT, headers1.iterator().next().getType());
BlogCommentHeader ch = (BlogCommentHeader) headers1.iterator().next();
assertEquals(comment, ch.getComment());
comment = "This is a comment on a post with a comment.";
blogManager1.addLocalComment(author1, blog1.getId(), comment, ch);
// sync both comments over (2 comments + 1 wrapped post)
sync1To0();
deliveryWaiter.await(TIMEOUT, 3);
// make sure both comments arrived
Collection<BlogPostHeader> headers0 =
blogManager0.getPostHeaders(blog1.getId());
assertEquals(2, headers0.size());
stopLifecycles();
}
@After @After
public void tearDown() throws Exception { public void tearDown() throws Exception {
TestUtils.deleteTestDirectory(testDir); TestUtils.deleteTestDirectory(testDir);

View File

@@ -212,9 +212,10 @@ public class BlogControllerImpl extends DbControllerImpl
} }
private BlogPostHeader getPostHeader(MessageId m) throws DbException { private BlogPostHeader getPostHeader(MessageId m) throws DbException {
if (groupId == null) throw new IllegalStateException();
BlogPostHeader header = headerCache.get(m); BlogPostHeader header = headerCache.get(m);
if (header == null) { if (header == null) {
header = blogManager.getPostHeader(m); header = blogManager.getPostHeader(groupId, m);
headerCache.put(m, header); headerCache.put(m, header);
} }
return header; return header;

View File

@@ -136,11 +136,8 @@ public class BlogPostFragment extends BaseFragment {
ui.date.setText(AndroidUtils.formatDate(ctx, post.getTimestamp())); ui.date.setText(AndroidUtils.formatDate(ctx, post.getTimestamp()));
} }
if (post.getTitle() != null) { // TODO remove #598
ui.title.setText(post.getTitle()); ui.title.setVisibility(GONE);
} else {
ui.title.setVisibility(GONE);
}
ui.body.setText(StringUtils.fromUtf8(post.getBody())); ui.body.setText(StringUtils.fromUtf8(post.getBody()));
} }

View File

@@ -32,11 +32,6 @@ class BlogPostItem implements Comparable<BlogPostItem> {
return groupId; return groupId;
} }
@Nullable
public String getTitle() {
return header.getTitle();
}
public long getTimestamp() { public long getTimestamp() {
return header.getTimestamp(); return header.getTimestamp();
} }
@@ -72,11 +67,6 @@ class BlogPostItem implements Comparable<BlogPostItem> {
long aTime = getTimeReceived(), bTime = other.getTimeReceived(); long aTime = getTimeReceived(), bTime = other.getTimeReceived();
if (aTime > bTime) return -1; if (aTime > bTime) return -1;
if (aTime < bTime) return 1; if (aTime < bTime) return 1;
// Break ties by post title
if (getTitle() != null && other.getTitle() != null) {
return String.CASE_INSENSITIVE_ORDER
.compare(getTitle(), other.getTitle());
}
return 0; return 0;
} }
} }

View File

@@ -2,8 +2,6 @@ package org.briarproject.android.blogs;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.design.widget.TextInputEditText;
import android.support.design.widget.TextInputLayout;
import android.text.Editable; import android.text.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.view.KeyEvent; import android.view.KeyEvent;
@@ -39,19 +37,16 @@ import static android.view.View.GONE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH; import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH;
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_TITLE_LENGTH;
public class WriteBlogPostActivity extends BriarActivity public class WriteBlogPostActivity extends BriarActivity
implements OnEditorActionListener { implements OnEditorActionListener {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(WriteBlogPostActivity.class.getName()); Logger.getLogger(WriteBlogPostActivity.class.getName());
private static final String contentType = "text/plain";
@Inject @Inject
protected AndroidNotificationManager notificationManager; protected AndroidNotificationManager notificationManager;
private TextInputEditText titleInput;
private EditText bodyInput; private EditText bodyInput;
private Button publishButton; private Button publishButton;
private ProgressBar progressBar; private ProgressBar progressBar;
@@ -76,16 +71,6 @@ public class WriteBlogPostActivity extends BriarActivity
setContentView(R.layout.activity_write_blog_post); setContentView(R.layout.activity_write_blog_post);
TextInputLayout titleLayout =
(TextInputLayout) findViewById(R.id.titleLayout);
if (titleLayout != null) {
titleLayout.setCounterMaxLength(MAX_BLOG_POST_TITLE_LENGTH);
}
titleInput = (TextInputEditText) findViewById(R.id.titleInput);
if (titleInput != null) {
titleInput.setOnEditorActionListener(this);
}
bodyInput = (EditText) findViewById(R.id.bodyInput); bodyInput = (EditText) findViewById(R.id.bodyInput);
bodyInput.addTextChangedListener(new TextWatcher() { bodyInput.addTextChangedListener(new TextWatcher() {
@Override @Override
@@ -152,30 +137,24 @@ public class WriteBlogPostActivity extends BriarActivity
private void enableOrDisablePublishButton() { private void enableOrDisablePublishButton() {
int bodyLength = int bodyLength =
StringUtils.toUtf8(bodyInput.getText().toString()).length; StringUtils.toUtf8(bodyInput.getText().toString()).length;
if (bodyLength > 0 && bodyLength <= MAX_BLOG_POST_BODY_LENGTH && if (bodyLength > 0 && bodyLength <= MAX_BLOG_POST_BODY_LENGTH)
titleInput.getText().length() <= MAX_BLOG_POST_TITLE_LENGTH)
publishButton.setEnabled(true); publishButton.setEnabled(true);
else else
publishButton.setEnabled(false); publishButton.setEnabled(false);
} }
private void publish() { private void publish() {
// title
String title = titleInput.getText().toString();
if (title.length() > MAX_BLOG_POST_TITLE_LENGTH) return;
if (title.length() == 0) title = null;
// body // body
byte[] body = StringUtils.toUtf8(bodyInput.getText().toString()); String body = bodyInput.getText().toString();
// hide publish button, show progress bar // hide publish button, show progress bar
publishButton.setVisibility(GONE); publishButton.setVisibility(GONE);
progressBar.setVisibility(VISIBLE); progressBar.setVisibility(VISIBLE);
storePost(title, body); storePost(body);
} }
private void storePost(final String title, final byte[] body) { private void storePost(final String body) {
runOnDbThread(new Runnable() { runOnDbThread(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -185,8 +164,7 @@ public class WriteBlogPostActivity extends BriarActivity
identityManager.getLocalAuthors(); identityManager.getLocalAuthors();
LocalAuthor author = authors.iterator().next(); LocalAuthor author = authors.iterator().next();
BlogPost p = blogPostFactory BlogPost p = blogPostFactory
.createBlogPost(groupId, title, now, null, author, .createBlogPost(groupId, now, null, author, body);
contentType, body);
blogManager.addLocalPost(p); blogManager.addLocalPost(p);
postPublished(); postPublished();
} catch (DbException | GeneralSecurityException | FormatException e) { } catch (DbException | GeneralSecurityException | FormatException e) {

View File

@@ -404,7 +404,7 @@ public class ForumControllerImpl extends DbControllerImpl
ForumPostHeader h = ForumPostHeader h =
new ForumPostHeader(p.getMessage().getId(), p.getParent(), new ForumPostHeader(p.getMessage().getId(), p.getParent(),
p.getMessage().getTimestamp(), p.getAuthor(), VERIFIED, p.getMessage().getTimestamp(), p.getAuthor(), VERIFIED,
p.getContentType(), false); false);
addNewPost(h); addNewPost(h);
} }

View File

@@ -0,0 +1,42 @@
package org.briarproject.api.blogs;
import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.Author.Status;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static org.briarproject.api.blogs.MessageType.COMMENT;
import static org.briarproject.api.blogs.MessageType.WRAPPED_COMMENT;
public class BlogCommentHeader extends BlogPostHeader {
private final String comment;
private final BlogPostHeader parent;
public BlogCommentHeader(@NotNull MessageType type,
@NotNull GroupId groupId, @Nullable String comment,
@NotNull BlogPostHeader parent, @NotNull MessageId id,
long timestamp, long timeReceived, @NotNull Author author,
@NotNull Status authorStatus, boolean read) {
super(type, groupId, id, parent.getId(), timestamp,
timeReceived, author, authorStatus, read);
if (type != COMMENT && type != WRAPPED_COMMENT)
throw new IllegalArgumentException("Incompatible Message Type");
this.comment = comment;
this.parent = parent;
}
@Nullable
public String getComment() {
return comment;
}
public BlogPostHeader getParent() {
return parent;
}
}

View File

@@ -10,12 +10,6 @@ public interface BlogConstants {
/** The length of a blogs's description in UTF-8 bytes. */ /** The length of a blogs's description in UTF-8 bytes. */
int MAX_BLOG_DESC_LENGTH = 240; int MAX_BLOG_DESC_LENGTH = 240;
/** The maximum length of a blog post's content type in UTF-8 bytes. */
int MAX_CONTENT_TYPE_LENGTH = 50;
/** The length of a blog post's title in UTF-8 bytes. */
int MAX_BLOG_POST_TITLE_LENGTH = 100;
/** The maximum length of a blog post's body in bytes. */ /** The maximum length of a blog post's body in bytes. */
int MAX_BLOG_POST_BODY_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024; int MAX_BLOG_POST_BODY_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
@@ -31,17 +25,20 @@ public interface BlogConstants {
// Metadata keys // Metadata keys
String KEY_TYPE = "type"; String KEY_TYPE = "type";
String KEY_DESCRIPTION = "description"; String KEY_DESCRIPTION = "description";
String KEY_TITLE = "title";
String KEY_TIMESTAMP = "timestamp"; String KEY_TIMESTAMP = "timestamp";
String KEY_TIME_RECEIVED = "timeReceived"; String KEY_TIME_RECEIVED = "timeReceived";
String KEY_AUTHOR_ID = "id"; String KEY_AUTHOR_ID = "id";
String KEY_AUTHOR_NAME = "name"; String KEY_AUTHOR_NAME = "name";
String KEY_PUBLIC_KEY = "publicKey"; String KEY_PUBLIC_KEY = "publicKey";
String KEY_AUTHOR = "author"; String KEY_AUTHOR = "author";
String KEY_CONTENT_TYPE = "contentType";
String KEY_READ = "read"; String KEY_READ = "read";
String KEY_COMMENT = "comment"; String KEY_COMMENT = "comment";
String KEY_ORIGINAL_MSG_ID = "originalMessageId"; String KEY_ORIGINAL_MSG_ID = "originalMessageId";
String KEY_CURRENT_MSG_ID = "currentMessageId"; String KEY_ORIGINAL_PARENT_MSG_ID = "originalParentMessageId";
/**
* This is the ID of either a message wrapped from a different group
* or of a message from the same group that therefore needed no wrapping.
*/
String KEY_PARENT_MSG_ID = "parentMessageId";
} }

View File

@@ -7,6 +7,7 @@ import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageId;
import org.jetbrains.annotations.Nullable;
import java.util.Collection; import java.util.Collection;
@@ -31,6 +32,11 @@ public interface BlogManager {
/** Stores a local blog post. */ /** Stores a local blog post. */
void addLocalPost(Transaction txn, BlogPost p) throws DbException; void addLocalPost(Transaction txn, BlogPost p) throws DbException;
/** Add a comment to an existing blog post or reblog it. */
void addLocalComment(LocalAuthor author, GroupId groupId,
@Nullable String comment, BlogPostHeader wHeader)
throws DbException;
/** Returns the blog with the given ID. */ /** Returns the blog with the given ID. */
Blog getBlog(GroupId g) throws DbException; Blog getBlog(GroupId g) throws DbException;
@@ -47,7 +53,7 @@ public interface BlogManager {
Collection<Blog> getBlogs() throws DbException; Collection<Blog> getBlogs() throws DbException;
/** Returns the header of the blog post with the given ID. */ /** Returns the header of the blog post with the given ID. */
BlogPostHeader getPostHeader(MessageId m) throws DbException; BlogPostHeader getPostHeader(GroupId g, MessageId m) throws DbException;
/** Returns the body of the blog post with the given ID. */ /** Returns the body of the blog post with the given ID. */
byte[] getPostBody(MessageId m) throws DbException; byte[] getPostBody(MessageId m) throws DbException;

View File

@@ -9,19 +9,8 @@ import org.jetbrains.annotations.Nullable;
public class BlogPost extends ForumPost { public class BlogPost extends ForumPost {
@Nullable public BlogPost(@NotNull Message message, @Nullable MessageId parent,
private final String title; @NotNull Author author) {
super(message, parent, author);
public BlogPost(@Nullable String title, @NotNull Message message,
@Nullable MessageId parent, @NotNull Author author,
@NotNull String contentType) {
super(message, parent, author, contentType);
this.title = title;
}
@Nullable
public String getTitle() {
return title;
} }
} }

View File

@@ -1,8 +1,10 @@
package org.briarproject.api.blogs; package org.briarproject.api.blogs;
import org.briarproject.api.FormatException; import org.briarproject.api.FormatException;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageId;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -11,9 +13,30 @@ import java.security.GeneralSecurityException;
public interface BlogPostFactory { public interface BlogPostFactory {
BlogPost createBlogPost(@NotNull GroupId groupId, @Nullable String title, BlogPost createBlogPost(@NotNull GroupId groupId, long timestamp,
long timestamp, @Nullable MessageId parent, @Nullable MessageId parent, @NotNull LocalAuthor author,
@NotNull LocalAuthor author, @NotNull String contentType, @NotNull String body)
@NotNull byte[] body)
throws FormatException, GeneralSecurityException; throws FormatException, GeneralSecurityException;
Message createBlogComment(GroupId groupId, LocalAuthor author,
@Nullable String comment, MessageId originalId, MessageId wrappedId)
throws FormatException, GeneralSecurityException;
/** Wraps a blog post */
Message wrapPost(GroupId groupId, byte[] descriptor,
long timestamp, BdfList body)
throws FormatException;
/** Re-wraps a previously wrapped post */
Message rewrapWrappedPost(GroupId groupId, BdfList body)
throws FormatException;
/** Wraps a blog comment */
Message wrapComment(GroupId groupId, byte[] descriptor,
long timestamp, BdfList body, MessageId currentId)
throws FormatException;
/** Re-wraps a previously wrapped comment */
Message rewrapWrappedComment(GroupId groupId, BdfList body,
MessageId currentId) throws FormatException;
} }

View File

@@ -3,33 +3,45 @@ package org.briarproject.api.blogs;
import org.briarproject.api.clients.PostHeader; import org.briarproject.api.clients.PostHeader;
import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.Author.Status; import org.briarproject.api.identity.Author.Status;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageId;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
public class BlogPostHeader extends PostHeader { public class BlogPostHeader extends PostHeader {
@Nullable private final MessageType type;
private final String title; private final GroupId groupId;
private final long timeReceived; private final long timeReceived;
public BlogPostHeader(@Nullable String title, @NotNull MessageId id, public BlogPostHeader(@NotNull MessageType type, @NotNull GroupId groupId,
long timestamp, long timeReceived, @NotNull Author author, @NotNull MessageId id, @Nullable MessageId parentId, long timestamp,
@NotNull Status authorStatus, @NotNull String contentType, long timeReceived, @NotNull Author author,
boolean read) { @NotNull Status authorStatus, boolean read) {
super(id, null, timestamp, author, authorStatus, contentType, read); super(id, parentId, timestamp, author, authorStatus, read);
this.title = title; this.type = type;
this.groupId = groupId;
this.timeReceived = timeReceived; this.timeReceived = timeReceived;
} }
@Nullable public BlogPostHeader(@NotNull MessageType type, @NotNull GroupId groupId,
public String getTitle() { @NotNull MessageId id, long timestamp, long timeReceived,
return title; @NotNull Author author, @NotNull Status authorStatus,
boolean read) {
this(type, groupId, id, null, timestamp, timeReceived, author,
authorStatus, read);
}
public MessageType getType() {
return type;
}
public GroupId getGroupId() {
return groupId;
} }
public long getTimeReceived() { public long getTimeReceived() {
return timeReceived; return timeReceived;
} }
} }

View File

@@ -10,6 +10,7 @@ import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.Message; import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageId;
import java.security.GeneralSecurityException;
import java.util.Map; import java.util.Map;
public interface ClientHelper { public interface ClientHelper {
@@ -72,7 +73,7 @@ public interface ClientHelper {
/** /**
* Marks the given message as shared or unshared with other contacts. * Marks the given message as shared or unshared with other contacts.
*/ */
void setMessageShared(Transaction txn, Message m, boolean shared) void setMessageShared(Transaction txn, MessageId m, boolean shared)
throws DbException; throws DbException;
byte[] toByteArray(BdfDictionary dictionary) throws FormatException; byte[] toByteArray(BdfDictionary dictionary) throws FormatException;
@@ -83,4 +84,9 @@ public interface ClientHelper {
throws FormatException; throws FormatException;
BdfList toList(byte[] b, int off, int len) throws FormatException; BdfList toList(byte[] b, int off, int len) throws FormatException;
BdfList toList(byte[] b) throws FormatException;
byte[] sign(BdfList toSign, byte[] privateKey)
throws FormatException, GeneralSecurityException;
} }

View File

@@ -1,6 +1,7 @@
package org.briarproject.api.clients; package org.briarproject.api.clients;
import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.Author.Status;
import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageId;
public abstract class PostHeader { public abstract class PostHeader {
@@ -9,19 +10,16 @@ public abstract class PostHeader {
private final MessageId parentId; private final MessageId parentId;
private final long timestamp; private final long timestamp;
private final Author author; private final Author author;
private final Author.Status authorStatus; private final Status authorStatus;
private final String contentType;
private final boolean read; private final boolean read;
public PostHeader(MessageId id, MessageId parentId, long timestamp, public PostHeader(MessageId id, MessageId parentId, long timestamp,
Author author, Author.Status authorStatus, String contentType, Author author, Status authorStatus, boolean read) {
boolean read) {
this.id = id; this.id = id;
this.parentId = parentId; this.parentId = parentId;
this.timestamp = timestamp; this.timestamp = timestamp;
this.author = author; this.author = author;
this.authorStatus = authorStatus; this.authorStatus = authorStatus;
this.contentType = contentType;
this.read = read; this.read = read;
} }
@@ -33,14 +31,10 @@ public abstract class PostHeader {
return author; return author;
} }
public Author.Status getAuthorStatus() { public Status getAuthorStatus() {
return authorStatus; return authorStatus;
} }
public String getContentType() {
return contentType;
}
public long getTimestamp() { public long getTimestamp() {
return timestamp; return timestamp;
} }

View File

@@ -437,7 +437,7 @@ public interface DatabaseComponent {
/** /**
* Marks the given message as shared or unshared. * Marks the given message as shared or unshared.
*/ */
void setMessageShared(Transaction txn, Message m, boolean shared) void setMessageShared(Transaction txn, MessageId m, boolean shared)
throws DbException; throws DbException;
/** /**

View File

@@ -1,17 +1,17 @@
package org.briarproject.api.event; package org.briarproject.api.event;
import org.briarproject.api.sync.Message; import org.briarproject.api.sync.MessageId;
/** An event that is broadcast when a message is shared. */ /** An event that is broadcast when a message is shared. */
public class MessageSharedEvent extends Event { public class MessageSharedEvent extends Event {
private final Message message; private final MessageId messageId;
public MessageSharedEvent(Message message) { public MessageSharedEvent(MessageId message) {
this.message = message; this.messageId = message;
} }
public Message getMessage() { public MessageId getMessageId() {
return message; return messageId;
} }
} }

View File

@@ -27,7 +27,6 @@ public interface ForumConstants {
String KEY_NAME = "name"; String KEY_NAME = "name";
String KEY_PUBLIC_NAME = "publicKey"; String KEY_PUBLIC_NAME = "publicKey";
String KEY_AUTHOR = "author"; String KEY_AUTHOR = "author";
String KEY_CONTENT_TYPE = "contentType";
String KEY_LOCAL = "local"; String KEY_LOCAL = "local";
String KEY_READ = "read"; String KEY_READ = "read";

View File

@@ -9,14 +9,11 @@ public class ForumPost {
private final Message message; private final Message message;
private final MessageId parent; private final MessageId parent;
private final Author author; private final Author author;
private final String contentType;
public ForumPost(Message message, MessageId parent, Author author, public ForumPost(Message message, MessageId parent, Author author) {
String contentType) {
this.message = message; this.message = message;
this.parent = parent; this.parent = parent;
this.author = author; this.author = author;
this.contentType = contentType;
} }
public Message getMessage() { public Message getMessage() {
@@ -30,8 +27,4 @@ public class ForumPost {
public Author getAuthor() { public Author getAuthor() {
return author; return author;
} }
public String getContentType() {
return contentType;
}
} }

View File

@@ -9,9 +9,8 @@ public class ForumPostHeader extends PostHeader
implements MessageTree.MessageNode { implements MessageTree.MessageNode {
public ForumPostHeader(MessageId id, MessageId parentId, long timestamp, public ForumPostHeader(MessageId id, MessageId parentId, long timestamp,
Author author, Author.Status authorStatus, String contentType, Author author, Author.Status authorStatus, boolean read) {
boolean read) { super(id, parentId, timestamp, author, authorStatus, read);
super(id, parentId, timestamp, author, authorStatus, contentType, read);
} }
} }

View File

@@ -59,7 +59,7 @@ class BlogFactoryImpl implements BlogFactory {
byte[] descriptor = g.getDescriptor(); byte[] descriptor = g.getDescriptor();
// Blog Name, Author Name, Public Key // Blog Name, Author Name, Public Key
BdfList blog = clientHelper.toList(descriptor, 0, descriptor.length); BdfList blog = clientHelper.toList(descriptor);
String name = blog.getString(0); String name = blog.getString(0);
Author a = Author a =
authorFactory.createAuthor(blog.getString(1), blog.getRaw(2)); authorFactory.createAuthor(blog.getString(1), blog.getRaw(2));

View File

@@ -2,10 +2,13 @@ package org.briarproject.blogs;
import org.briarproject.api.FormatException; import org.briarproject.api.FormatException;
import org.briarproject.api.blogs.Blog; import org.briarproject.api.blogs.Blog;
import org.briarproject.api.blogs.BlogCommentHeader;
import org.briarproject.api.blogs.BlogFactory; import org.briarproject.api.blogs.BlogFactory;
import org.briarproject.api.blogs.BlogManager; import org.briarproject.api.blogs.BlogManager;
import org.briarproject.api.blogs.BlogPost; import org.briarproject.api.blogs.BlogPost;
import org.briarproject.api.blogs.BlogPostFactory;
import org.briarproject.api.blogs.BlogPostHeader; import org.briarproject.api.blogs.BlogPostHeader;
import org.briarproject.api.blogs.MessageType;
import org.briarproject.api.clients.Client; import org.briarproject.api.clients.Client;
import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.Contact;
@@ -33,10 +36,14 @@ import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageId;
import org.briarproject.clients.BdfIncomingMessageHook; import org.briarproject.clients.BdfIncomingMessageHook;
import org.briarproject.util.StringUtils; import org.briarproject.util.StringUtils;
import org.jetbrains.annotations.Nullable;
import java.security.GeneralSecurityException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@@ -45,19 +52,26 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR; import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR;
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_ID; import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_ID;
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_NAME; import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_NAME;
import static org.briarproject.api.blogs.BlogConstants.KEY_CONTENT_TYPE; import static org.briarproject.api.blogs.BlogConstants.KEY_COMMENT;
import static org.briarproject.api.blogs.BlogConstants.KEY_DESCRIPTION; import static org.briarproject.api.blogs.BlogConstants.KEY_DESCRIPTION;
import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_MSG_ID;
import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_PARENT_MSG_ID;
import static org.briarproject.api.blogs.BlogConstants.KEY_PUBLIC_KEY; import static org.briarproject.api.blogs.BlogConstants.KEY_PUBLIC_KEY;
import static org.briarproject.api.blogs.BlogConstants.KEY_READ; import static org.briarproject.api.blogs.BlogConstants.KEY_READ;
import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP; import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP;
import static org.briarproject.api.blogs.BlogConstants.KEY_TIME_RECEIVED; import static org.briarproject.api.blogs.BlogConstants.KEY_TIME_RECEIVED;
import static org.briarproject.api.blogs.BlogConstants.KEY_TITLE; import static org.briarproject.api.blogs.BlogConstants.KEY_TYPE;
import static org.briarproject.api.blogs.BlogConstants.KEY_PARENT_MSG_ID;
import static org.briarproject.api.blogs.MessageType.COMMENT;
import static org.briarproject.api.blogs.MessageType.POST;
import static org.briarproject.api.blogs.MessageType.WRAPPED_COMMENT;
import static org.briarproject.api.blogs.MessageType.WRAPPED_POST;
import static org.briarproject.api.contact.ContactManager.AddContactHook; import static org.briarproject.api.contact.ContactManager.AddContactHook;
import static org.briarproject.api.contact.ContactManager.RemoveContactHook; import static org.briarproject.api.contact.ContactManager.RemoveContactHook;
import static org.briarproject.blogs.BlogPostValidator.authorToBdfDictionary;
class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
AddContactHook, RemoveContactHook, Client, AddContactHook, RemoveContactHook, Client,
@@ -74,18 +88,21 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
private final IdentityManager identityManager; private final IdentityManager identityManager;
private final ContactManager contactManager; private final ContactManager contactManager;
private final BlogFactory blogFactory; private final BlogFactory blogFactory;
private final BlogPostFactory blogPostFactory;
private final List<RemoveBlogHook> removeHooks; private final List<RemoveBlogHook> removeHooks;
@Inject @Inject
BlogManagerImpl(DatabaseComponent db, IdentityManager identityManager, BlogManagerImpl(DatabaseComponent db, IdentityManager identityManager,
ClientHelper clientHelper, MetadataParser metadataParser, ClientHelper clientHelper, MetadataParser metadataParser,
ContactManager contactManager, BlogFactory blogFactory) { ContactManager contactManager, BlogFactory blogFactory,
BlogPostFactory blogPostFactory) {
super(clientHelper, metadataParser); super(clientHelper, metadataParser);
this.db = db; this.db = db;
this.identityManager = identityManager; this.identityManager = identityManager;
this.contactManager = contactManager; this.contactManager = contactManager;
this.blogFactory = blogFactory; this.blogFactory = blogFactory;
this.blogPostFactory = blogPostFactory;
removeHooks = new CopyOnWriteArrayList<RemoveBlogHook>(); removeHooks = new CopyOnWriteArrayList<RemoveBlogHook>();
} }
@@ -160,13 +177,43 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
protected void incomingMessage(Transaction txn, Message m, BdfList list, protected void incomingMessage(Transaction txn, Message m, BdfList list,
BdfDictionary meta) throws DbException, FormatException { BdfDictionary meta) throws DbException, FormatException {
clientHelper.setMessageShared(txn, m, true);
GroupId groupId = m.getGroupId(); GroupId groupId = m.getGroupId();
BlogPostHeader h = getPostHeaderFromMetadata(txn, m.getId(), meta); MessageType type = getMessageType(meta);
BlogPostAddedEvent event =
new BlogPostAddedEvent(groupId, h, false); if (type == POST || type == COMMENT) {
txn.attach(event); BlogPostHeader h =
getPostHeaderFromMetadata(txn, groupId, m.getId(), meta);
// check that original message IDs match
if (type == COMMENT) {
BdfDictionary d = clientHelper
.getMessageMetadataAsDictionary(txn, h.getParentId());
byte[] original1 = d.getRaw(KEY_ORIGINAL_MSG_ID);
byte[] original2 = meta.getRaw(KEY_ORIGINAL_PARENT_MSG_ID);
if (!Arrays.equals(original1, original2)) {
throw new FormatException();
}
}
// share dependencies recursively - TODO remove with #598
share(txn, h);
// broadcast event about new post or comment
BlogPostAddedEvent event =
new BlogPostAddedEvent(groupId, h, false);
txn.attach(event);
} else if (type == WRAPPED_COMMENT) {
// Check that the original message ID in the dependency's metadata
// matches the original parent ID of the wrapped comment
MessageId dependencyId =
new MessageId(meta.getRaw(KEY_PARENT_MSG_ID));
BdfDictionary d = clientHelper
.getMessageMetadataAsDictionary(txn, dependencyId);
byte[] original1 = d.getRaw(KEY_ORIGINAL_MSG_ID);
byte[] original2 = meta.getRaw(KEY_ORIGINAL_PARENT_MSG_ID);
if (!Arrays.equals(original1, original2)) {
throw new FormatException();
}
}
} }
@Override @Override
@@ -247,17 +294,9 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
public void addLocalPost(Transaction txn, BlogPost p) throws DbException { public void addLocalPost(Transaction txn, BlogPost p) throws DbException {
try { try {
BdfDictionary meta = new BdfDictionary(); BdfDictionary meta = new BdfDictionary();
if (p.getTitle() != null) meta.put(KEY_TITLE, p.getTitle()); meta.put(KEY_TYPE, POST.getInt());
meta.put(KEY_TIMESTAMP, p.getMessage().getTimestamp()); meta.put(KEY_TIMESTAMP, p.getMessage().getTimestamp());
meta.put(KEY_AUTHOR, authorToBdfDictionary(p.getAuthor()));
Author a = p.getAuthor();
BdfDictionary authorMeta = new BdfDictionary();
authorMeta.put(KEY_AUTHOR_ID, a.getId());
authorMeta.put(KEY_AUTHOR_NAME, a.getName());
authorMeta.put(KEY_PUBLIC_KEY, a.getPublicKey());
meta.put(KEY_AUTHOR, authorMeta);
meta.put(KEY_CONTENT_TYPE, p.getContentType());
meta.put(KEY_READ, true); meta.put(KEY_READ, true);
clientHelper.addLocalMessage(txn, p.getMessage(), CLIENT_ID, meta, clientHelper.addLocalMessage(txn, p.getMessage(), CLIENT_ID, meta,
true); true);
@@ -265,7 +304,8 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
// broadcast event about new post // broadcast event about new post
GroupId groupId = p.getMessage().getGroupId(); GroupId groupId = p.getMessage().getGroupId();
MessageId postId = p.getMessage().getId(); MessageId postId = p.getMessage().getId();
BlogPostHeader h = getPostHeaderFromMetadata(txn, postId, meta); BlogPostHeader h =
getPostHeaderFromMetadata(txn, groupId, postId, meta);
BlogPostAddedEvent event = BlogPostAddedEvent event =
new BlogPostAddedEvent(groupId, h, true); new BlogPostAddedEvent(groupId, h, true);
txn.attach(event); txn.attach(event);
@@ -274,6 +314,125 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
} }
} }
@Override
public void addLocalComment(LocalAuthor author, GroupId groupId,
@Nullable String comment, BlogPostHeader pOriginalHeader)
throws DbException {
MessageType type = pOriginalHeader.getType();
if (type != POST && type != COMMENT)
throw new IllegalArgumentException("Comment on unknown type!");
Transaction txn = db.startTransaction(false);
try {
// Wrap post that we are commenting on
MessageId parentId = wrapMessage(txn, groupId, pOriginalHeader);
// Get ID of new parent's original message.
// Assumes that pOriginalHeader is a POST or COMMENT
MessageId pOriginalId = pOriginalHeader.getId();
// Create actual comment
Message message = blogPostFactory
.createBlogComment(groupId, author, comment, pOriginalId,
parentId);
BdfDictionary meta = new BdfDictionary();
meta.put(KEY_TYPE, COMMENT.getInt());
if (comment != null) meta.put(KEY_COMMENT, comment);
meta.put(KEY_TIMESTAMP, message.getTimestamp());
meta.put(KEY_ORIGINAL_MSG_ID, message.getId());
meta.put(KEY_ORIGINAL_PARENT_MSG_ID, pOriginalId);
meta.put(KEY_PARENT_MSG_ID, parentId);
meta.put(KEY_AUTHOR, authorToBdfDictionary(author));
// Send comment
clientHelper.addLocalMessage(txn, message, CLIENT_ID, meta, true);
// broadcast event
BlogPostHeader h =
getPostHeaderFromMetadata(txn, groupId, message.getId(),
meta);
BlogPostAddedEvent event = new BlogPostAddedEvent(groupId, h, true);
txn.attach(event);
txn.setComplete();
} catch (FormatException e) {
throw new DbException(e);
} catch (GeneralSecurityException e) {
throw new IllegalArgumentException("Invalid key of author", e);
} finally {
//noinspection ThrowFromFinallyBlock
db.endTransaction(txn);
}
}
private MessageId wrapMessage(Transaction txn, GroupId groupId,
BlogPostHeader pOriginalHeader)
throws DbException, FormatException {
if (groupId.equals(pOriginalHeader.getGroupId())) {
// We are trying to wrap a post that is already in our group.
// This is unnecessary, so just return the post's MessageId
return pOriginalHeader.getId();
}
// Get body of message to be wrapped
BdfList body =
clientHelper.getMessageAsList(txn, pOriginalHeader.getId());
long wTimestamp = pOriginalHeader.getTimestamp();
Message wMessage;
BdfDictionary meta = new BdfDictionary();
MessageType type = pOriginalHeader.getType();
if (type == POST) {
Group wGroup = db.getGroup(txn, pOriginalHeader.getGroupId());
byte[] wDescriptor = wGroup.getDescriptor();
// Wrap post
wMessage = blogPostFactory
.wrapPost(groupId, wDescriptor, wTimestamp, body);
meta.put(KEY_TYPE, WRAPPED_POST.getInt());
} else if (type == COMMENT) {
Group wGroup = db.getGroup(txn, pOriginalHeader.getGroupId());
byte[] wDescriptor = wGroup.getDescriptor();
BlogCommentHeader wComment = (BlogCommentHeader) pOriginalHeader;
MessageId wrappedId =
wrapMessage(txn, groupId, wComment.getParent());
// Wrap comment
wMessage = blogPostFactory
.wrapComment(groupId, wDescriptor, wTimestamp,
body, wrappedId);
meta.put(KEY_TYPE, WRAPPED_COMMENT.getInt());
if(wComment.getComment() != null)
meta.put(KEY_COMMENT, wComment.getComment());
meta.put(KEY_PARENT_MSG_ID, wrappedId);
} else if (type == WRAPPED_POST) {
// Re-wrap wrapped post without adding another wrapping layer
wMessage = blogPostFactory.rewrapWrappedPost(groupId, body);
meta.put(KEY_TYPE, WRAPPED_POST.getInt());
} else if (type == WRAPPED_COMMENT) {
BlogCommentHeader wComment = (BlogCommentHeader) pOriginalHeader;
MessageId wrappedId =
wrapMessage(txn, groupId, wComment.getParent());
// Re-wrap wrapped comment
wMessage = blogPostFactory
.rewrapWrappedComment(groupId, body, wrappedId);
meta.put(KEY_TYPE, WRAPPED_COMMENT.getInt());
if(wComment.getComment() != null)
meta.put(KEY_COMMENT, wComment.getComment());
meta.put(KEY_PARENT_MSG_ID, wrappedId);
} else {
throw new IllegalArgumentException(
"Unknown Message Type: " + type);
}
meta.put(KEY_ORIGINAL_MSG_ID, pOriginalHeader.getId());
meta.put(KEY_AUTHOR, authorToBdfDictionary(pOriginalHeader.getAuthor()));
meta.put(KEY_TIMESTAMP, pOriginalHeader.getTimestamp());
meta.put(KEY_TIME_RECEIVED, pOriginalHeader.getTimeReceived());
// Send wrapped message and store metadata
clientHelper.addLocalMessage(txn, wMessage, CLIENT_ID, meta, true);
return wMessage.getId();
}
@Override @Override
public Blog getBlog(GroupId g) throws DbException { public Blog getBlog(GroupId g) throws DbException {
Blog blog; Blog blog;
@@ -340,12 +499,13 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
} }
@Override @Override
public BlogPostHeader getPostHeader(MessageId m) throws DbException { public BlogPostHeader getPostHeader(GroupId g, MessageId m)
throws DbException {
Transaction txn = db.startTransaction(true); Transaction txn = db.startTransaction(true);
try { try {
BdfDictionary meta = BdfDictionary meta =
clientHelper.getMessageMetadataAsDictionary(txn, m); clientHelper.getMessageMetadataAsDictionary(txn, m);
BlogPostHeader h = getPostHeaderFromMetadata(txn, m, meta); BlogPostHeader h = getPostHeaderFromMetadata(txn, g, m, meta);
txn.setComplete(); txn.setComplete();
return h; return h;
} catch (FormatException e) { } catch (FormatException e) {
@@ -366,25 +526,50 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
} }
} }
// TODO directly return String (#598)
private byte[] getPostBody(BdfList message) throws FormatException { private byte[] getPostBody(BdfList message) throws FormatException {
// content, signature MessageType type = MessageType.valueOf(message.getLong(0).intValue());
// content: parent, contentType, title, body, attachments if (type == POST) {
BdfList content = message.getList(0); // type, body, signature
return content.getRaw(3); return StringUtils.toUtf8(message.getString(1));
} else if (type == WRAPPED_POST) {
// type, p_group descriptor, p_timestamp, p_content, p_signature
return StringUtils.toUtf8(message.getString(3));
} else {
throw new FormatException();
}
} }
@Override @Override
public Collection<BlogPostHeader> getPostHeaders(GroupId g) public Collection<BlogPostHeader> getPostHeaders(GroupId g)
throws DbException { throws DbException {
// Query for posts and comments only
BdfDictionary query1 = BdfDictionary.of(
new BdfEntry(KEY_TYPE, POST.getInt())
);
BdfDictionary query2 = BdfDictionary.of(
new BdfEntry(KEY_TYPE, COMMENT.getInt())
);
// TODO this could be optimized by looking up author status once (#625)
Collection<BlogPostHeader> headers = new ArrayList<BlogPostHeader>();
Transaction txn = db.startTransaction(true); Transaction txn = db.startTransaction(true);
try { try {
Map<MessageId, BdfDictionary> metadata1 =
clientHelper.getMessageMetadataAsDictionary(txn, g, query1);
Map<MessageId, BdfDictionary> metadata2 =
clientHelper.getMessageMetadataAsDictionary(txn, g, query2);
Map<MessageId, BdfDictionary> metadata = Map<MessageId, BdfDictionary> metadata =
clientHelper.getMessageMetadataAsDictionary(txn, g); new HashMap<MessageId, BdfDictionary>(
List<BlogPostHeader> headers = new ArrayList<BlogPostHeader>(); metadata1.size() + metadata2.size());
metadata.putAll(metadata1);
metadata.putAll(metadata2);
for (Entry<MessageId, BdfDictionary> entry : metadata.entrySet()) { for (Entry<MessageId, BdfDictionary> entry : metadata.entrySet()) {
BlogPostHeader h = getPostHeaderFromMetadata(txn, BdfDictionary meta = entry.getValue();
entry.getKey(), entry.getValue()); BlogPostHeader h =
getPostHeaderFromMetadata(txn, g, entry.getKey(), meta);
headers.add(h); headers.add(h);
} }
txn.setComplete(); txn.setComplete();
@@ -419,10 +604,18 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
} }
private BlogPostHeader getPostHeaderFromMetadata(Transaction txn, private BlogPostHeader getPostHeaderFromMetadata(Transaction txn,
MessageId id, BdfDictionary meta) GroupId groupId, MessageId id) throws DbException, FormatException {
BdfDictionary meta =
clientHelper.getMessageMetadataAsDictionary(txn, id);
return getPostHeaderFromMetadata(txn, groupId, id, meta);
}
private BlogPostHeader getPostHeaderFromMetadata(Transaction txn,
GroupId groupId, MessageId id, BdfDictionary meta)
throws DbException, FormatException { throws DbException, FormatException {
String title = meta.getOptionalString(KEY_TITLE); MessageType type = getMessageType(meta);
long timestamp = meta.getLong(KEY_TIMESTAMP); long timestamp = meta.getLong(KEY_TIMESTAMP);
long timeReceived = meta.getLong(KEY_TIME_RECEIVED, timestamp); long timeReceived = meta.getLong(KEY_TIME_RECEIVED, timestamp);
@@ -431,12 +624,35 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
String name = d.getString(KEY_AUTHOR_NAME); String name = d.getString(KEY_AUTHOR_NAME);
byte[] publicKey = d.getRaw(KEY_PUBLIC_KEY); byte[] publicKey = d.getRaw(KEY_PUBLIC_KEY);
Author author = new Author(authorId, name, publicKey); Author author = new Author(authorId, name, publicKey);
Status authorStatus; Status authorStatus = identityManager.getAuthorStatus(txn, authorId);
authorStatus = identityManager.getAuthorStatus(txn, authorId);
String contentType = meta.getString(KEY_CONTENT_TYPE); boolean read = meta.getBoolean(KEY_READ, false);
boolean read = meta.getBoolean(KEY_READ);
return new BlogPostHeader(title, id, timestamp, timeReceived, author, if (type == COMMENT || type == WRAPPED_COMMENT) {
authorStatus, contentType, read); String comment = meta.getOptionalString(KEY_COMMENT);
MessageId parentId = new MessageId(meta.getRaw(KEY_PARENT_MSG_ID));
BlogPostHeader parent =
getPostHeaderFromMetadata(txn, groupId, parentId);
return new BlogCommentHeader(type, groupId, comment, parent, id,
timestamp, timeReceived, author, authorStatus, read);
} else {
return new BlogPostHeader(type, groupId, id, timestamp,
timeReceived, author, authorStatus, read);
}
}
private MessageType getMessageType(BdfDictionary d) throws FormatException {
Long longType = d.getLong(KEY_TYPE);
return MessageType.valueOf(longType.intValue());
}
// TODO remove when implementing #589
@Deprecated
private void share(Transaction txn, BlogPostHeader h) throws DbException {
clientHelper.setMessageShared(txn, h.getId(), true);
if (h instanceof BlogCommentHeader) {
BlogPostHeader h2 = ((BlogCommentHeader) h).getParent();
share(txn, h2);
}
} }
} }

View File

@@ -3,17 +3,14 @@ package org.briarproject.blogs;
import org.briarproject.api.FormatException; import org.briarproject.api.FormatException;
import org.briarproject.api.blogs.BlogPost; import org.briarproject.api.blogs.BlogPost;
import org.briarproject.api.blogs.BlogPostFactory; import org.briarproject.api.blogs.BlogPostFactory;
import org.briarproject.api.blogs.MessageType;
import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.KeyParser;
import org.briarproject.api.crypto.PrivateKey;
import org.briarproject.api.crypto.Signature;
import org.briarproject.api.data.BdfList; import org.briarproject.api.data.BdfList;
import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.Message; import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageId;
import org.briarproject.util.StringUtils; import org.briarproject.api.system.Clock;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -22,52 +19,143 @@ import java.security.GeneralSecurityException;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH; import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH;
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_TITLE_LENGTH; import static org.briarproject.api.blogs.MessageType.COMMENT;
import static org.briarproject.api.blogs.BlogConstants.MAX_CONTENT_TYPE_LENGTH; import static org.briarproject.api.blogs.MessageType.POST;
import static org.briarproject.api.blogs.MessageType.WRAPPED_COMMENT;
import static org.briarproject.api.blogs.MessageType.WRAPPED_POST;
class BlogPostFactoryImpl implements BlogPostFactory { class BlogPostFactoryImpl implements BlogPostFactory {
private final CryptoComponent crypto;
private final ClientHelper clientHelper; private final ClientHelper clientHelper;
private final Clock clock;
@Inject @Inject
BlogPostFactoryImpl(CryptoComponent crypto, ClientHelper clientHelper) { BlogPostFactoryImpl(ClientHelper clientHelper, Clock clock) {
this.crypto = crypto;
this.clientHelper = clientHelper; this.clientHelper = clientHelper;
this.clock = clock;
} }
@Override @Override
public BlogPost createBlogPost(@NotNull GroupId groupId, public BlogPost createBlogPost(@NotNull GroupId groupId, long timestamp,
@Nullable String title, long timestamp, @Nullable MessageId parent, @NotNull LocalAuthor author,
@Nullable MessageId parent, @NotNull LocalAuthor author, @NotNull String body)
@NotNull String contentType, @NotNull byte[] body)
throws FormatException, GeneralSecurityException { throws FormatException, GeneralSecurityException {
// Validate the arguments // Validate the arguments
if (title != null && if (body.length() > MAX_BLOG_POST_BODY_LENGTH)
StringUtils.toUtf8(title).length > MAX_BLOG_POST_TITLE_LENGTH)
throw new IllegalArgumentException();
if (StringUtils.toUtf8(contentType).length > MAX_CONTENT_TYPE_LENGTH)
throw new IllegalArgumentException();
if (body.length > MAX_BLOG_POST_BODY_LENGTH)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
// Serialise the data to be signed // Serialise the data to be signed
BdfList content = BdfList.of(parent, contentType, title, body, null); BdfList signed = BdfList.of(groupId, timestamp, body);
BdfList signed = BdfList.of(groupId, timestamp, content);
// Generate the signature // Generate the signature
Signature signature = crypto.getSignature(); byte[] sig = clientHelper.sign(signed, author.getPrivateKey());
KeyParser keyParser = crypto.getSignatureKeyParser();
PrivateKey privateKey =
keyParser.parsePrivateKey(author.getPrivateKey());
signature.initSign(privateKey);
signature.update(clientHelper.toByteArray(signed));
byte[] sig = signature.sign();
// Serialise the signed message // Serialise the signed message
BdfList message = BdfList.of(content, sig); BdfList message = BdfList.of(POST.getInt(), body, sig);
Message m = clientHelper.createMessage(groupId, timestamp, message); Message m = clientHelper.createMessage(groupId, timestamp, message);
return new BlogPost(title, m, parent, author, contentType); return new BlogPost(m, parent, author);
}
@Override
public Message createBlogComment(GroupId groupId, LocalAuthor author,
@Nullable String comment, MessageId pOriginalId, MessageId parentId)
throws FormatException, GeneralSecurityException {
long timestamp = clock.currentTimeMillis();
// Generate the signature
BdfList signed =
BdfList.of(groupId, timestamp, comment, pOriginalId, parentId);
byte[] sig = clientHelper.sign(signed, author.getPrivateKey());
// Serialise the signed message
BdfList message =
BdfList.of(COMMENT.getInt(), comment, pOriginalId, parentId,
sig);
return clientHelper.createMessage(groupId, timestamp, message);
}
@Override
public Message wrapPost(GroupId groupId, byte[] descriptor,
long timestamp, BdfList body)
throws FormatException {
if (getType(body) != POST)
throw new IllegalArgumentException("Needs to wrap a POST");
// Serialise the message
String content = body.getString(1);
byte[] signature = body.getRaw(2);
BdfList message =
BdfList.of(WRAPPED_POST.getInt(), descriptor, timestamp,
content, signature);
return clientHelper
.createMessage(groupId, clock.currentTimeMillis(), message);
}
@Override
public Message rewrapWrappedPost(GroupId groupId, BdfList body)
throws FormatException {
if (getType(body) != WRAPPED_POST)
throw new IllegalArgumentException("Needs to wrap a WRAPPED_POST");
// Serialise the message
byte[] descriptor = body.getRaw(1);
long timestamp = body.getLong(2);
String content = body.getString(3);
byte[] signature = body.getRaw(4);
BdfList message =
BdfList.of(WRAPPED_POST.getInt(), descriptor, timestamp,
content, signature);
return clientHelper
.createMessage(groupId, clock.currentTimeMillis(), message);
}
@Override
public Message wrapComment(GroupId groupId, byte[] descriptor,
long timestamp, BdfList body, MessageId parentId)
throws FormatException {
if (getType(body) != COMMENT)
throw new IllegalArgumentException("Needs to wrap a COMMENT");
// Serialise the message
String comment = body.getOptionalString(1);
byte[] pOriginalId = body.getRaw(2);
byte[] oldParentId = body.getRaw(3);
byte[] signature = body.getRaw(4);
BdfList message =
BdfList.of(WRAPPED_COMMENT.getInt(), descriptor, timestamp,
comment, pOriginalId, oldParentId, signature, parentId);
return clientHelper
.createMessage(groupId, clock.currentTimeMillis(), message);
}
@Override
public Message rewrapWrappedComment(GroupId groupId, BdfList body,
MessageId parentId) throws FormatException {
if (getType(body) != WRAPPED_COMMENT)
throw new IllegalArgumentException(
"Needs to wrap a WRAPPED_COMMENT");
// Serialise the message
byte[] descriptor = body.getRaw(1);
long timestamp = body.getLong(2);
String comment = body.getOptionalString(3);
byte[] pOriginalId = body.getRaw(4);
byte[] oldParentId = body.getRaw(5);
byte[] signature = body.getRaw(6);
BdfList message =
BdfList.of(WRAPPED_COMMENT.getInt(), descriptor, timestamp,
comment, pOriginalId, oldParentId, signature, parentId);
return clientHelper
.createMessage(groupId, clock.currentTimeMillis(), message);
}
private MessageType getType(BdfList body) throws FormatException {
return MessageType.valueOf(body.getLong(0).intValue());
} }
} }

View File

@@ -1,7 +1,6 @@
package org.briarproject.blogs; package org.briarproject.blogs;
import org.briarproject.api.FormatException; import org.briarproject.api.FormatException;
import org.briarproject.api.UniqueId;
import org.briarproject.api.blogs.Blog; import org.briarproject.api.blogs.Blog;
import org.briarproject.api.blogs.BlogFactory; import org.briarproject.api.blogs.BlogFactory;
import org.briarproject.api.blogs.MessageType; import org.briarproject.api.blogs.MessageType;
@@ -33,18 +32,17 @@ import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR;
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_ID; import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_ID;
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_NAME; import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_NAME;
import static org.briarproject.api.blogs.BlogConstants.KEY_COMMENT; import static org.briarproject.api.blogs.BlogConstants.KEY_COMMENT;
import static org.briarproject.api.blogs.BlogConstants.KEY_CONTENT_TYPE; import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_PARENT_MSG_ID;
import static org.briarproject.api.blogs.BlogConstants.KEY_CURRENT_MSG_ID; import static org.briarproject.api.blogs.BlogConstants.KEY_PARENT_MSG_ID;
import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_MSG_ID; import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_MSG_ID;
import static org.briarproject.api.blogs.BlogConstants.KEY_PUBLIC_KEY; import static org.briarproject.api.blogs.BlogConstants.KEY_PUBLIC_KEY;
import static org.briarproject.api.blogs.BlogConstants.KEY_READ; import static org.briarproject.api.blogs.BlogConstants.KEY_READ;
import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP; import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP;
import static org.briarproject.api.blogs.BlogConstants.KEY_TIME_RECEIVED; import static org.briarproject.api.blogs.BlogConstants.KEY_TIME_RECEIVED;
import static org.briarproject.api.blogs.BlogConstants.KEY_TITLE;
import static org.briarproject.api.blogs.BlogConstants.KEY_TYPE; import static org.briarproject.api.blogs.BlogConstants.KEY_TYPE;
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH; import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH;
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_TITLE_LENGTH; import static org.briarproject.api.blogs.MessageType.COMMENT;
import static org.briarproject.api.blogs.BlogConstants.MAX_CONTENT_TYPE_LENGTH; import static org.briarproject.api.blogs.MessageType.POST;
import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH; import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
class BlogPostValidator extends BdfMessageValidator { class BlogPostValidator extends BdfMessageValidator {
@@ -72,13 +70,6 @@ class BlogPostValidator extends BdfMessageValidator {
BdfMessageContext c; BdfMessageContext c;
// TODO Remove! For Temporary Backwards Compatibility only!
if (body.get(0) instanceof BdfList) {
c = validatePost(m, g, body);
addMessageMetadata(c, m.getTimestamp());
return c;
}
int type = body.getLong(0).intValue(); int type = body.getLong(0).intValue();
body.removeElementAt(0); body.removeElementAt(0);
switch (MessageType.valueOf(type)) { switch (MessageType.valueOf(type)) {
@@ -108,83 +99,64 @@ class BlogPostValidator extends BdfMessageValidator {
// Content, Signature // Content, Signature
checkSize(body, 2); checkSize(body, 2);
BdfList content = body.getList(0); String postBody = body.getString(0);
// Content: content type, title (optional), post body,
// attachments (optional)
checkSize(content, 5);
// Parent ID is optional
// TODO remove when breaking backwards compatibility
byte[] parent = content.getOptionalRaw(0);
checkLength(parent, UniqueId.LENGTH);
// Content type
String contentType = content.getString(1);
checkLength(contentType, 0, MAX_CONTENT_TYPE_LENGTH);
if (!contentType.equals("text/plain"))
throw new InvalidMessageException("Invalid content type");
// Blog post title is optional
String title = content.getOptionalString(2);
checkLength(contentType, 0, MAX_BLOG_POST_TITLE_LENGTH);
// Blog post body
byte[] postBody = content.getRaw(3);
checkLength(postBody, 0, MAX_BLOG_POST_BODY_LENGTH); checkLength(postBody, 0, MAX_BLOG_POST_BODY_LENGTH);
// Attachments
content.getOptionalDictionary(4);
// Verify Signature // Verify Signature
byte[] sig = body.getRaw(1); byte[] sig = body.getRaw(1);
checkLength(sig, 1, MAX_SIGNATURE_LENGTH); checkLength(sig, 1, MAX_SIGNATURE_LENGTH);
BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), content); BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), postBody);
Blog b = blogFactory.parseBlog(g, ""); // description doesn't matter Blog b = blogFactory.parseBlog(g, ""); // description doesn't matter
Author a = b.getAuthor(); Author a = b.getAuthor();
verifySignature(sig, a.getPublicKey(), signed); verifySignature(sig, a.getPublicKey(), signed);
// Return the metadata and dependencies // Return the metadata and dependencies
BdfDictionary meta = new BdfDictionary(); BdfDictionary meta = new BdfDictionary();
if (title != null) meta.put(KEY_TITLE, title); meta.put(KEY_ORIGINAL_MSG_ID, m.getId());
meta.put(KEY_AUTHOR, authorToBdfDictionary(a)); meta.put(KEY_AUTHOR, authorToBdfDictionary(a));
meta.put(KEY_CONTENT_TYPE, contentType);
return new BdfMessageContext(meta, null); return new BdfMessageContext(meta, null);
} }
private BdfMessageContext validateComment(Message m, Group g, BdfList body) private BdfMessageContext validateComment(Message m, Group g, BdfList body)
throws InvalidMessageException, FormatException { throws InvalidMessageException, FormatException {
// comment, parent_original_id, signature, parent_current_id // comment, parent_original_id, parent_id, signature
checkSize(body, 4); checkSize(body, 4);
// Comment // Comment
String comment = body.getOptionalString(0); String comment = body.getOptionalString(0);
checkLength(comment, 0, MAX_BLOG_POST_BODY_LENGTH); checkLength(comment, 1, MAX_BLOG_POST_BODY_LENGTH);
// parent_original_id // parent_original_id
// The ID of a post or comment in this group or another group // The ID of a post or comment in this group or another group
byte[] originalIdBytes = body.getRaw(1); byte[] pOriginalIdBytes = body.getRaw(1);
checkLength(originalIdBytes, MessageId.LENGTH); checkLength(pOriginalIdBytes, MessageId.LENGTH);
MessageId originalId = new MessageId(originalIdBytes); MessageId pOriginalId = new MessageId(pOriginalIdBytes);
// parent_id
// The ID of a post, comment, wrapped post or wrapped comment in this
// group, which had the ID parent_original_id in the group
// where it was originally posted
byte[] currentIdBytes = body.getRaw(2);
checkLength(currentIdBytes, MessageId.LENGTH);
MessageId currentId = new MessageId(currentIdBytes);
// Signature // Signature
byte[] sig = body.getRaw(2); byte[] sig = body.getRaw(3);
checkLength(sig, 0, MAX_SIGNATURE_LENGTH); checkLength(sig, 0, MAX_SIGNATURE_LENGTH);
BdfList signed = BdfList signed =
BdfList.of(g.getId(), m.getTimestamp(), comment, originalId); BdfList.of(g.getId(), m.getTimestamp(), comment, pOriginalId,
currentId);
Blog b = blogFactory.parseBlog(g, ""); // description doesn't matter Blog b = blogFactory.parseBlog(g, ""); // description doesn't matter
Author a = b.getAuthor(); Author a = b.getAuthor();
verifySignature(sig, a.getPublicKey(), signed); verifySignature(sig, a.getPublicKey(), signed);
// parent_current_id
// The ID of a post, comment, wrapped post or wrapped comment in this
// group, which had the ID parent_original_id in the group
// where it was originally posted
byte[] currentIdBytes = body.getRaw(3);
checkLength(currentIdBytes, MessageId.LENGTH);
MessageId currentId = new MessageId(currentIdBytes);
// Return the metadata and dependencies // Return the metadata and dependencies
BdfDictionary meta = new BdfDictionary(); BdfDictionary meta = new BdfDictionary();
if (comment != null) meta.put(KEY_COMMENT, comment); if (comment != null) meta.put(KEY_COMMENT, comment);
meta.put(KEY_ORIGINAL_MSG_ID, originalId); meta.put(KEY_ORIGINAL_MSG_ID, m.getId());
meta.put(KEY_CURRENT_MSG_ID, currentId); meta.put(KEY_ORIGINAL_PARENT_MSG_ID, pOriginalId);
meta.put(KEY_PARENT_MSG_ID, currentId);
meta.put(KEY_AUTHOR, authorToBdfDictionary(a)); meta.put(KEY_AUTHOR, authorToBdfDictionary(a));
Collection<MessageId> dependencies = Collections.singleton(currentId); Collection<MessageId> dependencies = Collections.singleton(currentId);
return new BdfMessageContext(meta, dependencies); return new BdfMessageContext(meta, dependencies);
@@ -193,7 +165,7 @@ class BlogPostValidator extends BdfMessageValidator {
private BdfMessageContext validateWrappedPost(Message m, Group g, private BdfMessageContext validateWrappedPost(Message m, Group g,
BdfList body) throws InvalidMessageException, FormatException { BdfList body) throws InvalidMessageException, FormatException {
// group descriptor, timestamp, content, signature // p_group descriptor, p_timestamp, p_content, p_signature
checkSize(body, 4); checkSize(body, 4);
// Group Descriptor // Group Descriptor
@@ -201,9 +173,10 @@ class BlogPostValidator extends BdfMessageValidator {
// Timestamp of Wrapped Post // Timestamp of Wrapped Post
long wTimestamp = body.getLong(1); long wTimestamp = body.getLong(1);
if (wTimestamp < 0) throw new FormatException();
// Content of Wrapped Post // Content of Wrapped Post
BdfList content = body.getList(2); String content = body.getString(2);
// Signature of Wrapped Post // Signature of Wrapped Post
byte[] signature = body.getRaw(3); byte[] signature = body.getRaw(3);
@@ -212,71 +185,81 @@ class BlogPostValidator extends BdfMessageValidator {
// Get and Validate the Wrapped Message // Get and Validate the Wrapped Message
Group wGroup = groupFactory Group wGroup = groupFactory
.createGroup(BlogManagerImpl.CLIENT_ID, descriptor); .createGroup(BlogManagerImpl.CLIENT_ID, descriptor);
BdfList wBodyList = BdfList.of(content, signature); BdfList wBodyList = BdfList.of(POST.getInt(), content, signature);
byte[] wBody = clientHelper.toByteArray(wBodyList); byte[] wBody = clientHelper.toByteArray(wBodyList);
Message wMessage = Message wMessage =
messageFactory.createMessage(wGroup.getId(), wTimestamp, wBody); messageFactory.createMessage(wGroup.getId(), wTimestamp, wBody);
wBodyList.remove(0);
BdfMessageContext c = validatePost(wMessage, wGroup, wBodyList); BdfMessageContext c = validatePost(wMessage, wGroup, wBodyList);
// Return the metadata and dependencies // Return the metadata and dependencies
BdfDictionary meta = new BdfDictionary(); BdfDictionary meta = new BdfDictionary();
meta.put(KEY_ORIGINAL_MSG_ID, wMessage.getId());
meta.put(KEY_TIMESTAMP, wTimestamp); meta.put(KEY_TIMESTAMP, wTimestamp);
meta.put(KEY_AUTHOR, c.getDictionary().getDictionary(KEY_AUTHOR)); meta.put(KEY_AUTHOR, c.getDictionary().getDictionary(KEY_AUTHOR));
meta.put(KEY_CONTENT_TYPE,
c.getDictionary().getString(KEY_CONTENT_TYPE));
return new BdfMessageContext(meta, null); return new BdfMessageContext(meta, null);
} }
private BdfMessageContext validateWrappedComment(Message m, Group g, private BdfMessageContext validateWrappedComment(Message m, Group g,
BdfList body) throws InvalidMessageException, FormatException { BdfList body) throws InvalidMessageException, FormatException {
// group descriptor, timestamp, comment, parent_original_id, signature, // c_group descriptor, c_timestamp, c_comment, c_parent_original_id,
// parent_current_id // c_parent_id, c_signature, parent_id
checkSize(body, 6); checkSize(body, 7);
// Group Descriptor // Group Descriptor
byte[] descriptor = body.getRaw(0); byte[] descriptor = body.getRaw(0);
// Timestamp of Wrapped Comment // Timestamp of Wrapped Comment
long wTimestamp = body.getLong(1); long wTimestamp = body.getLong(1);
if (wTimestamp < 0) throw new FormatException();
// Body of Wrapped Comment // Body of Wrapped Comment
String comment = body.getOptionalString(2); String comment = body.getOptionalString(2);
checkLength(comment, 1, MAX_BLOG_POST_BODY_LENGTH);
// parent_original_id // c_parent_original_id
// Taken from the original comment // Taken from the original comment
byte[] originalIdBytes = body.getRaw(3); byte[] pOriginalIdBytes = body.getRaw(3);
checkLength(originalIdBytes, MessageId.LENGTH); checkLength(pOriginalIdBytes, MessageId.LENGTH);
MessageId originalId = new MessageId(originalIdBytes); MessageId pOriginalId = new MessageId(pOriginalIdBytes);
// signature // c_parent_id
// Taken from the original comment // Taken from the original comment
byte[] signature = body.getRaw(4); byte[] oldIdBytes = body.getRaw(4);
checkLength(oldIdBytes, MessageId.LENGTH);
MessageId oldId = new MessageId(oldIdBytes);
// c_signature
// Taken from the original comment
byte[] signature = body.getRaw(5);
checkLength(signature, 1, MAX_SIGNATURE_LENGTH); checkLength(signature, 1, MAX_SIGNATURE_LENGTH);
// parent_current_id // parent_id
// The ID of a post, comment, wrapped post or wrapped comment in this // The ID of a post, comment, wrapped post or wrapped comment in this
// group, which had the ID parent_original_id in the group // group, which had the ID c_parent_original_id in the group
// where it was originally posted // where it was originally posted
byte[] currentIdBytes = body.getRaw(5); byte[] parentIdBytes = body.getRaw(6);
checkLength(currentIdBytes, MessageId.LENGTH); checkLength(parentIdBytes, MessageId.LENGTH);
MessageId currentId = new MessageId(currentIdBytes); MessageId parentId = new MessageId(parentIdBytes);
// Get and Validate the Wrapped Comment // Get and Validate the Wrapped Comment
Group wGroup = groupFactory Group wGroup = groupFactory
.createGroup(BlogManagerImpl.CLIENT_ID, descriptor); .createGroup(BlogManagerImpl.CLIENT_ID, descriptor);
BdfList wBodyList = BdfList.of(comment, originalId, signature, BdfList wBodyList = BdfList.of(COMMENT.getInt(), comment, pOriginalId,
currentId); oldId, signature);
byte[] wBody = clientHelper.toByteArray(wBodyList); byte[] wBody = clientHelper.toByteArray(wBodyList);
Message wMessage = Message wMessage =
messageFactory.createMessage(wGroup.getId(), wTimestamp, wBody); messageFactory.createMessage(wGroup.getId(), wTimestamp, wBody);
wBodyList.remove(0);
BdfMessageContext c = validateComment(wMessage, wGroup, wBodyList); BdfMessageContext c = validateComment(wMessage, wGroup, wBodyList);
// Return the metadata and dependencies // Return the metadata and dependencies
Collection<MessageId> dependencies = Collections.singleton(currentId); Collection<MessageId> dependencies = Collections.singleton(parentId);
BdfDictionary meta = new BdfDictionary(); BdfDictionary meta = new BdfDictionary();
meta.put(KEY_ORIGINAL_MSG_ID, wMessage.getId()); meta.put(KEY_ORIGINAL_MSG_ID, wMessage.getId());
meta.put(KEY_CURRENT_MSG_ID, currentId); meta.put(KEY_ORIGINAL_PARENT_MSG_ID, pOriginalId);
meta.put(KEY_PARENT_MSG_ID, parentId);
meta.put(KEY_TIMESTAMP, wTimestamp); meta.put(KEY_TIMESTAMP, wTimestamp);
if (comment != null) meta.put(KEY_COMMENT, comment); if (comment != null) meta.put(KEY_COMMENT, comment);
meta.put(KEY_AUTHOR, c.getDictionary().getDictionary(KEY_AUTHOR)); meta.put(KEY_AUTHOR, c.getDictionary().getDictionary(KEY_AUTHOR));

View File

@@ -50,9 +50,9 @@ public class BlogsModule {
} }
@Provides @Provides
BlogPostFactory provideBlogPostFactory(CryptoComponent crypto, BlogPostFactory provideBlogPostFactory(ClientHelper clientHelper,
ClientHelper clientHelper) { Clock clock) {
return new BlogPostFactoryImpl(crypto, clientHelper); return new BlogPostFactoryImpl(clientHelper, clock);
} }
@Provides @Provides

View File

@@ -2,6 +2,10 @@ package org.briarproject.clients;
import org.briarproject.api.FormatException; import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.KeyParser;
import org.briarproject.api.crypto.PrivateKey;
import org.briarproject.api.crypto.Signature;
import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfList; import org.briarproject.api.data.BdfList;
import org.briarproject.api.data.BdfReader; import org.briarproject.api.data.BdfReader;
@@ -23,6 +27,7 @@ import org.briarproject.api.sync.MessageId;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -40,18 +45,20 @@ class ClientHelperImpl implements ClientHelper {
private final BdfWriterFactory bdfWriterFactory; private final BdfWriterFactory bdfWriterFactory;
private final MetadataParser metadataParser; private final MetadataParser metadataParser;
private final MetadataEncoder metadataEncoder; private final MetadataEncoder metadataEncoder;
private final CryptoComponent cryptoComponent;
@Inject @Inject
ClientHelperImpl(DatabaseComponent db, MessageFactory messageFactory, ClientHelperImpl(DatabaseComponent db, MessageFactory messageFactory,
BdfReaderFactory bdfReaderFactory, BdfReaderFactory bdfReaderFactory,
BdfWriterFactory bdfWriterFactory, MetadataParser metadataParser, BdfWriterFactory bdfWriterFactory, MetadataParser metadataParser,
MetadataEncoder metadataEncoder) { MetadataEncoder metadataEncoder, CryptoComponent cryptoComponent) {
this.db = db; this.db = db;
this.messageFactory = messageFactory; this.messageFactory = messageFactory;
this.bdfReaderFactory = bdfReaderFactory; this.bdfReaderFactory = bdfReaderFactory;
this.bdfWriterFactory = bdfWriterFactory; this.bdfWriterFactory = bdfWriterFactory;
this.metadataParser = metadataParser; this.metadataParser = metadataParser;
this.metadataEncoder = metadataEncoder; this.metadataEncoder = metadataEncoder;
this.cryptoComponent = cryptoComponent;
} }
@Override @Override
@@ -240,7 +247,7 @@ class ClientHelperImpl implements ClientHelper {
} }
@Override @Override
public void setMessageShared(Transaction txn, Message m, boolean shared) public void setMessageShared(Transaction txn, MessageId m, boolean shared)
throws DbException { throws DbException {
db.setMessageShared(txn, m, shared); db.setMessageShared(txn, m, shared);
} }
@@ -303,4 +310,21 @@ class ClientHelperImpl implements ClientHelper {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
@Override
public BdfList toList(byte[] b) throws FormatException {
return toList(b, 0, b.length);
}
@Override
public byte[] sign(BdfList toSign, byte[] privateKey)
throws FormatException, GeneralSecurityException {
Signature signature = cryptoComponent.getSignature();
KeyParser keyParser = cryptoComponent.getSignatureKeyParser();
PrivateKey key =
keyParser.parsePrivateKey(privateKey);
signature.initSign(key);
signature.update(toByteArray(toSign));
return signature.sign();
}
} }

View File

@@ -26,9 +26,10 @@ public class ClientsModule {
ClientHelper provideClientHelper(DatabaseComponent db, ClientHelper provideClientHelper(DatabaseComponent db,
MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory, MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory,
BdfWriterFactory bdfWriterFactory, MetadataParser metadataParser, BdfWriterFactory bdfWriterFactory, MetadataParser metadataParser,
MetadataEncoder metadataEncoder) { MetadataEncoder metadataEncoder, CryptoComponent cryptoComponent) {
return new ClientHelperImpl(db, messageFactory, bdfReaderFactory, return new ClientHelperImpl(db, messageFactory, bdfReaderFactory,
bdfWriterFactory, metadataParser, metadataEncoder); bdfWriterFactory, metadataParser, metadataEncoder,
cryptoComponent);
} }
@Provides @Provides

View File

@@ -199,7 +199,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
transaction.attach(new MessageAddedEvent(m, null)); transaction.attach(new MessageAddedEvent(m, null));
transaction.attach(new MessageStateChangedEvent(m, c, true, transaction.attach(new MessageStateChangedEvent(m, c, true,
DELIVERED)); DELIVERED));
if (shared) transaction.attach(new MessageSharedEvent(m)); if (shared) transaction.attach(new MessageSharedEvent(m.getId()));
} }
db.mergeMessageMetadata(txn, m.getId(), meta); db.mergeMessageMetadata(txn, m.getId(), meta);
} }
@@ -704,13 +704,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
transaction.attach(new ContactStatusChangedEvent(c, active)); transaction.attach(new ContactStatusChangedEvent(c, active));
} }
public void setMessageShared(Transaction transaction, Message m, public void setMessageShared(Transaction transaction, MessageId m,
boolean shared) throws DbException { boolean shared) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsMessage(txn, m.getId())) if (!db.containsMessage(txn, m))
throw new NoSuchMessageException(); throw new NoSuchMessageException();
db.setMessageShared(txn, m.getId(), shared); db.setMessageShared(txn, m, shared);
if (shared) transaction.attach(new MessageSharedEvent(m)); if (shared) transaction.attach(new MessageSharedEvent(m));
} }

View File

@@ -23,10 +23,10 @@ import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction; import org.briarproject.api.db.Transaction;
import org.briarproject.api.feed.Feed; import org.briarproject.api.feed.Feed;
import org.briarproject.api.feed.FeedManager; import org.briarproject.api.feed.FeedManager;
import org.briarproject.api.lifecycle.IoExecutor;
import org.briarproject.api.identity.AuthorId; import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.identity.IdentityManager; import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.lifecycle.IoExecutor;
import org.briarproject.api.lifecycle.Service; import org.briarproject.api.lifecycle.Service;
import org.briarproject.api.lifecycle.ServiceException; import org.briarproject.api.lifecycle.ServiceException;
import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.ClientId;
@@ -41,7 +41,6 @@ import java.net.InetSocketAddress;
import java.net.Proxy; import java.net.Proxy;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date; import java.util.Date;
@@ -440,15 +439,14 @@ class FeedManagerImpl implements FeedManager, Service, Client {
// get other information for post // get other information for post
GroupId groupId = feed.getBlogId(); GroupId groupId = feed.getBlogId();
long time = clock.currentTimeMillis(); long time = clock.currentTimeMillis();
byte[] body = getPostBody(b.toString()); String body = getPostBody(b.toString());
try { try {
// create and store post // create and store post
Blog blog = blogManager.getBlog(txn, groupId); Blog blog = blogManager.getBlog(txn, groupId);
AuthorId authorId = blog.getAuthor().getId(); AuthorId authorId = blog.getAuthor().getId();
LocalAuthor author = identityManager.getLocalAuthor(txn, authorId); LocalAuthor author = identityManager.getLocalAuthor(txn, authorId);
BlogPost post = blogPostFactory BlogPost post = blogPostFactory
.createBlogPost(groupId, null, time, null, author, .createBlogPost(groupId, time, null, author, body);
"text/plain", body);
blogManager.addLocalPost(txn, post); blogManager.addLocalPost(txn, post);
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
@@ -472,10 +470,9 @@ class FeedManagerImpl implements FeedManager, Service, Client {
return StringUtils.trim(s.replaceAll("<(?s).*?>", "")); return StringUtils.trim(s.replaceAll("<(?s).*?>", ""));
} }
private byte[] getPostBody(String text) { private String getPostBody(String text) {
byte[] body = StringUtils.toUtf8(text); if (text.length() <= MAX_BLOG_POST_BODY_LENGTH) return text;
if (body.length <= MAX_BLOG_POST_BODY_LENGTH) return body; else return text.substring(0, MAX_BLOG_POST_BODY_LENGTH);
else return Arrays.copyOfRange(body, 0, MAX_BLOG_POST_BODY_LENGTH - 1);
} }
/** /**

View File

@@ -38,7 +38,6 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.api.forum.ForumConstants.KEY_AUTHOR; import static org.briarproject.api.forum.ForumConstants.KEY_AUTHOR;
import static org.briarproject.api.forum.ForumConstants.KEY_CONTENT_TYPE;
import static org.briarproject.api.forum.ForumConstants.KEY_ID; import static org.briarproject.api.forum.ForumConstants.KEY_ID;
import static org.briarproject.api.forum.ForumConstants.KEY_LOCAL; import static org.briarproject.api.forum.ForumConstants.KEY_LOCAL;
import static org.briarproject.api.forum.ForumConstants.KEY_NAME; import static org.briarproject.api.forum.ForumConstants.KEY_NAME;
@@ -78,7 +77,7 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
protected void incomingMessage(Transaction txn, Message m, BdfList body, protected void incomingMessage(Transaction txn, Message m, BdfList body,
BdfDictionary meta) throws DbException, FormatException { BdfDictionary meta) throws DbException, FormatException {
clientHelper.setMessageShared(txn, m, true); clientHelper.setMessageShared(txn, m.getId(), true);
ForumPostHeader post = getForumPostHeader(txn, m.getId(), meta); ForumPostHeader post = getForumPostHeader(txn, m.getId(), meta);
ForumPostReceivedEvent event = ForumPostReceivedEvent event =
@@ -132,7 +131,6 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
authorMeta.put(KEY_PUBLIC_NAME, a.getPublicKey()); authorMeta.put(KEY_PUBLIC_NAME, a.getPublicKey());
meta.put(KEY_AUTHOR, authorMeta); meta.put(KEY_AUTHOR, authorMeta);
} }
meta.put(KEY_CONTENT_TYPE, p.getContentType());
meta.put(KEY_LOCAL, true); meta.put(KEY_LOCAL, true);
meta.put(KEY_READ, true); meta.put(KEY_READ, true);
clientHelper.addLocalMessage(p.getMessage(), CLIENT_ID, meta, true); clientHelper.addLocalMessage(p.getMessage(), CLIENT_ID, meta, true);
@@ -236,7 +234,7 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
private Forum parseForum(Group g) throws FormatException { private Forum parseForum(Group g) throws FormatException {
byte[] descriptor = g.getDescriptor(); byte[] descriptor = g.getDescriptor();
// Name, salt // Name, salt
BdfList forum = clientHelper.toList(descriptor, 0, descriptor.length); BdfList forum = clientHelper.toList(descriptor);
return new Forum(g, forum.getString(0), forum.getRaw(1)); return new Forum(g, forum.getString(0), forum.getRaw(1));
} }
@@ -262,11 +260,10 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
identityManager.getAuthorStatus(txn, author.getId()); identityManager.getAuthorStatus(txn, author.getId());
} }
} }
String contentType = meta.getString(KEY_CONTENT_TYPE);
boolean read = meta.getBoolean(KEY_READ); boolean read = meta.getBoolean(KEY_READ);
return new ForumPostHeader(id, parentId, timestamp, author, return new ForumPostHeader(id, parentId, timestamp, author,
authorStatus, contentType, read); authorStatus, read);
} }
private ForumPostHeader getForumPostHeader(MessageId id, private ForumPostHeader getForumPostHeader(MessageId id,

View File

@@ -44,7 +44,7 @@ class ForumPostFactoryImpl implements ForumPostFactory {
// Serialise the message // Serialise the message
BdfList message = BdfList.of(parent, null, contentType, body, null); BdfList message = BdfList.of(parent, null, contentType, body, null);
Message m = clientHelper.createMessage(groupId, timestamp, message); Message m = clientHelper.createMessage(groupId, timestamp, message);
return new ForumPost(m, parent, null, contentType); return new ForumPost(m, parent, null);
} }
@Override @Override
@@ -71,6 +71,6 @@ class ForumPostFactoryImpl implements ForumPostFactory {
BdfList message = BdfList.of(parent, authorList, contentType, body, BdfList message = BdfList.of(parent, authorList, contentType, body,
sig); sig);
Message m = clientHelper.createMessage(groupId, timestamp, message); Message m = clientHelper.createMessage(groupId, timestamp, message);
return new ForumPost(m, parent, author, contentType); return new ForumPost(m, parent, author);
} }
} }

View File

@@ -5,6 +5,7 @@ import org.briarproject.api.FormatException;
import org.briarproject.api.blogs.Blog; import org.briarproject.api.blogs.Blog;
import org.briarproject.api.blogs.BlogFactory; import org.briarproject.api.blogs.BlogFactory;
import org.briarproject.api.blogs.BlogPost; import org.briarproject.api.blogs.BlogPost;
import org.briarproject.api.blogs.BlogPostFactory;
import org.briarproject.api.blogs.BlogPostHeader; import org.briarproject.api.blogs.BlogPostHeader;
import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.Contact;
@@ -33,17 +34,20 @@ import org.junit.Test;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import javax.inject.Inject;
import static org.briarproject.TestUtils.getRandomBytes; import static org.briarproject.TestUtils.getRandomBytes;
import static org.briarproject.TestUtils.getRandomId; import static org.briarproject.TestUtils.getRandomId;
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR; import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR;
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_ID; import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_ID;
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_NAME; import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_NAME;
import static org.briarproject.api.blogs.BlogConstants.KEY_CONTENT_TYPE;
import static org.briarproject.api.blogs.BlogConstants.KEY_DESCRIPTION; import static org.briarproject.api.blogs.BlogConstants.KEY_DESCRIPTION;
import static org.briarproject.api.blogs.BlogConstants.KEY_PUBLIC_KEY; import static org.briarproject.api.blogs.BlogConstants.KEY_PUBLIC_KEY;
import static org.briarproject.api.blogs.BlogConstants.KEY_READ; import static org.briarproject.api.blogs.BlogConstants.KEY_READ;
import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP; import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP;
import static org.briarproject.api.blogs.BlogConstants.KEY_TIME_RECEIVED; import static org.briarproject.api.blogs.BlogConstants.KEY_TIME_RECEIVED;
import static org.briarproject.api.blogs.BlogConstants.KEY_TYPE;
import static org.briarproject.api.blogs.MessageType.POST;
import static org.briarproject.api.identity.Author.Status.VERIFIED; import static org.briarproject.api.identity.Author.Status.VERIFIED;
import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.blogs.BlogManagerImpl.CLIENT_ID; import static org.briarproject.blogs.BlogManagerImpl.CLIENT_ID;
@@ -68,9 +72,13 @@ public class BlogManagerImplTest extends BriarTestCase {
private final Message message; private final Message message;
private final MessageId messageId; private final MessageId messageId;
@Inject
@SuppressWarnings("WeakerAccess")
BlogPostFactory blogPostFactory;
public BlogManagerImplTest() { public BlogManagerImplTest() {
blogManager = new BlogManagerImpl(db, identityManager, clientHelper, blogManager = new BlogManagerImpl(db, identityManager, clientHelper,
metadataParser, contactManager, blogFactory); metadataParser, contactManager, blogFactory, blogPostFactory);
blog1 = getBlog("Test Blog 1", "Test Description 1"); blog1 = getBlog("Test Blog 1", "Test Description 1");
blog2 = getBlog("Test Blog 2", "Test Description 2"); blog2 = getBlog("Test Blog 2", "Test Description 2");
@@ -183,16 +191,15 @@ public class BlogManagerImplTest extends BriarTestCase {
BdfList list = new BdfList(); BdfList list = new BdfList();
BdfDictionary author = authorToBdfDictionary(blog1.getAuthor()); BdfDictionary author = authorToBdfDictionary(blog1.getAuthor());
BdfDictionary meta = BdfDictionary.of( BdfDictionary meta = BdfDictionary.of(
new BdfEntry(KEY_TYPE, POST.getInt()),
new BdfEntry(KEY_TIMESTAMP, 0), new BdfEntry(KEY_TIMESTAMP, 0),
new BdfEntry(KEY_TIME_RECEIVED, 1), new BdfEntry(KEY_TIME_RECEIVED, 1),
new BdfEntry(KEY_AUTHOR, author), new BdfEntry(KEY_AUTHOR, author),
new BdfEntry(KEY_CONTENT_TYPE, 0), new BdfEntry(KEY_READ, false)
new BdfEntry(KEY_READ, false),
new BdfEntry(KEY_CONTENT_TYPE, "text/plain")
); );
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(clientHelper).setMessageShared(txn, message, true); oneOf(clientHelper).setMessageShared(txn, messageId, true);
oneOf(identityManager) oneOf(identityManager)
.getAuthorStatus(txn, blog1.getAuthor().getId()); .getAuthorStatus(txn, blog1.getAuthor().getId());
will(returnValue(VERIFIED)); will(returnValue(VERIFIED));
@@ -211,7 +218,6 @@ public class BlogManagerImplTest extends BriarTestCase {
assertEquals(messageId, h.getId()); assertEquals(messageId, h.getId());
assertEquals(null, h.getParentId()); assertEquals(null, h.getParentId());
assertEquals(VERIFIED, h.getAuthorStatus()); assertEquals(VERIFIED, h.getAuthorStatus());
assertEquals("text/plain", h.getContentType());
assertEquals(blog1.getAuthor(), h.getAuthor()); assertEquals(blog1.getAuthor(), h.getAuthor());
} }
@@ -273,13 +279,12 @@ public class BlogManagerImplTest extends BriarTestCase {
public void testAddLocalPost() throws DbException, FormatException { public void testAddLocalPost() throws DbException, FormatException {
final Transaction txn = new Transaction(null, true); final Transaction txn = new Transaction(null, true);
final BlogPost post = final BlogPost post =
new BlogPost(null, message, null, blog1.getAuthor(), new BlogPost(message, null, blog1.getAuthor());
"text/plain");
BdfDictionary authorMeta = authorToBdfDictionary(blog1.getAuthor()); BdfDictionary authorMeta = authorToBdfDictionary(blog1.getAuthor());
final BdfDictionary meta = BdfDictionary.of( final BdfDictionary meta = BdfDictionary.of(
new BdfEntry(KEY_TYPE, POST.getInt()),
new BdfEntry(KEY_TIMESTAMP, message.getTimestamp()), new BdfEntry(KEY_TIMESTAMP, message.getTimestamp()),
new BdfEntry(KEY_AUTHOR, authorMeta), new BdfEntry(KEY_AUTHOR, authorMeta),
new BdfEntry(KEY_CONTENT_TYPE, "text/plain"),
new BdfEntry(KEY_READ, true) new BdfEntry(KEY_READ, true)
); );
@@ -308,7 +313,6 @@ public class BlogManagerImplTest extends BriarTestCase {
assertEquals(messageId, h.getId()); assertEquals(messageId, h.getId());
assertEquals(null, h.getParentId()); assertEquals(null, h.getParentId());
assertEquals(VERIFIED, h.getAuthorStatus()); assertEquals(VERIFIED, h.getAuthorStatus());
assertEquals("text/plain", h.getContentType());
assertEquals(blog1.getAuthor(), h.getAuthor()); assertEquals(blog1.getAuthor(), h.getAuthor());
} }

View File

@@ -37,12 +37,10 @@ import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR;
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_ID; import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_ID;
import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_NAME; import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_NAME;
import static org.briarproject.api.blogs.BlogConstants.KEY_COMMENT; import static org.briarproject.api.blogs.BlogConstants.KEY_COMMENT;
import static org.briarproject.api.blogs.BlogConstants.KEY_CONTENT_TYPE; import static org.briarproject.api.blogs.BlogConstants.KEY_PARENT_MSG_ID;
import static org.briarproject.api.blogs.BlogConstants.KEY_CURRENT_MSG_ID;
import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_MSG_ID; import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_MSG_ID;
import static org.briarproject.api.blogs.BlogConstants.KEY_PUBLIC_KEY; import static org.briarproject.api.blogs.BlogConstants.KEY_PUBLIC_KEY;
import static org.briarproject.api.blogs.BlogConstants.KEY_READ; import static org.briarproject.api.blogs.BlogConstants.KEY_READ;
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH;
import static org.briarproject.api.blogs.MessageType.COMMENT; import static org.briarproject.api.blogs.MessageType.COMMENT;
import static org.briarproject.api.blogs.MessageType.POST; import static org.briarproject.api.blogs.MessageType.POST;
import static org.briarproject.api.blogs.MessageType.WRAPPED_COMMENT; import static org.briarproject.api.blogs.MessageType.WRAPPED_COMMENT;
@@ -70,8 +68,7 @@ public class BlogPostValidatorTest extends BriarTestCase {
private final BlogFactory blogFactory = context.mock(BlogFactory.class); private final BlogFactory blogFactory = context.mock(BlogFactory.class);
private final ClientHelper clientHelper = context.mock(ClientHelper.class); private final ClientHelper clientHelper = context.mock(ClientHelper.class);
private final Clock clock = new SystemClock(); private final Clock clock = new SystemClock();
private final byte[] body = TestUtils.getRandomBytes( private final String body = TestUtils.getRandomString(42);
MAX_BLOG_POST_BODY_LENGTH);
private final String contentType = "text/plain"; private final String contentType = "text/plain";
public BlogPostValidatorTest() { public BlogPostValidatorTest() {
@@ -105,18 +102,15 @@ public class BlogPostValidatorTest extends BriarTestCase {
@Test @Test
public void testValidateProperBlogPost() public void testValidateProperBlogPost()
throws IOException, GeneralSecurityException { throws IOException, GeneralSecurityException {
// content type, title (optional), post body, attachments
BdfList content = BdfList.of(null, contentType, null, body, null);
final byte[] sigBytes = TestUtils.getRandomBytes(42); final byte[] sigBytes = TestUtils.getRandomBytes(42);
BdfList m = BdfList.of(POST.getInt(), content, sigBytes); BdfList m = BdfList.of(POST.getInt(), body, sigBytes);
BdfList signed = BdfList signed =
BdfList.of(blog.getId(), message.getTimestamp(), content); BdfList.of(blog.getId(), message.getTimestamp(), body);
expectCrypto(signed, sigBytes, true); expectCrypto(signed, sigBytes, true);
final BdfDictionary result = final BdfDictionary result =
validator.validateMessage(message, group, m).getDictionary(); validator.validateMessage(message, group, m).getDictionary();
assertEquals(contentType, result.getString(KEY_CONTENT_TYPE));
assertEquals(authorDict, result.getDictionary(KEY_AUTHOR)); assertEquals(authorDict, result.getDictionary(KEY_AUTHOR));
assertFalse(result.getBoolean(KEY_READ)); assertFalse(result.getBoolean(KEY_READ));
context.assertIsSatisfied(); context.assertIsSatisfied();
@@ -143,13 +137,11 @@ public class BlogPostValidatorTest extends BriarTestCase {
@Test(expected = InvalidMessageException.class) @Test(expected = InvalidMessageException.class)
public void testValidateBlogPostWithBadSignature() public void testValidateBlogPostWithBadSignature()
throws IOException, GeneralSecurityException { throws IOException, GeneralSecurityException {
// content type, title (optional), post body, attachments
BdfList content = BdfList.of(null, contentType, null, body, null);
final byte[] sigBytes = TestUtils.getRandomBytes(42); final byte[] sigBytes = TestUtils.getRandomBytes(42);
BdfList m = BdfList.of(POST.getInt(), content, sigBytes); BdfList m = BdfList.of(POST.getInt(), body, sigBytes);
BdfList signed = BdfList signed =
BdfList.of(blog.getId(), message.getTimestamp(), content); BdfList.of(blog.getId(), message.getTimestamp(), body);
expectCrypto(signed, sigBytes, false); expectCrypto(signed, sigBytes, false);
validator.validateMessage(message, group, m).getDictionary(); validator.validateMessage(message, group, m).getDictionary();
} }
@@ -157,17 +149,18 @@ public class BlogPostValidatorTest extends BriarTestCase {
@Test @Test
public void testValidateProperBlogComment() public void testValidateProperBlogComment()
throws IOException, GeneralSecurityException { throws IOException, GeneralSecurityException {
// comment, parent_original_id, signature, parent_current_id // comment, parent_original_id, parent_id, signature
String comment = "This is a blog comment"; String comment = "This is a blog comment";
MessageId originalId = new MessageId(TestUtils.getRandomId()); MessageId originalId = new MessageId(TestUtils.getRandomId());
byte[] currentId = TestUtils.getRandomId(); MessageId currentId = new MessageId(TestUtils.getRandomId());
final byte[] sigBytes = TestUtils.getRandomBytes(42); final byte[] sigBytes = TestUtils.getRandomBytes(42);
BdfList m = BdfList.of(COMMENT.getInt(), comment, originalId, BdfList m =
sigBytes, currentId); BdfList.of(COMMENT.getInt(), comment, originalId, currentId,
sigBytes);
BdfList signed = BdfList signed =
BdfList.of(blog.getId(), message.getTimestamp(), comment, BdfList.of(blog.getId(), message.getTimestamp(), comment,
originalId); originalId, currentId);
expectCrypto(signed, sigBytes, true); expectCrypto(signed, sigBytes, true);
final BdfDictionary result = final BdfDictionary result =
validator.validateMessage(message, group, m).getDictionary(); validator.validateMessage(message, group, m).getDictionary();
@@ -175,7 +168,7 @@ public class BlogPostValidatorTest extends BriarTestCase {
assertEquals(comment, result.getString(KEY_COMMENT)); assertEquals(comment, result.getString(KEY_COMMENT));
assertEquals(authorDict, result.getDictionary(KEY_AUTHOR)); assertEquals(authorDict, result.getDictionary(KEY_AUTHOR));
assertEquals(originalId.getBytes(), result.getRaw(KEY_ORIGINAL_MSG_ID)); assertEquals(originalId.getBytes(), result.getRaw(KEY_ORIGINAL_MSG_ID));
assertEquals(currentId, result.getRaw(KEY_CURRENT_MSG_ID)); assertEquals(currentId.getBytes(), result.getRaw(KEY_PARENT_MSG_ID));
assertFalse(result.getBoolean(KEY_READ)); assertFalse(result.getBoolean(KEY_READ));
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -185,14 +178,15 @@ public class BlogPostValidatorTest extends BriarTestCase {
throws IOException, GeneralSecurityException { throws IOException, GeneralSecurityException {
// comment, parent_original_id, signature, parent_current_id // comment, parent_original_id, signature, parent_current_id
MessageId originalId = new MessageId(TestUtils.getRandomId()); MessageId originalId = new MessageId(TestUtils.getRandomId());
byte[] currentId = TestUtils.getRandomId(); MessageId currentId = new MessageId(TestUtils.getRandomId());
final byte[] sigBytes = TestUtils.getRandomBytes(42); final byte[] sigBytes = TestUtils.getRandomBytes(42);
BdfList m = BdfList.of(COMMENT.getInt(), null, originalId, sigBytes, BdfList m =
currentId); BdfList.of(COMMENT.getInt(), null, originalId, currentId,
sigBytes);
BdfList signed = BdfList signed =
BdfList.of(blog.getId(), message.getTimestamp(), null, BdfList.of(blog.getId(), message.getTimestamp(), null,
originalId); originalId, currentId);
expectCrypto(signed, sigBytes, true); expectCrypto(signed, sigBytes, true);
final BdfDictionary result = final BdfDictionary result =
validator.validateMessage(message, group, m).getDictionary(); validator.validateMessage(message, group, m).getDictionary();
@@ -205,17 +199,16 @@ public class BlogPostValidatorTest extends BriarTestCase {
public void testValidateProperWrappedPost() public void testValidateProperWrappedPost()
throws IOException, GeneralSecurityException { throws IOException, GeneralSecurityException {
// group descriptor, timestamp, content, signature // group descriptor, timestamp, content, signature
BdfList content = BdfList.of(null, contentType, null, body, null);
final byte[] sigBytes = TestUtils.getRandomBytes(42); final byte[] sigBytes = TestUtils.getRandomBytes(42);
BdfList m = BdfList m =
BdfList.of(WRAPPED_POST.getInt(), descriptor, BdfList.of(WRAPPED_POST.getInt(), descriptor,
message.getTimestamp(), content, sigBytes); message.getTimestamp(), body, sigBytes);
BdfList signed = BdfList signed =
BdfList.of(blog.getId(), message.getTimestamp(), content); BdfList.of(blog.getId(), message.getTimestamp(), body);
expectCrypto(signed, sigBytes, true); expectCrypto(signed, sigBytes, true);
final BdfList originalList = BdfList.of(content, sigBytes); final BdfList originalList = BdfList.of(POST.getInt(), body, sigBytes);
final byte[] originalBody = TestUtils.getRandomBytes(42); final byte[] originalBody = TestUtils.getRandomBytes(42);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
@@ -232,7 +225,6 @@ public class BlogPostValidatorTest extends BriarTestCase {
final BdfDictionary result = final BdfDictionary result =
validator.validateMessage(message, group, m).getDictionary(); validator.validateMessage(message, group, m).getDictionary();
assertEquals(contentType, result.getString(KEY_CONTENT_TYPE));
assertEquals(authorDict, result.getDictionary(KEY_AUTHOR)); assertEquals(authorDict, result.getDictionary(KEY_AUTHOR));
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -244,18 +236,19 @@ public class BlogPostValidatorTest extends BriarTestCase {
// parent_current_id // parent_current_id
String comment = "This is another comment"; String comment = "This is another comment";
MessageId originalId = new MessageId(TestUtils.getRandomId()); MessageId originalId = new MessageId(TestUtils.getRandomId());
MessageId oldId = new MessageId(TestUtils.getRandomId());
final byte[] sigBytes = TestUtils.getRandomBytes(42); final byte[] sigBytes = TestUtils.getRandomBytes(42);
MessageId currentId = new MessageId(TestUtils.getRandomId()); MessageId currentId = new MessageId(TestUtils.getRandomId());
BdfList m = BdfList.of(WRAPPED_COMMENT.getInt(), descriptor, BdfList m = BdfList.of(WRAPPED_COMMENT.getInt(), descriptor,
message.getTimestamp(), comment, originalId, sigBytes, message.getTimestamp(), comment, originalId, oldId, sigBytes,
currentId); currentId);
BdfList signed = BdfList.of(blog.getId(), message.getTimestamp(), BdfList signed = BdfList.of(blog.getId(), message.getTimestamp(),
comment, originalId); comment, originalId, oldId);
expectCrypto(signed, sigBytes, true); expectCrypto(signed, sigBytes, true);
final BdfList originalList = BdfList.of(comment, originalId, sigBytes, final BdfList originalList = BdfList.of(COMMENT.getInt(), comment,
currentId); originalId, oldId, sigBytes);
final byte[] originalBody = TestUtils.getRandomBytes(42); final byte[] originalBody = TestUtils.getRandomBytes(42);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
@@ -276,7 +269,7 @@ public class BlogPostValidatorTest extends BriarTestCase {
assertEquals(authorDict, result.getDictionary(KEY_AUTHOR)); assertEquals(authorDict, result.getDictionary(KEY_AUTHOR));
assertEquals( assertEquals(
message.getId().getBytes(), result.getRaw(KEY_ORIGINAL_MSG_ID)); message.getId().getBytes(), result.getRaw(KEY_ORIGINAL_MSG_ID));
assertEquals(currentId.getBytes(), result.getRaw(KEY_CURRENT_MSG_ID)); assertEquals(currentId.getBytes(), result.getRaw(KEY_PARENT_MSG_ID));
context.assertIsSatisfied(); context.assertIsSatisfied();
} }

View File

@@ -751,7 +751,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
transaction = db.startTransaction(false); transaction = db.startTransaction(false);
try { try {
db.setMessageShared(transaction, message, true); db.setMessageShared(transaction, message.getId(), true);
fail(); fail();
} catch (NoSuchMessageException expected) { } catch (NoSuchMessageException expected) {
// Expected // Expected