diff --git a/lib/diaspora_federation/entities/message.rb b/lib/diaspora_federation/entities/message.rb index 11332e7..e24e20c 100644 --- a/lib/diaspora_federation/entities/message.rb +++ b/lib/diaspora_federation/entities/message.rb @@ -8,9 +8,6 @@ module DiasporaFederation # @deprecated LEGACY_SIGNATURE_ORDER = %i(guid parent_guid text created_at author conversation_guid).freeze - # The {Message} parent is a {Conversation} - PARENT_TYPE = "Conversation".freeze - include Relayable # @!attribute [r] text @@ -28,6 +25,40 @@ module DiasporaFederation # @see Conversation#guid # @return [String] conversation guid property :conversation_guid + + # It is only valid to receive a {Message} from the author itself, + # or from the author of the parent {Conversation} if the author signature is valid. + # @deprecated remove after {Message} doesn't include {Relayable} anymore + def sender_valid?(sender) + sender == author || (sender == parent_author && verify_author_signature) + end + + private + + # @deprecated remove after {Message} doesn't include {Relayable} anymore + def verify_author_signature + pubkey = DiasporaFederation.callbacks.trigger(:fetch_public_key, author) + raise PublicKeyNotFound, "author_signature author=#{author} guid=#{guid}" if pubkey.nil? + raise SignatureVerificationFailed, "wrong author_signature" unless verify_signature(pubkey, author_signature) + true + end + + # @deprecated remove after {Message} doesn't include {Relayable} anymore + def parent_author + parent = DiasporaFederation.callbacks.trigger(:fetch_related_entity, "Conversation", conversation_guid) + raise Federation::Fetcher::NotFetchable, "Conversation:#{conversation_guid} not found" unless parent + parent.author + end + + # Default implementation, don't verify signatures for a {Message}. + # @see Entity.populate_entity + # @deprecated remove after {Message} doesn't include {Relayable} anymore + # @param [Nokogiri::XML::Element] root_node xml nodes + # @return [Entity] instance + def self.populate_entity(root_node) + new({parent_guid: nil, parent: nil}.merge(entity_data(root_node))) + end + private_class_method :populate_entity end end end diff --git a/lib/diaspora_federation/validators/message_validator.rb b/lib/diaspora_federation/validators/message_validator.rb index bb3aa04..84650d3 100644 --- a/lib/diaspora_federation/validators/message_validator.rb +++ b/lib/diaspora_federation/validators/message_validator.rb @@ -4,8 +4,8 @@ module DiasporaFederation class MessageValidator < Validation::Validator include Validation - include RelayableValidator - + rule :author, %i(not_empty diaspora_id) + rule :guid, :guid rule :conversation_guid, :guid end end diff --git a/spec/lib/diaspora_federation/entities/conversation_spec.rb b/spec/lib/diaspora_federation/entities/conversation_spec.rb index 93d2158..0bcb251 100644 --- a/spec/lib/diaspora_federation/entities/conversation_spec.rb +++ b/spec/lib/diaspora_federation/entities/conversation_spec.rb @@ -35,7 +35,7 @@ XML it_behaves_like "an Entity subclass" - it_behaves_like "an XML Entity" + it_behaves_like "an XML Entity", %i(parent parent_guid) context "default values" do let(:minimal_xml) { diff --git a/spec/lib/diaspora_federation/entities/message_spec.rb b/spec/lib/diaspora_federation/entities/message_spec.rb index e0497b8..04769d1 100644 --- a/spec/lib/diaspora_federation/entities/message_spec.rb +++ b/spec/lib/diaspora_federation/entities/message_spec.rb @@ -25,8 +25,84 @@ XML it_behaves_like "an Entity subclass" - it_behaves_like "an XML Entity" + it_behaves_like "an XML Entity", %i(parent parent_guid) it_behaves_like "a relayable Entity" + + describe "#sender_valid?" do + let(:entity) { Entities::Message.new(data) } + + it "allows the author" do + expect(entity.sender_valid?(alice.diaspora_id)).to be_truthy + end + + it "allows parent author if the signature is valid" do + expect_callback(:fetch_related_entity, "Conversation", entity.conversation_guid).and_return(data[:parent]) + expect_callback(:fetch_public_key, alice.diaspora_id).and_return(alice.private_key) + expect(entity.sender_valid?(bob.diaspora_id)).to be_truthy + end + + it "does not allow any other person" do + expect_callback(:fetch_related_entity, "Conversation", entity.conversation_guid).and_return(data[:parent]) + invalid_sender = FactoryGirl.generate(:diaspora_id) + expect(entity.sender_valid?(invalid_sender)).to be_falsey + end + + it "does not allow the parent author if the signature is invalid" do + expect_callback(:fetch_related_entity, "Conversation", entity.conversation_guid).and_return(data[:parent]) + expect_callback(:fetch_public_key, alice.diaspora_id).and_return(alice.private_key) + invalid_entity = Entities::Message.new(data.merge(author_signature: "aa")) + expect { + invalid_entity.sender_valid?(bob.diaspora_id) + }.to raise_error Entities::Relayable::SignatureVerificationFailed, "wrong author_signature" + end + + it "raises NotFetchable if the parent Conversation can not be found" do + expect_callback(:fetch_related_entity, "Conversation", entity.conversation_guid).and_return(nil) + expect { + entity.sender_valid?(bob.diaspora_id) + }.to raise_error Federation::Fetcher::NotFetchable + end + end + + context "relayable signature verification" do + it "does not verify the signature" do + data.merge!(author_signature: "aa", parent_author_signature: "bb") + xml = Entities::Message.new(data).to_xml + + expect { + Entities::Message.from_xml(xml) + }.not_to raise_error + end + end + + describe ".populate_entity" do + it "adds a nil parent" do + xml = Entities::Message.new(data).to_xml + parsed = Entities::Message.from_xml(xml) + expect(parsed.parent).to be_nil + end + + it "uses the parent_guid from the parsed xml" do + xml = Entities::Message.new(data).to_xml + parsed = Entities::Message.from_xml(xml) + expect(parsed.parent_guid).to eq(data[:parent_guid]) + end + + it "uses nil for parent_guid if not in the xml" do + xml = <<-XML + + #{data[:author]} + #{data[:guid]} + #{data[:text]} + #{data[:created_at]} + #{data[:conversation_guid]} + +XML + + parsed = Entities::Message.from_xml(Nokogiri::XML::Document.parse(xml).root) + expect(parsed.parent_guid).to be_nil + end + end end end diff --git a/spec/lib/diaspora_federation/validators/message_validator_spec.rb b/spec/lib/diaspora_federation/validators/message_validator_spec.rb index 17adfb1..90b5d23 100644 --- a/spec/lib/diaspora_federation/validators/message_validator_spec.rb +++ b/spec/lib/diaspora_federation/validators/message_validator_spec.rb @@ -3,7 +3,16 @@ module DiasporaFederation let(:entity) { :message_entity } it_behaves_like "a common validator" - it_behaves_like "a relayable validator" + it_behaves_like "a diaspora id validator" do + let(:property) { :author } + let(:mandatory) { true } + end + + describe "#guid" do + it_behaves_like "a guid validator" do + let(:property) { :guid } + end + end describe "#conversation_guid" do it_behaves_like "a guid validator" do diff --git a/spec/support/shared_entity_specs.rb b/spec/support/shared_entity_specs.rb index 83e5598..105e6dc 100644 --- a/spec/support/shared_entity_specs.rb +++ b/spec/support/shared_entity_specs.rb @@ -66,21 +66,21 @@ shared_examples "an XML Entity" do |ignored_props=[]| end end - def check_entity(entity, parsed_entity, ignored_props=[]) - entity.class.class_props.each do |name, type| - validate_values(entity.send(name), parsed_entity.send(name), type) unless ignored_props.include?(name) + def check_entity(entity, parsed_entity, ignored_props) + entity.class.class_props.reject {|name| ignored_props.include?(name) }.each do |name, type| + validate_values(entity.send(name), parsed_entity.send(name), type, ignored_props) end end - def validate_values(value, parsed_value, type) + def validate_values(value, parsed_value, type, ignored_props) if value.nil? expect(parsed_value).to be_nil elsif type == String expect(parsed_value.to_s).to eq(value.to_s) elsif type.instance_of?(Array) - value.each_with_index {|entity, index| check_entity(entity, parsed_value[index]) } + value.each_with_index {|entity, index| check_entity(entity, parsed_value[index], ignored_props) } elsif type.ancestors.include?(DiasporaFederation::Entity) - check_entity(value, parsed_value) + check_entity(value, parsed_value, ignored_props) end end end