From a6a8634cab0fae3324a181945355d6b4c21d239f Mon Sep 17 00:00:00 2001 From: Rick Carlino Date: Tue, 7 Apr 2020 08:58:53 -0500 Subject: [PATCH] WIP. 7 tests failing. Refactor Pigeon::Draft / ::Message Pigeon::Message was holding on to too many drafting responsibilities. It also was possible for the local identity to inadvertantly author malformed messages. This is because local messages were not passed through the ::Lexer / ::Parser and thereby did not receive the same scrutiny of remote messages. To avoid security problems later, and for additional security, I will only allow messages to be saved *after* passing through the lexer/parser. This means moving much of Pigeon::Message's logic into Pigeon::Draft. --- README.md | 1 + dist/pigeon.rb | 2 +- dist/pigeon/draft.rb | 31 ++++++++++++++++++++++++++++++- dist/pigeon/message.rb | 35 +++++------------------------------ spec/pigeon/lexer_spec.rb | 2 +- spec/pigeon/message_spec.rb | 14 +++++++------- 6 files changed, 45 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index a2b15ae..a2ac373 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ Eg: `pigeon identity show` becomes `./pigeon-cli show`. - [X] Fix diagram in spec doc - [X] refactor `Bundle.create` to use `message find-all`. - [X] Rename `message find` to `message read`, since other finders return a multihash. + - [ ] Message.ingest should be the only code path to message authoring. - [ ] Don't allow any type of whitespace in `kind` or `string` keys. Write a test for this. - [ ] Add Lipmaa links like the Bamboo folks do. - [ ] Create regexes in ::Lexer using strings and Regexp.new() for cleaner regexes. diff --git a/dist/pigeon.rb b/dist/pigeon.rb index 340edd7..4b8933c 100644 --- a/dist/pigeon.rb +++ b/dist/pigeon.rb @@ -111,7 +111,7 @@ module Pigeon def self.create_message(kind, params) draft = Pigeon::Draft.create(kind: kind) params.map { |(k, v)| draft[k] = v } - Pigeon::Message.publish(draft) + draft.publish end def self.verify_string(identity, string_signature, string) diff --git a/dist/pigeon/draft.rb b/dist/pigeon/draft.rb index 4da309e..8452f46 100644 --- a/dist/pigeon/draft.rb +++ b/dist/pigeon/draft.rb @@ -2,7 +2,8 @@ require "digest" module Pigeon class Draft - attr_reader :kind, :body, :internal_id + attr_reader :signature, :prev, :kind, :internal_id, + :depth, :body, :author def self.create(kind:, body: {}) self.new(kind: kind, body: body).save @@ -23,8 +24,12 @@ module Pigeon end def initialize(kind:, body: {}) + @signature = Pigeon::EMPTY_MESSAGE + @prev = Pigeon::EMPTY_MESSAGE @kind = kind + @depth = -1 @body = body + @author = Pigeon::EMPTY_MESSAGE @internal_id = SecureRandom.uuid end @@ -49,12 +54,36 @@ module Pigeon end def save + puts "Rename to `save_as_draft` to avoid confusion" Pigeon::Storage.current.set_config(CURRENT_DRAFT, self) self end + # Author a new message. + def publish + count = store.get_message_count_for(author) - 1 + template = MessageSerializer.new(self) + + @author = LocalIdentity.current + @prev = store.get_message_by_depth(author.multihash, count) + @depth = store.get_message_count_for(author.multihash) + @signature = author.sign(template.render_without_signature) + + candidate = template.render + tokens = Lexer.tokenize(candidate) + message = Parser.parse(tokens)[0] + self.discard + message + end + def render + puts "Rename to `render_as_draft` to avoid confusion." + puts "Do we even need DraftSerializer any more?" DraftSerializer.new(self).render end + + def store + Pigeon::Storage.current + end end end diff --git a/dist/pigeon/message.rb b/dist/pigeon/message.rb index a31a55c..f25e72a 100644 --- a/dist/pigeon/message.rb +++ b/dist/pigeon/message.rb @@ -7,23 +7,6 @@ module Pigeon class VerificationError < StandardError; end VERFIY_ERROR = "Expected field `%s` to equal %s, got: %s" - # Author a new message. - def self.publish(draft) - author = LocalIdentity.current - depth = Pigeon::Storage - .current - .get_message_count_for(author.multihash) - count = store.get_message_count_for(author.multihash) - prev = store.get_message_by_depth(author.multihash, count - 1) - msg = self.new(author: author, - kind: draft.kind, - body: draft.body, - depth: depth, - prev: prev) - msg.save! - draft.discard - msg - end # Store a message that someone (not the LocalIdentity) # has authored. @@ -41,13 +24,15 @@ module Pigeon end def multihash - sha256 = Helpers.b32_encode(Digest::SHA256.digest(self.render)) + tpl = self.render + digest = Digest::SHA256.digest(tpl) + sha256 = Helpers.b32_encode(digest) "#{MESSAGE_SIGIL}#{sha256}#{BLOB_FOOTER}" end def save! + puts "TODO: Make this method private." return store.read_message(multihash) if store.message?(multihash) - calculate_signature verify_depth_prev_and_depth verify_signature self.freeze @@ -86,22 +71,12 @@ module Pigeon @signature = signature end - def calculate_signature - return if @signature - #TODO: Verify that the author is Pigeon::LocalIdentity.current? - @signature = author.sign(template.render_without_signature) - end - def template MessageSerializer.new(self) end - def self.store - Pigeon::Storage.current - end - def store - self.class.store + Pigeon::Storage.current end end end diff --git a/spec/pigeon/lexer_spec.rb b/spec/pigeon/lexer_spec.rb index fab22f9..176c913 100644 --- a/spec/pigeon/lexer_spec.rb +++ b/spec/pigeon/lexer_spec.rb @@ -108,7 +108,7 @@ RSpec.describe Pigeon::Lexer do let(:message) do draft = Pigeon::Draft.create(kind: "unit_test") draft["foo"] = "bar" - Pigeon::Message.publish(draft) + draft.publish end before(:each) do diff --git a/spec/pigeon/message_spec.rb b/spec/pigeon/message_spec.rb index ae61618..aa03476 100644 --- a/spec/pigeon/message_spec.rb +++ b/spec/pigeon/message_spec.rb @@ -14,7 +14,7 @@ RSpec.describe Pigeon::Message do def create_message(params) draft = create_draft(params) - Pigeon::Message.publish(draft) + draft.publish end let(:draft) do @@ -29,13 +29,13 @@ RSpec.describe Pigeon::Message do it "discards a draft after signing" do expect(draft.internal_id).to eq(Pigeon::Draft.current.internal_id) - Pigeon::Message.publish(draft) + draft.publish expect { Pigeon::Draft.current }.to raise_error("NO DRAFT FOUND") end it "creates a single message" do - message = Pigeon::Message.publish(draft) - expect(message.author).to eq(Pigeon::LocalIdentity.current) + message = draft.publish + expect(message.author.multihash).to eq(Pigeon::LocalIdentity.current.multihash) expect(message.body).to eq(draft.body) expect(message.depth).to eq(0) expect(message.kind).to eq("unit_test") @@ -64,7 +64,7 @@ RSpec.describe Pigeon::Message do 0.upto(4) do |expected_depth| draft1 = Pigeon::Draft.create(kind: "unit_test") draft1["description"] = "Message number #{expected_depth}" - message = Pigeon::Message.publish(draft1) + message = draft1.publish all.push(message) expect(message.depth).to eq(expected_depth) if expected_depth == 0 @@ -143,7 +143,7 @@ RSpec.describe Pigeon::Message do kind[rand(0...8)] = n draft = Pigeon::Draft.create(kind: kind) draft["body"] = "empty" - tpl = Pigeon::Message.publish(draft).render + tpl = draft.publish.render boom = ->() { Pigeon::Lexer.tokenize(tpl) } expect(boom).to raise_error(Pigeon::Lexer::LexError) end @@ -155,7 +155,7 @@ RSpec.describe Pigeon::Message do key = SecureRandom.alphanumeric(8) key[rand(0...8)] = n draft[key] = "should crash" - tpl = Pigeon::Message.publish(draft).render + tpl = draft.publish.render boom = ->() { Pigeon::Lexer.tokenize(tpl) } expect(boom).to raise_error(Pigeon::Lexer::LexError) end