From da68ef78f160589eebe9eac0e6ea061112627a63 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 6 Jun 2016 17:06:43 -0300 Subject: [PATCH 1/2] 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 --- briar-api/build.gradle | 4 +- .../src/org/briarproject/api/blogs/Blog.java | 32 +++ .../briarproject/api/blogs/BlogConstants.java | 39 +++ .../briarproject/api/blogs/BlogFactory.java | 17 ++ .../briarproject/api/blogs/BlogManager.java | 48 ++++ .../org/briarproject/api/blogs/BlogPost.java | 43 +++ .../api/blogs/BlogPostFactory.java | 19 ++ .../api/blogs/BlogPostHeader.java | 43 +++ .../briarproject/api/clients/PostHeader.java | 55 ++++ .../api/forum/ForumPostHeader.java | 47 +--- .../api/identity/IdentityManager.java | 4 + .../org/briarproject/CoreEagerSingletons.java | 3 + .../src/org/briarproject/CoreModule.java | 3 + .../briarproject/blogs/BlogFactoryImpl.java | 61 ++++ .../briarproject/blogs/BlogManagerImpl.java | 260 ++++++++++++++++++ .../blogs/BlogPostFactoryImpl.java | 78 ++++++ .../briarproject/blogs/BlogPostValidator.java | 125 +++++++++ .../org/briarproject/blogs/BlogsModule.java | 63 +++++ .../briarproject/forum/ForumManagerImpl.java | 39 +-- .../org/briarproject/forum/ForumModule.java | 8 +- .../identity/IdentityManagerImpl.java | 25 ++ 21 files changed, 940 insertions(+), 76 deletions(-) create mode 100644 briar-api/src/org/briarproject/api/blogs/Blog.java create mode 100644 briar-api/src/org/briarproject/api/blogs/BlogConstants.java create mode 100644 briar-api/src/org/briarproject/api/blogs/BlogFactory.java create mode 100644 briar-api/src/org/briarproject/api/blogs/BlogManager.java create mode 100644 briar-api/src/org/briarproject/api/blogs/BlogPost.java create mode 100644 briar-api/src/org/briarproject/api/blogs/BlogPostFactory.java create mode 100644 briar-api/src/org/briarproject/api/blogs/BlogPostHeader.java create mode 100644 briar-api/src/org/briarproject/api/clients/PostHeader.java create mode 100644 briar-core/src/org/briarproject/blogs/BlogFactoryImpl.java create mode 100644 briar-core/src/org/briarproject/blogs/BlogManagerImpl.java create mode 100644 briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java create mode 100644 briar-core/src/org/briarproject/blogs/BlogPostValidator.java create mode 100644 briar-core/src/org/briarproject/blogs/BlogsModule.java diff --git a/briar-api/build.gradle b/briar-api/build.gradle index d38a8b520..479b38fdc 100644 --- a/briar-api/build.gradle +++ b/briar-api/build.gradle @@ -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', ] } diff --git a/briar-api/src/org/briarproject/api/blogs/Blog.java b/briar-api/src/org/briarproject/api/blogs/Blog.java new file mode 100644 index 000000000..c97e42be2 --- /dev/null +++ b/briar-api/src/org/briarproject/api/blogs/Blog.java @@ -0,0 +1,32 @@ +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; + } +} diff --git a/briar-api/src/org/briarproject/api/blogs/BlogConstants.java b/briar-api/src/org/briarproject/api/blogs/BlogConstants.java new file mode 100644 index 000000000..60f06a641 --- /dev/null +++ b/briar-api/src/org/briarproject/api/blogs/BlogConstants.java @@ -0,0 +1,39 @@ +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"; + +} diff --git a/briar-api/src/org/briarproject/api/blogs/BlogFactory.java b/briar-api/src/org/briarproject/api/blogs/BlogFactory.java new file mode 100644 index 000000000..8da9db192 --- /dev/null +++ b/briar-api/src/org/briarproject/api/blogs/BlogFactory.java @@ -0,0 +1,17 @@ +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; +} diff --git a/briar-api/src/org/briarproject/api/blogs/BlogManager.java b/briar-api/src/org/briarproject/api/blogs/BlogManager.java new file mode 100644 index 000000000..e01fa5efb --- /dev/null +++ b/briar-api/src/org/briarproject/api/blogs/BlogManager.java @@ -0,0 +1,48 @@ +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 getBlogs(LocalAuthor localAuthor) throws DbException; + + /** Returns all blogs to which the user subscribes. */ + Collection 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 getPostHeaders(GroupId g) throws DbException; + + /** Marks a blog post as read or unread. */ + void setReadFlag(MessageId m, boolean read) throws DbException; + +} diff --git a/briar-api/src/org/briarproject/api/blogs/BlogPost.java b/briar-api/src/org/briarproject/api/blogs/BlogPost.java new file mode 100644 index 000000000..a6aa1167a --- /dev/null +++ b/briar-api/src/org/briarproject/api/blogs/BlogPost.java @@ -0,0 +1,43 @@ +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; + } +} diff --git a/briar-api/src/org/briarproject/api/blogs/BlogPostFactory.java b/briar-api/src/org/briarproject/api/blogs/BlogPostFactory.java new file mode 100644 index 000000000..2565bfb0e --- /dev/null +++ b/briar-api/src/org/briarproject/api/blogs/BlogPostFactory.java @@ -0,0 +1,19 @@ +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; +} diff --git a/briar-api/src/org/briarproject/api/blogs/BlogPostHeader.java b/briar-api/src/org/briarproject/api/blogs/BlogPostHeader.java new file mode 100644 index 000000000..24d421c13 --- /dev/null +++ b/briar-api/src/org/briarproject/api/blogs/BlogPostHeader.java @@ -0,0 +1,43 @@ +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; + } +} diff --git a/briar-api/src/org/briarproject/api/clients/PostHeader.java b/briar-api/src/org/briarproject/api/clients/PostHeader.java new file mode 100644 index 000000000..33f0d2877 --- /dev/null +++ b/briar-api/src/org/briarproject/api/clients/PostHeader.java @@ -0,0 +1,55 @@ +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; + } +} diff --git a/briar-api/src/org/briarproject/api/forum/ForumPostHeader.java b/briar-api/src/org/briarproject/api/forum/ForumPostHeader.java index 407793905..c25a9e251 100644 --- a/briar-api/src/org/briarproject/api/forum/ForumPostHeader.java +++ b/briar-api/src/org/briarproject/api/forum/ForumPostHeader.java @@ -1,56 +1,17 @@ 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; + super(id, parentId, timestamp, author, authorStatus, contentType, 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; - } } diff --git a/briar-api/src/org/briarproject/api/identity/IdentityManager.java b/briar-api/src/org/briarproject/api/identity/IdentityManager.java index f2a4db2d9..e5420a043 100644 --- a/briar-api/src/org/briarproject/api/identity/IdentityManager.java +++ b/briar-api/src/org/briarproject/api/identity/IdentityManager.java @@ -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; } diff --git a/briar-core/src/org/briarproject/CoreEagerSingletons.java b/briar-core/src/org/briarproject/CoreEagerSingletons.java index 174ee9c57..6847f4612 100644 --- a/briar-core/src/org/briarproject/CoreEagerSingletons.java +++ b/briar-core/src/org/briarproject/CoreEagerSingletons.java @@ -1,5 +1,6 @@ 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); diff --git a/briar-core/src/org/briarproject/CoreModule.java b/briar-core/src/org/briarproject/CoreModule.java index 238c859e7..6a909b3f6 100644 --- a/briar-core/src/org/briarproject/CoreModule.java +++ b/briar-core/src/org/briarproject/CoreModule.java @@ -1,5 +1,6 @@ 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()); diff --git a/briar-core/src/org/briarproject/blogs/BlogFactoryImpl.java b/briar-core/src/org/briarproject/blogs/BlogFactoryImpl.java new file mode 100644 index 000000000..b04401f5d --- /dev/null +++ b/briar-core/src/org/briarproject/blogs/BlogFactoryImpl.java @@ -0,0 +1,61 @@ +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); + } + +} diff --git a/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java b/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java new file mode 100644 index 000000000..eabfc251e --- /dev/null +++ b/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java @@ -0,0 +1,260 @@ +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 getBlogs(LocalAuthor localAuthor) + throws DbException { + + Collection allBlogs = getBlogs(); + List blogs = new ArrayList(); + for (Blog b : allBlogs) { + if (b.getAuthor().equals(localAuthor)) { + blogs.add(b); + } + } + return Collections.unmodifiableList(blogs); + } + + @Override + public Collection getBlogs() throws DbException { + try { + List blogs = new ArrayList(); + Collection 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 getPostHeaders(GroupId g) + throws DbException { + + Map metadata; + try { + metadata = clientHelper.getMessageMetadataAsDictionary(g); + } catch (FormatException e) { + throw new DbException(e); + } + Collection headers = new ArrayList(); + for (Entry 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); + } + +} diff --git a/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java b/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java new file mode 100644 index 000000000..e9ced2d3f --- /dev/null +++ b/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java @@ -0,0 +1,78 @@ +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); + } +} diff --git a/briar-core/src/org/briarproject/blogs/BlogPostValidator.java b/briar-core/src/org/briarproject/blogs/BlogPostValidator.java new file mode 100644 index 000000000..bacf02dff --- /dev/null +++ b/briar-core/src/org/briarproject/blogs/BlogPostValidator.java @@ -0,0 +1,125 @@ +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 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); + } +} diff --git a/briar-core/src/org/briarproject/blogs/BlogsModule.java b/briar-core/src/org/briarproject/blogs/BlogsModule.java new file mode 100644 index 000000000..4cf7894d1 --- /dev/null +++ b/briar-core/src/org/briarproject/blogs/BlogsModule.java @@ -0,0 +1,63 @@ +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; + } + +} diff --git a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java index 29bf817aa..36f8357ba 100644 --- a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java +++ b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java @@ -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 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(); @@ -183,24 +181,12 @@ class ForumManagerImpl implements ForumManager { @Override public Collection getPostHeaders(GroupId g) throws DbException { - Set localAuthorIds = new HashSet(); - Set contactAuthorIds = new HashSet(); + Map 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 headers = new ArrayList(); @@ -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); diff --git a/briar-core/src/org/briarproject/forum/ForumModule.java b/briar-core/src/org/briarproject/forum/ForumModule.java index e7494850e..59f56af2a 100644 --- a/briar-core/src/org/briarproject/forum/ForumModule.java +++ b/briar-core/src/org/briarproject/forum/ForumModule.java @@ -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); diff --git a/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java b/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java index b3d7a1f4d..e04e20602 100644 --- a/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java +++ b/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java @@ -1,18 +1,25 @@ package org.briarproject.identity; +import org.briarproject.api.contact.Contact; import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DbException; import org.briarproject.api.db.Transaction; +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 java.util.Collection; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import javax.inject.Inject; +import static org.briarproject.api.identity.Author.Status.UNKNOWN; +import static org.briarproject.api.identity.Author.Status.VERIFIED; + class IdentityManagerImpl implements IdentityManager { private final DatabaseComponent db; private final List addHooks; @@ -87,4 +94,22 @@ class IdentityManagerImpl implements IdentityManager { db.endTransaction(txn); } } + + @Override + 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; + } finally { + db.endTransaction(txn); + } + } } From dc048187f53be02a8f8be30b4c37fefd4caf4d81 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 15 Jun 2016 12:41:43 -0300 Subject: [PATCH 2/2] New Trust Level Indicator Replaces The Stars This implements a generic `TrustIndicatorView` with a `setTrustLevel()` method which updates the drawable according to the `Author.State`. Closes #78 --- briar-android/artwork/trust-indicator.svg | 92 +++++++++++++++++ .../res/drawable-hdpi/identity_anonymous.png | Bin 975 -> 0 bytes .../res/drawable-hdpi/identity_unknown.png | Bin 1416 -> 0 bytes .../res/drawable-hdpi/identity_unverified.png | Bin 1429 -> 0 bytes .../res/drawable-hdpi/identity_verified.png | Bin 1009 -> 0 bytes .../res/drawable-mdpi/identity_anonymous.png | Bin 362 -> 0 bytes .../res/drawable-mdpi/identity_unknown.png | Bin 708 -> 0 bytes .../res/drawable-mdpi/identity_unverified.png | Bin 713 -> 0 bytes .../res/drawable-mdpi/identity_verified.png | Bin 531 -> 0 bytes .../res/drawable-xhdpi/identity_anonymous.png | Bin 853 -> 0 bytes .../res/drawable-xhdpi/identity_unknown.png | Bin 1470 -> 0 bytes .../drawable-xhdpi/identity_unverified.png | Bin 1565 -> 0 bytes .../res/drawable-xhdpi/identity_verified.png | Bin 1139 -> 0 bytes .../drawable/trust_indicator_anonymous.xml | 40 ++++++++ .../res/drawable/trust_indicator_unknown.xml | 40 ++++++++ .../drawable/trust_indicator_unverified.xml | 40 ++++++++ .../res/drawable/trust_indicator_verified.xml | 40 ++++++++ briar-android/res/layout/author_view.xml | 45 --------- .../res/layout/forum_discussion_cell.xml | 15 ++- .../android/forum/ForumActivity.java | 4 + .../android/forum/ForumAdapter.java | 93 ------------------ .../android/forum/ForumEntry.java | 11 ++- .../forum/ForumTestControllerImpl.java | 4 +- .../briarproject/android/util/AuthorView.java | 81 --------------- .../android/util/TrustIndicatorView.java | 44 +++++++++ 25 files changed, 326 insertions(+), 223 deletions(-) create mode 100644 briar-android/artwork/trust-indicator.svg delete mode 100644 briar-android/res/drawable-hdpi/identity_anonymous.png delete mode 100644 briar-android/res/drawable-hdpi/identity_unknown.png delete mode 100644 briar-android/res/drawable-hdpi/identity_unverified.png delete mode 100644 briar-android/res/drawable-hdpi/identity_verified.png delete mode 100644 briar-android/res/drawable-mdpi/identity_anonymous.png delete mode 100644 briar-android/res/drawable-mdpi/identity_unknown.png delete mode 100644 briar-android/res/drawable-mdpi/identity_unverified.png delete mode 100644 briar-android/res/drawable-mdpi/identity_verified.png delete mode 100644 briar-android/res/drawable-xhdpi/identity_anonymous.png delete mode 100644 briar-android/res/drawable-xhdpi/identity_unknown.png delete mode 100644 briar-android/res/drawable-xhdpi/identity_unverified.png delete mode 100644 briar-android/res/drawable-xhdpi/identity_verified.png create mode 100644 briar-android/res/drawable/trust_indicator_anonymous.xml create mode 100644 briar-android/res/drawable/trust_indicator_unknown.xml create mode 100644 briar-android/res/drawable/trust_indicator_unverified.xml create mode 100644 briar-android/res/drawable/trust_indicator_verified.xml delete mode 100644 briar-android/res/layout/author_view.xml delete mode 100644 briar-android/src/org/briarproject/android/forum/ForumAdapter.java delete mode 100644 briar-android/src/org/briarproject/android/util/AuthorView.java create mode 100644 briar-android/src/org/briarproject/android/util/TrustIndicatorView.java diff --git a/briar-android/artwork/trust-indicator.svg b/briar-android/artwork/trust-indicator.svg new file mode 100644 index 000000000..c8f94b6f6 --- /dev/null +++ b/briar-android/artwork/trust-indicator.svg @@ -0,0 +1,92 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/briar-android/res/drawable-hdpi/identity_anonymous.png b/briar-android/res/drawable-hdpi/identity_anonymous.png deleted file mode 100644 index aa539d31b7aad168d93e4d2a45a28a6c3af18c7f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 975 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 diff --git a/briar-android/res/drawable-xhdpi/identity_unknown.png b/briar-android/res/drawable-xhdpi/identity_unknown.png deleted file mode 100644 index e3b91a85841af0985cc2635ea3ec9e516719520e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1470 zcmV;v1ws0WP)Px)cu7P-RA}DqnQe#_RT#&A&&-|mx~5_;t~=sto0Ti6t0!tnT~iYBB`btV=u028 zFibQkNGRCog@}qu2_+N~6v`qj`;eFvmIP-v3{x{J6dBEz)phT>&b>2FAG{mewz_xs zF6+H<|8Tf>&YUy9bI$)fGv~}eNhOt3QgO-{GsqY-sKoEPoIKkwGn=lJik_dyvjO*K ze|KJ^0VsODlSlZCuXUm+wN5m}7&HDFg#UP;8E7tMexD6!QyyI4W+pChGZWjCMb}{V zJqmo3O^*~S-|3_KL(i*>sgT&Okk~)3Qe})8I)JlpC@{%dd(>Kc6qsDB{5}~V=z>*V zI3um1X%$`Ng);Bt6S}b^DnAsN8UvhUG0VQjWKlt5dL+*)^1L1Ek=H#8$dZQ3>cbC zBY+!FDo!C~K}^*IFG`3Y|J;%Unczf;AYHbkYTBLCZ$d+alfaqmO5s8_wODK0dgx%c zd{st@Xa#UP@N_p-zXR49GwqP7oD|`&V&F>`_)=g_trLy7MdERQB%sYhQ?5tLT~RbI z76?_evjG6AXql)u3O~B;S5cZ_X=F$S$^kv1&YwTUZZojUTD##2onH#f0rmlZWFxc< zXiW}_Gkd;i+MUa+nm&8#z3nYd0=SOL{1~_ym|SRl>825LCrh+uaWXybx(roa2fDXX zMOzab-PZ2?xli3!thHbFFazhRGJVwT=?`{n*>|E}o|sD}k2-P4drE{og~oTg77l{o zcem1S`YiOWpPk554TAE%nxKldz^RoNlV7%oylidb_dV4Kg5Y=GuWW8T?Y`#w{HGd3;2q!he+Yu$LLWOn8hBfTRfWoTuhNJM)xN*$^C)q{OgE|-B088HJrI;L zIOm=Xlbc2e&s%God)xRR2rl@(|0VFI@B2+b5S;I2=c|AQU|AvaC5H>S7nK){6OCEs zg-^siRB=$&r^`0l*gZB_Xt35sd4{kHyb2t**1nr#oo9g2zzc=W?{Obst&Ii?4ZEU5 z%FO}p0=uF_sy_z5gAcp_)c2xVeSZx8K=5h%ndEm&@veV&LawKx-^_Oe)X#{~Bmj z&ML>6m1A^?i%I1fzcrRS`mOu1++DjUwfDdG+G(SWndW!RuU(YdtFhc&4E&r7$Ox6g zLn=F#mr0Zl%e;@*TuOfO?4{(^-Qw&`qNrZBVt7bpMlP4{4~fIrjSUA+bl` z%NkXAsc!nz$2*_f*l_TaIE*a@evbwiV=8Aj(bxz`sB*YW_CMTSzIK1g-TX_FU45j< zKDRyeUVEs${QbjavOl3p1k7-vvBsFZO~_v3K*#Z%g;VN{F`r&)b@nwj+2xIm_Q`a+ zbKV&9$()5#IzK266azn(W$4Vu-1LT9ohZ32>;*UCIrKVxFGlM)xgF zq;ESMyX{*eZ>hERyF3SACU7IL7j(JCk`jmay5noi3UPD@$U2}7I9=%cTt3RzZpwH& z!1I#Ax}Tctfh#Q6QotLoG20tMcR7$w3bom{g!!lzr~&4Kyw!Mh{_Dn=n(PKBot?P_ z&CkVviAiCHwRU@s>*fQ%3S-Oz^v_qSiO_5Q(*FhD7~>gZD)M;|WsLDk{E|v4sicC` Y-%B_^7(+Az6#xJL07*qoM6N<$f)t6=NdN!< diff --git a/briar-android/res/drawable-xhdpi/identity_unverified.png b/briar-android/res/drawable-xhdpi/identity_unverified.png deleted file mode 100644 index 85785c09cceadbf4357e8ac8e0f25366ab5c5f19..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1565 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 + + + + + \ No newline at end of file diff --git a/briar-android/res/drawable/trust_indicator_unknown.xml b/briar-android/res/drawable/trust_indicator_unknown.xml new file mode 100644 index 000000000..ec65b5a55 --- /dev/null +++ b/briar-android/res/drawable/trust_indicator_unknown.xml @@ -0,0 +1,40 @@ + + + + + + \ No newline at end of file diff --git a/briar-android/res/drawable/trust_indicator_unverified.xml b/briar-android/res/drawable/trust_indicator_unverified.xml new file mode 100644 index 000000000..3697685b0 --- /dev/null +++ b/briar-android/res/drawable/trust_indicator_unverified.xml @@ -0,0 +1,40 @@ + + + + + + \ No newline at end of file diff --git a/briar-android/res/drawable/trust_indicator_verified.xml b/briar-android/res/drawable/trust_indicator_verified.xml new file mode 100644 index 000000000..40a8ff035 --- /dev/null +++ b/briar-android/res/drawable/trust_indicator_verified.xml @@ -0,0 +1,40 @@ + + + + + + \ No newline at end of file diff --git a/briar-android/res/layout/author_view.xml b/briar-android/res/layout/author_view.xml deleted file mode 100644 index 4fb0b9d6b..000000000 --- a/briar-android/res/layout/author_view.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/briar-android/res/layout/forum_discussion_cell.xml b/briar-android/res/layout/forum_discussion_cell.xml index 57383a720..e8fbf8aa3 100644 --- a/briar-android/res/layout/forum_discussion_cell.xml +++ b/briar-android/res/layout/forum_discussion_cell.xml @@ -131,6 +131,9 @@ android:layout_height="wrap_content" android:layout_alignBaseline="@id/btn_reply" android:layout_toLeftOf="@id/btn_reply" + android:layout_toRightOf="@+id/trustIndicator" + android:gravity="right|end" + android:maxLines="1" android:padding="@dimen/margin_medium" android:textSize="@dimen/text_size_tiny" tools:text="2 replies"/> @@ -140,13 +143,23 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@id/replies" - android:layout_toLeftOf="@id/replies" android:layout_toRightOf="@id/avatar" android:ellipsize="end" android:maxLines="1" android:textSize="@dimen/text_size_tiny" tools:text="09:09 John Smith"/> + + { - - private final int pad; - - ForumAdapter(Context ctx) { - super(ctx, android.R.layout.simple_expandable_list_item_1, - new ArrayList()); - pad = LayoutUtils.getPadding(ctx); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - ForumItem item = getItem(position); - ForumPostHeader header = item.getHeader(); - Context ctx = getContext(); - Resources res = ctx.getResources(); - - LinearLayout layout = new LinearLayout(ctx); - layout.setOrientation(VERTICAL); - layout.setGravity(CENTER_HORIZONTAL); - if (!header.isRead()) - layout.setBackgroundColor(res.getColor(R.color.unread_background)); - - LinearLayout headerLayout = new LinearLayout(ctx); - headerLayout.setOrientation(HORIZONTAL); - headerLayout.setGravity(CENTER_VERTICAL); - - AuthorView authorView = new AuthorView(ctx); - authorView.setLayoutParams(WRAP_WRAP_1); - authorView.setPadding(0, pad, pad, pad); - Author author = header.getAuthor(); - if (author == null) { - authorView.init(null, null, header.getAuthorStatus()); - } else { - authorView.init(author.getName(), author.getId(), - header.getAuthorStatus()); - } - headerLayout.addView(authorView); - - TextView date = new TextView(ctx); - date.setPadding(pad, pad, pad, pad); - long timestamp = header.getTimestamp(); - date.setText(DateUtils.getRelativeTimeSpanString(ctx, timestamp)); - headerLayout.addView(date); - layout.addView(headerLayout); - - if (item.getBody() == null) { - TextView ellipsis = new TextView(ctx); - ellipsis.setPadding(pad, 0, pad, pad); - ellipsis.setText("\u2026"); - layout.addView(ellipsis); - } else if (header.getContentType().equals("text/plain")) { - TextView text = new TextView(ctx); - text.setPadding(pad, 0, pad, pad); - text.setText(StringUtils.fromUtf8(item.getBody())); - layout.addView(text); - } else { - ImageButton attachment = new ImageButton(ctx); - attachment.setPadding(pad, 0, pad, pad); - attachment.setImageResource(R.drawable.content_attachment); - layout.addView(attachment); - } - - return layout; - } -} \ No newline at end of file diff --git a/briar-android/src/org/briarproject/android/forum/ForumEntry.java b/briar-android/src/org/briarproject/android/forum/ForumEntry.java index c3c668f7a..a24d94bbc 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumEntry.java +++ b/briar-android/src/org/briarproject/android/forum/ForumEntry.java @@ -1,6 +1,7 @@ package org.briarproject.android.forum; import org.briarproject.api.forum.ForumPostHeader; +import org.briarproject.api.identity.Author.Status; import org.briarproject.api.identity.AuthorId; import org.briarproject.api.sync.MessageId; @@ -12,23 +13,25 @@ public class ForumEntry { private final long timestamp; private final String author; private final AuthorId authorId; + private Status status; private boolean isShowingDescendants = true; private boolean isRead = true; public ForumEntry(ForumPostHeader h, String text, int level) { this(h.getId(), text, level, h.getTimestamp(), h.getAuthor().getName(), - h.getAuthor().getId()); + h.getAuthor().getId(), h.getAuthorStatus()); this.isRead = h.isRead(); } public ForumEntry(MessageId messageId, String text, int level, - long timestamp, String author, AuthorId authorId) { + long timestamp, String author, AuthorId authorId, Status status) { this.messageId = messageId; this.text = text; this.level = level; this.timestamp = timestamp; this.author = author; this.authorId = authorId; + this.status = status; } public String getText() { @@ -51,6 +54,10 @@ public class ForumEntry { return authorId; } + public Status getStatus() { + return status; + } + public boolean isShowingDescendants() { return isShowingDescendants; } diff --git a/briar-android/src/org/briarproject/android/forum/ForumTestControllerImpl.java b/briar-android/src/org/briarproject/android/forum/ForumTestControllerImpl.java index ec1470ed8..2aef2ae5e 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumTestControllerImpl.java +++ b/briar-android/src/org/briarproject/android/forum/ForumTestControllerImpl.java @@ -15,6 +15,8 @@ import java.util.logging.Logger; import javax.inject.Inject; +import static org.briarproject.api.identity.Author.Status.UNVERIFIED; + public class ForumTestControllerImpl implements ForumController { private static final Logger LOG = @@ -115,7 +117,7 @@ public class ForumTestControllerImpl implements ForumController { forumEntries[e] = new ForumEntry(new MessageId(b), SAGA.substring(0, i[e]), l[e], timestamp, AUTHORS[authorIndex], - AUTHOR_ID[authorIndex]); + AUTHOR_ID[authorIndex], UNVERIFIED); } LOG.info("forum entries: " + forumEntries.length); resultHandler.onResult(true); diff --git a/briar-android/src/org/briarproject/android/util/AuthorView.java b/briar-android/src/org/briarproject/android/util/AuthorView.java deleted file mode 100644 index 4b11d51e8..000000000 --- a/briar-android/src/org/briarproject/android/util/AuthorView.java +++ /dev/null @@ -1,81 +0,0 @@ -package org.briarproject.android.util; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.TextView; - -import org.briarproject.R; -import org.briarproject.api.crypto.CryptoComponent; -import org.briarproject.api.identity.Author; -import org.briarproject.api.identity.AuthorId; - -import javax.inject.Inject; - -import im.delight.android.identicons.IdenticonDrawable; - -public class AuthorView extends FrameLayout { - - private ImageView avatarView; - private TextView nameView; - private ImageView statusView; - - public AuthorView(Context ctx) { - super(ctx); - - initViews(); - } - - public AuthorView(Context context, AttributeSet attrs) { - super(context, attrs); - - initViews(); - } - - public AuthorView(Context context, AttributeSet attrs, - int defStyle) { - super(context, attrs, defStyle); - - initViews(); - } - - private void initViews() { - if (isInEditMode()) - return; - - View v = LayoutInflater.from(getContext()).inflate( - R.layout.author_view, this, true); - - avatarView = (ImageView) v.findViewById(R.id.avatarView); - nameView = (TextView) v.findViewById(R.id.nameView); - statusView = (ImageView) v.findViewById(R.id.statusView); - } - - public void init(String name, AuthorId id, Author.Status status) { - if (name == null) { - nameView.setText(R.string.anonymous); - } else { - nameView.setText(name); - avatarView.setImageDrawable( - new IdenticonDrawable(id.getBytes())); - } - - switch(status) { - case ANONYMOUS: - statusView.setImageResource(R.drawable.identity_anonymous); - break; - case UNKNOWN: - statusView.setImageResource(R.drawable.identity_unknown); - break; - case UNVERIFIED: - statusView.setImageResource(R.drawable.identity_unverified); - break; - case VERIFIED: - statusView.setImageResource(R.drawable.identity_verified); - break; - } - } -} diff --git a/briar-android/src/org/briarproject/android/util/TrustIndicatorView.java b/briar-android/src/org/briarproject/android/util/TrustIndicatorView.java new file mode 100644 index 000000000..ce01bf301 --- /dev/null +++ b/briar-android/src/org/briarproject/android/util/TrustIndicatorView.java @@ -0,0 +1,44 @@ +package org.briarproject.android.util; + +import android.content.Context; +import android.support.v4.content.ContextCompat; +import android.util.AttributeSet; +import android.widget.ImageView; + +import org.briarproject.R; +import org.briarproject.api.identity.Author.Status; + +public class TrustIndicatorView extends ImageView { + + public TrustIndicatorView(Context context) { + super(context); + } + + public TrustIndicatorView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public TrustIndicatorView(Context context, AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public void setTrustLevel(Status status) { + int res; + switch (status) { + case ANONYMOUS: + res = R.drawable.trust_indicator_anonymous; + break; + case UNVERIFIED: + res = R.drawable.trust_indicator_unverified; + break; + case VERIFIED: + res = R.drawable.trust_indicator_verified; + break; + default: + res = R.drawable.trust_indicator_unknown; + } + setImageDrawable(ContextCompat.getDrawable(getContext(), res)); + } + +}