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.
This commit is contained in:
Netscape Navigator 2020-04-07 08:58:53 -05:00
parent d76f72ed88
commit a6a8634cab
6 changed files with 45 additions and 40 deletions

View File

@ -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.

2
dist/pigeon.rb vendored
View File

@ -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)

31
dist/pigeon/draft.rb vendored
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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