Merge pull request #65 from SuperTux88/refactor-relayable-signatures

Refactor relayable signatures
This commit is contained in:
cmrd Senya 2017-06-03 11:57:54 +03:00
commit 7a28db7d76
No known key found for this signature in database
GPG key ID: 5FCC5BA680E67BFE
13 changed files with 62 additions and 143 deletions

View file

@ -16,7 +16,6 @@ See also: [Relayable][relayable]
| `text` | [Markdown][markdown] (65535) | The comment text. | | `text` | [Markdown][markdown] (65535) | The comment text. |
| `created_at` | [Timestamp][timestamp] | The create timestamp of the comment. | | `created_at` | [Timestamp][timestamp] | The create timestamp of the comment. |
| `author_signature` | [Signature][signature] | The signature from the author of the comment. | | `author_signature` | [Signature][signature] | The signature from the author of the comment. |
| `parent_author_signature` | [Signature][signature] | The signature from the parent entity author. |
## Optional Properties ## Optional Properties
@ -26,8 +25,6 @@ See also: [Relayable][relayable]
## Examples ## Examples
### From author
~~~xml ~~~xml
<comment> <comment>
<author>alice@example.org</author> <author>alice@example.org</author>
@ -36,21 +33,6 @@ See also: [Relayable][relayable]
<parent_guid>c3893bf029e7013487753131731751e9</parent_guid> <parent_guid>c3893bf029e7013487753131731751e9</parent_guid>
<text>this is a very informative comment</text> <text>this is a very informative comment</text>
<author_signature>cGIsxB5hU/94+rmgIg/Z+OUvXVYcY/kMOvc267ybpk1pT44P1JiWfnI26F1Mta62UjzIW/SjeAO0RIsJRguaISLpXX/d5DJCMpePAZaZiagUbdgH/w4L++fXiPxBKkSm+PB4txxmHGN8FHjwEUJFHJ1m3VfU4w2JC8+IBU93eag=</author_signature> <author_signature>cGIsxB5hU/94+rmgIg/Z+OUvXVYcY/kMOvc267ybpk1pT44P1JiWfnI26F1Mta62UjzIW/SjeAO0RIsJRguaISLpXX/d5DJCMpePAZaZiagUbdgH/w4L++fXiPxBKkSm+PB4txxmHGN8FHjwEUJFHJ1m3VfU4w2JC8+IBU93eag=</author_signature>
<parent_author_signature/>
</comment>
~~~
### From parent author
~~~xml
<comment>
<author>alice@example.org</author>
<guid>5c241a3029f8013487763131731751e9</guid>
<created_at>2016-07-12T00:49:06Z</created_at>
<parent_guid>c3893bf029e7013487753131731751e9</parent_guid>
<text>this is a very informative comment</text>
<author_signature>cGIsxB5hU/94+rmgIg/Z+OUvXVYcY/kMOvc267ybpk1pT44P1JiWfnI26F1Mta62UjzIW/SjeAO0RIsJRguaISLpXX/d5DJCMpePAZaZiagUbdgH/w4L++fXiPxBKkSm+PB4txxmHGN8FHjwEUJFHJ1m3VfU4w2JC8+IBU93eag=</author_signature>
<parent_author_signature>uzjxUSqR8DQBSBa6abY7R/s9DVzT6UAgTctRcUu5rV5o0iXJD2MR6kp6bsVH3nMbbNvOjwAtrdfz3SVHT2gD8M5PmoFagxK7m5T1c9FB0i+wknoAah0Si0c2sP/BPLnkQ83DgLjF+JZCzMX4sWKvYfyaMjnih1MtQILGyuiwA0E=</parent_author_signature>
</comment> </comment>
~~~ ~~~

View file

@ -21,12 +21,9 @@ See also: [Relayable][relayable]
| `parent_type` | [Type][type] | The entity type of the parent. | | `parent_type` | [Type][type] | The entity type of the parent. |
| `positive` | [Boolean][boolean] | `true` if it is a like, `false` if it is a dislike. | | `positive` | [Boolean][boolean] | `true` if it is a like, `false` if it is a dislike. |
| `author_signature` | [Signature][signature] | The signature from the author of the like. | | `author_signature` | [Signature][signature] | The signature from the author of the like. |
| `parent_author_signature` | [Signature][signature] | The signature from the parent entity author. |
## Examples ## Examples
### From author
~~~xml ~~~xml
<like> <like>
<positive>true</positive> <positive>true</positive>
@ -35,21 +32,6 @@ See also: [Relayable][relayable]
<parent_guid>947a854029f7013487753131731751e9</parent_guid> <parent_guid>947a854029f7013487753131731751e9</parent_guid>
<author>alice@example.org</author> <author>alice@example.org</author>
<author_signature>gk8e+K7XRjVRblv8B8PVOf7BpURbf5HrXO5rmq8D/AkPO7lA0+Akwouu5JGKAHIhPR3dfXVp0o6bIDD+e8gtMYRdDd5IHRfBGNk3WsQecnbhmesHy40Qca/dCQcdcXd5aeWHJKeyUrSAvS55U6VUpk/DK/4IIEZfnr0T9+jM8I0=</author_signature> <author_signature>gk8e+K7XRjVRblv8B8PVOf7BpURbf5HrXO5rmq8D/AkPO7lA0+Akwouu5JGKAHIhPR3dfXVp0o6bIDD+e8gtMYRdDd5IHRfBGNk3WsQecnbhmesHy40Qca/dCQcdcXd5aeWHJKeyUrSAvS55U6VUpk/DK/4IIEZfnr0T9+jM8I0=</author_signature>
<parent_author_signature/>
</like>
~~~
### From parent author
~~~xml
<like>
<positive>true</positive>
<guid>947a88f029f7013487753131731751e9</guid>
<parent_type>Post</parent_type>
<parent_guid>947a854029f7013487753131731751e9</parent_guid>
<author>alice@example.org</author>
<author_signature>gk8e+K7XRjVRblv8B8PVOf7BpURbf5HrXO5rmq8D/AkPO7lA0+Akwouu5JGKAHIhPR3dfXVp0o6bIDD+e8gtMYRdDd5IHRfBGNk3WsQecnbhmesHy40Qca/dCQcdcXd5aeWHJKeyUrSAvS55U6VUpk/DK/4IIEZfnr0T9+jM8I0=</author_signature>
<parent_author_signature>0oAjHO8uIn2Z3Gcmo1KF8su0c7bqI6MzTRq5JagGaZVkFVU8WlNqtwamu6xlmpcAoClGpI5xvbnHzyw5YA8NS8KmUy8BUpg67Mq4QsHHBtueNxHuhgRjszN2V0S8BUKFHGJnnvXmZ/P6YGOOomDgp9I/7zIOownvIm5wj2MotWw=</parent_author_signature>
</like> </like>
~~~ ~~~

View file

@ -15,12 +15,9 @@ See also: [Relayable][relayable]
| `parent_guid` | [GUID][guid] | The GUID of the [Poll][poll]. | | `parent_guid` | [GUID][guid] | The GUID of the [Poll][poll]. |
| `poll_answer_guid` | [GUID][guid] | The GUID of the [PollAnswer][poll_answer]. | | `poll_answer_guid` | [GUID][guid] | The GUID of the [PollAnswer][poll_answer]. |
| `author_signature` | [Signature][signature] | The signature from the author of the poll participation. | | `author_signature` | [Signature][signature] | The signature from the author of the poll participation. |
| `parent_author_signature` | [Signature][signature] | The signature from the author of the [Poll][poll]. |
## Examples ## Examples
### From author
~~~xml ~~~xml
<poll_participation> <poll_participation>
<guid>f1eb866029f7013487753131731751e9</guid> <guid>f1eb866029f7013487753131731751e9</guid>
@ -28,20 +25,6 @@ See also: [Relayable][relayable]
<author>alice@example.org</author> <author>alice@example.org</author>
<poll_answer_guid>2a22db2029e9013487753131731751e9</poll_answer_guid> <poll_answer_guid>2a22db2029e9013487753131731751e9</poll_answer_guid>
<author_signature>dT6KbT7kp0bE+s3//ZErxO1wvVIqtD0lY67i81+dO43B4D2m5kjCdzW240eWt/jZmcHIsdxXf4WHNdrb6ZDnamA8I1FUVnLjHA9xexBITQsSLXrcV88UdammSmmOxl1Ac4VUXqFpdavm6a7/MwOJ7+JHP8TbUO9siN+hMfgUbtY=</author_signature> <author_signature>dT6KbT7kp0bE+s3//ZErxO1wvVIqtD0lY67i81+dO43B4D2m5kjCdzW240eWt/jZmcHIsdxXf4WHNdrb6ZDnamA8I1FUVnLjHA9xexBITQsSLXrcV88UdammSmmOxl1Ac4VUXqFpdavm6a7/MwOJ7+JHP8TbUO9siN+hMfgUbtY=</author_signature>
<parent_author_signature/>
</poll_participation>
~~~
### From parent author
~~~xml
<poll_participation>
<guid>f1eb866029f7013487753131731751e9</guid>
<parent_guid>2a22d6c029e9013487753131731751e9</parent_guid>
<author>alice@example.org</author>
<poll_answer_guid>2a22db2029e9013487753131731751e9</poll_answer_guid>
<author_signature>dT6KbT7kp0bE+s3//ZErxO1wvVIqtD0lY67i81+dO43B4D2m5kjCdzW240eWt/jZmcHIsdxXf4WHNdrb6ZDnamA8I1FUVnLjHA9xexBITQsSLXrcV88UdammSmmOxl1Ac4VUXqFpdavm6a7/MwOJ7+JHP8TbUO9siN+hMfgUbtY=</author_signature>
<parent_author_signature>gWasNPpSnMcKBIMWyzfoVO6sr8eRYkhUqy3PIkkh53n/ki+DM9mnh3ayotI0+6un9aq1N3XkS7Vn05ZD3+nHVby6i21XkYgPnbD8pWYuBBj7VGPyahT70BUs/vSvY8KX8V3wYfsPsaiAgJsAFg2UHYdY3r4/oWdIIbBZc21O3zk=</parent_author_signature>
</poll_participation> </poll_participation>
~~~ ~~~

View file

@ -21,24 +21,25 @@ All relayables have the following properties:
| `guid` | [GUID][guid] | The GUID of the relayable. | | `guid` | [GUID][guid] | The GUID of the relayable. |
| `parent_guid` | [GUID][guid] | The GUID of the parent entity. | | `parent_guid` | [GUID][guid] | The GUID of the parent entity. |
| `author_signature` | [Signature][signature] | The signature from the author of the relayable. | | `author_signature` | [Signature][signature] | The signature from the author of the relayable. |
| `parent_author_signature` | [Signature][signature] | The signature from the parent entity author. |
## Relaying ## Relaying
The author of the relayable sends the entity to the parent author. The author must include the `author_signature`. The If the author is not the same as the parent author, the author of the relayable sends the entity to the parent author
`parent_author_signature` may be empty or missing. and the author must include the `author_signature`.
The parent author then must add the `parent_author_signature` and send the entity to all the recipients of the parent The parent author then must envelop it in a new [Magic Envelope][magicsig] and send the entity to all the recipients
entity. of the parent entity. If the author and the parent author are on the same server, the author must sign the
`author_signature` and the parent author needs to sign the Magic Envelope.
If someone other then the parent author receives an relayable without a valid `parent_author_signature`, it must be If someone other then the parent author receives a relayable without a valid Magic Envelope signed from
ignored. If the `author_signature` is missing or invalid, it also must be ignored. the parent author, it must be ignored. If the author is not the same as the parent author and the `author_signature`
is missing or invalid, it also must be ignored. If the author is the same as the parent author, the `author_signature`
can be missing, because a valid signature in the Magic Envelope from the author is enough in that case.
## Signatures ## Signatures
The string to sign is built with the content of all properties (except the `author_signature` and The string to sign is built with the content of all properties (except the `author_signature` itself),
`parent_author_signature` itself), concatenated using `;` as separator in the same order as they appear in the XML. The concatenated using `;` as separator in the same order as they appear in the XML. The order in the XML is not specified.
order in the XML is not specified.
This ensures that relayables even work, if the parent author or another recipient does not know all properties of the This ensures that relayables even work, if the parent author or another recipient does not know all properties of the
relayable entity (e.g. older version of diaspora\*). relayable entity (e.g. older version of diaspora\*).

