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] Fix diagram in spec doc
|
||||||
- [X] refactor `Bundle.create` to use `message find-all`.
|
- [X] refactor `Bundle.create` to use `message find-all`.
|
||||||
- [X] Rename `message find` to `message read`, since other finders return a multihash.
|
- [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.
|
- [ ] 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.
|
- [ ] Add Lipmaa links like the Bamboo folks do.
|
||||||
- [ ] Create regexes in ::Lexer using strings and Regexp.new() for cleaner regexes.
|
- [ ] Create regexes in ::Lexer using strings and Regexp.new() for cleaner regexes.
|
||||||
|
|
|
@ -111,7 +111,7 @@ module Pigeon
|
||||||
def self.create_message(kind, params)
|
def self.create_message(kind, params)
|
||||||
draft = Pigeon::Draft.create(kind: kind)
|
draft = Pigeon::Draft.create(kind: kind)
|
||||||
params.map { |(k, v)| draft[k] = v }
|
params.map { |(k, v)| draft[k] = v }
|
||||||
Pigeon::Message.publish(draft)
|
draft.publish
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.verify_string(identity, string_signature, string)
|
def self.verify_string(identity, string_signature, string)
|
||||||
|
|
|
@ -2,7 +2,8 @@ require "digest"
|
||||||
|
|
||||||
module Pigeon
|
module Pigeon
|
||||||
class Draft
|
class Draft
|
||||||
attr_reader :kind, :body, :internal_id
|
attr_reader :signature, :prev, :kind, :internal_id,
|
||||||
|
:depth, :body, :author
|
||||||
|
|
||||||
def self.create(kind:, body: {})
|
def self.create(kind:, body: {})
|
||||||
self.new(kind: kind, body: body).save
|
self.new(kind: kind, body: body).save
|
||||||
|
@ -23,8 +24,12 @@ module Pigeon
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(kind:, body: {})
|
def initialize(kind:, body: {})
|
||||||
|
@signature = Pigeon::EMPTY_MESSAGE
|
||||||
|
@prev = Pigeon::EMPTY_MESSAGE
|
||||||
@kind = kind
|
@kind = kind
|
||||||
|
@depth = -1
|
||||||
@body = body
|
@body = body
|
||||||
|
@author = Pigeon::EMPTY_MESSAGE
|
||||||
@internal_id = SecureRandom.uuid
|
@internal_id = SecureRandom.uuid
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -49,12 +54,36 @@ module Pigeon
|
||||||
end
|
end
|
||||||
|
|
||||||
def save
|
def save
|
||||||
|
puts "Rename to `save_as_draft` to avoid confusion"
|
||||||
Pigeon::Storage.current.set_config(CURRENT_DRAFT, self)
|
Pigeon::Storage.current.set_config(CURRENT_DRAFT, self)
|
||||||
self
|
self
|
||||||
end
|
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
|
def render
|
||||||
|
puts "Rename to `render_as_draft` to avoid confusion."
|
||||||
|
puts "Do we even need DraftSerializer any more?"
|
||||||
DraftSerializer.new(self).render
|
DraftSerializer.new(self).render
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def store
|
||||||
|
Pigeon::Storage.current
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,23 +7,6 @@ module Pigeon
|
||||||
class VerificationError < StandardError; end
|
class VerificationError < StandardError; end
|
||||||
|
|
||||||
VERFIY_ERROR = "Expected field `%s` to equal %s, got: %s"
|
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)
|
# Store a message that someone (not the LocalIdentity)
|
||||||
# has authored.
|
# has authored.
|
||||||
|
@ -41,13 +24,15 @@ module Pigeon
|
||||||
end
|
end
|
||||||
|
|
||||||
def multihash
|
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}"
|
"#{MESSAGE_SIGIL}#{sha256}#{BLOB_FOOTER}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def save!
|
def save!
|
||||||
|
puts "TODO: Make this method private."
|
||||||
return store.read_message(multihash) if store.message?(multihash)
|
return store.read_message(multihash) if store.message?(multihash)
|
||||||
calculate_signature
|
|
||||||
verify_depth_prev_and_depth
|
verify_depth_prev_and_depth
|
||||||
verify_signature
|
verify_signature
|
||||||
self.freeze
|
self.freeze
|
||||||
|
@ -86,22 +71,12 @@ module Pigeon
|
||||||
@signature = signature
|
@signature = signature
|
||||||
end
|
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
|
def template
|
||||||
MessageSerializer.new(self)
|
MessageSerializer.new(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.store
|
|
||||||
Pigeon::Storage.current
|
|
||||||
end
|
|
||||||
|
|
||||||
def store
|
def store
|
||||||
self.class.store
|
Pigeon::Storage.current
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -108,7 +108,7 @@ RSpec.describe Pigeon::Lexer do
|
||||||
let(:message) do
|
let(:message) do
|
||||||
draft = Pigeon::Draft.create(kind: "unit_test")
|
draft = Pigeon::Draft.create(kind: "unit_test")
|
||||||
draft["foo"] = "bar"
|
draft["foo"] = "bar"
|
||||||
Pigeon::Message.publish(draft)
|
draft.publish
|
||||||
end
|
end
|
||||||
|
|
||||||
before(:each) do
|
before(:each) do
|
||||||
|
|
|
@ -14,7 +14,7 @@ RSpec.describe Pigeon::Message do
|
||||||
|
|
||||||
def create_message(params)
|
def create_message(params)
|
||||||
draft = create_draft(params)
|
draft = create_draft(params)
|
||||||
Pigeon::Message.publish(draft)
|
draft.publish
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:draft) do
|
let(:draft) do
|
||||||
|
@ -29,13 +29,13 @@ RSpec.describe Pigeon::Message do
|
||||||
|
|
||||||
it "discards a draft after signing" do
|
it "discards a draft after signing" do
|
||||||
expect(draft.internal_id).to eq(Pigeon::Draft.current.internal_id)
|
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")
|
expect { Pigeon::Draft.current }.to raise_error("NO DRAFT FOUND")
|
||||||
end
|
end
|
||||||
|
|
||||||
it "creates a single message" do
|
it "creates a single message" do
|
||||||
message = Pigeon::Message.publish(draft)
|
message = draft.publish
|
||||||
expect(message.author).to eq(Pigeon::LocalIdentity.current)
|
expect(message.author.multihash).to eq(Pigeon::LocalIdentity.current.multihash)
|
||||||
expect(message.body).to eq(draft.body)
|
expect(message.body).to eq(draft.body)
|
||||||
expect(message.depth).to eq(0)
|
expect(message.depth).to eq(0)
|
||||||
expect(message.kind).to eq("unit_test")
|
expect(message.kind).to eq("unit_test")
|
||||||
|
@ -64,7 +64,7 @@ RSpec.describe Pigeon::Message do
|
||||||
0.upto(4) do |expected_depth|
|
0.upto(4) do |expected_depth|
|
||||||
draft1 = Pigeon::Draft.create(kind: "unit_test")
|
draft1 = Pigeon::Draft.create(kind: "unit_test")
|
||||||
draft1["description"] = "Message number #{expected_depth}"
|
draft1["description"] = "Message number #{expected_depth}"
|
||||||
message = Pigeon::Message.publish(draft1)
|
message = draft1.publish
|
||||||
all.push(message)
|
all.push(message)
|
||||||
expect(message.depth).to eq(expected_depth)
|
expect(message.depth).to eq(expected_depth)
|
||||||
if expected_depth == 0
|
if expected_depth == 0
|
||||||
|
@ -143,7 +143,7 @@ RSpec.describe Pigeon::Message do
|
||||||
kind[rand(0...8)] = n
|
kind[rand(0...8)] = n
|
||||||
draft = Pigeon::Draft.create(kind: kind)
|
draft = Pigeon::Draft.create(kind: kind)
|
||||||
draft["body"] = "empty"
|
draft["body"] = "empty"
|
||||||
tpl = Pigeon::Message.publish(draft).render
|
tpl = draft.publish.render
|
||||||
boom = ->() { Pigeon::Lexer.tokenize(tpl) }
|
boom = ->() { Pigeon::Lexer.tokenize(tpl) }
|
||||||
expect(boom).to raise_error(Pigeon::Lexer::LexError)
|
expect(boom).to raise_error(Pigeon::Lexer::LexError)
|
||||||
end
|
end
|
||||||
|
@ -155,7 +155,7 @@ RSpec.describe Pigeon::Message do
|
||||||
key = SecureRandom.alphanumeric(8)
|
key = SecureRandom.alphanumeric(8)
|
||||||
key[rand(0...8)] = n
|
key[rand(0...8)] = n
|
||||||
draft[key] = "should crash"
|
draft[key] = "should crash"
|
||||||
tpl = Pigeon::Message.publish(draft).render
|
tpl = draft.publish.render
|
||||||
boom = ->() { Pigeon::Lexer.tokenize(tpl) }
|
boom = ->() { Pigeon::Lexer.tokenize(tpl) }
|
||||||
expect(boom).to raise_error(Pigeon::Lexer::LexError)
|
expect(boom).to raise_error(Pigeon::Lexer::LexError)
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue