Merge branch '436-automatic-personal-blogs-backend' into 'master'

Backend for Automatic Micro Blogs

This MR introduces automatic personal blogs to the Blog client. When a contact is added, her personal blog will also be added automatically. Also, when a new identity is added, a personal blog for that identity is created.

The first commit changes the blog paradigm to short-form blogs and introduces other things that will be useful later in the UI (!214) such as a BlogPostAdded event and the possibility to delete/remove blogs (not the personal ones).

This MR is based on !224 to prevent crashes that are fixed by it. So please review and merge !224 first.

See merge request !223
This commit is contained in:
Torsten Grote
2016-06-23 15:05:46 +00:00
18 changed files with 346 additions and 121 deletions

View File

@@ -11,13 +11,16 @@ public class Blog extends Forum {
private final String description;
@NotNull
private final Author author;
private final boolean permanent;
public Blog(@NotNull Group group, @NotNull String name,
@NotNull String description, @NotNull Author author) {
@NotNull String description, @NotNull Author author,
boolean permanent) {
super(group, name, null);
this.description = description;
this.author = author;
this.permanent = permanent;
}
@NotNull
@@ -29,4 +32,8 @@ public class Blog extends Forum {
public Author getAuthor() {
return author;
}
public boolean isPermanent() {
return permanent;
}
}

View File

@@ -16,12 +16,12 @@ public interface BlogConstants {
/** The length of a blog post's title in UTF-8 bytes. */
int MAX_BLOG_POST_TITLE_LENGTH = 100;
/** The length of a blog post's teaser in UTF-8 bytes. */
int MAX_BLOG_POST_TEASER_LENGTH = 240;
/** The maximum length of a blog post's body in bytes. */
int MAX_BLOG_POST_BODY_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
/** The internal name of personal blogs that are created automatically */
String PERSONAL_BLOG_NAME = "briar.PERSONAL_BLOG_NAME";
/* Blog Sharing Constants */
String BLOG_TITLE = "blogTitle";
String BLOG_DESC = "blogDescription";
@@ -31,9 +31,8 @@ public interface BlogConstants {
// Metadata keys
String KEY_DESCRIPTION = "description";
String KEY_TITLE = "title";
String KEY_TEASER = "teaser";
String KEY_HAS_BODY = "hasBody";
String KEY_TIMESTAMP = "timestamp";
String KEY_TIME_RECEIVED = "timeReceived";
String KEY_PARENT = "parent";
String KEY_AUTHOR_ID = "id";
String KEY_AUTHOR_NAME = "name";

View File

@@ -1,6 +1,7 @@
package org.briarproject.api.blogs;
import org.briarproject.api.FormatException;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.identity.Author;
import org.briarproject.api.sync.Group;
import org.jetbrains.annotations.NotNull;
@@ -11,6 +12,9 @@ public interface BlogFactory {
Blog createBlog(@NotNull String name, @NotNull String description,
@NotNull Author author);
/** Creates a personal blog for a given author. */
Blog createPersonalBlog(@NotNull Author author);
/** Parses a blog with the given Group and description */
Blog parseBlog(@NotNull Group g, @NotNull String description)
throws FormatException;

View File

@@ -1,8 +1,8 @@
package org.briarproject.api.blogs;
import org.briarproject.api.FormatException;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.GroupId;
@@ -20,6 +20,9 @@ public interface BlogManager {
Blog addBlog(LocalAuthor localAuthor, String name, String description)
throws DbException;
/** Removes and deletes a blog. */
void removeBlog(Blog b) throws DbException;
/** Stores a local blog post. */
void addLocalPost(BlogPost p) throws DbException;
@@ -29,9 +32,12 @@ public interface BlogManager {
/** Returns the blog with the given ID. */
Blog getBlog(Transaction txn, GroupId g) throws DbException;
/** Returns all blogs to which the localAuthor created. */
/** Returns all blogs owned by the given localAuthor. */
Collection<Blog> getBlogs(LocalAuthor localAuthor) throws DbException;
/** Returns only the personal blog of the given author. */
Blog getPersonalBlog(Author author) throws DbException;
/** Returns all blogs to which the user subscribes. */
Collection<Blog> getBlogs() throws DbException;
@@ -45,4 +51,11 @@ public interface BlogManager {
/** Marks a blog post as read or unread. */
void setReadFlag(MessageId m, boolean read) throws DbException;
/** Registers a hook to be called whenever a blog is removed. */
void registerRemoveBlogHook(RemoveBlogHook hook);
interface RemoveBlogHook {
void removingBlog(Transaction txn, Blog b) throws DbException;
}
}

View File

@@ -1,43 +1,27 @@
package org.briarproject.api.blogs;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.briarproject.api.forum.ForumPost;
import org.briarproject.api.identity.Author;
import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageId;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class BlogPost extends ForumPost {
@Nullable
private final String title;
@NotNull
private final String teaser;
private final boolean hasBody;
public BlogPost(@Nullable String title, @NotNull String teaser,
boolean hasBody, @NotNull Message message,
@Nullable MessageId parent, @NotNull Author 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;
this.teaser = teaser;
this.hasBody = hasBody;
}
@Nullable
public String getTitle() {
return title;
}
@NotNull
public String getTeaser() {
return teaser;
}
public boolean hasBody() {
return hasBody;
}
}

View File

@@ -12,8 +12,8 @@ import java.security.GeneralSecurityException;
public interface BlogPostFactory {
BlogPost createBlogPost(@NotNull GroupId groupId, @Nullable String title,
@NotNull String teaser, long timestamp, @Nullable MessageId parent,
long timestamp, @Nullable MessageId parent,
@NotNull LocalAuthor author, @NotNull String contentType,
@Nullable byte[] body)
@NotNull byte[] body)
throws FormatException, GeneralSecurityException;
}

View File

@@ -11,20 +11,16 @@ public class BlogPostHeader extends PostHeader {
@Nullable
private final String title;
@NotNull
private final String teaser;
private final boolean hasBody;
private final long timeReceived;
public BlogPostHeader(@Nullable String title, @NotNull String teaser,
boolean hasBody, @NotNull MessageId id,
@Nullable MessageId parentId, long timestamp,
public BlogPostHeader(@Nullable String title, @NotNull MessageId id,
@Nullable MessageId parentId, long timestamp, long timeReceived,
@NotNull Author author, @NotNull Status authorStatus,
@NotNull String contentType, boolean read) {
super(id, parentId, timestamp, author, authorStatus, contentType, read);
this.title = title;
this.teaser = teaser;
this.hasBody = hasBody;
this.timeReceived = timeReceived;
}
@Nullable
@@ -32,12 +28,8 @@ public class BlogPostHeader extends PostHeader {
return title;
}
@NotNull
public String getTeaser() {
return teaser;
public long getTimeReceived() {
return timeReceived;
}
public boolean hasBody() {
return hasBody;
}
}

View File

@@ -0,0 +1,32 @@
package org.briarproject.api.event;
import org.briarproject.api.blogs.BlogPostHeader;
import org.briarproject.api.sync.GroupId;
/** An event that is broadcast when a blog post was added to the database. */
public class BlogPostAddedEvent extends Event {
private final GroupId groupId;
private final BlogPostHeader header;
private final boolean local;
public BlogPostAddedEvent(GroupId groupId, BlogPostHeader header,
boolean local) {
this.groupId = groupId;
this.header = header;
this.local = local;
}
public GroupId getGroupId() {
return groupId;
}
public BlogPostHeader getHeader() {
return header;
}
public boolean isLocal() {
return local;
}
}

View File

@@ -29,6 +29,9 @@ public interface IdentityManager {
/** Returns the trust-level status of the author */
Status getAuthorStatus(AuthorId a) throws DbException;
/** Returns the trust-level status of the author */
Status getAuthorStatus(Transaction txn, AuthorId a) throws DbException;
interface AddIdentityHook {
void addingIdentity(Transaction txn, LocalAuthor a) throws DbException;
}

View File

@@ -4,6 +4,7 @@ import org.briarproject.api.FormatException;
import org.briarproject.api.blogs.Blog;
import org.briarproject.api.blogs.BlogFactory;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorFactory;
@@ -13,6 +14,8 @@ import org.jetbrains.annotations.NotNull;
import javax.inject.Inject;
import static org.briarproject.api.blogs.BlogConstants.PERSONAL_BLOG_NAME;
class BlogFactoryImpl implements BlogFactory {
private final GroupFactory groupFactory;
@@ -31,6 +34,16 @@ class BlogFactoryImpl implements BlogFactory {
@Override
public Blog createBlog(@NotNull String name, @NotNull String description,
@NotNull Author author) {
return createBlog(name, description, author, false);
}
@Override
public Blog createPersonalBlog(@NotNull Author a) {
return createBlog(PERSONAL_BLOG_NAME, "", a, true);
}
private Blog createBlog(@NotNull String name, @NotNull String description,
@NotNull Author author, boolean permanent) {
try {
BdfList blog = BdfList.of(
name,
@@ -40,7 +53,7 @@ class BlogFactoryImpl implements BlogFactory {
byte[] descriptor = clientHelper.toByteArray(blog);
Group g = groupFactory
.createGroup(BlogManagerImpl.CLIENT_ID, descriptor);
return new Blog(g, name, description, author);
return new Blog(g, name, description, author, permanent);
} catch (FormatException e) {
throw new RuntimeException(e);
}
@@ -55,7 +68,9 @@ class BlogFactoryImpl implements BlogFactory {
BdfList blog = clientHelper.toList(descriptor, 0, descriptor.length);
Author a =
authorFactory.createAuthor(blog.getString(1), blog.getRaw(2));
return new Blog(g, blog.getString(0), description, a);
// TODO change permanent depending on how this will be used
boolean permanent = false;
return new Blog(g, blog.getString(0), description, a, permanent);
}
}

View File

@@ -6,22 +6,31 @@ import org.briarproject.api.blogs.BlogFactory;
import org.briarproject.api.blogs.BlogManager;
import org.briarproject.api.blogs.BlogPost;
import org.briarproject.api.blogs.BlogPostHeader;
import org.briarproject.api.clients.Client;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfEntry;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.data.MetadataParser;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.BlogPostAddedEvent;
import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.Author.Status;
import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.identity.IdentityManager.AddIdentityHook;
import org.briarproject.api.identity.IdentityManager.RemoveIdentityHook;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageId;
import org.briarproject.clients.BdfIncomingMessageHook;
import org.briarproject.util.StringUtils;
import org.jetbrains.annotations.Nullable;
@@ -31,24 +40,29 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Logger;
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_ID;
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_HAS_BODY;
import static org.briarproject.api.blogs.BlogConstants.KEY_PARENT;
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_TEASER;
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_TITLE;
import static org.briarproject.api.contact.ContactManager.AddContactHook;
import static org.briarproject.api.contact.ContactManager.RemoveContactHook;
class BlogManagerImpl implements BlogManager {
class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
AddContactHook, RemoveContactHook, Client,
AddIdentityHook, RemoveIdentityHook {
private static final Logger LOG =
Logger.getLogger(BlogManagerImpl.class.getName());
@@ -59,17 +73,19 @@ class BlogManagerImpl implements BlogManager {
private final DatabaseComponent db;
private final IdentityManager identityManager;
private final ClientHelper clientHelper;
private final BlogFactory blogFactory;
private final List<RemoveBlogHook> removeHooks;
@Inject
BlogManagerImpl(DatabaseComponent db, IdentityManager identityManager,
ClientHelper clientHelper, BlogFactory blogFactory) {
ClientHelper clientHelper, MetadataParser metadataParser,
BlogFactory blogFactory) {
super(clientHelper, metadataParser);
this.db = db;
this.identityManager = identityManager;
this.clientHelper = clientHelper;
this.blogFactory = blogFactory;
removeHooks = new CopyOnWriteArrayList<RemoveBlogHook>();
}
@Override
@@ -77,6 +93,79 @@ class BlogManagerImpl implements BlogManager {
return CLIENT_ID;
}
@Override
public void createLocalState(Transaction txn) throws DbException {
// Ensure every identity does have its own personal blog
// TODO this can probably be removed once #446 is resolved and all users migrated to a new version
for (LocalAuthor a : db.getLocalAuthors(txn)) {
Blog b = blogFactory.createPersonalBlog(a);
Group g = b.getGroup();
if (!db.containsGroup(txn, g.getId())) {
db.addGroup(txn, g);
for (ContactId c : db.getContacts(txn, a.getId())) {
db.setVisibleToContact(txn, c, g.getId(), true);
}
}
}
// Ensure that we have the personal blogs of all pre-existing contacts
for (Contact c : db.getContacts(txn)) addingContact(txn, c);
}
@Override
public void addingContact(Transaction txn, Contact c) throws DbException {
// get personal blog of the contact
Blog b = blogFactory.createPersonalBlog(c.getAuthor());
Group g = b.getGroup();
if (!db.containsGroup(txn, g.getId())) {
// add the personal blog of the contact
db.addGroup(txn, g);
db.setVisibleToContact(txn, c.getId(), g.getId(), true);
// share our personal blog with the new contact
LocalAuthor a = db.getLocalAuthor(txn, c.getLocalAuthorId());
Blog b2 = blogFactory.createPersonalBlog(a);
db.setVisibleToContact(txn, c.getId(), b2.getId(), true);
}
}
@Override
public void removingContact(Transaction txn, Contact c) throws DbException {
if (c != null) {
Blog b = blogFactory.createPersonalBlog(c.getAuthor());
db.removeGroup(txn, b.getGroup());
}
}
@Override
public void addingIdentity(Transaction txn, LocalAuthor a)
throws DbException {
// add a personal blog for the new identity
LOG.info("New Personal Blog Added.");
Blog b = blogFactory.createPersonalBlog(a);
db.addGroup(txn, b.getGroup());
}
@Override
public void removingIdentity(Transaction txn, LocalAuthor a)
throws DbException {
// remove the personal blog of that identity
Blog b = blogFactory.createPersonalBlog(a);
db.removeGroup(txn, b.getGroup());
}
@Override
protected void incomingMessage(Transaction txn, Message m, BdfList list,
BdfDictionary meta) throws DbException, FormatException {
GroupId groupId = m.getGroupId();
BlogPostHeader h = getPostHeaderFromMetadata(txn, m.getId(), meta);
BlogPostAddedEvent event =
new BlogPostAddedEvent(groupId, h, false);
txn.attach(event);
}
@Override
public Blog addBlog(LocalAuthor localAuthor, String name,
String description) throws DbException {
@@ -101,13 +190,25 @@ class BlogManagerImpl implements BlogManager {
}
@Override
public void addLocalPost(BlogPost p) throws DbException {
public void removeBlog(Blog b) throws DbException {
Transaction txn = db.startTransaction(false);
try {
BdfDictionary meta = new BdfDictionary();
for (RemoveBlogHook hook : removeHooks)
hook.removingBlog(txn, b);
db.removeGroup(txn, b.getGroup());
txn.setComplete();
} finally {
db.endTransaction(txn);
}
}
@Override
public void addLocalPost(BlogPost p) throws DbException {
BdfDictionary meta;
try {
meta = new BdfDictionary();
if (p.getTitle() != null) meta.put(KEY_TITLE, p.getTitle());
meta.put(KEY_TEASER, p.getTeaser());
meta.put(KEY_TIMESTAMP, p.getMessage().getTimestamp());
meta.put(KEY_HAS_BODY, p.hasBody());
if (p.getParent() != null) meta.put(KEY_PARENT, p.getParent());
Author a = p.getAuthor();
@@ -123,6 +224,22 @@ class BlogManagerImpl implements BlogManager {
} catch (FormatException e) {
throw new RuntimeException(e);
}
// broadcast event about new post
Transaction txn = db.startTransaction(true);
try {
GroupId groupId = p.getMessage().getGroupId();
MessageId postId = p.getMessage().getId();
BlogPostHeader h = getPostHeaderFromMetadata(txn, postId, meta);
BlogPostAddedEvent event =
new BlogPostAddedEvent(groupId, h, true);
txn.attach(event);
txn.setComplete();
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally {
db.endTransaction(txn);
}
}
@Override
@@ -163,6 +280,11 @@ class BlogManagerImpl implements BlogManager {
return Collections.unmodifiableList(blogs);
}
@Override
public Blog getPersonalBlog(Author author) throws DbException {
return blogFactory.createPersonalBlog(author);
}
@Override
public Collection<Blog> getBlogs() throws DbException {
try {
@@ -189,16 +311,20 @@ class BlogManagerImpl implements BlogManager {
@Nullable
public byte[] getPostBody(MessageId m) throws DbException {
try {
// content, signature
// content: parent, contentType, title, teaser, body, attachments
BdfList message = clientHelper.getMessageAsList(m);
BdfList content = message.getList(0);
return content.getRaw(4);
return getPostBody(message);
} catch (FormatException e) {
throw new DbException(e);
}
}
private byte[] getPostBody(BdfList message) throws FormatException {
// content, signature
// content: parent, contentType, title, body, attachments
BdfList content = message.getList(0);
return content.getRaw(3);
}
@Override
public Collection<BlogPostHeader> getPostHeaders(GroupId g)
throws DbException {
@@ -213,26 +339,9 @@ class BlogManagerImpl implements BlogManager {
for (Entry<MessageId, BdfDictionary> entry : metadata.entrySet()) {
try {
BdfDictionary meta = entry.getValue();
String title = meta.getOptionalString(KEY_TITLE);
String teaser = meta.getString(KEY_TEASER);
boolean hasBody = meta.getBoolean(KEY_HAS_BODY);
long timestamp = meta.getLong(KEY_TIMESTAMP);
MessageId parentId = null;
if (meta.containsKey(KEY_PARENT))
parentId = new MessageId(meta.getRaw(KEY_PARENT));
BdfDictionary d = meta.getDictionary(KEY_AUTHOR);
AuthorId authorId = new AuthorId(d.getRaw(KEY_AUTHOR_ID));
String name = d.getString(KEY_AUTHOR_NAME);
byte[] publicKey = d.getRaw(KEY_PUBLIC_KEY);
Author author = new Author(authorId, name, publicKey);
Status authorStatus = identityManager.getAuthorStatus(authorId);
String contentType = meta.getString(KEY_CONTENT_TYPE);
boolean read = meta.getBoolean(KEY_READ);
headers.add(new BlogPostHeader(title, teaser, hasBody,
entry.getKey(), parentId, timestamp, author,
authorStatus, contentType, read));
BlogPostHeader h =
getPostHeaderFromMetadata(null, entry.getKey(), meta);
headers.add(h);
} catch (FormatException e) {
throw new DbException(e);
}
@@ -251,10 +360,43 @@ class BlogManagerImpl implements BlogManager {
}
}
@Override
public void registerRemoveBlogHook(RemoveBlogHook hook) {
removeHooks.add(hook);
}
private String getBlogDescription(Transaction txn, GroupId g)
throws DbException, FormatException {
BdfDictionary d = clientHelper.getGroupMetadataAsDictionary(txn, g);
return d.getString(KEY_DESCRIPTION);
return d.getString(KEY_DESCRIPTION, "");
}
private BlogPostHeader getPostHeaderFromMetadata(@Nullable Transaction txn,
MessageId id, BdfDictionary meta)
throws DbException, FormatException {
String title = meta.getOptionalString(KEY_TITLE);
long timestamp = meta.getLong(KEY_TIMESTAMP);
long timeReceived = meta.getLong(KEY_TIME_RECEIVED, timestamp);
MessageId parentId = null;
if (meta.containsKey(KEY_PARENT))
parentId = new MessageId(meta.getRaw(KEY_PARENT));
BdfDictionary d = meta.getDictionary(KEY_AUTHOR);
AuthorId authorId = new AuthorId(d.getRaw(KEY_AUTHOR_ID));
String name = d.getString(KEY_AUTHOR_NAME);
byte[] publicKey = d.getRaw(KEY_PUBLIC_KEY);
Author author = new Author(authorId, name, publicKey);
Status authorStatus;
if (txn == null)
authorStatus = identityManager.getAuthorStatus(authorId);
else {
authorStatus = identityManager.getAuthorStatus(txn, authorId);
}
String contentType = meta.getString(KEY_CONTENT_TYPE);
boolean read = meta.getBoolean(KEY_READ);
return new BlogPostHeader(title, id, parentId, timestamp, timeReceived,
author, authorStatus, contentType, read);
}
}

View File

@@ -22,7 +22,6 @@ import java.security.GeneralSecurityException;
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_TEASER_LENGTH;
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_TITLE_LENGTH;
import static org.briarproject.api.blogs.BlogConstants.MAX_CONTENT_TYPE_LENGTH;
@@ -39,25 +38,22 @@ class BlogPostFactoryImpl implements BlogPostFactory {
@Override
public BlogPost createBlogPost(@NotNull GroupId groupId,
@Nullable String title, @NotNull String teaser, long timestamp,
@Nullable String title, long timestamp,
@Nullable MessageId parent, @NotNull LocalAuthor author,
@NotNull String contentType, @Nullable byte[] body)
@NotNull String contentType, @NotNull byte[] body)
throws FormatException, GeneralSecurityException {
// Validate the arguments
if (title != null &&
StringUtils.toUtf8(title).length > MAX_BLOG_POST_TITLE_LENGTH)
throw new IllegalArgumentException();
if (StringUtils.toUtf8(teaser).length > MAX_BLOG_POST_TEASER_LENGTH)
throw new IllegalArgumentException();
if (StringUtils.toUtf8(contentType).length > MAX_CONTENT_TYPE_LENGTH)
throw new IllegalArgumentException();
if (body != null && body.length > MAX_BLOG_POST_BODY_LENGTH)
if (body.length > MAX_BLOG_POST_BODY_LENGTH)
throw new IllegalArgumentException();
// Serialise the data to be signed
BdfList content =
BdfList.of(parent, contentType, title, teaser, body, null);
BdfList content = BdfList.of(parent, contentType, title, body, null);
BdfList signed = BdfList.of(groupId, timestamp, content);
// Generate the signature
@@ -72,7 +68,6 @@ class BlogPostFactoryImpl implements BlogPostFactory {
// Serialise the signed message
BdfList message = BdfList.of(content, sig);
Message m = clientHelper.createMessage(groupId, timestamp, message);
return new BlogPost(title, teaser, body != null, m, parent, author,
contentType);
return new BlogPost(title, m, parent, author, contentType);
}
}

View File

@@ -11,6 +11,7 @@ import org.briarproject.api.crypto.KeyParser;
import org.briarproject.api.crypto.PublicKey;
import org.briarproject.api.crypto.Signature;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfEntry;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.data.MetadataEncoder;
import org.briarproject.api.identity.Author;
@@ -25,15 +26,17 @@ import java.security.GeneralSecurityException;
import java.util.Collection;
import java.util.Collections;
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_NAME;
import static org.briarproject.api.blogs.BlogConstants.KEY_CONTENT_TYPE;
import static org.briarproject.api.blogs.BlogConstants.KEY_HAS_BODY;
import static org.briarproject.api.blogs.BlogConstants.KEY_PARENT;
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_TEASER;
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_TITLE;
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH;
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_TEASER_LENGTH;
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_TITLE_LENGTH;
import static org.briarproject.api.blogs.BlogConstants.MAX_CONTENT_TYPE_LENGTH;
import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
@@ -60,37 +63,36 @@ class BlogPostValidator extends BdfMessageValidator {
checkSize(body, 2);
BdfList content = body.getList(0);
// Content: Parent ID, content type, title (optional), teaser,
// post body (optional), attachments (optional)
checkSize(body, 6);
// Content: Parent ID, content type, title (optional), post body,
// attachments (optional)
checkSize(content, 5);
// Parent ID is optional
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 teaser
String teaser = content.getString(3);
// TODO make sure that there is only text in the teaser
checkLength(contentType, 0, MAX_BLOG_POST_TEASER_LENGTH);
// Blog post body is optional
byte[] postBody = content.getOptionalRaw(4);
// Blog post body
byte[] postBody = content.getRaw(3);
checkLength(postBody, 0, MAX_BLOG_POST_BODY_LENGTH);
// Attachments
BdfDictionary attachments = content.getOptionalDictionary(5);
BdfDictionary attachments = content.getOptionalDictionary(4);
// TODO handle attachments somehow
// Signature
byte[] sig = body.getRaw(1);
checkLength(sig, 0, MAX_SIGNATURE_LENGTH);
// Verify the signature
Author a;
try {
// Get the blog author
Blog b = blogFactory.parseBlog(g, ""); // description doesn't matter
Author a = b.getAuthor();
a = b.getAuthor();
// Parse the public key
KeyParser keyParser = crypto.getSignatureKeyParser();
PublicKey key = keyParser.parsePublicKey(a.getPublicKey());
@@ -111,9 +113,14 @@ class BlogPostValidator extends BdfMessageValidator {
BdfDictionary meta = new BdfDictionary();
Collection<MessageId> dependencies = null;
if (title != null) meta.put(KEY_TITLE, title);
meta.put(KEY_TEASER, teaser);
meta.put(KEY_HAS_BODY, postBody != null);
BdfDictionary author = BdfDictionary.of(
new BdfEntry(KEY_AUTHOR_ID, a.getId()),
new BdfEntry(KEY_AUTHOR_NAME, a.getName()),
new BdfEntry(KEY_PUBLIC_KEY, a.getPublicKey())
);
meta.put(KEY_AUTHOR, author);
meta.put(KEY_TIMESTAMP, m.getTimestamp());
meta.put(KEY_TIME_RECEIVED, clock.currentTimeMillis());
if (parent != null) {
meta.put(KEY_PARENT, parent);
dependencies = Collections.singletonList(new MessageId(parent));

View File

@@ -4,11 +4,12 @@ import org.briarproject.api.blogs.BlogFactory;
import org.briarproject.api.blogs.BlogManager;
import org.briarproject.api.blogs.BlogPostFactory;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.data.MetadataEncoder;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.identity.AuthorFactory;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.sync.GroupFactory;
import org.briarproject.api.sync.ValidationManager;
import org.briarproject.api.system.Clock;
@@ -19,17 +20,31 @@ import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import static org.briarproject.blogs.BlogManagerImpl.CLIENT_ID;
@Module
public class BlogsModule {
public static class EagerSingletons {
@Inject
BlogPostValidator blogPostValidator;
@Inject
BlogManager blogManager;
}
@Provides
@Singleton
BlogManager provideBlogManager(BlogManagerImpl blogManager) {
BlogManager provideBlogManager(BlogManagerImpl blogManager,
LifecycleManager lifecycleManager, ContactManager contactManager,
IdentityManager identityManager,
ValidationManager validationManager) {
lifecycleManager.registerClient(blogManager);
contactManager.registerAddContactHook(blogManager);
contactManager.registerRemoveContactHook(blogManager);
identityManager.registerAddIdentityHook(blogManager);
identityManager.registerRemoveIdentityHook(blogManager);
validationManager.registerIncomingMessageHook(CLIENT_ID, blogManager);
return blogManager;
}
@@ -54,8 +69,7 @@ public class BlogsModule {
BlogPostValidator validator = new BlogPostValidator(crypto,
blogFactory, clientHelper, metadataEncoder, clock);
validationManager.registerMessageValidator(
BlogManagerImpl.CLIENT_ID, validator);
validationManager.registerMessageValidator(CLIENT_ID, validator);
return validator;
}

View File

@@ -99,17 +99,24 @@ class IdentityManagerImpl implements IdentityManager {
public Status getAuthorStatus(AuthorId authorId) throws DbException {
Transaction txn = db.startTransaction(false);
try {
// Compare to the IDs of the user's identities
for (LocalAuthor a : db.getLocalAuthors(txn))
if (a.getId().equals(authorId)) return VERIFIED;
// Compare to the IDs of contacts' identities
for (Contact c : db.getContacts(txn))
if (c.getAuthor().getId().equals(authorId)) return VERIFIED;
// TODO also handle UNVERIFIED when #261 is implemented
return UNKNOWN;
return getAuthorStatus(txn, authorId);
} finally {
db.endTransaction(txn);
}
}
@Override
public Status getAuthorStatus(Transaction txn, AuthorId authorId)
throws DbException {
// Compare to the IDs of the user's identities
for (LocalAuthor a : db.getLocalAuthors(txn))
if (a.getId().equals(authorId)) return VERIFIED;
// Compare to the IDs of contacts' identities
for (Contact c : db.getContacts(txn))
if (c.getAuthor().getId().equals(authorId)) return VERIFIED;
// TODO also handle UNVERIFIED when #261 is implemented
return UNKNOWN;
}
}

View File

@@ -5,6 +5,7 @@ import org.briarproject.api.blogs.Blog;
import org.briarproject.api.blogs.BlogFactory;
import org.briarproject.api.blogs.BlogInvitationMessage;
import org.briarproject.api.blogs.BlogManager;
import org.briarproject.api.blogs.BlogManager.RemoveBlogHook;
import org.briarproject.api.blogs.BlogSharingManager;
import org.briarproject.api.blogs.BlogSharingMessage.BlogInvitation;
import org.briarproject.api.clients.ClientHelper;
@@ -40,7 +41,7 @@ import static org.briarproject.api.blogs.BlogConstants.BLOG_TITLE;
class BlogSharingManagerImpl extends
SharingManagerImpl<Blog, BlogInvitation, BlogInvitationMessage, BlogInviteeSessionState, BlogSharerSessionState, BlogInvitationReceivedEvent, BlogInvitationResponseReceivedEvent>
implements BlogSharingManager {
implements BlogSharingManager, RemoveBlogHook {
static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString(
"bee438b5de0b3a685badc4e49d76e72d"
@@ -124,6 +125,11 @@ class BlogSharingManagerImpl extends
return irrFactory;
}
@Override
public void removingBlog(Transaction txn, Blog b) throws DbException {
removingShareable(txn, b);
}
static class SFactory implements
ShareableFactory<Blog, BlogInvitation, BlogInviteeSessionState, BlogSharerSessionState> {

View File

@@ -792,6 +792,8 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IM
} else if (task == TASK_REMOVE_SHAREABLE_FROM_LIST_SHARED_WITH_US) {
removeFromList(txn, groupId, SHARED_WITH_US, f);
} else if (task == TASK_ADD_SHARED_SHAREABLE) {
// TODO we might want to call the add() method of the respective
// manager here, because blogs add a description for example
db.addGroup(txn, f.getGroup());
db.setVisibleToContact(txn, contactId, f.getId(), true);
} else if (task == TASK_ADD_SHAREABLE_TO_LIST_TO_BE_SHARED_BY_US) {

View File

@@ -1,5 +1,6 @@
package org.briarproject.sharing;
import org.briarproject.api.blogs.BlogManager;
import org.briarproject.api.blogs.BlogSharingManager;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.MessageQueueManager;
@@ -49,6 +50,7 @@ public class SharingModule {
LifecycleManager lifecycleManager,
ContactManager contactManager,
MessageQueueManager messageQueueManager,
BlogManager blogManager,
BlogSharingManagerImpl blogSharingManager) {
lifecycleManager.registerClient(blogSharingManager);
@@ -56,6 +58,7 @@ public class SharingModule {
contactManager.registerRemoveContactHook(blogSharingManager);
messageQueueManager.registerIncomingMessageHook(
BlogSharingManagerImpl.CLIENT_ID, blogSharingManager);
blogManager.registerRemoveBlogHook(blogSharingManager);
return blogSharingManager;
}