View file

@ -28,16 +28,15 @@ module DiasporaFederation
# @return [String] parent guid # @return [String] parent guid
# #
# @!attribute [r] author_signature # @!attribute [r] author_signature
# Contains a signature of the entity using the private key of the author of a post itself # Contains a signature of the entity using the private key of the author of a relayable itself.
# The presence of this signature is mandatory. Without it the entity won't be accepted by # The presence of this signature is mandatory. Without it the entity won't be accepted by
# a target pod. # a target pod.
# @return [String] author signature # @return [String] author signature
# #
# @!attribute [r] parent_author_signature # @!attribute [r] parent_author_signature
# Contains a signature of the entity using the private key of the author of a parent post # Contains a signature of the entity using the private key of the author of a parent post.
# This signature is required only when federating from upstream (parent) post author to # @deprecated This signature isn't required anymore, because we can check the signature from
# downstream subscribers. This is the case when the parent author has to resend a relayable # the parent author in the MagicEnvelope.
# received from one of their subscribers to all others.
# @return [String] parent author signature # @return [String] parent author signature
# #
# @!attribute [r] parent # @!attribute [r] parent
@ -71,18 +70,17 @@ module DiasporaFederation
super(data) super(data)
end end
# Verifies the signatures (+author_signature+ and +parent_author_signature+ if needed). # Verifies the +author_signature+ if needed.
# @see DiasporaFederation::Entities::Signable#verify_signature
#
# @raise [SignatureVerificationFailed] if the signature is not valid # @raise [SignatureVerificationFailed] if the signature is not valid
# @raise [PublicKeyNotFound] if no public key is found # @raise [PublicKeyNotFound] if no public key is found
def verify_signatures def verify_signature
verify_signature(author, :author_signature) super(author, :author_signature) unless author == parent.author
# This happens only on downstream federation.
verify_signature(parent.author, :parent_author_signature) unless parent.local
end end
def sender_valid?(sender) def sender_valid?(sender)
sender == author || sender == parent.author (sender == author && parent.local) || sender == parent.author
end end
# @return [String] string representation of this object # @return [String] string representation of this object
@ -136,7 +134,7 @@ module DiasporaFederation
def enriched_properties def enriched_properties
super.merge(additional_data).tap do |hash| super.merge(additional_data).tap do |hash|
hash[:author_signature] = author_signature || sign_with_author hash[:author_signature] = author_signature || sign_with_author
hash[:parent_author_signature] = parent_author_signature || sign_with_parent_author_if_available.to_s hash.delete(:parent_author_signature)
end end
end end
@ -144,7 +142,9 @@ module DiasporaFederation
# #
# @return [Hash] sorted xml elements # @return [Hash] sorted xml elements
def xml_elements def xml_elements
data = super data = super.tap do |hash|
hash[:parent_author_signature] = parent_author_signature || sign_with_parent_author_if_available.to_s
end
order = signature_order + %i(author_signature parent_author_signature) order = signature_order + %i(author_signature parent_author_signature)
order.map {|element| [element, data[element] || ""] }.to_h order.map {|element| [element, data[element] || ""] }.to_h
end end
@ -178,7 +178,7 @@ module DiasporaFederation
additional_data = properties_hash.reject {|key, _| class_props.has_key?(key) } additional_data = properties_hash.reject {|key, _| class_props.has_key?(key) }
fetch_parent(properties_hash) fetch_parent(properties_hash)
new(properties_hash, property_order, additional_data).tap(&:verify_signatures) new(properties_hash, property_order, additional_data).tap(&:verify_signature)
end end
private private

View file

@ -46,8 +46,7 @@
"author": { "type": "string" }, "author": { "type": "string" },
"guid": { "$ref": "#/definitions/guid" }, "guid": { "$ref": "#/definitions/guid" },
"parent_guid": { "$ref": "#/definitions/guid" }, "parent_guid": { "$ref": "#/definitions/guid" },
"author_signature": { "$ref": "#/definitions/signature" }, "author_signature": { "$ref": "#/definitions/signature" }
"parent_author_signature": { "$ref": "#/definitions/signature" }
}, },
"required": [ "required": [
"author", "guid", "parent_guid" "author", "guid", "parent_guid"

View file

@ -224,7 +224,6 @@ XML
before do before do
expect_callback(:fetch_public_key, author).and_return(author_key.public_key) expect_callback(:fetch_public_key, author).and_return(author_key.public_key)
expect_callback(:fetch_public_key, parent.author).and_return(parent_key.public_key)
expect_callback(:fetch_related_entity, "Post", parent_guid).and_return(parent) expect_callback(:fetch_related_entity, "Post", parent_guid).and_return(parent)
end end

View file

@ -32,7 +32,6 @@ XML
"guid": "#{data[:guid]}", "guid": "#{data[:guid]}",
"parent_guid": "#{parent.guid}", "parent_guid": "#{parent.guid}",
"author_signature": "#{data[:author_signature]}", "author_signature": "#{data[:author_signature]}",
"parent_author_signature": "#{data[:parent_author_signature]}",
"text": "#{data[:text]}", "text": "#{data[:text]}",
"created_at": "#{data[:created_at].iso8601}" "created_at": "#{data[:created_at].iso8601}"
}, },

View file

@ -32,7 +32,6 @@ XML
"guid": "#{data[:guid]}", "guid": "#{data[:guid]}",
"parent_guid": "#{parent.guid}", "parent_guid": "#{parent.guid}",
"author_signature": "#{data[:author_signature]}", "author_signature": "#{data[:author_signature]}",
"parent_author_signature": "#{data[:parent_author_signature]}",
"parent_type": "#{parent.entity_type}", "parent_type": "#{parent.entity_type}",
"positive": #{data[:positive]} "positive": #{data[:positive]}
}, },

View file

