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:
parent
d76f72ed88
commit
a6a8634cab
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue