diff --git a/Gemfile b/Gemfile index 67cf53b1a..eec480e66 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 @@ -283,7 +283,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 7d873fb3b..050ba10f9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -152,18 +152,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) @@ -836,8 +836,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 (= 3.0.0.pre.rc2) eye (= 0.7) factory_girl_rails (= 4.5.0) 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/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 diff --git a/spec/integration/federation/federation_helper.rb b/spec/integration/federation/federation_helper.rb index 5d536098d..dd3609b71 100644 --- a/spec/integration/federation/federation_helper.rb +++ b/spec/integration/federation/federation_helper.rb @@ -1,26 +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) - 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) +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 + ) + 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/receive_federation_messages_spec.rb b/spec/integration/federation/receive_federation_messages_spec.rb index fdb479dc5..ab114e8e7 100644 --- a/spec/integration/federation/receive_federation_messages_spec.rb +++ b/spec/integration/federation/receive_federation_messages_spec.rb @@ -2,128 +2,149 @@ require "spec_helper" require "integration/federation/federation_helper" 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) - - expect(Diaspora::Fetcher::Public).to receive(:queue_for) - Workers::ReceiveEncryptedSalmon.new.perform(alice.id, generate_xml(entity, remote_user_on_pod_c, alice)) - - 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) +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 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)) + let(:sender) { remote_user_on_pod_b } + let(:sender_id) { remote_user_on_pod_b.diaspora_handle } - expect(StatusMessage.exists?(guid: entity.guid)).to be(false) - end + context "with public receive" do + let(:recipient) { nil } - describe "with messages which require sharing" do - before do - contact = alice.contacts.find_or_initialize_by(person_id: remote_user_on_pod_b.person.id) - contact.sharing = true - contact.save + it "receives account deletion correctly" do + post_message(generate_xml(DiasporaFederation::Entities::AccountDeletion.new(diaspora_id: sender_id), sender)) + + expect(AccountDeletion.exists?(diaspora_handle: sender_id)).to be_truthy 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 "rejects account deletion with wrong diaspora_id" do + delete_id = FactoryGirl.generate(:diaspora_id) + post_message(generate_xml(DiasporaFederation::Entities::AccountDeletion.new(diaspora_id: delete_id), sender)) - expect(StatusMessage.exists?(guid: entity.guid)).to be(true) + expect(AccountDeletion.exists?(diaspora_handle: delete_id)).to be_falsey + expect(AccountDeletion.exists?(diaspora_handle: sender_id)).to be_falsey 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)) + 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)) - 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(Reshare.exists?(root_guid: post.guid, diaspora_handle: sender_id)).to be_truthy + end - expect(StatusMessage.exists?(guid: entity.guid)).to be(false) - end + 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)) - 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 + expect(Reshare.exists?(root_guid: post.guid, diaspora_handle: sender_id)).to be_falsey end 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_behaves_like "messages which are indifferent about sharing fact" - %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 + context "with sharing" do + before do + contact = alice.contacts.find_or_initialize_by(person_id: sender.person.id) + contact.sharing = true + contact.save 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 + let(:recipient) { alice } - 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 + 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(sender_id) + + expect( + Notifications::StartedSharing.exists?( + recipient_id: alice.id, + target_type: "Person", + target_id: sender.person.id + ) + ).to be_truthy + end + + it "doesn't save the private status message if there is no sharing" do + 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_falsey + end + + context "with sharing" do + before do + 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 + entity = FactoryGirl.build(:profile_entity, diaspora_id: sender_id) + post_message(generate_xml(entity, sender, alice), alice) + + expect(Profile.exists?(diaspora_handle: entity.diaspora_id)).to be_truthy + end + + it "receives conversation correctly" do + 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_truthy + end + + context "with message" do + 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 - 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" end end end diff --git a/spec/integration/federation/shared_receive_relayable.rb b/spec/integration/federation/shared_receive_relayable.rb index 519a2793b..71c675743 100644 --- a/spec/integration/federation/shared_receive_relayable.rb +++ b/spec/integration/federation/shared_receive_relayable.rb @@ -1,70 +1,36 @@ 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 + let(:entity) { create_relayable_entity(entity_name, local_target, sender_id, nil) } it "treats upstream receive correctly" do - mock_private_keys + expect(Postzord::Dispatcher).to receive(:build).with(alice, kind_of(klass)).and_call_original + post_message(generate_xml(entity, sender, recipient), recipient) - 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) + 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)) - mock_private_keys + post_message(generate_xml(entity, sender, recipient), recipient) - Workers::ReceiveEncryptedSalmon.new.perform(alice.id, generate_xml(entity, remote_user_on_pod_b, alice)) - expect(klass.exists?(guid: entity.guid)).to be(false) + expect(klass.exists?(guid: entity.guid)).to be_falsey end 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 - ) - } - - 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) - - 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 + 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 - mock_private_keys + expect(Postzord::Dispatcher).to receive(:build) + .with(alice, kind_of(klass)).and_call_original unless recipient.nil? + + post_message(generate_xml(entity, sender, recipient), recipient) - 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) @@ -73,21 +39,21 @@ shared_examples_for "it deals correctly with a relayable" do # 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)) - mock_private_keys + post_message(generate_xml(entity, sender, recipient), recipient) - Workers::ReceiveEncryptedSalmon.new.perform(alice.id, generate_xml(entity, remote_user_on_pod_b, alice)) - expect(klass.exists?(guid: entity.guid)).to be(false) + 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 - allow(remote_user_on_pod_b).to receive(:encryption_key).and_return(OpenSSL::PKey::RSA.new(1024)) - mock_private_keys + 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) - Workers::ReceiveEncryptedSalmon.new.perform(alice.id, generate_xml(entity, remote_user_on_pod_b, alice)) - expect(klass.exists?(guid: entity.guid)).to be(false) + 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 4264bc1ad..c9182e539 100644 --- a/spec/integration/federation/shared_receive_retraction.rb +++ b/spec/integration/federation/shared_receive_retraction.rb @@ -1,63 +1,57 @@ -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 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) -def generate_retraction(entity_name, target_object, sender) - entity = FactoryGirl.build( + 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 } - end + it "retracts object by a correct retraction message" do + entity = retraction_entity(entity_name, target_object, sender) + post_message(generate_xml(entity, sender, recipient), recipient) - 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, - generate_retraction(entity_name, target_object, remote_user_on_pod_c) - ) - - expect(target_klass.exists?(guid: target_object.guid)).to be(true) - end -end - -shared_examples_for "it retracts 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)) - - 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 - 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) - Workers::ReceiveEncryptedSalmon.new.perform(alice.id, generate_retraction(entity_name, target_object, sender)) + expect(target_object.class.exists?(guid: target_object.guid)).to be_truthy + end - expect(target_klass.exists?(guid: target_object.guid)).to be(true) + it "doesn't retract object when sender is different from target object" do + 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_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 + entity = retraction_entity(entity_name, target_object, sender) + post_message(generate_xml(entity, sender, recipient), recipient) + + expect(target_object.class.exists?(guid: target_object.guid)).to be_falsey + end + + it "doesn't retract object when retraction has wrong signatures" do + 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_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 new file mode 100644 index 000000000..93e876481 --- /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 +shared_examples_for "messages which are indifferent about sharing fact" do + let(:public) { recipient.nil? } + + 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 + 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) + + 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 + 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 + 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.exists?( + recipient_id: alice.id, + target_type: "Post", + target_id: local_target.id + ) + ).to be_truthy + end + + it "for like on local post" do + 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.exists?( + recipient_id: alice.id, + target_type: "Post", + target_id: local_target.id + ) + ).to be_truthy + end + end + + %w(comment like participation).each do |entity| + context "with #{entity}" do + 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 + 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" + 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 + let(:entity_name) { "#{retraction_entity_name}_entity".to_sym } + + %w(status_message photo).each do |target| + context "with #{target}" do + 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 + end + end + + describe "with messages which require a status to operate on" do + 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) + 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.exists?( + recipient_id: alice.id, + target_type: "Post", + 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| + 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(:target_object) { + FactoryGirl.create(:comment, author: remote_user_on_pod_b.person, post: local_target) + } + 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) + } + end + end + + context "with like" do + it_behaves_like "it retracts relayable object" do + # case for to-upstream federation + let(:target_object) { + FactoryGirl.create(:like, author: remote_user_on_pod_b.person, target: local_target) + } + 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) + } + end + end + end + end + end + end +end 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!