@ -30,7 +30,6 @@ XML
"guid": "#{data[:guid]}", "guid": "#{data[:guid]}",
"parent_guid": "#{parent.guid}", "parent_guid": "#{parent.guid}",
"author_signature": "#{data[:author_signature]}", "author_signature": "#{data[:author_signature]}",
"parent_author_signature": "#{data[:parent_author_signature]}",
"poll_answer_guid": "#{data[:poll_answer_guid]}" "poll_answer_guid": "#{data[:poll_answer_guid]}"
}, },
"property_order": [ "property_order": [

View file

@ -25,16 +25,15 @@ module DiasporaFederation
end end
end end
describe "#verify_signatures" do describe "#verify_signature" do
it "doesn't raise anything if correct signatures were passed" do it "doesn't raise anything if correct signatures were passed" do
hash[:author_signature] = sign_with_key(author_pkey, signature_data) hash[:author_signature] = sign_with_key(author_pkey, signature_data)
hash[:parent_author_signature] = sign_with_key(parent_pkey, signature_data) hash[:parent_author_signature] = sign_with_key(parent_pkey, signature_data)
hash[:parent] = remote_parent hash[:parent] = remote_parent
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key) expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect_callback(:fetch_public_key, remote_parent.author).and_return(parent_pkey.public_key)
expect { Entities::SomeRelayable.new(hash, signature_order).verify_signatures }.not_to raise_error expect { Entities::SomeRelayable.new(hash, signature_order).verify_signature }.not_to raise_error
end end
it "doesn't raise anything if correct signatures with new property were passed" do it "doesn't raise anything if correct signatures with new property were passed" do
@ -46,10 +45,9 @@ module DiasporaFederation
hash[:parent] = remote_parent hash[:parent] = remote_parent
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key) expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect_callback(:fetch_public_key, remote_parent.author).and_return(parent_pkey.public_key)
expect { expect {
Entities::SomeRelayable.new(hash, signature_order, "new_property" => new_property).verify_signatures Entities::SomeRelayable.new(hash, signature_order, "new_property" => new_property).verify_signature
}.not_to raise_error }.not_to raise_error
end end
@ -57,7 +55,7 @@ module DiasporaFederation
expect_callback(:fetch_public_key, anything).and_return(nil) expect_callback(:fetch_public_key, anything).and_return(nil)
expect { expect {
Entities::SomeRelayable.new(hash, signature_order).verify_signatures Entities::SomeRelayable.new(hash, signature_order).verify_signature
}.to raise_error Entities::Relayable::PublicKeyNotFound }.to raise_error Entities::Relayable::PublicKeyNotFound
end end
@ -67,66 +65,47 @@ module DiasporaFederation
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key) expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect { expect {
Entities::SomeRelayable.new(hash, signature_order).verify_signatures Entities::SomeRelayable.new(hash, signature_order).verify_signature
}.to raise_error Entities::Relayable::SignatureVerificationFailed }.to raise_error Entities::Relayable::SignatureVerificationFailed
end end
it "doesn't raise when no author signature was passed, but the author is also the parent author" do
hash[:author_signature] = nil
hash[:parent] = Fabricate(:related_entity, author: author, local: false)
expect {
Entities::SomeRelayable.new(hash, signature_order).verify_signature
}.not_to raise_error
end
it "raises when bad author signature was passed" do it "raises when bad author signature was passed" do
hash[:author_signature] = sign_with_key(author_pkey, "bad signed string") hash[:author_signature] = sign_with_key(author_pkey, "bad signed string")
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key) expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect { expect {
Entities::SomeRelayable.new(hash, signature_order).verify_signatures Entities::SomeRelayable.new(hash, signature_order).verify_signature
}.to raise_error Entities::Relayable::SignatureVerificationFailed }.to raise_error Entities::Relayable::SignatureVerificationFailed
end end
it "raises when no public key for parent author was fetched" do it "doesn't raise when no parent author signature was passed" do
hash[:author_signature] = sign_with_key(author_pkey, signature_data)
hash[:parent] = remote_parent
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect_callback(:fetch_public_key, remote_parent.author).and_return(nil)
expect {
Entities::SomeRelayable.new(hash, signature_order).verify_signatures
}.to raise_error Entities::Relayable::PublicKeyNotFound
end
it "raises when no parent author signature was passed" do
hash[:author_signature] = sign_with_key(author_pkey, signature_data) hash[:author_signature] = sign_with_key(author_pkey, signature_data)
hash[:parent_author_signature] = nil hash[:parent_author_signature] = nil
hash[:parent] = remote_parent hash[:parent] = remote_parent
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key) expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect_callback(:fetch_public_key, remote_parent.author).and_return(parent_pkey.public_key)
expect { expect { Entities::SomeRelayable.new(hash, signature_order).verify_signature }.not_to raise_error
Entities::SomeRelayable.new(hash, signature_order).verify_signatures
}.to raise_error Entities::Relayable::SignatureVerificationFailed
end end
it "raises when bad parent author signature was passed" do it "doesn't raise when no parent author signature was passed and we're on upstream federation" do
hash[:author_signature] = sign_with_key(author_pkey, signature_data)
hash[:parent_author_signature] = sign_with_key(parent_pkey, "bad signed string")
hash[:parent] = remote_parent
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect_callback(:fetch_public_key, remote_parent.author).and_return(parent_pkey.public_key)
expect {
Entities::SomeRelayable.new(hash, signature_order).verify_signatures
}.to raise_error Entities::Relayable::SignatureVerificationFailed
end
it "doesn't raise if parent_author_signature isn't set but we're on upstream federation" do
hash[:author_signature] = sign_with_key(author_pkey, signature_data) hash[:author_signature] = sign_with_key(author_pkey, signature_data)
hash[:parent_author_signature] = nil hash[:parent_author_signature] = nil
hash[:parent] = local_parent hash[:parent] = local_parent
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key) expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect { Entities::SomeRelayable.new(hash, signature_order).verify_signatures }.not_to raise_error expect { Entities::SomeRelayable.new(hash, signature_order).verify_signature }.not_to raise_error
end end
end end
@ -228,7 +207,6 @@ XML
before do before do
expect_callback(:fetch_related_entity, "Parent", parent_guid).and_return(remote_parent) expect_callback(:fetch_related_entity, "Parent", parent_guid).and_return(remote_parent)
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key) expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect_callback(:fetch_public_key, remote_parent.author).and_return(parent_pkey.public_key)
end end
let(:new_signature_data) { "#{author};#{guid};#{parent_guid};#{new_property};#{property}" } let(:new_signature_data) { "#{author};#{guid};#{parent_guid};#{new_property};#{property}" }
@ -329,25 +307,21 @@ XML
) )
end end
it "computes correct signatures for the entity with new unknown elements" do it "computes correct author_signature for the entity with new unknown elements" do
expect_callback(:fetch_private_key, author).and_return(author_pkey) expect_callback(:fetch_private_key, author).and_return(author_pkey)
expect_callback(:fetch_private_key, local_parent.author).and_return(parent_pkey)
property_order = [:author, :guid, :parent_guid, "new_property", :property] property_order = [:author, :guid, :parent_guid, "new_property", :property]
signature_data_with_new_property = "#{author};#{guid};#{parent_guid};#{new_property};#{property}" signature_data_with_new_property = "#{author};#{guid};#{parent_guid};#{new_property};#{property}"
json_hash = Entities::SomeRelayable.new(hash, property_order, "new_property" => new_property).to_json json_hash = Entities::SomeRelayable.new(hash, property_order, "new_property" => new_property).to_json
author_signature = json_hash[:entity_data][:author_signature] author_signature = json_hash[:entity_data][:author_signature]
parent_author_signature = json_hash[:entity_data][:parent_author_signature]
expect(verify_signature(author_pkey, author_signature, signature_data_with_new_property)).to be_truthy expect(verify_signature(author_pkey, author_signature, signature_data_with_new_property)).to be_truthy
expect(verify_signature(parent_pkey, parent_author_signature, signature_data_with_new_property)).to be_truthy
end end
it "doesn't change signatures if they are already set" do it "doesn't change author_signature if it is already set" do
json = Entities::SomeRelayable.new(hash_with_fake_signatures).to_json.to_json json = Entities::SomeRelayable.new(hash_with_fake_signatures).to_json.to_json
expect(json).to include_json(entity_data: {author_signature: "aa"}) expect(json).to include_json(entity_data: {author_signature: "aa"})
expect(json).to include_json(entity_data: {parent_author_signature: "bb"})
end end
it "raises when author_signature not set and key isn't supplied" do it "raises when author_signature not set and key isn't supplied" do
@ -358,12 +332,11 @@ XML
}.to raise_error Entities::Relayable::AuthorPrivateKeyNotFound }.to raise_error Entities::Relayable::AuthorPrivateKeyNotFound
end end
it "doesn't set parent_author_signature if key isn't supplied" do it "doesn't contain the parent_author_signature" do
expect_callback(:fetch_private_key, author).and_return(author_pkey) expect_callback(:fetch_private_key, author).and_return(author_pkey)
expect_callback(:fetch_private_key, local_parent.author).and_return(nil)
json = Entities::SomeRelayable.new(hash).to_json.to_json json = Entities::SomeRelayable.new(hash).to_json
expect(json).to include_json(entity_data: {parent_author_signature: ""}) expect(json[:entity_data]).not_to include(:parent_author_signature)
end end
end end
@ -374,7 +347,6 @@ XML
before do before do
expect_callback(:fetch_related_entity, "Parent", parent_guid).and_return(remote_parent) expect_callback(:fetch_related_entity, "Parent", parent_guid).and_return(remote_parent)
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key) expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect_callback(:fetch_public_key, remote_parent.author).and_return(parent_pkey.public_key)
end end
context "when properties are sorted and there is an unknown property" do context "when properties are sorted and there is an unknown property" do
@ -475,9 +447,7 @@ XML
context "fetch parent" do context "fetch parent" do
before do before do
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key) expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect_callback(:fetch_public_key, remote_parent.author).and_return(parent_pkey.public_key)
expect_callback(:fetch_private_key, author).and_return(author_pkey) expect_callback(:fetch_private_key, author).and_return(author_pkey)
expect_callback(:fetch_private_key, remote_parent.author).and_return(parent_pkey)
end end
let(:entity) { Entities::SomeRelayable.new(hash) } let(:entity) { Entities::SomeRelayable.new(hash) }
@ -508,12 +478,18 @@ XML
end end
describe "#sender_valid?" do describe "#sender_valid?" do
it "allows author" do it "allows author if the parent is local" do
entity = Entities::SomeRelayable.new(hash) entity = Entities::SomeRelayable.new(hash)
expect(entity.sender_valid?(author)).to be_truthy expect(entity.sender_valid?(author)).to be_truthy
end end
it "does not allow the author if the parent is not local" do
entity = Entities::SomeRelayable.new(hash.merge(parent: remote_parent))
expect(entity.sender_valid?(author)).to be_falsey
end
it "allows parent author" do it "allows parent author" do
entity = Entities::SomeRelayable.new(hash) entity = Entities::SomeRelayable.new(hash)

View file

@ -14,7 +14,7 @@ end
# signature methods # signature methods
def add_signatures(hash, klass=described_class) def add_signatures(hash, klass=described_class)
properties = klass.new(hash).send(:enriched_properties) properties = klass.new(hash).send(:xml_elements)
hash[:author_signature] = properties[:author_signature] hash[:author_signature] = properties[:author_signature]
hash[:parent_author_signature] = properties[:parent_author_signature] hash[:parent_author_signature] = properties[:parent_author_signature]
end end

View file

@ -1,4 +1,5 @@
def entity_hash_from(hash) def entity_hash_from(hash)
hash.delete(:parent_author_signature)
hash.map {|key, value| hash.map {|key, value|
if [String, TrueClass, FalseClass, Integer, NilClass].any? {|c| value.is_a? c } if [String, TrueClass, FalseClass, Integer, NilClass].any? {|c| value.is_a? c }
[key, value] [key, value]
@ -136,10 +137,9 @@ shared_examples "a JSON Entity" do
it "contains JSON properties for each of the entity properties with the entity_data property" do it "contains JSON properties for each of the entity properties with the entity_data property" do
entity_data = entity_hash_from(data) entity_data = entity_hash_from(data)
entity_data.delete(:parent) entity_data.delete(:parent)
nested_elements = entity_data.select {|_key, value| value.is_a?(Array) || value.is_a?(Hash) } nested_elements, simple_props = entity_data.partition {|_key, value| value.is_a?(Array) || value.is_a?(Hash) }
entity_data.reject! {|_key, value| value.is_a?(Array) || value.is_a?(Hash) }
expect(to_json_output).to include_json(entity_data: entity_data) expect(to_json_output).to include_json(entity_data: simple_props.to_h)
nested_elements.each {|key, value| nested_elements.each {|key, value|
type = described_class.class_props[key] type = described_class.class_props[key]
if value.is_a?(Array) if value.is_a?(Array)