From e54f87b7a6cd39208dfea07fbcf4fcbbeff4edff Mon Sep 17 00:00:00 2001 From: cmrd Senya Date: Fri, 11 Dec 2015 14:18:41 +0300 Subject: [PATCH 1/5] Add rspec persistance file --- spec/spec_helper.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ca444256a..586ec2824 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -73,6 +73,8 @@ RSpec.configure do |config| config.include Devise::TestHelpers, :type => :controller config.mock_with :rspec + config.example_status_persistence_file_path = "tmp/rspec-persistance.txt" + config.render_views config.use_transactional_fixtures = true config.infer_spec_type_from_file_location! From 56f022f28ce9dc534a40e5065fc7674ab86a4d11 Mon Sep 17 00:00:00 2001 From: cmrd Senya Date: Tue, 22 Dec 2015 17:10:07 +0300 Subject: [PATCH 2/5] Fix a few issues with public receiver which include: * Make Retraction be allowed to be received publicly (probably just never used before anywhere) * Since public receiver bypasses @object.receive in some cases add the author signature verification for relayables to protect from relayables forgery * xml_author was wrong in some cases for RelayableRetraction --- lib/postzord/receiver/public.rb | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/postzord/receiver/public.rb b/lib/postzord/receiver/public.rb index fc870ba42..bb821585b 100644 --- a/lib/postzord/receiver/public.rb +++ b/lib/postzord/receiver/public.rb @@ -24,7 +24,7 @@ class Postzord::Receiver::Public < Postzord::Receiver parse_and_receive(@salmon.parsed_data) logger.info "received a #{@object.inspect}" - if @object.is_a?(SignedRetraction) # feels like a hack + if @object.is_a?(SignedRetraction) || @object.is_a?(Retraction) # feels like a hack self.recipient_user_ids.each do |user_id| user = User.where(id: user_id).first @object.perform user if user @@ -44,6 +44,11 @@ class Postzord::Receiver::Public < Postzord::Receiver # receive relayable object only for the owner of the parent object @object.receive(@object.parent_author.owner, @author) end + unless @object.signature_valid? + @object.destroy + logger.warn "event=receive status=abort reason='object signature not valid' " + return + end # notify everyone who can see the parent object receiver = Postzord::Receiver::LocalBatch.new(@object, self.recipient_user_ids) receiver.notify_users @@ -74,7 +79,11 @@ class Postzord::Receiver::Public < Postzord::Receiver end def xml_author - if @object.respond_to?(:relayable?) + if @object.is_a?(RelayableRetraction) + if [@object.parent_diaspora_handle, @object.target.parent.diaspora_handle].include?(@author.diaspora_handle) + @author.diaspora_handle + end + elsif @object.respond_to?(:relayable?) #this is public, so it would only be owners sending us other people comments etc @object.parent_author.local? ? @object.diaspora_handle : @object.parent_diaspora_handle else From 2aaf45166bebf353e5d41e74b3ae81334bb4eca4 Mon Sep 17 00:00:00 2001 From: cmrd Senya Date: Wed, 23 Dec 2015 20:56:24 +0300 Subject: [PATCH 3/5] bump diaspora_federation --- Gemfile | 4 ++-- Gemfile.lock | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Gemfile b/Gemfile index 525645e02..2351ff5da 100644 --- a/Gemfile +++ b/Gemfile @@ -12,7 +12,7 @@ gem "unicorn", "5.0.1", require: false # Federation -gem "diaspora_federation-rails", "0.0.10" +gem "diaspora_federation-rails", "0.0.11" # API and JSON @@ -270,7 +270,7 @@ group :test do gem "webmock", "1.22.3", require: false gem "shoulda-matchers", "3.0.1" - gem "diaspora_federation-test", "0.0.10" + gem "diaspora_federation-test", "0.0.11" end group :development, :test do diff --git a/Gemfile.lock b/Gemfile.lock index bc1aa9959..f55ff7696 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -150,18 +150,18 @@ GEM eventmachine (~> 1.0.8) http_parser.rb (~> 0.6) nokogiri (~> 1.6) - diaspora_federation (0.0.10) + diaspora_federation (0.0.11) faraday (~> 0.9.0) faraday_middleware (~> 0.10.0) nokogiri (~> 1.6, >= 1.6.7.1) typhoeus (~> 0.7) valid (~> 1.0) - diaspora_federation-rails (0.0.10) - diaspora_federation (= 0.0.10) + diaspora_federation-rails (0.0.11) + diaspora_federation (= 0.0.11) rails (~> 4.2) - diaspora_federation-test (0.0.10) - diaspora_federation (= 0.0.10) - factory_girl (~> 4.5.0) + diaspora_federation-test (0.0.11) + diaspora_federation (= 0.0.11) + factory_girl (~> 4.5, >= 4.5.0) diff-lcs (1.2.5) docile (1.1.5) domain_name (0.5.25) @@ -792,8 +792,8 @@ DEPENDENCIES devise-token_authenticatable (~> 0.4.0) devise_lastseenable (= 0.0.6) diaspora-vines (~> 0.2.0.develop) - diaspora_federation-rails (= 0.0.10) - diaspora_federation-test (= 0.0.10) + diaspora_federation-rails (= 0.0.11) + diaspora_federation-test (= 0.0.11) entypo-rails (= 2.2.3) eye (= 0.7) facebox-rails (= 0.2.0) From b6c7f004e42713598cf5fd3152155e8e9d2c408f Mon Sep 17 00:00:00 2001 From: cmrd Senya Date: Mon, 21 Dec 2015 22:20:31 +0300 Subject: [PATCH 4/5] Further receive tests development --- lib/diaspora/relayable.rb | 2 +- .../federation/federation_helper.rb | 20 +- .../federation_messages_generation.rb | 164 ++++++++++++ .../receive_federation_messages_spec.rb | 244 +++++++++++------- .../federation/shared_receive_relayable.rb | 121 +++------ .../federation/shared_receive_retraction.rb | 59 ++--- .../federation/shared_receive_stream_items.rb | 166 ++++++++++++ 7 files changed, 547 insertions(+), 229 deletions(-) create mode 100644 spec/integration/federation/federation_messages_generation.rb create mode 100644 spec/integration/federation/shared_receive_stream_items.rb diff --git a/lib/diaspora/relayable.rb b/lib/diaspora/relayable.rb index 3edd989c4..a99ef1a95 100644 --- a/lib/diaspora/relayable.rb +++ b/lib/diaspora/relayable.rb @@ -71,7 +71,7 @@ module Diaspora unless comment_or_like.signature_valid? logger.warn "event=receive status=abort reason='object signature not valid' recipient=#{user.diaspora_handle} "\ - "sender=#{parent.author.diaspora_handle} payload_type=#{self.class} parent_id=#{parent.id}" + "sender=#{comment_or_like.author.diaspora_handle} payload_type=#{self.class} parent_id=#{parent.id}" return end diff --git a/spec/integration/federation/federation_helper.rb b/spec/integration/federation/federation_helper.rb index 5d536098d..6570f7c0a 100644 --- a/spec/integration/federation/federation_helper.rb +++ b/spec/integration/federation/federation_helper.rb @@ -17,10 +17,18 @@ def remote_user_on_pod_c end def generate_xml(entity, remote_user, user) - DiasporaFederation::Salmon::EncryptedSlap.generate_xml( - remote_user.diaspora_handle, - OpenSSL::PKey::RSA.new(remote_user.encryption_key), - entity, - OpenSSL::PKey::RSA.new(user.encryption_key) - ) + if @public + DiasporaFederation::Salmon::Slap.generate_xml( + remote_user.diaspora_handle, + OpenSSL::PKey::RSA.new(remote_user.encryption_key), + entity + ) + else + DiasporaFederation::Salmon::EncryptedSlap.generate_xml( + remote_user.diaspora_handle, + OpenSSL::PKey::RSA.new(remote_user.encryption_key), + entity, + OpenSSL::PKey::RSA.new(user.encryption_key) + ) + end end diff --git a/spec/integration/federation/federation_messages_generation.rb b/spec/integration/federation/federation_messages_generation.rb new file mode 100644 index 000000000..3e6b466db --- /dev/null +++ b/spec/integration/federation/federation_messages_generation.rb @@ -0,0 +1,164 @@ +def generate_profile + @entity = FactoryGirl.build(:profile_entity, diaspora_id: remote_user_on_pod_b.person.diaspora_handle) + + generate_xml(@entity, remote_user_on_pod_b, alice) +end + +def generate_conversation + @entity = FactoryGirl.build( + :conversation_entity, + diaspora_id: remote_user_on_pod_b.diaspora_handle, + participant_ids: "#{remote_user_on_pod_b.diaspora_handle};#{alice.diaspora_handle}" + ) + + generate_xml(@entity, remote_user_on_pod_b, alice) +end + +def generate_status_message + @entity = FactoryGirl.build( + :status_message_entity, + diaspora_id: remote_user_on_pod_b.diaspora_handle, + public: @public + ) + + generate_xml(@entity, remote_user_on_pod_b, alice) +end + +def generate_forged_status_message + substitute_wrong_key(remote_user_on_pod_b, 1) + generate_status_message +end + +def generate_reshare + @entity = FactoryGirl.build( + :reshare_entity, + root_diaspora_id: alice.diaspora_handle, + root_guid: @local_target.guid, + diaspora_id: remote_user_on_pod_b.diaspora_handle, + public: true + ) + + generate_xml(@entity, remote_user_on_pod_b, alice) +end + +def mock_private_key_for_user(user) + expect(DiasporaFederation.callbacks).to receive(:trigger) + .with(:fetch_private_key_by_diaspora_id, user.person.diaspora_handle) + .and_return(user.encryption_key) +end + +def retraction_mock_callbacks(entity, sender) + return unless [ + DiasporaFederation::Entities::SignedRetraction, + DiasporaFederation::Entities::RelayableRetraction + ].include?(entity.class) + + mock_private_key_for_user(sender) + + allow(DiasporaFederation.callbacks).to receive(:trigger) + .with( + :fetch_entity_author_id_by_guid, + entity.target_type, + entity.target_guid + ) + .and_return(sender.encryption_key) +end + +def generate_retraction(entity_name, target_object, sender=remote_user_on_pod_b) + @entity = FactoryGirl.build( + entity_name, + diaspora_id: sender.diaspora_handle, + target_guid: target_object.guid, + target_type: target_object.class.to_s + ) + + retraction_mock_callbacks(@entity, sender) + + generate_xml(@entity, sender, alice) +end + +def generate_forged_retraction(entity_name, target_object, sender=remote_user_on_pod_b) + times = 1 + if %i(signed_retraction_entity relayable_retraction_entity).include?(entity_name) + times += 2 + end + + substitute_wrong_key(sender, times) + generate_retraction(entity_name, target_object, sender) +end + +def generate_relayable_entity(entity_name, target, diaspora_id) + @entity = FactoryGirl.build( + entity_name, + conversation_guid: target.guid, + parent_guid: target.guid, + diaspora_id: diaspora_id, + poll_answer_guid: target.respond_to?(:poll_answers) ? target.poll_answers.first.guid : nil + ) +end + +def mock_entity_author_private_key_unavailable(klass) + expect(DiasporaFederation.callbacks).to receive(:trigger) + .with( + :fetch_author_private_key_by_entity_guid, + klass.get_target_entity_type(@entity.to_h), + kind_of(String) + ) + .and_return(nil) +end + +def mock_entity_author_private_key_as(klass, key) + expect(DiasporaFederation.callbacks).to receive(:trigger) + .with( + :fetch_author_private_key_by_entity_guid, + klass.get_target_entity_type(@entity.to_h), + @remote_target.guid + ) + .and_return(key) +end + +def generate_relayable_local_parent(entity_name) + klass = FactoryGirl.factory_by_name(entity_name).build_class + generate_relayable_entity(entity_name, @local_target, remote_user_on_pod_b.person.diaspora_handle) + + mock_private_key_for_user(remote_user_on_pod_b) + mock_entity_author_private_key_unavailable(klass) + + generate_xml(@entity, remote_user_on_pod_b, alice) +end + +def generate_relayable_remote_parent(entity_name) + klass = FactoryGirl.factory_by_name(entity_name).build_class + generate_relayable_entity(entity_name, @remote_target, remote_user_on_pod_c.person.diaspora_handle) + + mock_private_key_for_user(remote_user_on_pod_c) + mock_entity_author_private_key_as(klass, remote_user_on_pod_b.encryption_key) + + generate_xml(@entity, remote_user_on_pod_b, alice) +end + +def substitute_wrong_key(user, times_number) + expect(user).to receive(:encryption_key).exactly(times_number).times.and_return( + OpenSSL::PKey::RSA.new(1024) + ) +end + +# Checks when a remote pod wants to send us a relayable without having a key for declared diaspora ID +def generate_relayable_local_parent_wrong_author_key(entity_name) + substitute_wrong_key(remote_user_on_pod_b, 2) + generate_relayable_local_parent(entity_name) +end + +# Checks when a remote pod B wants to send us a relayable with authorship from a remote pod C user +# without having correct signature from him. +def generate_relayable_remote_parent_wrong_author_key(entity_name) + substitute_wrong_key(remote_user_on_pod_c, 1) + generate_relayable_remote_parent(entity_name) +end + +# Checks when a remote pod C wants to send us a relayable from its user, but bypassing the pod B where +# remote status came from. +def generate_relayable_remote_parent_wrong_parent_key(entity_name) + substitute_wrong_key(remote_user_on_pod_b, 2) + generate_relayable_remote_parent(entity_name) +end diff --git a/spec/integration/federation/receive_federation_messages_spec.rb b/spec/integration/federation/receive_federation_messages_spec.rb index fdb479dc5..b3697a3ac 100644 --- a/spec/integration/federation/receive_federation_messages_spec.rb +++ b/spec/integration/federation/receive_federation_messages_spec.rb @@ -1,128 +1,176 @@ require "spec_helper" +require "diaspora_federation/test" require "integration/federation/federation_helper" +require "integration/federation/federation_messages_generation" require "integration/federation/shared_receive_relayable" require "integration/federation/shared_receive_retraction" +require "integration/federation/shared_receive_stream_items" -describe Workers::ReceiveEncryptedSalmon do - it "treats sharing request receive correctly" do - entity = FactoryGirl.build(:request_entity, recipient_id: alice.diaspora_handle) +def post_private_message(recipient_guid, xml) + inlined_jobs do + post "/receive/users/#{recipient_guid}", guid: recipient_guid, xml: xml + end +end - expect(Diaspora::Fetcher::Public).to receive(:queue_for) - Workers::ReceiveEncryptedSalmon.new.perform(alice.id, generate_xml(entity, remote_user_on_pod_c, alice)) +def post_public_message(xml) + inlined_jobs do + post "/receive/public", xml: xml + end +end - new_contact = alice.contacts.find {|c| c.person.diaspora_handle == remote_user_on_pod_c.diaspora_handle } - expect(new_contact).not_to be_nil - expect(new_contact.sharing).to eq(true) +def post_message(recipient_guid, xml) + if @public + post_public_message(xml) + else + post_private_message(recipient_guid, xml) + end +end + +def set_up_sharing + contact = alice.contacts.find_or_initialize_by(person_id: remote_user_on_pod_b.person.id) + contact.sharing = true + contact.save +end + +describe "Receive federation messages feature" do + before do + allow(DiasporaFederation.callbacks).to receive(:trigger) + .with(:queue_public_receive, any_args).and_call_original + allow(DiasporaFederation.callbacks).to receive(:trigger) + .with(:queue_private_receive, any_args).and_call_original + allow(DiasporaFederation.callbacks).to receive(:trigger) + .with(:save_person_after_webfinger, any_args).and_call_original end - it "doesn't save the status message if there is no sharing" do - entity = FactoryGirl.build(:status_message_entity, diaspora_id: remote_user_on_pod_b.diaspora_handle, public: false) - Workers::ReceiveEncryptedSalmon.new.perform(alice.id, generate_xml(entity, remote_user_on_pod_b, alice)) - - expect(StatusMessage.exists?(guid: entity.guid)).to be(false) - end - - describe "with messages which require sharing" do + context "with public receive" do before do - contact = alice.contacts.find_or_initialize_by(person_id: remote_user_on_pod_b.person.id) - contact.sharing = true - contact.save + @public = true end - it "treats status message receive correctly" do - entity = FactoryGirl.build(:status_message_entity, - diaspora_id: remote_user_on_pod_b.diaspora_handle, public: false) - Workers::ReceiveEncryptedSalmon.new.perform(alice.id, generate_xml(entity, remote_user_on_pod_b, alice)) + it "receives account deletion correctly" do + post_public_message( + generate_xml( + DiasporaFederation::Entities::AccountDeletion.new(diaspora_id: remote_user_on_pod_b.diaspora_handle), + remote_user_on_pod_b, + nil + ) + ) - expect(StatusMessage.exists?(guid: entity.guid)).to be(true) + expect(AccountDeletion.where(diaspora_handle: remote_user_on_pod_b.diaspora_handle).exists?).to be(true) end - it "doesn't accept status message with wrong signature" do - expect(remote_user_on_pod_b).to receive(:encryption_key).and_return(OpenSSL::PKey::RSA.new(1024)) + it "rejects account deletion with wrong diaspora_id" do + delete_id = FactoryGirl.generate(:diaspora_id) + post_public_message( + generate_xml( + DiasporaFederation::Entities::AccountDeletion.new(diaspora_id: delete_id), + remote_user_on_pod_b, + nil + ) + ) - entity = FactoryGirl.build(:status_message_entity, - diaspora_id: remote_user_on_pod_b.diaspora_handle, public: false) - Workers::ReceiveEncryptedSalmon.new.perform(alice.id, generate_xml(entity, remote_user_on_pod_b, alice)) - - expect(StatusMessage.exists?(guid: entity.guid)).to be(false) + expect(AccountDeletion.where(diaspora_handle: delete_id).exists?).to be(false) + expect(AccountDeletion.where(diaspora_handle: remote_user_on_pod_b.diaspora_handle).exists?).to be(false) end - describe "retractions for non-relayable objects" do - %w( - retraction - signed_retraction - ).each do |retraction_entity_name| - context "with #{retraction_entity_name}" do - %w(status_message photo).each do |target| - context "with #{target}" do - it_behaves_like "it retracts non-relayable object" do - let(:target_object) { FactoryGirl.create(target.to_sym, author: remote_user_on_pod_b.person) } - let(:entity_name) { "#{retraction_entity_name}_entity".to_sym } - end - end - end - end - end + it "reshare of public post passes" do + @local_target = FactoryGirl.create(:status_message, author: alice.person, public: true) + post_public_message(generate_reshare) + + expect( + Reshare.where(root_guid: @local_target.guid, diaspora_handle: remote_user_on_pod_b.diaspora_handle).first + ).not_to be_nil end - describe "with messages which require a status to operate on" do - let(:local_message) { FactoryGirl.create(:status_message, author: alice.person) } - let(:remote_message) { FactoryGirl.create(:status_message, author: remote_user_on_pod_b.person) } + it "reshare of private post fails" do + @local_target = FactoryGirl.create(:status_message, author: alice.person, public: false) + post_public_message(generate_reshare) - %w(comment like participation).each do |entity| - context "with #{entity}" do - it_behaves_like "it deals correctly with a relayable" do - let(:entity_name) { "#{entity}_entity".to_sym } - let(:klass) { entity.camelize.constantize } - end - end + expect( + Reshare.where(root_guid: @local_target.guid, diaspora_handle: remote_user_on_pod_b.diaspora_handle).first + ).to be_nil + end + + it_behaves_like "messages which are indifferent about sharing fact" + + context "with sharing" do + before do + set_up_sharing end - describe "retractions for relayable objects" do - let(:sender) { remote_user_on_pod_b } + it_behaves_like "messages which are indifferent about sharing fact" + it_behaves_like "messages which can't be send without sharing" + end + end - %w( - retraction - signed_retraction - relayable_retraction - ).each do |retraction_entity_name| - context "with #{retraction_entity_name}" do - context "with comment" do - it_behaves_like "it retracts object" do - # case for to-upstream federation - let(:entity_name) { "#{retraction_entity_name}_entity".to_sym } - let(:target_object) { - FactoryGirl.create(:comment, author: remote_user_on_pod_b.person, post: local_message) - } - end + context "with private receive" do + before do + @public = false + end - it_behaves_like "it retracts object" do - # case for to-downsteam federation - let(:entity_name) { "#{retraction_entity_name}_entity".to_sym } - let(:target_object) { - FactoryGirl.create(:comment, author: remote_user_on_pod_c.person, post: remote_message) - } - end - end + it "treats sharing request recive correctly" do + entity = FactoryGirl.build(:request_entity, recipient_id: alice.diaspora_handle) - context "with like" do - it_behaves_like "it retracts object" do - # case for to-upstream federation - let(:entity_name) { "#{retraction_entity_name}_entity".to_sym } - let(:target_object) { - FactoryGirl.create(:like, author: remote_user_on_pod_b.person, target: local_message) - } - end + expect(Diaspora::Fetcher::Public).to receive(:queue_for).exactly(1).times - it_behaves_like "it retracts object" do - # case for to-downsteam federation - let(:entity_name) { "#{retraction_entity_name}_entity".to_sym } - let(:target_object) { - FactoryGirl.create(:like, author: remote_user_on_pod_c.person, target: remote_message) - } - end - end - end + post_private_message(alice.guid, generate_xml(entity, remote_user_on_pod_b, alice)) + + expect(alice.contacts.count).to eq(2) + new_contact = alice.contacts.order(created_at: :asc).last + expect(new_contact).not_to be_nil + expect(new_contact.sharing).to eq(true) + expect(new_contact.person.diaspora_handle).to eq(remote_user_on_pod_b.diaspora_handle) + + expect( + Notifications::StartedSharing.where( + recipient_id: alice.id, + target_type: "Person", + target_id: remote_user_on_pod_b.person.id + ).first + ).not_to be_nil + end + + it "doesn't save the private status message if there is no sharing" do + post_private_message(alice.guid, generate_status_message) + + expect(StatusMessage.exists?(guid: @entity.guid)).to be(false) + end + + context "with sharing" do + before do + set_up_sharing + end + + it_behaves_like "messages which are indifferent about sharing fact" + it_behaves_like "messages which can't be send without sharing" + + it "treats profile receive correctly" do + post_private_message(alice.guid, generate_profile) + + expect(Profile.where(diaspora_handle: @entity.diaspora_id).exists?).to be(true) + end + + it "receives conversation correctly" do + post_private_message(alice.guid, generate_conversation) + + expect(Conversation.exists?(guid: @entity.guid)).to be(true) + end + + context "with message" do + before do + @local_target = FactoryGirl.build(:conversation, author: alice.person) + @local_target.participants << remote_user_on_pod_b.person + @local_target.participants << remote_user_on_pod_c.person + @local_target.save + @remote_target = FactoryGirl.build(:conversation, author: remote_user_on_pod_b.person) + @remote_target.participants << alice.person + @remote_target.participants << remote_user_on_pod_c.person + @remote_target.save + end + + it_behaves_like "it deals correctly with a relayable" do + let(:entity_name) { :message_entity } + let(:klass) { Message } end end end diff --git a/spec/integration/federation/shared_receive_relayable.rb b/spec/integration/federation/shared_receive_relayable.rb index 519a2793b..7a8f25fa9 100644 --- a/spec/integration/federation/shared_receive_relayable.rb +++ b/spec/integration/federation/shared_receive_relayable.rb @@ -1,93 +1,44 @@ shared_examples_for "it deals correctly with a relayable" do - context "local" do - let(:entity) { - FactoryGirl.build( - entity_name, - parent_guid: local_message.guid, - diaspora_id: remote_user_on_pod_b.diaspora_handle - ) - } - - def mock_private_keys - allow(DiasporaFederation.callbacks).to receive(:trigger) - .with(:fetch_private_key_by_diaspora_id, - remote_user_on_pod_b.diaspora_handle) - .and_return(remote_user_on_pod_b.encryption_key) - allow(DiasporaFederation.callbacks).to receive(:trigger) - .with(:fetch_author_private_key_by_entity_guid, "Post", kind_of(String)) - .and_return(nil) - end - - it "treats upstream receive correctly" do - mock_private_keys - - Workers::ReceiveEncryptedSalmon.new.perform(alice.id, generate_xml(entity, remote_user_on_pod_b, alice)) - received_entity = klass.find_by(guid: entity.guid) - expect(received_entity).not_to be_nil - expect(received_entity.author.diaspora_handle).to eq(remote_user_on_pod_b.person.diaspora_handle) - end - - # Checks when a remote pod wants to send us a relayable without having a key for declared diaspora ID - it "rejects an upstream entity with a malformed author signature" do - allow(remote_user_on_pod_b).to receive(:encryption_key).and_return(OpenSSL::PKey::RSA.new(1024)) - mock_private_keys - - Workers::ReceiveEncryptedSalmon.new.perform(alice.id, generate_xml(entity, remote_user_on_pod_b, alice)) - expect(klass.exists?(guid: entity.guid)).to be(false) - end + it "treats upstream receive correctly" do + expect(Postzord::Dispatcher).to receive(:build).with(alice, kind_of(klass)).and_call_original + post_message(alice.guid, generate_relayable_local_parent(entity_name)) + received_entity = klass.find_by(guid: @entity.guid) + expect(received_entity).not_to be_nil + expect(received_entity.author.diaspora_handle).to eq(remote_user_on_pod_b.person.diaspora_handle) end - context "remote parent" do - let(:entity) { - FactoryGirl.build( - entity_name, - parent_guid: remote_message.guid, - diaspora_id: remote_user_on_pod_c.diaspora_handle - ) - } + it "rejects an upstream entity with a malformed author signature" do + expect(Postzord::Dispatcher).not_to receive(:build) + post_message( + alice.guid, + generate_relayable_local_parent_wrong_author_key(entity_name) + ) + expect(klass.exists?(guid: @entity.guid)).to be(false) + end - def mock_private_keys - allow(DiasporaFederation.callbacks).to receive(:trigger) - .with(:fetch_private_key_by_diaspora_id, - remote_user_on_pod_c.diaspora_handle) - .and_return(remote_user_on_pod_c.encryption_key) + it "treats downstream receive correctly" do + expect(Postzord::Dispatcher).to receive(:build).with(alice, kind_of(klass)).and_call_original unless @public + post_message(alice.guid, generate_relayable_remote_parent(entity_name)) + received_entity = klass.find_by(guid: @entity.guid) + expect(received_entity).not_to be_nil + expect(received_entity.author.diaspora_handle).to eq(remote_user_on_pod_c.person.diaspora_handle) + end - allow(DiasporaFederation.callbacks).to receive(:trigger) - .with( - :fetch_author_private_key_by_entity_guid, - "Post", - remote_message.guid - ) - .and_return(remote_user_on_pod_b.encryption_key) - end + it "rejects a downstream entity with a malformed author signature" do + expect(Postzord::Dispatcher).not_to receive(:build) + post_message( + alice.guid, + generate_relayable_remote_parent_wrong_author_key(entity_name) + ) + expect(klass.exists?(guid: @entity.guid)).to be(false) + end - it "treats downstream receive correctly" do - mock_private_keys - - Workers::ReceiveEncryptedSalmon.new.perform(alice.id, generate_xml(entity, remote_user_on_pod_b, alice)) - received_entity = klass.find_by(guid: entity.guid) - expect(received_entity).not_to be_nil - expect(received_entity.author.diaspora_handle).to eq(remote_user_on_pod_c.diaspora_handle) - end - - # Checks when a remote pod B wants to send us a relayable with authorship from a remote pod C user - # without having correct signature from him. - it "rejects a downstream entity with a malformed author signature" do - allow(remote_user_on_pod_c).to receive(:encryption_key).and_return(OpenSSL::PKey::RSA.new(1024)) - mock_private_keys - - Workers::ReceiveEncryptedSalmon.new.perform(alice.id, generate_xml(entity, remote_user_on_pod_b, alice)) - expect(klass.exists?(guid: entity.guid)).to be(false) - end - - # Checks when a remote pod C wants to send us a relayable from its user, but bypassing the pod B where - # remote status came from. - it "declines downstream receive when sender signed with a wrong key" do - allow(remote_user_on_pod_b).to receive(:encryption_key).and_return(OpenSSL::PKey::RSA.new(1024)) - mock_private_keys - - Workers::ReceiveEncryptedSalmon.new.perform(alice.id, generate_xml(entity, remote_user_on_pod_b, alice)) - expect(klass.exists?(guid: entity.guid)).to be(false) - end + it "declines downstream receive when sender signed with a wrong key" do + expect(Postzord::Dispatcher).not_to receive(:build) + post_message( + alice.guid, + generate_relayable_remote_parent_wrong_parent_key(entity_name) + ) + expect(klass.exists?(guid: @entity.guid)).to be(false) end end diff --git a/spec/integration/federation/shared_receive_retraction.rb b/spec/integration/federation/shared_receive_retraction.rb index 4264bc1ad..71ae13905 100644 --- a/spec/integration/federation/shared_receive_retraction.rb +++ b/spec/integration/federation/shared_receive_retraction.rb @@ -1,41 +1,22 @@ -def mock_private_keys_for_retraction(entity_name, entity, sender) - if %i(signed_retraction_entity relayable_retraction_entity).include?(entity_name) - allow(DiasporaFederation.callbacks).to receive(:trigger) - .with(:fetch_private_key_by_diaspora_id, sender.diaspora_handle) - .and_return(sender.encryption_key) - end - if entity_name == :relayable_retraction_entity - allow(DiasporaFederation.callbacks).to receive(:trigger) - .with( - :fetch_entity_author_id_by_guid, - entity.target_type, - entity.target_guid - ) - .and_return(sender.encryption_key) - end -end - -def generate_retraction(entity_name, target_object, sender) - entity = FactoryGirl.build( - entity_name, - diaspora_id: sender.diaspora_handle, - target_guid: target_object.guid, - target_type: target_object.class.to_s - ) - - mock_private_keys_for_retraction(entity_name, entity, sender) - generate_xml(entity, sender, alice) -end - shared_examples_for "it retracts non-relayable object" do - it_behaves_like "it retracts object" do - let(:sender) { remote_user_on_pod_b } + it "retracts object by a correct retraction message" do + target_klass = target_object.class.to_s.constantize + post_message(alice.guid, generate_retraction(entity_name, target_object)) + + expect(target_klass.exists?(guid: target_object.guid)).to be(false) + end + + it "doesn't retract object when retraction has wrong signatures" do + target_klass = target_object.class.to_s.constantize + post_message(alice.guid, generate_forged_retraction(entity_name, target_object)) + + expect(target_klass.exists?(guid: target_object.guid)).to be(true) end it "doesn't retract object when sender is different from target object" do target_klass = target_object.class.to_s.constantize - Workers::ReceiveEncryptedSalmon.new.perform( - alice.id, + post_message( + alice.guid, generate_retraction(entity_name, target_object, remote_user_on_pod_c) ) @@ -43,20 +24,20 @@ shared_examples_for "it retracts non-relayable object" do end end -shared_examples_for "it retracts object" do +shared_examples_for "it retracts relayable object" do it "retracts object by a correct message" do target_klass = target_object.class.to_s.constantize - Workers::ReceiveEncryptedSalmon.new.perform(alice.id, generate_retraction(entity_name, target_object, sender)) + post_message(alice.guid, generate_retraction(entity_name, target_object, sender)) expect(target_klass.exists?(guid: target_object.guid)).to be(false) end it "doesn't retract object when retraction has wrong signatures" do target_klass = target_object.class.to_s.constantize - - allow(sender).to receive(:encryption_key).and_return(OpenSSL::PKey::RSA.new(1024)) - - Workers::ReceiveEncryptedSalmon.new.perform(alice.id, generate_retraction(entity_name, target_object, sender)) + post_message( + alice.guid, + generate_forged_retraction(entity_name, target_object, sender) + ) expect(target_klass.exists?(guid: target_object.guid)).to be(true) end diff --git a/spec/integration/federation/shared_receive_stream_items.rb b/spec/integration/federation/shared_receive_stream_items.rb new file mode 100644 index 000000000..109488bcc --- /dev/null +++ b/spec/integration/federation/shared_receive_stream_items.rb @@ -0,0 +1,166 @@ +# by "stream items" we mean everything that could appear in the stream - post, comment, like, poll, etc and therefore +# could be send either publicly or privately +def set_up_messages + @local_target = FactoryGirl.create(:status_message, author: alice.person, public: @public) + @remote_target = FactoryGirl.create(:status_message, author: remote_user_on_pod_b.person, public: @public) +end + +shared_examples_for "messages which are indifferent about sharing fact" do + it "treats status message receive correctly" do + post_message(alice.guid, generate_status_message) + + expect(StatusMessage.exists?(guid: @entity.guid)).to be(true) + end + + it "doesn't accept status message with wrong signature" do + post_message(alice.guid, generate_forged_status_message) + + expect(StatusMessage.exists?(guid: @entity.guid)).to be(false) + end + + describe "with messages which require a status to operate on" do + before do + set_up_messages + end + + describe "notifications are sent where required" do + it "for comment on local post" do + post_message(alice.guid, generate_relayable_local_parent(:comment_entity)) + + expect( + Notifications::CommentOnPost.where( + recipient_id: alice.id, + target_type: "Post", + target_id: @local_target.id + ).first + ).not_to be_nil + end + + it "for like on local post" do + post_message(alice.guid, generate_relayable_local_parent(:like_entity)) + + expect( + Notifications::Liked.where( + recipient_id: alice.id, + target_type: "Post", + target_id: @local_target.id + ).first + ).not_to be_nil + end + end + + %w(comment like participation).each do |entity| + context "with #{entity}" do + it_behaves_like "it deals correctly with a relayable" do + let(:entity_name) { "#{entity}_entity".to_sym } + let(:klass) { entity.camelize.constantize } + end + end + end + + context "with poll_participation" do + before do + @local_target = FactoryGirl.create(:poll, status_message: @local_target) + @remote_target = FactoryGirl.create(:poll, status_message: @remote_target) + end + + it_behaves_like "it deals correctly with a relayable" do + let(:entity_name) { :poll_participation_entity } + let(:klass) { PollParticipation } + end + end + end +end + +shared_examples_for "messages which can't be send without sharing" do + # retractions shouldn't depend on sharing fact + describe "retractions for non-relayable objects" do + %w( + retraction + signed_retraction + ).each do |retraction_entity_name| + context "with #{retraction_entity_name}" do + %w(status_message photo).each do |target| + context "with #{target}" do + it_behaves_like "it retracts non-relayable object" do + let(:target_object) { FactoryGirl.create(target.to_sym, author: remote_user_on_pod_b.person) } + let(:entity_name) { "#{retraction_entity_name}_entity".to_sym } + end + end + end + end + end + end + + describe "with messages which require a status to operate on" do + before do + set_up_messages + end + + # this one shouldn't depend on the sharing fact. this must be fixed + describe "notifications are sent where required" do + it "for comment on remote post where we participate" do + alice.participate!(@remote_target) + post_message(alice.guid, generate_relayable_remote_parent(:comment_entity)) + + expect( + Notifications::AlsoCommented.where( + recipient_id: alice.id, + target_type: "Post", + target_id: @remote_target.id + ).first + ).not_to be_nil + end + end + + describe "retractions for relayable objects" do + %w( + retraction + signed_retraction + relayable_retraction + ).each do |retraction_entity_name| + context "with #{retraction_entity_name}" do + context "with comment" do + it_behaves_like "it retracts relayable object" do + # case for to-upstream federation + let(:entity_name) { "#{retraction_entity_name}_entity".to_sym } + let(:target_object) { + FactoryGirl.create(:comment, author: remote_user_on_pod_b.person, post: @local_target) + } + let(:sender) { remote_user_on_pod_b } + end + + it_behaves_like "it retracts relayable object" do + # case for to-downsteam federation + let(:target_object) { + FactoryGirl.create(:comment, author: remote_user_on_pod_c.person, post: @remote_target) + } + let(:entity_name) { "#{retraction_entity_name}_entity".to_sym } + let(:sender) { remote_user_on_pod_b } + end + end + + context "with like" do + it_behaves_like "it retracts relayable object" do + # case for to-upstream federation + let(:entity_name) { "#{retraction_entity_name}_entity".to_sym } + let(:target_object) { + FactoryGirl.create(:like, author: remote_user_on_pod_b.person, target: @local_target) + } + let(:sender) { remote_user_on_pod_b } + end + + it_behaves_like "it retracts relayable object" do + # case for to-downsteam federation + let(:target_object) { + FactoryGirl.create(:like, author: remote_user_on_pod_c.person, target: @remote_target) + } + let(:entity_name) { "#{retraction_entity_name}_entity".to_sym } + let(:sender) { remote_user_on_pod_b } + end + end + end + end + end + end +end From 812510b44afa644523fd486debd3874f1142a5ed Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Sun, 3 Jan 2016 03:12:52 +0100 Subject: [PATCH 5/5] refactoring tests again closes #6595 --- .../federation/federation_helper.rb | 68 ++++++-- .../federation_messages_generation.rb | 164 ------------------ .../receive_federation_messages_spec.rb | 161 +++++++---------- .../federation/shared_receive_relayable.rb | 87 ++++++---- .../federation/shared_receive_retraction.rb | 55 +++--- .../federation/shared_receive_stream_items.rb | 138 +++++++-------- 6 files changed, 272 insertions(+), 401 deletions(-) delete mode 100644 spec/integration/federation/federation_messages_generation.rb diff --git a/spec/integration/federation/federation_helper.rb b/spec/integration/federation/federation_helper.rb index 6570f7c0a..dd3609b71 100644 --- a/spec/integration/federation/federation_helper.rb +++ b/spec/integration/federation/federation_helper.rb @@ -1,34 +1,68 @@ def remote_user_on_pod_b - @remote_on_b ||= FactoryGirl.build(:user).tap do |user| - user.person = FactoryGirl.create(:person, - profile: FactoryGirl.build(:profile), - serialized_public_key: user.encryption_key.public_key.export, - diaspora_handle: "#{user.username}@remote-b.net") - end + @remote_on_b ||= create_remote_user("remote-b.net") end def remote_user_on_pod_c - @remote_on_c ||= FactoryGirl.build(:user).tap do |user| + @remote_on_c ||= create_remote_user("remote-c.net") +end + +def create_remote_user(pod) + FactoryGirl.build(:user).tap do |user| user.person = FactoryGirl.create(:person, profile: FactoryGirl.build(:profile), serialized_public_key: user.encryption_key.public_key.export, - diaspora_handle: "#{user.username}@remote-c.net") + diaspora_handle: "#{user.username}@#{pod}") + allow(DiasporaFederation.callbacks).to receive(:trigger) + .with(:fetch_private_key_by_diaspora_id, user.diaspora_handle) { + user.encryption_key + } end end -def generate_xml(entity, remote_user, user) - if @public +def create_relayable_entity(entity_name, target, diaspora_id, parent_author_key) + target_entity_type = FactoryGirl.factory_by_name(entity_name).build_class.get_target_entity_type(@entity.to_h) + expect(DiasporaFederation.callbacks).to receive(:trigger) + .with( + :fetch_author_private_key_by_entity_guid, + target_entity_type, + target.guid + ) + .and_return(parent_author_key) + + FactoryGirl.build( + entity_name, + conversation_guid: target.guid, + parent_guid: target.guid, + diaspora_id: diaspora_id, + poll_answer_guid: target.respond_to?(:poll_answers) ? target.poll_answers.first.guid : nil + ) +end + +def generate_xml(entity, remote_user, recipient=nil) + if recipient + DiasporaFederation::Salmon::EncryptedSlap.generate_xml( + remote_user.diaspora_handle, + OpenSSL::PKey::RSA.new(remote_user.encryption_key), + entity, + OpenSSL::PKey::RSA.new(recipient.encryption_key) + ) + else DiasporaFederation::Salmon::Slap.generate_xml( remote_user.diaspora_handle, OpenSSL::PKey::RSA.new(remote_user.encryption_key), entity ) - else - DiasporaFederation::Salmon::EncryptedSlap.generate_xml( - remote_user.diaspora_handle, - OpenSSL::PKey::RSA.new(remote_user.encryption_key), - entity, - OpenSSL::PKey::RSA.new(user.encryption_key) - ) + end +end + +def post_message(xml, recipient=nil) + if recipient + inlined_jobs do + post "/receive/users/#{recipient.guid}", guid: recipient.guid, xml: xml + end + else + inlined_jobs do + post "/receive/public", xml: xml + end end end diff --git a/spec/integration/federation/federation_messages_generation.rb b/spec/integration/federation/federation_messages_generation.rb deleted file mode 100644 index 3e6b466db..000000000 --- a/spec/integration/federation/federation_messages_generation.rb +++ /dev/null @@ -1,164 +0,0 @@ -def generate_profile - @entity = FactoryGirl.build(:profile_entity, diaspora_id: remote_user_on_pod_b.person.diaspora_handle) - - generate_xml(@entity, remote_user_on_pod_b, alice) -end - -def generate_conversation - @entity = FactoryGirl.build( - :conversation_entity, - diaspora_id: remote_user_on_pod_b.diaspora_handle, - participant_ids: "#{remote_user_on_pod_b.diaspora_handle};#{alice.diaspora_handle}" - ) - - generate_xml(@entity, remote_user_on_pod_b, alice) -end - -def generate_status_message - @entity = FactoryGirl.build( - :status_message_entity, - diaspora_id: remote_user_on_pod_b.diaspora_handle, - public: @public - ) - - generate_xml(@entity, remote_user_on_pod_b, alice) -end - -def generate_forged_status_message - substitute_wrong_key(remote_user_on_pod_b, 1) - generate_status_message -end - -def generate_reshare - @entity = FactoryGirl.build( - :reshare_entity, - root_diaspora_id: alice.diaspora_handle, - root_guid: @local_target.guid, - diaspora_id: remote_user_on_pod_b.diaspora_handle, - public: true - ) - - generate_xml(@entity, remote_user_on_pod_b, alice) -end - -def mock_private_key_for_user(user) - expect(DiasporaFederation.callbacks).to receive(:trigger) - .with(:fetch_private_key_by_diaspora_id, user.person.diaspora_handle) - .and_return(user.encryption_key) -end - -def retraction_mock_callbacks(entity, sender) - return unless [ - DiasporaFederation::Entities::SignedRetraction, - DiasporaFederation::Entities::RelayableRetraction - ].include?(entity.class) - - mock_private_key_for_user(sender) - - allow(DiasporaFederation.callbacks).to receive(:trigger) - .with( - :fetch_entity_author_id_by_guid, - entity.target_type, - entity.target_guid - ) - .and_return(sender.encryption_key) -end - -def generate_retraction(entity_name, target_object, sender=remote_user_on_pod_b) - @entity = FactoryGirl.build( - entity_name, - diaspora_id: sender.diaspora_handle, - target_guid: target_object.guid, - target_type: target_object.class.to_s - ) - - retraction_mock_callbacks(@entity, sender) - - generate_xml(@entity, sender, alice) -end - -def generate_forged_retraction(entity_name, target_object, sender=remote_user_on_pod_b) - times = 1 - if %i(signed_retraction_entity relayable_retraction_entity).include?(entity_name) - times += 2 - end - - substitute_wrong_key(sender, times) - generate_retraction(entity_name, target_object, sender) -end - -def generate_relayable_entity(entity_name, target, diaspora_id) - @entity = FactoryGirl.build( - entity_name, - conversation_guid: target.guid, - parent_guid: target.guid, - diaspora_id: diaspora_id, - poll_answer_guid: target.respond_to?(:poll_answers) ? target.poll_answers.first.guid : nil - ) -end - -def mock_entity_author_private_key_unavailable(klass) - expect(DiasporaFederation.callbacks).to receive(:trigger) - .with( - :fetch_author_private_key_by_entity_guid, - klass.get_target_entity_type(@entity.to_h), - kind_of(String) - ) - .and_return(nil) -end - -def mock_entity_author_private_key_as(klass, key) - expect(DiasporaFederation.callbacks).to receive(:trigger) - .with( - :fetch_author_private_key_by_entity_guid, - klass.get_target_entity_type(@entity.to_h), - @remote_target.guid - ) - .and_return(key) -end - -def generate_relayable_local_parent(entity_name) - klass = FactoryGirl.factory_by_name(entity_name).build_class - generate_relayable_entity(entity_name, @local_target, remote_user_on_pod_b.person.diaspora_handle) - - mock_private_key_for_user(remote_user_on_pod_b) - mock_entity_author_private_key_unavailable(klass) - - generate_xml(@entity, remote_user_on_pod_b, alice) -end - -def generate_relayable_remote_parent(entity_name) - klass = FactoryGirl.factory_by_name(entity_name).build_class - generate_relayable_entity(entity_name, @remote_target, remote_user_on_pod_c.person.diaspora_handle) - - mock_private_key_for_user(remote_user_on_pod_c) - mock_entity_author_private_key_as(klass, remote_user_on_pod_b.encryption_key) - - generate_xml(@entity, remote_user_on_pod_b, alice) -end - -def substitute_wrong_key(user, times_number) - expect(user).to receive(:encryption_key).exactly(times_number).times.and_return( - OpenSSL::PKey::RSA.new(1024) - ) -end - -# Checks when a remote pod wants to send us a relayable without having a key for declared diaspora ID -def generate_relayable_local_parent_wrong_author_key(entity_name) - substitute_wrong_key(remote_user_on_pod_b, 2) - generate_relayable_local_parent(entity_name) -end - -# Checks when a remote pod B wants to send us a relayable with authorship from a remote pod C user -# without having correct signature from him. -def generate_relayable_remote_parent_wrong_author_key(entity_name) - substitute_wrong_key(remote_user_on_pod_c, 1) - generate_relayable_remote_parent(entity_name) -end - -# Checks when a remote pod C wants to send us a relayable from its user, but bypassing the pod B where -# remote status came from. -def generate_relayable_remote_parent_wrong_parent_key(entity_name) - substitute_wrong_key(remote_user_on_pod_b, 2) - generate_relayable_remote_parent(entity_name) -end diff --git a/spec/integration/federation/receive_federation_messages_spec.rb b/spec/integration/federation/receive_federation_messages_spec.rb index b3697a3ac..ab114e8e7 100644 --- a/spec/integration/federation/receive_federation_messages_spec.rb +++ b/spec/integration/federation/receive_federation_messages_spec.rb @@ -1,101 +1,64 @@ require "spec_helper" -require "diaspora_federation/test" require "integration/federation/federation_helper" -require "integration/federation/federation_messages_generation" require "integration/federation/shared_receive_relayable" require "integration/federation/shared_receive_retraction" require "integration/federation/shared_receive_stream_items" -def post_private_message(recipient_guid, xml) - inlined_jobs do - post "/receive/users/#{recipient_guid}", guid: recipient_guid, xml: xml - end -end - -def post_public_message(xml) - inlined_jobs do - post "/receive/public", xml: xml - end -end - -def post_message(recipient_guid, xml) - if @public - post_public_message(xml) - else - post_private_message(recipient_guid, xml) - end -end - -def set_up_sharing - contact = alice.contacts.find_or_initialize_by(person_id: remote_user_on_pod_b.person.id) - contact.sharing = true - contact.save -end - describe "Receive federation messages feature" do before do allow(DiasporaFederation.callbacks).to receive(:trigger) .with(:queue_public_receive, any_args).and_call_original allow(DiasporaFederation.callbacks).to receive(:trigger) .with(:queue_private_receive, any_args).and_call_original - allow(DiasporaFederation.callbacks).to receive(:trigger) - .with(:save_person_after_webfinger, any_args).and_call_original end + let(:sender) { remote_user_on_pod_b } + let(:sender_id) { remote_user_on_pod_b.diaspora_handle } + context "with public receive" do - before do - @public = true - end + let(:recipient) { nil } it "receives account deletion correctly" do - post_public_message( - generate_xml( - DiasporaFederation::Entities::AccountDeletion.new(diaspora_id: remote_user_on_pod_b.diaspora_handle), - remote_user_on_pod_b, - nil - ) - ) + post_message(generate_xml(DiasporaFederation::Entities::AccountDeletion.new(diaspora_id: sender_id), sender)) - expect(AccountDeletion.where(diaspora_handle: remote_user_on_pod_b.diaspora_handle).exists?).to be(true) + expect(AccountDeletion.exists?(diaspora_handle: sender_id)).to be_truthy end it "rejects account deletion with wrong diaspora_id" do delete_id = FactoryGirl.generate(:diaspora_id) - post_public_message( - generate_xml( - DiasporaFederation::Entities::AccountDeletion.new(diaspora_id: delete_id), - remote_user_on_pod_b, - nil - ) - ) + post_message(generate_xml(DiasporaFederation::Entities::AccountDeletion.new(diaspora_id: delete_id), sender)) - expect(AccountDeletion.where(diaspora_handle: delete_id).exists?).to be(false) - expect(AccountDeletion.where(diaspora_handle: remote_user_on_pod_b.diaspora_handle).exists?).to be(false) + expect(AccountDeletion.exists?(diaspora_handle: delete_id)).to be_falsey + expect(AccountDeletion.exists?(diaspora_handle: sender_id)).to be_falsey end - it "reshare of public post passes" do - @local_target = FactoryGirl.create(:status_message, author: alice.person, public: true) - post_public_message(generate_reshare) + context "reshare" do + it "reshare of public post passes" do + post = FactoryGirl.create(:status_message, author: alice.person, public: true) + reshare = FactoryGirl.build( + :reshare_entity, root_diaspora_id: alice.diaspora_handle, root_guid: post.guid, diaspora_id: sender_id) + post_message(generate_xml(reshare, sender)) - expect( - Reshare.where(root_guid: @local_target.guid, diaspora_handle: remote_user_on_pod_b.diaspora_handle).first - ).not_to be_nil - end + expect(Reshare.exists?(root_guid: post.guid, diaspora_handle: sender_id)).to be_truthy + end - it "reshare of private post fails" do - @local_target = FactoryGirl.create(:status_message, author: alice.person, public: false) - post_public_message(generate_reshare) + it "reshare of private post fails" do + post = FactoryGirl.create(:status_message, author: alice.person, public: false) + reshare = FactoryGirl.build( + :reshare_entity, root_diaspora_id: alice.diaspora_handle, root_guid: post.guid, diaspora_id: sender_id) + post_message(generate_xml(reshare, sender)) - expect( - Reshare.where(root_guid: @local_target.guid, diaspora_handle: remote_user_on_pod_b.diaspora_handle).first - ).to be_nil + expect(Reshare.exists?(root_guid: post.guid, diaspora_handle: sender_id)).to be_falsey + end end it_behaves_like "messages which are indifferent about sharing fact" context "with sharing" do before do - set_up_sharing + contact = alice.contacts.find_or_initialize_by(person_id: sender.person.id) + contact.sharing = true + contact.save end it_behaves_like "messages which are indifferent about sharing fact" @@ -104,74 +67,84 @@ describe "Receive federation messages feature" do end context "with private receive" do - before do - @public = false - end + let(:recipient) { alice } it "treats sharing request recive correctly" do entity = FactoryGirl.build(:request_entity, recipient_id: alice.diaspora_handle) expect(Diaspora::Fetcher::Public).to receive(:queue_for).exactly(1).times - post_private_message(alice.guid, generate_xml(entity, remote_user_on_pod_b, alice)) + post_message(generate_xml(entity, sender, alice), alice) expect(alice.contacts.count).to eq(2) new_contact = alice.contacts.order(created_at: :asc).last expect(new_contact).not_to be_nil expect(new_contact.sharing).to eq(true) - expect(new_contact.person.diaspora_handle).to eq(remote_user_on_pod_b.diaspora_handle) + expect(new_contact.person.diaspora_handle).to eq(sender_id) expect( - Notifications::StartedSharing.where( + Notifications::StartedSharing.exists?( recipient_id: alice.id, target_type: "Person", - target_id: remote_user_on_pod_b.person.id - ).first - ).not_to be_nil + target_id: sender.person.id + ) + ).to be_truthy end it "doesn't save the private status message if there is no sharing" do - post_private_message(alice.guid, generate_status_message) + entity = FactoryGirl.build(:status_message_entity, diaspora_id: sender_id, public: false) + post_message(generate_xml(entity, sender, alice), alice) - expect(StatusMessage.exists?(guid: @entity.guid)).to be(false) + expect(StatusMessage.exists?(guid: entity.guid)).to be_falsey end context "with sharing" do before do - set_up_sharing + contact = alice.contacts.find_or_initialize_by(person_id: sender.person.id) + contact.sharing = true + contact.save end it_behaves_like "messages which are indifferent about sharing fact" it_behaves_like "messages which can't be send without sharing" it "treats profile receive correctly" do - post_private_message(alice.guid, generate_profile) + entity = FactoryGirl.build(:profile_entity, diaspora_id: sender_id) + post_message(generate_xml(entity, sender, alice), alice) - expect(Profile.where(diaspora_handle: @entity.diaspora_id).exists?).to be(true) + expect(Profile.exists?(diaspora_handle: entity.diaspora_id)).to be_truthy end it "receives conversation correctly" do - post_private_message(alice.guid, generate_conversation) + entity = FactoryGirl.build( + :conversation_entity, + diaspora_id: sender_id, + participant_ids: "#{sender_id};#{alice.diaspora_handle}" + ) + post_message(generate_xml(entity, sender, alice), alice) - expect(Conversation.exists?(guid: @entity.guid)).to be(true) + expect(Conversation.exists?(guid: entity.guid)).to be_truthy end context "with message" do - before do - @local_target = FactoryGirl.build(:conversation, author: alice.person) - @local_target.participants << remote_user_on_pod_b.person - @local_target.participants << remote_user_on_pod_c.person - @local_target.save - @remote_target = FactoryGirl.build(:conversation, author: remote_user_on_pod_b.person) - @remote_target.participants << alice.person - @remote_target.participants << remote_user_on_pod_c.person - @remote_target.save - end + let(:local_target) { + FactoryGirl.build(:conversation, author: alice.person).tap do |target| + target.participants << remote_user_on_pod_b.person + target.participants << remote_user_on_pod_c.person + target.save + end + } + let(:remote_target) { + FactoryGirl.build(:conversation, author: remote_user_on_pod_b.person).tap do |target| + target.participants << alice.person + target.participants << remote_user_on_pod_c.person + target.save + end + } + let(:entity_name) { :message_entity } + let(:klass) { Message } - it_behaves_like "it deals correctly with a relayable" do - let(:entity_name) { :message_entity } - let(:klass) { Message } - end + it_behaves_like "it deals correctly with a relayable" end end end diff --git a/spec/integration/federation/shared_receive_relayable.rb b/spec/integration/federation/shared_receive_relayable.rb index 7a8f25fa9..71c675743 100644 --- a/spec/integration/federation/shared_receive_relayable.rb +++ b/spec/integration/federation/shared_receive_relayable.rb @@ -1,44 +1,59 @@ shared_examples_for "it deals correctly with a relayable" do - it "treats upstream receive correctly" do - expect(Postzord::Dispatcher).to receive(:build).with(alice, kind_of(klass)).and_call_original - post_message(alice.guid, generate_relayable_local_parent(entity_name)) - received_entity = klass.find_by(guid: @entity.guid) - expect(received_entity).not_to be_nil - expect(received_entity.author.diaspora_handle).to eq(remote_user_on_pod_b.person.diaspora_handle) + context "local" do + let(:entity) { create_relayable_entity(entity_name, local_target, sender_id, nil) } + + it "treats upstream receive correctly" do + expect(Postzord::Dispatcher).to receive(:build).with(alice, kind_of(klass)).and_call_original + post_message(generate_xml(entity, sender, recipient), recipient) + + received_entity = klass.find_by(guid: entity.guid) + expect(received_entity).not_to be_nil + expect(received_entity.author.diaspora_handle).to eq(remote_user_on_pod_b.diaspora_handle) + end + + # Checks when a remote pod wants to send us a relayable without having a key for declared diaspora ID + it "rejects an upstream entity with a malformed author signature" do + expect(Postzord::Dispatcher).not_to receive(:build) + allow(remote_user_on_pod_b).to receive(:encryption_key).and_return(OpenSSL::PKey::RSA.new(1024)) + post_message(generate_xml(entity, sender, recipient), recipient) + + expect(klass.exists?(guid: entity.guid)).to be_falsey + end end - it "rejects an upstream entity with a malformed author signature" do - expect(Postzord::Dispatcher).not_to receive(:build) - post_message( - alice.guid, - generate_relayable_local_parent_wrong_author_key(entity_name) - ) - expect(klass.exists?(guid: @entity.guid)).to be(false) - end + context "remote" do + let(:author_id) { remote_user_on_pod_c.diaspora_handle } + let(:entity) { create_relayable_entity(entity_name, remote_target, author_id, sender.encryption_key) } - it "treats downstream receive correctly" do - expect(Postzord::Dispatcher).to receive(:build).with(alice, kind_of(klass)).and_call_original unless @public - post_message(alice.guid, generate_relayable_remote_parent(entity_name)) - received_entity = klass.find_by(guid: @entity.guid) - expect(received_entity).not_to be_nil - expect(received_entity.author.diaspora_handle).to eq(remote_user_on_pod_c.person.diaspora_handle) - end + it "treats downstream receive correctly" do + expect(Postzord::Dispatcher).to receive(:build) + .with(alice, kind_of(klass)).and_call_original unless recipient.nil? - it "rejects a downstream entity with a malformed author signature" do - expect(Postzord::Dispatcher).not_to receive(:build) - post_message( - alice.guid, - generate_relayable_remote_parent_wrong_author_key(entity_name) - ) - expect(klass.exists?(guid: @entity.guid)).to be(false) - end + post_message(generate_xml(entity, sender, recipient), recipient) - it "declines downstream receive when sender signed with a wrong key" do - expect(Postzord::Dispatcher).not_to receive(:build) - post_message( - alice.guid, - generate_relayable_remote_parent_wrong_parent_key(entity_name) - ) - expect(klass.exists?(guid: @entity.guid)).to be(false) + received_entity = klass.find_by(guid: entity.guid) + expect(received_entity).not_to be_nil + expect(received_entity.author.diaspora_handle).to eq(remote_user_on_pod_c.diaspora_handle) + end + + # Checks when a remote pod B wants to send us a relayable with authorship from a remote pod C user + # without having correct signature from him. + it "rejects a downstream entity with a malformed author signature" do + expect(Postzord::Dispatcher).not_to receive(:build) + allow(remote_user_on_pod_c).to receive(:encryption_key).and_return(OpenSSL::PKey::RSA.new(1024)) + post_message(generate_xml(entity, sender, recipient), recipient) + + expect(klass.exists?(guid: entity.guid)).to be_falsey + end + + # Checks when a remote pod C wants to send us a relayable from its user, but bypassing the pod B where + # remote status came from. + it "declines downstream receive when sender signed with a wrong key" do + expect(Postzord::Dispatcher).not_to receive(:build) + allow(sender).to receive(:encryption_key).and_return(OpenSSL::PKey::RSA.new(1024)) + post_message(generate_xml(entity, sender, recipient), recipient) + + expect(klass.exists?(guid: entity.guid)).to be_falsey + end end end diff --git a/spec/integration/federation/shared_receive_retraction.rb b/spec/integration/federation/shared_receive_retraction.rb index 71ae13905..c9182e539 100644 --- a/spec/integration/federation/shared_receive_retraction.rb +++ b/spec/integration/federation/shared_receive_retraction.rb @@ -1,44 +1,57 @@ +def retraction_entity(entity_name, target_object, sender) + allow(DiasporaFederation.callbacks).to receive(:trigger) + .with( + :fetch_entity_author_id_by_guid, + target_object.class.to_s, + target_object.guid + ) + .and_return(sender.encryption_key) + + FactoryGirl.build( + entity_name, + diaspora_id: sender.diaspora_handle, + target_guid: target_object.guid, + target_type: target_object.class.to_s + ) +end + shared_examples_for "it retracts non-relayable object" do it "retracts object by a correct retraction message" do - target_klass = target_object.class.to_s.constantize - post_message(alice.guid, generate_retraction(entity_name, target_object)) + entity = retraction_entity(entity_name, target_object, sender) + post_message(generate_xml(entity, sender, recipient), recipient) - expect(target_klass.exists?(guid: target_object.guid)).to be(false) + expect(target_object.class.exists?(guid: target_object.guid)).to be_falsey end it "doesn't retract object when retraction has wrong signatures" do - target_klass = target_object.class.to_s.constantize - post_message(alice.guid, generate_forged_retraction(entity_name, target_object)) + allow(sender).to receive(:encryption_key).and_return(OpenSSL::PKey::RSA.new(1024)) + entity = retraction_entity(entity_name, target_object, sender) + post_message(generate_xml(entity, sender, recipient), recipient) - expect(target_klass.exists?(guid: target_object.guid)).to be(true) + expect(target_object.class.exists?(guid: target_object.guid)).to be_truthy end it "doesn't retract object when sender is different from target object" do - target_klass = target_object.class.to_s.constantize - post_message( - alice.guid, - generate_retraction(entity_name, target_object, remote_user_on_pod_c) - ) + entity = retraction_entity(entity_name, target_object, remote_user_on_pod_c) + post_message(generate_xml(entity, remote_user_on_pod_c, recipient), recipient) - expect(target_klass.exists?(guid: target_object.guid)).to be(true) + expect(target_object.class.exists?(guid: target_object.guid)).to be_truthy end end shared_examples_for "it retracts relayable object" do it "retracts object by a correct message" do - target_klass = target_object.class.to_s.constantize - post_message(alice.guid, generate_retraction(entity_name, target_object, sender)) + entity = retraction_entity(entity_name, target_object, sender) + post_message(generate_xml(entity, sender, recipient), recipient) - expect(target_klass.exists?(guid: target_object.guid)).to be(false) + expect(target_object.class.exists?(guid: target_object.guid)).to be_falsey end it "doesn't retract object when retraction has wrong signatures" do - target_klass = target_object.class.to_s.constantize - post_message( - alice.guid, - generate_forged_retraction(entity_name, target_object, sender) - ) + allow(sender).to receive(:encryption_key).and_return(OpenSSL::PKey::RSA.new(1024)) + entity = retraction_entity(entity_name, target_object, sender) + post_message(generate_xml(entity, sender, recipient), recipient) - expect(target_klass.exists?(guid: target_object.guid)).to be(true) + expect(target_object.class.exists?(guid: target_object.guid)).to be_truthy end end diff --git a/spec/integration/federation/shared_receive_stream_items.rb b/spec/integration/federation/shared_receive_stream_items.rb index 109488bcc..93e876481 100644 --- a/spec/integration/federation/shared_receive_stream_items.rb +++ b/spec/integration/federation/shared_receive_stream_items.rb @@ -1,73 +1,83 @@ # by "stream items" we mean everything that could appear in the stream - post, comment, like, poll, etc and therefore # could be send either publicly or privately -def set_up_messages - @local_target = FactoryGirl.create(:status_message, author: alice.person, public: @public) - @remote_target = FactoryGirl.create(:status_message, author: remote_user_on_pod_b.person, public: @public) -end - shared_examples_for "messages which are indifferent about sharing fact" do - it "treats status message receive correctly" do - post_message(alice.guid, generate_status_message) + let(:public) { recipient.nil? } - expect(StatusMessage.exists?(guid: @entity.guid)).to be(true) + it "treats status message receive correctly" do + entity = FactoryGirl.build(:status_message_entity, diaspora_id: sender_id, public: public) + + post_message(generate_xml(entity, sender, recipient), recipient) + + expect(StatusMessage.exists?(guid: entity.guid)).to be_truthy end it "doesn't accept status message with wrong signature" do - post_message(alice.guid, generate_forged_status_message) + allow(sender).to receive(:encryption_key).and_return(OpenSSL::PKey::RSA.new(1024)) + entity = FactoryGirl.build(:status_message_entity, diaspora_id: sender_id, public: public) - expect(StatusMessage.exists?(guid: @entity.guid)).to be(false) + post_message(generate_xml(entity, sender, recipient), recipient) + + expect(StatusMessage.exists?(guid: entity.guid)).to be_falsey end describe "with messages which require a status to operate on" do - before do - set_up_messages - end + let(:local_target) { FactoryGirl.create(:status_message, author: alice.person, public: public) } + let(:remote_target) { FactoryGirl.create(:status_message, author: remote_user_on_pod_b.person, public: public) } describe "notifications are sent where required" do it "for comment on local post" do - post_message(alice.guid, generate_relayable_local_parent(:comment_entity)) + entity = create_relayable_entity(:comment_entity, local_target, remote_user_on_pod_b.diaspora_handle, nil) + post_message(generate_xml(entity, sender, recipient), recipient) expect( - Notifications::CommentOnPost.where( + Notifications::CommentOnPost.exists?( recipient_id: alice.id, target_type: "Post", - target_id: @local_target.id - ).first - ).not_to be_nil + target_id: local_target.id + ) + ).to be_truthy end it "for like on local post" do - post_message(alice.guid, generate_relayable_local_parent(:like_entity)) + entity = create_relayable_entity(:like_entity, local_target, remote_user_on_pod_b.diaspora_handle, nil) + post_message(generate_xml(entity, sender, recipient), recipient) expect( - Notifications::Liked.where( + Notifications::Liked.exists?( recipient_id: alice.id, target_type: "Post", - target_id: @local_target.id - ).first - ).not_to be_nil + target_id: local_target.id + ) + ).to be_truthy end end %w(comment like participation).each do |entity| context "with #{entity}" do - it_behaves_like "it deals correctly with a relayable" do - let(:entity_name) { "#{entity}_entity".to_sym } - let(:klass) { entity.camelize.constantize } - end + let(:entity_name) { "#{entity}_entity".to_sym } + let(:klass) { entity.camelize.constantize } + + it_behaves_like "it deals correctly with a relayable" end end context "with poll_participation" do - before do - @local_target = FactoryGirl.create(:poll, status_message: @local_target) - @remote_target = FactoryGirl.create(:poll, status_message: @remote_target) - end + let(:local_target) { + FactoryGirl.create( + :poll, + status_message: FactoryGirl.create(:status_message, author: alice.person, public: public) + ) + } + let(:remote_target) { + FactoryGirl.create( + :poll, + status_message: FactoryGirl.create(:status_message, author: remote_user_on_pod_b.person, public: public) + ) + } + let(:entity_name) { :poll_participation_entity } + let(:klass) { PollParticipation } - it_behaves_like "it deals correctly with a relayable" do - let(:entity_name) { :poll_participation_entity } - let(:klass) { PollParticipation } - end + it_behaves_like "it deals correctly with a relayable" end end end @@ -75,17 +85,15 @@ end shared_examples_for "messages which can't be send without sharing" do # retractions shouldn't depend on sharing fact describe "retractions for non-relayable objects" do - %w( - retraction - signed_retraction - ).each do |retraction_entity_name| + %w(retraction signed_retraction).each do |retraction_entity_name| context "with #{retraction_entity_name}" do + let(:entity_name) { "#{retraction_entity_name}_entity".to_sym } + %w(status_message photo).each do |target| context "with #{target}" do - it_behaves_like "it retracts non-relayable object" do - let(:target_object) { FactoryGirl.create(target.to_sym, author: remote_user_on_pod_b.person) } - let(:entity_name) { "#{retraction_entity_name}_entity".to_sym } - end + let(:target_object) { FactoryGirl.create(target.to_sym, author: remote_user_on_pod_b.person) } + + it_behaves_like "it retracts non-relayable object" end end end @@ -93,70 +101,62 @@ shared_examples_for "messages which can't be send without sharing" do end describe "with messages which require a status to operate on" do - before do - set_up_messages - end + let(:public) { recipient.nil? } + let(:local_target) { FactoryGirl.create(:status_message, author: alice.person, public: public) } + let(:remote_target) { FactoryGirl.create(:status_message, author: remote_user_on_pod_b.person, public: public) } # this one shouldn't depend on the sharing fact. this must be fixed describe "notifications are sent where required" do it "for comment on remote post where we participate" do - alice.participate!(@remote_target) - post_message(alice.guid, generate_relayable_remote_parent(:comment_entity)) + alice.participate!(remote_target) + author_id = remote_user_on_pod_c.diaspora_handle + entity = create_relayable_entity(:comment_entity, remote_target, author_id, sender.encryption_key) + post_message(generate_xml(entity, sender, recipient), recipient) expect( - Notifications::AlsoCommented.where( + Notifications::AlsoCommented.exists?( recipient_id: alice.id, target_type: "Post", - target_id: @remote_target.id - ).first - ).not_to be_nil + target_id: remote_target.id + ) + ).to be_truthy end end describe "retractions for relayable objects" do - %w( - retraction - signed_retraction - relayable_retraction - ).each do |retraction_entity_name| + %w(retraction signed_retraction relayable_retraction).each do |retraction_entity_name| context "with #{retraction_entity_name}" do + let(:entity_name) { "#{retraction_entity_name}_entity".to_sym } + context "with comment" do it_behaves_like "it retracts relayable object" do # case for to-upstream federation - let(:entity_name) { "#{retraction_entity_name}_entity".to_sym } let(:target_object) { - FactoryGirl.create(:comment, author: remote_user_on_pod_b.person, post: @local_target) + FactoryGirl.create(:comment, author: remote_user_on_pod_b.person, post: local_target) } - let(:sender) { remote_user_on_pod_b } end it_behaves_like "it retracts relayable object" do # case for to-downsteam federation let(:target_object) { - FactoryGirl.create(:comment, author: remote_user_on_pod_c.person, post: @remote_target) + FactoryGirl.create(:comment, author: remote_user_on_pod_c.person, post: remote_target) } - let(:entity_name) { "#{retraction_entity_name}_entity".to_sym } - let(:sender) { remote_user_on_pod_b } end end context "with like" do it_behaves_like "it retracts relayable object" do # case for to-upstream federation - let(:entity_name) { "#{retraction_entity_name}_entity".to_sym } let(:target_object) { - FactoryGirl.create(:like, author: remote_user_on_pod_b.person, target: @local_target) + FactoryGirl.create(:like, author: remote_user_on_pod_b.person, target: local_target) } - let(:sender) { remote_user_on_pod_b } end it_behaves_like "it retracts relayable object" do # case for to-downsteam federation let(:target_object) { - FactoryGirl.create(:like, author: remote_user_on_pod_c.person, target: @remote_target) + FactoryGirl.create(:like, author: remote_user_on_pod_c.person, target: remote_target) } - let(:entity_name) { "#{retraction_entity_name}_entity".to_sym } - let(:sender) { remote_user_on_pod_b } end end end