Skip to content
Snippets Groups Projects
Verified Commit da68ef78 authored by Torsten Grote's avatar Torsten Grote
Browse files

Blog Client with Factory and Validator

This implements a simple initial blog client that covers the basic blog
actions, but no deletion/removal of blogs, yet.

Classes for Blogs and Blog Post Headers have been introduced along with
the associated factories.

A `BlogPostValidator` has been added that validates incoming blog posts.

Closes #402
Closes #404
parent bbed6731
No related branches found
No related tags found
No related merge requests found
Showing
with 915 additions and 76 deletions
......@@ -7,14 +7,16 @@ apply plugin: 'witness'
dependencies {
compile "com.google.dagger:dagger:2.0.2"
compile 'com.google.dagger:dagger-compiler:2.0.2'
compile 'org.jetbrains:annotations-java5:15.0'
}
dependencyVerification {
verify = [
'com.google.dagger:dagger:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9',
'com.google.dagger:dagger-compiler:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3',
'com.google.dagger:dagger-producers:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b',
'org.jetbrains:annotations-java5:c84e6e9947f802ec2183bdc415dd496df02a749cac92e805f697e60f628a1e24',
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'com.google.dagger:dagger-producers:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b',
'com.google.guava:guava:d664fbfc03d2e5ce9cab2a44fb01f1d0bf9dfebeccc1a473b1f9ea31f79f6f99',
]
}
......
package org.briarproject.api.blogs;
import org.briarproject.api.forum.Forum;
import org.briarproject.api.identity.Author;
import org.briarproject.api.sync.Group;
import org.jetbrains.annotations.NotNull;
public class Blog extends Forum {
@NotNull
private final String description;
@NotNull
private final Author author;
public Blog(@NotNull Group group, @NotNull String name,
@NotNull String description, @NotNull Author author) {
super(group, name, null);
this.description = description;
this.author = author;
}
@NotNull
public String getDescription() {
return description;
}
@NotNull
public Author getAuthor() {
return author;
}
}
package org.briarproject.api.blogs;
import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
public interface BlogConstants {
/** The maximum length of a blogs's name in UTF-8 bytes. */
int MAX_BLOG_TITLE_LENGTH = 100;
/** The length of a blogs's description in UTF-8 bytes. */
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 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;
// 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_PARENT = "parent";
String KEY_AUTHOR_ID = "id";
String KEY_AUTHOR_NAME = "name";
String KEY_PUBLIC_KEY = "publicKey";
String KEY_AUTHOR = "author";
String KEY_CONTENT_TYPE = "contentType";
String KEY_READ = "read";
}
package org.briarproject.api.blogs;
import org.briarproject.api.FormatException;
import org.briarproject.api.identity.Author;
import org.briarproject.api.sync.Group;
import org.jetbrains.annotations.NotNull;
public interface BlogFactory {
/** Creates a blog with the given name, description and author. */
Blog createBlog(@NotNull String name, @NotNull String description,
@NotNull Author author);
/** Parses a blog with the given Group and description */
Blog parseBlog(@NotNull Group g, @NotNull String description)
throws FormatException;
}
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.LocalAuthor;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
public interface BlogManager {
/** Returns the unique ID of the blog client. */
ClientId getClientId();
/** Creates a new Blog. */
Blog addBlog(LocalAuthor localAuthor, String name, String description)
throws DbException;
/** Stores a local blog post. */
void addLocalPost(BlogPost p) throws DbException;
/** Returns the blog with the given ID. */
Blog getBlog(GroupId g) throws DbException;
/** Returns the blog with the given ID. */
Blog getBlog(Transaction txn, GroupId g) throws DbException;
/** Returns all blogs to which the localAuthor created. */
Collection<Blog> getBlogs(LocalAuthor localAuthor) throws DbException;
/** Returns all blogs to which the user subscribes. */
Collection<Blog> getBlogs() throws DbException;
/** Returns the body of the blog post with the given ID. */
@Nullable
byte[] getPostBody(MessageId m) throws DbException;
/** Returns the headers of all posts in the given blog. */
Collection<BlogPostHeader> getPostHeaders(GroupId g) throws DbException;
/** Marks a blog post as read or unread. */
void setReadFlag(MessageId m, boolean read) throws DbException;
}
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;
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,
@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;
}
}
package org.briarproject.api.blogs;
import org.briarproject.api.FormatException;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.security.GeneralSecurityException;
public interface BlogPostFactory {
BlogPost createBlogPost(@NotNull GroupId groupId, @Nullable String title,
@NotNull String teaser, long timestamp, @Nullable MessageId parent,
@NotNull LocalAuthor author, @NotNull String contentType,
@Nullable byte[] body)
throws FormatException, GeneralSecurityException;
}
package org.briarproject.api.blogs;
import org.briarproject.api.clients.PostHeader;
import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.Author.Status;
import org.briarproject.api.sync.MessageId;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class BlogPostHeader extends PostHeader {
@Nullable
private final String title;
@NotNull
private final String teaser;
private final boolean hasBody;
public BlogPostHeader(@Nullable String title, @NotNull String teaser,
boolean hasBody, @NotNull MessageId id,
@Nullable MessageId parentId, long timestamp,
@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;
}
@Nullable
public String getTitle() {
return title;
}
@NotNull
public String getTeaser() {
return teaser;
}
public boolean hasBody() {
return hasBody;
}
}
package org.briarproject.api.clients;
import org.briarproject.api.identity.Author;
import org.briarproject.api.sync.MessageId;
public abstract class PostHeader {
private final MessageId id;
private final MessageId parentId;
private final long timestamp;
private final Author author;
private final Author.Status authorStatus;
private final String contentType;
private final boolean read;
public PostHeader(MessageId id, MessageId parentId, long timestamp,
Author author, Author.Status authorStatus, String contentType,
boolean read) {
this.id = id;
this.parentId = parentId;
this.timestamp = timestamp;
this.author = author;
this.authorStatus = authorStatus;
this.contentType = contentType;
this.read = read;
}
public MessageId getId() {
return id;
}
public Author getAuthor() {
return author;
}
public Author.Status getAuthorStatus() {
return authorStatus;
}
public String getContentType() {
return contentType;
}
public long getTimestamp() {
return timestamp;
}
public boolean isRead() {
return read;
}
public MessageId getParentId() {
return parentId;
}
}
package org.briarproject.api.forum;
import org.briarproject.api.clients.MessageTree;
import org.briarproject.api.clients.PostHeader;
import org.briarproject.api.identity.Author;
import org.briarproject.api.sync.MessageId;
public class ForumPostHeader implements MessageTree.MessageNode {
private final MessageId id;
private final MessageId parentId;
private final long timestamp;
private final Author author;
private final Author.Status authorStatus;
private final String contentType;
private final boolean read;
public class ForumPostHeader extends PostHeader
implements MessageTree.MessageNode {
public ForumPostHeader(MessageId id, MessageId parentId, long timestamp,
Author author, Author.Status authorStatus, String contentType,
boolean read) {
this.id = id;
this.parentId = parentId;
this.timestamp = timestamp;
this.author = author;
this.authorStatus = authorStatus;
this.contentType = contentType;
this.read = read;
}
public MessageId getId() {
return id;
}
public Author getAuthor() {
return author;
}
public Author.Status getAuthorStatus() {
return authorStatus;
super(id, parentId, timestamp, author, authorStatus, contentType, read);
}
public String getContentType() {
return contentType;
}
public long getTimestamp() {
return timestamp;
}
public boolean isRead() {
return read;
}
public MessageId getParentId() {
return parentId;
}
}
......@@ -2,6 +2,7 @@ package org.briarproject.api.identity;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.identity.Author.Status;
import java.util.Collection;
......@@ -25,6 +26,9 @@ public interface IdentityManager {
/** Removes a local pseudonym and all associated state. */
void removeLocalAuthor(AuthorId a) throws DbException;
/** Returns the trust-level status of the author */
Status getAuthorStatus(AuthorId a) throws DbException;
interface AddIdentityHook {
void addingIdentity(Transaction txn, LocalAuthor a) throws DbException;
}
......
package org.briarproject;
import org.briarproject.blogs.BlogsModule;
import org.briarproject.contact.ContactModule;
import org.briarproject.crypto.CryptoModule;
import org.briarproject.db.DatabaseExecutorModule;
......@@ -15,6 +16,8 @@ import org.briarproject.transport.TransportModule;
public interface CoreEagerSingletons {
void inject(BlogsModule.EagerSingletons init);
void inject(ContactModule.EagerSingletons init);
void inject(CryptoModule.EagerSingletons init);
......
package org.briarproject;
import org.briarproject.blogs.BlogsModule;
import org.briarproject.clients.ClientsModule;
import org.briarproject.contact.ContactModule;
import org.briarproject.crypto.CryptoModule;
......@@ -26,6 +27,7 @@ import org.briarproject.transport.TransportModule;
import dagger.Module;
@Module(includes = {
BlogsModule.class,
ClientsModule.class,
ContactModule.class,
CryptoModule.class,
......@@ -52,6 +54,7 @@ import dagger.Module;
public class CoreModule {
public static void initEagerSingletons(CoreEagerSingletons c) {
c.inject(new BlogsModule.EagerSingletons());
c.inject(new ContactModule.EagerSingletons());
c.inject(new CryptoModule.EagerSingletons());
c.inject(new DatabaseExecutorModule.EagerSingletons());
......
package org.briarproject.blogs;
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.data.BdfList;
import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorFactory;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupFactory;
import org.jetbrains.annotations.NotNull;
import javax.inject.Inject;
class BlogFactoryImpl implements BlogFactory {
private final GroupFactory groupFactory;
private final AuthorFactory authorFactory;
private final ClientHelper clientHelper;
@Inject
BlogFactoryImpl(GroupFactory groupFactory, AuthorFactory authorFactory,
ClientHelper clientHelper) {
this.groupFactory = groupFactory;
this.authorFactory = authorFactory;
this.clientHelper = clientHelper;
}
@Override
public Blog createBlog(@NotNull String name, @NotNull String description,
@NotNull Author author) {
try {
BdfList blog = BdfList.of(
name,
author.getName(),
author.getPublicKey()
);
byte[] descriptor = clientHelper.toByteArray(blog);
Group g = groupFactory
.createGroup(BlogManagerImpl.CLIENT_ID, descriptor);
return new Blog(g, name, description, author);
} catch (FormatException e) {
throw new RuntimeException(e);
}
}
@Override
public Blog parseBlog(@NotNull Group g, @NotNull String description)
throws FormatException {
byte[] descriptor = g.getDescriptor();
// Blog Name, Author Name, Public Key
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);
}
}
package org.briarproject.blogs;
import org.briarproject.api.FormatException;
import org.briarproject.api.blogs.Blog;
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.ClientHelper;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfEntry;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.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.LocalAuthor;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import org.briarproject.util.StringUtils;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;
import javax.inject.Inject;
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_TITLE;
class BlogManagerImpl implements BlogManager {
private static final Logger LOG =
Logger.getLogger(BlogManagerImpl.class.getName());
static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString(
"dafbe56f0c8971365cea4bb5f08ec9a6" +
"1d686e058b943997b6ff259ba423f613"));
private final DatabaseComponent db;
private final IdentityManager identityManager;
private final ClientHelper clientHelper;
private final BlogFactory blogFactory;
@Inject
BlogManagerImpl(DatabaseComponent db, IdentityManager identityManager,
ClientHelper clientHelper, BlogFactory blogFactory) {
this.db = db;
this.identityManager = identityManager;
this.clientHelper = clientHelper;
this.blogFactory = blogFactory;
}
@Override
public ClientId getClientId() {
return CLIENT_ID;
}
@Override
public Blog addBlog(LocalAuthor localAuthor, String name,
String description) throws DbException {
Blog b = blogFactory
.createBlog(name, description, localAuthor);
BdfDictionary metadata = BdfDictionary.of(
new BdfEntry(KEY_DESCRIPTION, b.getDescription())
);
Transaction txn = db.startTransaction(false);
try {
db.addGroup(txn, b.getGroup());
clientHelper.mergeGroupMetadata(txn, b.getId(), metadata);
txn.setComplete();
} catch (FormatException e) {
throw new DbException(e);
} finally {
db.endTransaction(txn);
}
return b;
}
@Override
public void addLocalPost(BlogPost p) throws DbException {
try {
BdfDictionary 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();
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);
clientHelper.addLocalMessage(p.getMessage(), CLIENT_ID, meta, true);
} catch (FormatException e) {
throw new RuntimeException(e);
}
}
@Override
public Blog getBlog(GroupId g) throws DbException {
Blog blog;
Transaction txn = db.startTransaction(true);
try {
blog = getBlog(txn, g);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
return blog;
}
@Override
public Blog getBlog(Transaction txn, GroupId g) throws DbException {
try {
Group group = db.getGroup(txn, g);
String description = getBlogDescription(txn, g);
return blogFactory.parseBlog(group, description);
} catch (FormatException e) {
throw new DbException(e);
}
}
@Override
public Collection<Blog> getBlogs(LocalAuthor localAuthor)
throws DbException {
Collection<Blog> allBlogs = getBlogs();
List<Blog> blogs = new ArrayList<Blog>();
for (Blog b : allBlogs) {
if (b.getAuthor().equals(localAuthor)) {
blogs.add(b);
}
}
return Collections.unmodifiableList(blogs);
}
@Override
public Collection<Blog> getBlogs() throws DbException {
try {
List<Blog> blogs = new ArrayList<Blog>();
Collection<Group> groups;
Transaction txn = db.startTransaction(true);
try {
groups = db.getGroups(txn, CLIENT_ID);
for (Group g : groups) {
String description = getBlogDescription(txn, g.getId());
blogs.add(blogFactory.parseBlog(g, description));
}
txn.setComplete();
} finally {
db.endTransaction(txn);
}
return Collections.unmodifiableList(blogs);
} catch (FormatException e) {
throw new DbException(e);
}
}
@Override
@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);
} catch (FormatException e) {
throw new DbException(e);
}
}
@Override
public Collection<BlogPostHeader> getPostHeaders(GroupId g)
throws DbException {
Map<MessageId, BdfDictionary> metadata;
try {
metadata = clientHelper.getMessageMetadataAsDictionary(g);
} catch (FormatException e) {
throw new DbException(e);
}
Collection<BlogPostHeader> headers = new ArrayList<BlogPostHeader>();
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));
} catch (FormatException e) {
throw new DbException(e);
}
}
return headers;
}
@Override
public void setReadFlag(MessageId m, boolean read) throws DbException {
try {
BdfDictionary meta = new BdfDictionary();
meta.put(KEY_READ, read);
clientHelper.mergeMessageMetadata(m, meta);
} catch (FormatException e) {
throw new RuntimeException(e);
}
}
private String getBlogDescription(Transaction txn, GroupId g)
throws DbException, FormatException {
BdfDictionary d = clientHelper.getGroupMetadataAsDictionary(txn, g);
return d.getString(KEY_DESCRIPTION);
}
}
package org.briarproject.blogs;
import org.briarproject.api.FormatException;
import org.briarproject.api.blogs.BlogPost;
import org.briarproject.api.blogs.BlogPostFactory;
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.identity.LocalAuthor;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageId;
import org.briarproject.util.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
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;
class BlogPostFactoryImpl implements BlogPostFactory {
private final CryptoComponent crypto;
private final ClientHelper clientHelper;
@Inject
BlogPostFactoryImpl(CryptoComponent crypto, ClientHelper clientHelper) {
this.crypto = crypto;
this.clientHelper = clientHelper;
}
@Override
public BlogPost createBlogPost(@NotNull GroupId groupId,
@Nullable String title, @NotNull String teaser, long timestamp,
@Nullable MessageId parent, @NotNull LocalAuthor author,
@NotNull String contentType, @Nullable 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)
throw new IllegalArgumentException();
// Serialise the data to be signed
BdfList content =
BdfList.of(parent, contentType, title, teaser, body, null);
BdfList signed = BdfList.of(groupId, timestamp, content);
// Generate the signature
Signature signature = crypto.getSignature();
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
BdfList message = BdfList.of(content, sig);
Message m = clientHelper.createMessage(groupId, timestamp, message);
return new BlogPost(title, teaser, body != null, m, parent, author,
contentType);
}
}
package org.briarproject.blogs;
import org.briarproject.api.FormatException;
import org.briarproject.api.UniqueId;
import org.briarproject.api.blogs.Blog;
import org.briarproject.api.blogs.BlogFactory;
import org.briarproject.api.clients.BdfMessageContext;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.crypto.CryptoComponent;
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.BdfList;
import org.briarproject.api.data.MetadataEncoder;
import org.briarproject.api.identity.Author;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.InvalidMessageException;
import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageId;
import org.briarproject.api.system.Clock;
import org.briarproject.clients.BdfMessageValidator;
import java.security.GeneralSecurityException;
import java.util.Collection;
import java.util.Collections;
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_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_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;
class BlogPostValidator extends BdfMessageValidator {
private final CryptoComponent crypto;
private final BlogFactory blogFactory;
BlogPostValidator(CryptoComponent crypto, BlogFactory blogFactory,
ClientHelper clientHelper, MetadataEncoder metadataEncoder,
Clock clock) {
super(clientHelper, metadataEncoder, clock);
this.crypto = crypto;
this.blogFactory = blogFactory;
}
@Override
protected BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws InvalidMessageException, FormatException {
// Content, Signature
checkSize(body, 2);
BdfList content = body.getList(0);
// Content: Parent ID, content type, title (optional), teaser,
// post body (optional), attachments (optional)
checkSize(body, 6);
// 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);
// 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);
checkLength(postBody, 0, MAX_BLOG_POST_BODY_LENGTH);
// Attachments
BdfDictionary attachments = content.getOptionalDictionary(5);
// TODO handle attachments somehow
// Signature
byte[] sig = body.getRaw(1);
checkLength(sig, 0, MAX_SIGNATURE_LENGTH);
// Verify the signature
try {
// Get the blog author
Blog b = blogFactory.parseBlog(g, ""); // description doesn't matter
Author a = b.getAuthor();
// Parse the public key
KeyParser keyParser = crypto.getSignatureKeyParser();
PublicKey key = keyParser.parsePublicKey(a.getPublicKey());
// Serialise the data to be signed
BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), content);
// Verify the signature
Signature signature = crypto.getSignature();
signature.initVerify(key);
signature.update(clientHelper.toByteArray(signed));
if (!signature.verify(sig)) {
throw new InvalidMessageException("Invalid signature");
}
} catch (GeneralSecurityException e) {
throw new InvalidMessageException("Invalid public key");
}
// Return the metadata and dependencies
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);
meta.put(KEY_TIMESTAMP, m.getTimestamp());
if (parent != null) {
meta.put(KEY_PARENT, parent);
dependencies = Collections.singletonList(new MessageId(parent));
}
meta.put(KEY_CONTENT_TYPE, contentType);
meta.put(KEY_READ, false);
return new BdfMessageContext(meta, dependencies);
}
}
package org.briarproject.blogs;
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.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.sync.GroupFactory;
import org.briarproject.api.sync.ValidationManager;
import org.briarproject.api.system.Clock;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class BlogsModule {
public static class EagerSingletons {
@Inject
BlogPostValidator blogPostValidator;
}
@Provides
@Singleton
BlogManager provideBlogManager(BlogManagerImpl blogManager) {
return blogManager;
}
@Provides
BlogPostFactory provideBlogPostFactory(CryptoComponent crypto,
ClientHelper clientHelper) {
return new BlogPostFactoryImpl(crypto, clientHelper);
}
@Provides
BlogFactory provideBlogFactory(GroupFactory groupFactory,
AuthorFactory authorFactory, ClientHelper clientHelper) {
return new BlogFactoryImpl(groupFactory, authorFactory, clientHelper);
}
@Provides
@Singleton
BlogPostValidator provideBlogPostValidator(
ValidationManager validationManager, CryptoComponent crypto,
BlogFactory blogFactory, ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) {
BlogPostValidator validator = new BlogPostValidator(crypto,
blogFactory, clientHelper, metadataEncoder, clock);
validationManager.registerMessageValidator(
BlogManagerImpl.CLIENT_ID, validator);
return validator;
}
}
......@@ -2,7 +2,6 @@ package org.briarproject.forum;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.db.DatabaseComponent;
......@@ -14,8 +13,9 @@ import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.forum.ForumPost;
import org.briarproject.api.forum.ForumPostHeader;
import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.Author.Status;
import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupId;
......@@ -25,11 +25,9 @@ import org.briarproject.util.StringUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Logger;
......@@ -45,8 +43,6 @@ import static org.briarproject.api.forum.ForumConstants.KEY_PUBLIC_NAME;
import static org.briarproject.api.forum.ForumConstants.KEY_READ;
import static org.briarproject.api.forum.ForumConstants.KEY_TIMESTAMP;
import static org.briarproject.api.identity.Author.Status.ANONYMOUS;
import static org.briarproject.api.identity.Author.Status.UNKNOWN;
import static org.briarproject.api.identity.Author.Status.VERIFIED;
class ForumManagerImpl implements ForumManager {
......@@ -58,15 +54,17 @@ class ForumManagerImpl implements ForumManager {
+ "795af837abbf8c16d750b3c2ccc186ea"));
private final DatabaseComponent db;
private final IdentityManager identityManager;
private final ClientHelper clientHelper;
private final ForumFactory forumFactory;
private final List<RemoveForumHook> removeHooks;
@Inject
ForumManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
ForumFactory forumFactory) {
ForumManagerImpl(DatabaseComponent db, IdentityManager identityManager,
ClientHelper clientHelper, ForumFactory forumFactory) {
this.db = db;
this.identityManager = identityManager;
this.clientHelper = clientHelper;
this.forumFactory = forumFactory;
removeHooks = new CopyOnWriteArrayList<RemoveForumHook>();
......@@ -183,24 +181,12 @@ class ForumManagerImpl implements ForumManager {
@Override
public Collection<ForumPostHeader> getPostHeaders(GroupId g)
throws DbException {
Set<AuthorId> localAuthorIds = new HashSet<AuthorId>();
Set<AuthorId> contactAuthorIds = new HashSet<AuthorId>();
Map<MessageId, BdfDictionary> metadata;
Transaction txn = db.startTransaction(true);
try {
// Load the IDs of the user's identities
for (LocalAuthor a : db.getLocalAuthors(txn))
localAuthorIds.add(a.getId());
// Load the IDs of contacts' identities
for (Contact c : db.getContacts(txn))
contactAuthorIds.add(c.getAuthor().getId());
// Load the metadata
metadata = clientHelper.getMessageMetadataAsDictionary(txn, g);
txn.setComplete();
metadata = clientHelper.getMessageMetadataAsDictionary(g);
} catch (FormatException e) {
throw new DbException(e);
} finally {
db.endTransaction(txn);
}
// Parse the metadata
Collection<ForumPostHeader> headers = new ArrayList<ForumPostHeader>();
......@@ -209,7 +195,7 @@ class ForumManagerImpl implements ForumManager {
BdfDictionary meta = entry.getValue();
long timestamp = meta.getLong(KEY_TIMESTAMP);
Author author = null;
Author.Status authorStatus = ANONYMOUS;
Status authorStatus = ANONYMOUS;
MessageId parentId = null;
if (meta.containsKey(KEY_PARENT))
parentId = new MessageId(meta.getRaw(KEY_PARENT));
......@@ -219,11 +205,8 @@ class ForumManagerImpl implements ForumManager {
String name = d1.getString(KEY_NAME);
byte[] publicKey = d1.getRaw(KEY_PUBLIC_NAME);
author = new Author(authorId, name, publicKey);
if (localAuthorIds.contains(authorId))
authorStatus = VERIFIED;
else if (contactAuthorIds.contains(authorId))
authorStatus = VERIFIED;
else authorStatus = UNKNOWN;
authorStatus =
identityManager.getAuthorStatus(author.getId());
}
String contentType = meta.getString(KEY_CONTENT_TYPE);
boolean read = meta.getBoolean(KEY_READ);
......
......@@ -11,6 +11,7 @@ import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.forum.ForumPostFactory;
import org.briarproject.api.forum.ForumSharingManager;
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;
......@@ -38,9 +39,8 @@ public class ForumModule {
@Provides
@Singleton
ForumManager provideForumManager(DatabaseComponent db,
ClientHelper clientHelper, ForumFactory forumFactory) {
return new ForumManagerImpl(db, clientHelper, forumFactory);
ForumManager provideForumManager(ForumManagerImpl forumManager) {
return forumManager;
}
@Provides
......@@ -88,7 +88,7 @@ public class ForumModule {
LifecycleManager lifecycleManager,
ContactManager contactManager,
MessageQueueManager messageQueueManager,
ForumManager forumManager, ForumFactory forumFactory,
ForumManager forumManager,
ForumSharingManagerImpl forumSharingManager) {
lifecycleManager.registerClient(forumSharingManager);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment